.
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
/target
|
/target
|
||||||
.env
|
.env
|
||||||
|
nanodns.toml
|
||||||
test.sh
|
test.sh
|
||||||
+57
-4
@@ -1,16 +1,69 @@
|
|||||||
use anyhow::Result;
|
use anyhow::{Result, ensure};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ConfigData {
|
pub struct Entry {
|
||||||
|
pub name: String,
|
||||||
|
pub address: String,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub address_bytes: [u8; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct EnvConfigData {
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct FileConfigData {
|
||||||
|
#[serde(default, rename = "entry")]
|
||||||
|
pub entries: Vec<Entry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConfigData {
|
||||||
|
pub port: u16,
|
||||||
|
pub entries: Vec<Entry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigData {
|
||||||
|
pub fn get_address_by_domain(&self, domain: &str) -> Option<[u8; 4]> {
|
||||||
|
self.entries
|
||||||
|
.iter()
|
||||||
|
.find(|e| e.name.as_str() == domain)
|
||||||
|
.map(|e| e.address_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load() -> Result<ConfigData> {
|
pub fn load() -> Result<ConfigData> {
|
||||||
|
let env_config = {
|
||||||
let source = config::Environment::default();
|
let source = config::Environment::default();
|
||||||
let builder = config::Config::builder()
|
let builder = config::Config::builder()
|
||||||
.add_source(source)
|
.add_source(source)
|
||||||
.set_default("port", 53)?;
|
.set_default("port", 53)?;
|
||||||
let config = builder.build()?.try_deserialize()?;
|
let config: EnvConfigData = builder.build()?.try_deserialize()?;
|
||||||
Ok(config)
|
config
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut file_config = {
|
||||||
|
let source = config::File::with_name("nanodns").required(false);
|
||||||
|
let builder = config::Config::builder().add_source(source);
|
||||||
|
let config: FileConfigData = builder.build()?.try_deserialize()?;
|
||||||
|
config
|
||||||
|
};
|
||||||
|
|
||||||
|
for entry in &mut file_config.entries {
|
||||||
|
let address_parts: Vec<&str> = entry.address.split(".").collect();
|
||||||
|
ensure!(address_parts.len() == 4, "Invalid IP address");
|
||||||
|
entry.address_bytes = [
|
||||||
|
address_parts[0].parse()?,
|
||||||
|
address_parts[1].parse()?,
|
||||||
|
address_parts[2].parse()?,
|
||||||
|
address_parts[3].parse()?,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ConfigData {
|
||||||
|
port: env_config.port,
|
||||||
|
entries: file_config.entries,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
-179
@@ -1,179 +0,0 @@
|
|||||||
#![allow(unused)]
|
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use anyhow::{Result, bail};
|
|
||||||
|
|
||||||
const QUESTION_TYPE_A: u16 = 0x01;
|
|
||||||
const QUESTION_TYPE_NS: u16 = 0x02;
|
|
||||||
const QUESTION_TYPE_CNAME: u16 = 0x05;
|
|
||||||
const QUESTION_TYPE_AAAA: u16 = 0x1C;
|
|
||||||
const QUESTION_TYPE_MX: u16 = 0x0F;
|
|
||||||
|
|
||||||
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 DnsHeader {
|
|
||||||
pub fn parse(packet: &[u8]) -> Result<DnsHeader> {
|
|
||||||
if packet.len() < 12 {
|
|
||||||
bail!("malformed datagram");
|
|
||||||
}
|
|
||||||
|
|
||||||
let datagram = DnsHeader {
|
|
||||||
transaction_id: [packet[0], packet[1]],
|
|
||||||
query_response: packet[2] & (1 << 7) > 0,
|
|
||||||
op_code: (packet[2] >> 3) & 0x0F,
|
|
||||||
authoritative_answer: packet[2] & (1 << 2) > 0,
|
|
||||||
truncation: packet[2] & (1 << 1) > 0,
|
|
||||||
recursion_desired: packet[2] & (1 << 0) > 0,
|
|
||||||
recursion_available: packet[3] & (1 << 7) > 0,
|
|
||||||
authentic_data: packet[3] & (1 << 5) > 0,
|
|
||||||
checking_disabled: packet[3] & (1 << 4) > 0,
|
|
||||||
response_code: packet[3] & 0xF,
|
|
||||||
number_of_questions: u16::from_be_bytes([packet[4], packet[5]]),
|
|
||||||
number_of_answers: u16::from_be_bytes([packet[6], packet[7]]),
|
|
||||||
number_of_authority_resource_records: u16::from_be_bytes([packet[8], packet[9]]),
|
|
||||||
number_of_additional_resource_records: u16::from_be_bytes([packet[10], packet[11]]),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(datagram)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 DnsQuestion {
|
|
||||||
pub fn parse(packet: &[u8], pos: &mut usize) -> Result<DnsQuestion> {
|
|
||||||
let domain = parse_domain(packet, pos)?;
|
|
||||||
|
|
||||||
if *pos + 4 > packet.len() {
|
|
||||||
bail!("malformed datagram");
|
|
||||||
}
|
|
||||||
|
|
||||||
let question_type = ((packet[*pos] as u16) << 8) | (packet[*pos + 1] as u16);
|
|
||||||
let question_class = ((packet[*pos + 2] as u16) << 8) | (packet[*pos + 3] as u16);
|
|
||||||
*pos += 4;
|
|
||||||
|
|
||||||
let question = DnsQuestion {
|
|
||||||
domain,
|
|
||||||
question_type,
|
|
||||||
question_class,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(question)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
// TODO missing fields
|
|
||||||
}
|
|
||||||
|
|
||||||
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.push(0);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_domain(packet: &[u8], pos: &mut usize) -> Result<String> {
|
|
||||||
let packet_size = packet.len();
|
|
||||||
let mut domain_parts: Vec<String> = vec![];
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if *pos >= packet_size {
|
|
||||||
bail!("malformed datagram");
|
|
||||||
}
|
|
||||||
|
|
||||||
let len = packet[*pos] as usize;
|
|
||||||
if len > 63 {
|
|
||||||
bail!("malformed datagram");
|
|
||||||
}
|
|
||||||
if len == 0 {
|
|
||||||
*pos += 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (*pos + 1 + len) > packet_size {
|
|
||||||
bail!("malformed datagram");
|
|
||||||
}
|
|
||||||
|
|
||||||
let domain_part_bytes = &packet[(*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;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(domain_parts.join("."))
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
use anyhow::{Result, bail};
|
||||||
|
|
||||||
|
use crate::dns::{domain_to_bytes, parse_domain};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Answer {
|
||||||
|
pub domain: String,
|
||||||
|
pub record_type: u16,
|
||||||
|
pub class: u16,
|
||||||
|
pub ttl: u32,
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Answer {
|
||||||
|
pub fn parse(datagram: &[u8], pos: &mut usize) -> Result<Answer> {
|
||||||
|
let domain = parse_domain(datagram, pos)?;
|
||||||
|
|
||||||
|
if *pos + 10 > datagram.len() {
|
||||||
|
bail!("malformed datagram");
|
||||||
|
}
|
||||||
|
|
||||||
|
let record_type = u16::from_be_bytes([datagram[*pos], datagram[*pos + 1]]);
|
||||||
|
let class = u16::from_be_bytes([datagram[*pos + 2], datagram[*pos + 3]]);
|
||||||
|
let ttl = u32::from_be_bytes([
|
||||||
|
datagram[*pos + 4],
|
||||||
|
datagram[*pos + 5],
|
||||||
|
datagram[*pos + 6],
|
||||||
|
datagram[*pos + 7],
|
||||||
|
]);
|
||||||
|
let data_length = u16::from_be_bytes([datagram[*pos + 8], datagram[*pos + 9]]) as usize;
|
||||||
|
*pos += 10;
|
||||||
|
|
||||||
|
if *pos + data_length > datagram.len() {
|
||||||
|
bail!("malformed datagram");
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = datagram[*pos..*pos + data_length].to_vec();
|
||||||
|
*pos += data_length;
|
||||||
|
|
||||||
|
Ok(Answer {
|
||||||
|
domain,
|
||||||
|
record_type,
|
||||||
|
class,
|
||||||
|
ttl,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> Result<Vec<u8>> {
|
||||||
|
let mut result = vec![];
|
||||||
|
result.extend(domain_to_bytes(&self.domain)?);
|
||||||
|
result.extend(self.record_type.to_be_bytes());
|
||||||
|
result.extend(self.class.to_be_bytes());
|
||||||
|
result.extend(self.ttl.to_be_bytes());
|
||||||
|
result.extend((self.data.len() as u16).to_be_bytes());
|
||||||
|
result.extend_from_slice(&self.data);
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
pub const RECORD_TYPE_A: u16 = 0x01;
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const RECORD_TYPE_NS: u16 = 0x02;
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const RECORD_TYPE_CNAME: u16 = 0x05;
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const RECORD_TYPE_AAAA: u16 = 0x1C;
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const RECORD_TYPE_MX: u16 = 0x0F;
|
||||||
|
|
||||||
|
pub const CLASS_IN: u16 = 0x01;
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const CLASS_CHAOS: u16 = 0x03;
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const CLASS_HS: u16 = 0x04;
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const CLASS_ANY: u16 = 0xFF;
|
||||||
|
|
||||||
|
pub const RESPONSE_CODE_NO_ERROR: u8 = 0x00;
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const RESPONSE_CODE_NXDOMAIN: u8 = 0x03;
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const RESPONSE_CODE_NOT_IMPLEMENTED: u8 = 0x04;
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const RESPONSE_CODE_REFUSED: u8 = 0x05;
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
use anyhow::{Result, bail};
|
||||||
|
|
||||||
|
pub fn parse_domain(datagram: &[u8], pos: &mut usize) -> Result<String> {
|
||||||
|
let mut domain_parts: Vec<String> = vec![];
|
||||||
|
let mut cursor = *pos;
|
||||||
|
let mut jumped = false;
|
||||||
|
let mut jumps = 0usize;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if cursor >= datagram.len() {
|
||||||
|
bail!("malformed datagram");
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = datagram[cursor] as usize;
|
||||||
|
if (len & 0xC0) == 0xC0 {
|
||||||
|
if cursor + 1 >= datagram.len() {
|
||||||
|
bail!("malformed datagram");
|
||||||
|
}
|
||||||
|
|
||||||
|
let pointer = ((len & 0x3F) << 8) | datagram[cursor + 1] as usize;
|
||||||
|
if pointer >= datagram.len() {
|
||||||
|
bail!("malformed datagram");
|
||||||
|
}
|
||||||
|
if !jumped {
|
||||||
|
*pos = cursor + 2;
|
||||||
|
jumped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
jumps += 1;
|
||||||
|
if jumps > datagram.len() {
|
||||||
|
bail!("malformed datagram");
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = pointer;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (len & 0xC0) != 0 {
|
||||||
|
bail!("malformed datagram");
|
||||||
|
}
|
||||||
|
if len > 63 {
|
||||||
|
bail!("malformed datagram");
|
||||||
|
}
|
||||||
|
if len == 0 {
|
||||||
|
if !jumped {
|
||||||
|
*pos = cursor + 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor + 1 + len) > datagram.len() {
|
||||||
|
bail!("malformed datagram");
|
||||||
|
}
|
||||||
|
|
||||||
|
let domain_part_bytes = &datagram[(cursor + 1)..(cursor + 1 + len)];
|
||||||
|
let domain_part = String::from_utf8_lossy(&domain_part_bytes);
|
||||||
|
domain_parts.push(domain_part.to_string());
|
||||||
|
cursor += 1 + len;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(domain_parts.join("."))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn domain_to_bytes(domain: &str) -> Result<Vec<u8>> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
if domain.is_empty() {
|
||||||
|
result.push(0);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
for part in domain.split(".") {
|
||||||
|
if part.len() > 63 {
|
||||||
|
bail!("domain label too long");
|
||||||
|
}
|
||||||
|
if part.is_empty() {
|
||||||
|
bail!("empty domain label");
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(part.len() as u8);
|
||||||
|
result.extend_from_slice(part.as_bytes());
|
||||||
|
}
|
||||||
|
result.push(0);
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
use anyhow::{Result, bail};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Header {
|
||||||
|
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 Header {
|
||||||
|
pub fn parse(datagram: &[u8]) -> Result<Header> {
|
||||||
|
if datagram.len() < 12 {
|
||||||
|
bail!("malformed datagram");
|
||||||
|
}
|
||||||
|
|
||||||
|
let datagram = Header {
|
||||||
|
transaction_id: [datagram[0], datagram[1]],
|
||||||
|
query_response: datagram[2] & (1 << 7) > 0,
|
||||||
|
op_code: (datagram[2] >> 3) & 0x0F,
|
||||||
|
authoritative_answer: datagram[2] & (1 << 2) > 0,
|
||||||
|
truncation: datagram[2] & (1 << 1) > 0,
|
||||||
|
recursion_desired: datagram[2] & (1 << 0) > 0,
|
||||||
|
recursion_available: datagram[3] & (1 << 7) > 0,
|
||||||
|
authentic_data: datagram[3] & (1 << 5) > 0,
|
||||||
|
checking_disabled: datagram[3] & (1 << 4) > 0,
|
||||||
|
response_code: datagram[3] & 0xF,
|
||||||
|
number_of_questions: u16::from_be_bytes([datagram[4], datagram[5]]),
|
||||||
|
number_of_answers: u16::from_be_bytes([datagram[6], datagram[7]]),
|
||||||
|
number_of_authority_resource_records: u16::from_be_bytes([datagram[8], datagram[9]]),
|
||||||
|
number_of_additional_resource_records: u16::from_be_bytes([datagram[10], datagram[11]]),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(datagram)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> Result<Vec<u8>> {
|
||||||
|
let mut buffer = [0u8; 12];
|
||||||
|
let number_of_questions = self.number_of_questions.to_be_bytes();
|
||||||
|
let number_of_answers = self.number_of_answers.to_be_bytes();
|
||||||
|
let number_of_authority_resource_records =
|
||||||
|
self.number_of_authority_resource_records.to_be_bytes();
|
||||||
|
let number_of_additional_resource_records =
|
||||||
|
self.number_of_additional_resource_records.to_be_bytes();
|
||||||
|
|
||||||
|
buffer[0] = self.transaction_id[0];
|
||||||
|
buffer[1] = self.transaction_id[1];
|
||||||
|
if self.query_response {
|
||||||
|
buffer[2] |= 1 << 7;
|
||||||
|
}
|
||||||
|
buffer[2] |= (self.op_code & 0x0F) << 3;
|
||||||
|
if self.authoritative_answer {
|
||||||
|
buffer[2] |= 1 << 2;
|
||||||
|
}
|
||||||
|
if self.truncation {
|
||||||
|
buffer[2] |= 1 << 1;
|
||||||
|
}
|
||||||
|
if self.recursion_desired {
|
||||||
|
buffer[2] |= 1 << 0;
|
||||||
|
}
|
||||||
|
if self.recursion_available {
|
||||||
|
buffer[3] |= 1 << 7;
|
||||||
|
}
|
||||||
|
if self.authentic_data {
|
||||||
|
buffer[3] |= 1 << 5;
|
||||||
|
}
|
||||||
|
if self.checking_disabled {
|
||||||
|
buffer[3] |= 1 << 4;
|
||||||
|
}
|
||||||
|
buffer[3] |= self.response_code & 0x0F;
|
||||||
|
buffer[4] = number_of_questions[0];
|
||||||
|
buffer[5] = number_of_questions[1];
|
||||||
|
buffer[6] = number_of_answers[0];
|
||||||
|
buffer[7] = number_of_answers[1];
|
||||||
|
buffer[8] = number_of_authority_resource_records[0];
|
||||||
|
buffer[9] = number_of_authority_resource_records[1];
|
||||||
|
buffer[10] = number_of_additional_resource_records[0];
|
||||||
|
buffer[11] = number_of_additional_resource_records[1];
|
||||||
|
|
||||||
|
Ok(buffer.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::dns::{Answer, Header, Question, RESPONSE_CODE_NO_ERROR};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Message {
|
||||||
|
pub header: Header,
|
||||||
|
pub questions: Vec<Question>,
|
||||||
|
pub answers: Vec<Answer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message {
|
||||||
|
pub fn parse(datagram: &[u8]) -> Result<Message> {
|
||||||
|
let header = Header::parse(datagram)?;
|
||||||
|
|
||||||
|
let mut message = Message {
|
||||||
|
header: header,
|
||||||
|
questions: vec![],
|
||||||
|
answers: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut pos = 12usize;
|
||||||
|
for _ in 0..(message.header.number_of_questions) {
|
||||||
|
let question = Question::parse(datagram, &mut pos)?;
|
||||||
|
message.questions.push(question);
|
||||||
|
}
|
||||||
|
for _ in 0..(message.header.number_of_answers) {
|
||||||
|
let answer = Answer::parse(datagram, &mut pos)?;
|
||||||
|
message.answers.push(answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> Result<Vec<u8>> {
|
||||||
|
let mut result: Vec<u8> = vec![];
|
||||||
|
result.extend(self.header.to_bytes()?);
|
||||||
|
for question in &self.questions {
|
||||||
|
result.extend(question.to_bytes()?);
|
||||||
|
}
|
||||||
|
for answer in &self.answers {
|
||||||
|
result.extend(answer.to_bytes()?);
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare_response(&self) -> Message {
|
||||||
|
Message {
|
||||||
|
header: Header {
|
||||||
|
transaction_id: self.header.transaction_id,
|
||||||
|
query_response: true,
|
||||||
|
op_code: self.header.op_code,
|
||||||
|
authoritative_answer: true,
|
||||||
|
truncation: false,
|
||||||
|
recursion_desired: self.header.recursion_desired,
|
||||||
|
recursion_available: false,
|
||||||
|
authentic_data: false,
|
||||||
|
checking_disabled: self.header.checking_disabled,
|
||||||
|
response_code: RESPONSE_CODE_NO_ERROR,
|
||||||
|
number_of_questions: self.header.number_of_questions,
|
||||||
|
number_of_answers: 0,
|
||||||
|
number_of_authority_resource_records: 0,
|
||||||
|
number_of_additional_resource_records: 0,
|
||||||
|
},
|
||||||
|
questions: self.questions.clone(),
|
||||||
|
answers: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
mod answer;
|
||||||
|
mod constants;
|
||||||
|
mod domainname;
|
||||||
|
mod header;
|
||||||
|
mod message;
|
||||||
|
mod question;
|
||||||
|
|
||||||
|
pub use answer::*;
|
||||||
|
pub use constants::*;
|
||||||
|
pub use domainname::*;
|
||||||
|
pub use header::*;
|
||||||
|
pub use message::*;
|
||||||
|
pub use question::*;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn a_query_datagram() -> Vec<u8> {
|
||||||
|
let mut datagram = vec![
|
||||||
|
0x12, 0x34, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
datagram.extend_from_slice(&domain_to_bytes("example.com").unwrap());
|
||||||
|
datagram.extend_from_slice(&RECORD_TYPE_A.to_be_bytes());
|
||||||
|
datagram.extend_from_slice(&CLASS_IN.to_be_bytes());
|
||||||
|
datagram
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_keeps_original_question_section() {
|
||||||
|
let query = Message::parse(&a_query_datagram()).unwrap();
|
||||||
|
let mut response = query.prepare_response();
|
||||||
|
response.answers.push(Answer {
|
||||||
|
domain: "example.com".to_string(),
|
||||||
|
record_type: RECORD_TYPE_A,
|
||||||
|
class: CLASS_IN,
|
||||||
|
ttl: 1,
|
||||||
|
data: vec![127, 0, 0, 1],
|
||||||
|
});
|
||||||
|
response.header.number_of_answers = 1;
|
||||||
|
|
||||||
|
let bytes = response.to_bytes().unwrap();
|
||||||
|
assert_eq!(u16::from_be_bytes([bytes[4], bytes[5]]), 1);
|
||||||
|
assert_eq!(u16::from_be_bytes([bytes[6], bytes[7]]), 1);
|
||||||
|
assert_eq!(&bytes[12..29], &a_query_datagram()[12..29]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_compressed_answer_names() {
|
||||||
|
let mut datagram = a_query_datagram();
|
||||||
|
datagram[2] = 0x81;
|
||||||
|
datagram[3] = 0x80;
|
||||||
|
datagram[6] = 0x00;
|
||||||
|
datagram[7] = 0x01;
|
||||||
|
datagram.extend_from_slice(&[
|
||||||
|
0xC0, 0x0C, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 127, 0, 0, 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let message = Message::parse(&datagram).unwrap();
|
||||||
|
assert_eq!(message.answers.len(), 1);
|
||||||
|
assert_eq!(message.answers[0].domain, "example.com");
|
||||||
|
assert_eq!(message.answers[0].data, vec![127, 0, 0, 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
use anyhow::{Result, bail};
|
||||||
|
|
||||||
|
use crate::dns::{domain_to_bytes, parse_domain};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Question {
|
||||||
|
pub domain: String,
|
||||||
|
pub question_type: u16,
|
||||||
|
pub question_class: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Question {
|
||||||
|
pub fn parse(datagram: &[u8], pos: &mut usize) -> Result<Question> {
|
||||||
|
let domain = parse_domain(datagram, pos)?;
|
||||||
|
|
||||||
|
if *pos + 4 > datagram.len() {
|
||||||
|
bail!("malformed datagram");
|
||||||
|
}
|
||||||
|
|
||||||
|
let question_type = ((datagram[*pos] as u16) << 8) | (datagram[*pos + 1] as u16);
|
||||||
|
let question_class = ((datagram[*pos + 2] as u16) << 8) | (datagram[*pos + 3] as u16);
|
||||||
|
*pos += 4;
|
||||||
|
|
||||||
|
let question = Question {
|
||||||
|
domain,
|
||||||
|
question_type,
|
||||||
|
question_class,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(question)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> Result<Vec<u8>> {
|
||||||
|
let mut result = domain_to_bytes(&self.domain)?;
|
||||||
|
result.extend(self.question_type.to_be_bytes());
|
||||||
|
result.extend(self.question_class.to_be_bytes());
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! txid {
|
||||||
|
($bytes:expr) => {
|
||||||
|
format!("{:02x}{:02x}", $bytes[0], $bytes[1])
|
||||||
|
};
|
||||||
|
}
|
||||||
+75
-11
@@ -1,42 +1,106 @@
|
|||||||
mod config;
|
mod config;
|
||||||
mod dns;
|
mod dns;
|
||||||
mod logging;
|
mod logging;
|
||||||
|
mod macros;
|
||||||
|
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use tokio::net::UdpSocket;
|
use tokio::net::UdpSocket;
|
||||||
|
use tracing::{Instrument, Level};
|
||||||
|
|
||||||
use crate::dns::*;
|
use crate::dns::*;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
let config = config::load()?;
|
|
||||||
logging::setup();
|
logging::setup();
|
||||||
|
let config = config::load()?;
|
||||||
|
|
||||||
tracing::info!("start server on port {}", config.port);
|
tracing::info!("start server on port {}", config.port);
|
||||||
let sock = UdpSocket::bind(("0.0.0.0", config.port)).await?;
|
let sock = UdpSocket::bind(("0.0.0.0", config.port)).await?;
|
||||||
|
|
||||||
let mut buffer = [0u8; 512];
|
let mut buffer = [0u8; 512];
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (size, addr) = sock.recv_from(&mut buffer).await?;
|
let (size, addr) = sock.recv_from(&mut buffer).await?;
|
||||||
tracing::info!("Received datagram from {}", addr.to_string());
|
let result = handle_datagram(&config, &sock, &addr, &buffer[..size]).await;
|
||||||
if let Err(err) = handle_datagram(&buffer[..size]) {
|
if let Err(err) = result {
|
||||||
tracing::error!("failed to handle datagram: {err}");
|
tracing::error!("failed to handle datagram: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_datagram(packet: &[u8]) -> Result<()> {
|
async fn handle_datagram(
|
||||||
let header = DnsHeader::parse(packet)?;
|
config: &config::ConfigData,
|
||||||
println!("Received:\n{}", header);
|
sock: &UdpSocket,
|
||||||
|
addr: &SocketAddr,
|
||||||
|
datagram: &[u8],
|
||||||
|
) -> Result<()> {
|
||||||
|
let message = Message::parse(datagram)?;
|
||||||
|
let span = tracing::span!(
|
||||||
|
Level::INFO,
|
||||||
|
"handle_datagram",
|
||||||
|
transaction_id = %txid!(message.header.transaction_id),
|
||||||
|
client = %addr,
|
||||||
|
);
|
||||||
|
|
||||||
let mut pos = 12usize;
|
let result: Result<()> = async {
|
||||||
for _ in 0..(header.number_of_questions) {
|
tracing::info!("received message");
|
||||||
let question = DnsQuestion::parse(packet, &mut pos)?;
|
|
||||||
println!("Question: {}", question);
|
if message.header.query_response {
|
||||||
|
tracing::error!("message is response, drop");
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
if message.header.op_code != 0 {
|
||||||
|
tracing::error!("op code is not 0, drop");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut response = message.prepare_response();
|
||||||
|
for question in message.questions {
|
||||||
|
if question.question_type != RECORD_TYPE_A {
|
||||||
|
tracing::error!(
|
||||||
|
"only A records are supported yet, requested: {}",
|
||||||
|
question.question_type
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if question.question_class != CLASS_IN {
|
||||||
|
tracing::error!(
|
||||||
|
"only IN class records are supported yet, requested: {}",
|
||||||
|
question.question_class
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let address = config.get_address_by_domain(&question.domain);
|
||||||
|
match address {
|
||||||
|
Some(address) => {
|
||||||
|
let answer = Answer {
|
||||||
|
domain: question.domain,
|
||||||
|
record_type: RECORD_TYPE_A,
|
||||||
|
class: CLASS_IN,
|
||||||
|
ttl: 1,
|
||||||
|
data: address.to_vec(),
|
||||||
|
};
|
||||||
|
|
||||||
|
response.answers.push(answer);
|
||||||
|
response.header.number_of_answers += 1;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
response.header.response_code = RESPONSE_CODE_NXDOMAIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let response_bytes = response.to_bytes()?;
|
||||||
|
sock.send_to(&response_bytes, addr).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
.instrument(span)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user