add dns-server-tutorial
This commit is contained in:
@@ -155,6 +155,7 @@ Project or files:
|
|||||||
├── __network
|
├── __network
|
||||||
│ ├── async-speed-limit
|
│ ├── async-speed-limit
|
||||||
│ ├── dingo
|
│ ├── dingo
|
||||||
|
│ ├── dns-server-tutorial
|
||||||
│ ├── fetch-rs
|
│ ├── fetch-rs
|
||||||
│ ├── fuso-socks5-test
|
│ ├── fuso-socks5-test
|
||||||
│ ├── html-crawl-parse
|
│ ├── html-crawl-parse
|
||||||
@@ -247,7 +248,6 @@ Project or files:
|
|||||||
├── __translate
|
├── __translate
|
||||||
│ └── retranslate
|
│ └── retranslate
|
||||||
├── __wasm
|
├── __wasm
|
||||||
│ ├── connect.ssh
|
|
||||||
│ ├── deno_rust_wasm_import_functions
|
│ ├── deno_rust_wasm_import_functions
|
||||||
│ ├── deno_rust_wasm_js_sandbox
|
│ ├── deno_rust_wasm_js_sandbox
|
||||||
│ ├── deno_rust_wasm_qr_decode
|
│ ├── deno_rust_wasm_qr_decode
|
||||||
@@ -277,7 +277,6 @@ Project or files:
|
|||||||
├── scripts
|
├── scripts
|
||||||
│ └── build_readme_rs
|
│ └── build_readme_rs
|
||||||
└── single_file_tests
|
└── single_file_tests
|
||||||
├── 99.htm
|
|
||||||
├── 99.rs
|
├── 99.rs
|
||||||
├── chain.rs
|
├── chain.rs
|
||||||
├── closure.rs
|
├── closure.rs
|
||||||
@@ -300,6 +299,6 @@ Project or files:
|
|||||||
├── vec.rs
|
├── vec.rs
|
||||||
└── while.rs
|
└── while.rs
|
||||||
|
|
||||||
269 directories, 40 files
|
271 directories, 38 files
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
49
__network/dns-server-tutorial/README.md
Normal file
49
__network/dns-server-tutorial/README.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# DNS Server in Rust
|
||||||
|
|
||||||
|
This repository contains the code for a **DNS server implemented in Rust**, as part of the tutorial [**Building a DNS Server in Rust**](https://rust-trends.com/posts/building-a-dns-server-in-rust/) . The tutorial covers:
|
||||||
|
- Understanding DNS requests and responses.
|
||||||
|
- Handling UDP packets in Rust.
|
||||||
|
- Parsing and constructing DNS packets.
|
||||||
|
- Implementing decompression of DNS packets.
|
||||||
|
- Forwarding DNS queries to resolvers.
|
||||||
|
|
||||||
|
## 📖 Tutorial
|
||||||
|
For a step-by-step guide, check out the full tutorial:
|
||||||
|
[Building a DNS Server in Rust: Part 1 of 2](https://rust-trends.com/posts/building-a-dns-server-in-rust/)
|
||||||
|
|
||||||
|
## 🛠 Installation
|
||||||
|
|
||||||
|
Ensure you have **Rust** installed:
|
||||||
|
```sh
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Clone this repository:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/Rust-Trends/dns-server-tutorial.git
|
||||||
|
cd dns-server-tutorial
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Running the Server
|
||||||
|
|
||||||
|
Goto to the step you want to explore, e.g. step1, and start the DNS server on port 1053:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd step1
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 Testing with dig
|
||||||
|
|
||||||
|
To test your server, open another terminal and run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dig @localhost -p 1053 www.rust-trends.com
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🤝 Contributions
|
||||||
|
|
||||||
|
Feel free to open issues or submit pull requests to improve the project!
|
||||||
|
|
||||||
|
📜 License: MIT
|
||||||
9
__network/dns-server-tutorial/step1/Cargo.toml
Normal file
9
__network/dns-server-tutorial/step1/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "dns-server"
|
||||||
|
authors = ["Bob Peters <contact@rust-trends.com>"]
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.5.29", features = ["derive"] }
|
||||||
|
thiserror = "2.0.11"
|
||||||
78
__network/dns-server-tutorial/step1/src/dns.rs
Normal file
78
__network/dns-server-tutorial/step1/src/dns.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
// src/dns.rs
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ErrorCondition {
|
||||||
|
#[error("Serialization Error: {0}")]
|
||||||
|
SerializationErr(String),
|
||||||
|
|
||||||
|
#[error("Deserialization Error: {0}")]
|
||||||
|
DeserializationErr(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Header {
|
||||||
|
pub id: u16, // identifier
|
||||||
|
pub qr: bool, // 0 for query, 1 for response
|
||||||
|
pub opcode: u8, // 0 for standard query
|
||||||
|
pub aa: bool, // authoritative answer
|
||||||
|
pub tc: bool, // truncated message
|
||||||
|
pub rd: bool, // recursion desired
|
||||||
|
pub ra: bool, // recursion available
|
||||||
|
pub z: u8, // reserved for future use
|
||||||
|
pub rcode: u8, // 0 for no error
|
||||||
|
pub qdcount: u16, // number of entries in the question section
|
||||||
|
pub ancount: u16, // number of resource records in the answer section
|
||||||
|
pub nscount: u16, // number of name server resource records in the authority records section
|
||||||
|
pub arcount: u16, // number of resource records in the additional records section
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header {
|
||||||
|
const DNS_HEADER_LEN: usize = 12;
|
||||||
|
|
||||||
|
// Serialize the header to a byte array
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
let mut buf = Vec::with_capacity(Header::DNS_HEADER_LEN);
|
||||||
|
|
||||||
|
buf.extend_from_slice(&self.id.to_be_bytes());
|
||||||
|
buf.push(
|
||||||
|
(self.qr as u8) << 7
|
||||||
|
| self.opcode << 3
|
||||||
|
| (self.aa as u8) << 2
|
||||||
|
| (self.tc as u8) << 1
|
||||||
|
| self.rd as u8,
|
||||||
|
);
|
||||||
|
buf.push((self.ra as u8) << 7 | self.z << 4 | self.rcode);
|
||||||
|
buf.extend_from_slice(&self.qdcount.to_be_bytes());
|
||||||
|
buf.extend_from_slice(&self.ancount.to_be_bytes());
|
||||||
|
buf.extend_from_slice(&self.nscount.to_be_bytes());
|
||||||
|
buf.extend_from_slice(&self.arcount.to_be_bytes());
|
||||||
|
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize the header from a byte array
|
||||||
|
pub fn from_bytes(buf: &[u8]) -> Result<Header, ErrorCondition> {
|
||||||
|
if buf.len() < Header::DNS_HEADER_LEN {
|
||||||
|
return Err(ErrorCondition::DeserializationErr(
|
||||||
|
"Buffer length is less than header length".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Header {
|
||||||
|
id: u16::from_be_bytes([buf[0], buf[1]]),
|
||||||
|
qr: (buf[2] & 0b1000_0000) != 0,
|
||||||
|
opcode: (buf[2] & 0b0111_1000) >> 3,
|
||||||
|
aa: (buf[2] & 0b0000_0100) != 0,
|
||||||
|
tc: (buf[2] & 0b0000_0010) != 0,
|
||||||
|
rd: (buf[2] & 0b0000_0001) != 0,
|
||||||
|
ra: (buf[3] & 0b1000_0000) != 0,
|
||||||
|
z: (buf[3] & 0b0111_0000) >> 4,
|
||||||
|
rcode: buf[3] & 0b0000_1111,
|
||||||
|
qdcount: u16::from_be_bytes([buf[4], buf[5]]),
|
||||||
|
ancount: u16::from_be_bytes([buf[6], buf[7]]),
|
||||||
|
nscount: u16::from_be_bytes([buf[8], buf[9]]),
|
||||||
|
arcount: u16::from_be_bytes([buf[10], buf[11]]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
44
__network/dns-server-tutorial/step1/src/main.rs
Normal file
44
__network/dns-server-tutorial/step1/src/main.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// src/main.rs
|
||||||
|
use std::net::UdpSocket;
|
||||||
|
|
||||||
|
mod dns;
|
||||||
|
use dns::Header;
|
||||||
|
|
||||||
|
// Debug print hex bytes of a buffer 16 bytes width followed by the ASCII representation of the bytes
|
||||||
|
fn debug_print_bytes(buf: &[u8]) {
|
||||||
|
for (i, chunk) in buf.chunks(16).enumerate() {
|
||||||
|
print!("{:08x}: ", i * 16);
|
||||||
|
for byte in chunk {
|
||||||
|
print!("{:02x} ", byte);
|
||||||
|
}
|
||||||
|
for _ in 0..(16 - chunk.len()) {
|
||||||
|
print!(" ");
|
||||||
|
}
|
||||||
|
print!(" ");
|
||||||
|
for byte in chunk {
|
||||||
|
if *byte >= 32 && *byte <= 126 {
|
||||||
|
print!("{}", *byte as char);
|
||||||
|
} else {
|
||||||
|
print!(".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let socket = UdpSocket::bind("0.0.0.0:1053").expect("Could not bind to port 1053");
|
||||||
|
let mut buf = [0; 512];
|
||||||
|
|
||||||
|
println!("DNS server is running at port 1053");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (len, addr) = socket.recv_from(&mut buf).expect("Could not receive data");
|
||||||
|
|
||||||
|
println!("\nReceived query from {} with length {} bytes", addr, len);
|
||||||
|
debug_print_bytes(&buf[..len]);
|
||||||
|
|
||||||
|
let header = Header::from_bytes(&buf[..len]).expect("Could not parse DNS header");
|
||||||
|
println!("\n{:?}", header);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
__network/dns-server-tutorial/step2/Cargo.toml
Normal file
9
__network/dns-server-tutorial/step2/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "dns-server"
|
||||||
|
authors = ["Bob Peters <contact@rust-trends.com>"]
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.5.29", features = ["derive"] }
|
||||||
|
thiserror = "2.0.11"
|
||||||
306
__network/dns-server-tutorial/step2/src/dns.rs
Normal file
306
__network/dns-server-tutorial/step2/src/dns.rs
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
// src/dns.rs
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ErrorCondition {
|
||||||
|
#[error("Serialization Error: {0}")]
|
||||||
|
SerializationErr(String),
|
||||||
|
|
||||||
|
#[error("Deserialization Error: {0}")]
|
||||||
|
DeserializationErr(String),
|
||||||
|
|
||||||
|
#[error("Invalid Label")]
|
||||||
|
InvalidLabel,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maximum DNS message size without EDNS0
|
||||||
|
const MAX_DNS_MESSAGE_SIZE: usize = 512;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Header {
|
||||||
|
pub id: u16, // identifier
|
||||||
|
pub qr: bool, // 0 for query, 1 for response
|
||||||
|
pub opcode: u8, // 0 for standard query
|
||||||
|
pub aa: bool, // authoritative answer
|
||||||
|
pub tc: bool, // truncated message
|
||||||
|
pub rd: bool, // recursion desired
|
||||||
|
pub ra: bool, // recursion available
|
||||||
|
pub z: u8, // reserved for future use
|
||||||
|
pub rcode: u8, // 0 for no error
|
||||||
|
pub qdcount: u16, // number of entries in the question section
|
||||||
|
pub ancount: u16, // number of resource records in the answer section
|
||||||
|
pub nscount: u16, // number of name server resource records in the authority records section
|
||||||
|
pub arcount: u16, // number of resource records in the additional records section
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header {
|
||||||
|
const DNS_HEADER_LEN: usize = 12;
|
||||||
|
|
||||||
|
// Serialize the header to a byte array
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
let mut buf = Vec::with_capacity(Header::DNS_HEADER_LEN);
|
||||||
|
|
||||||
|
buf.extend_from_slice(&self.id.to_be_bytes());
|
||||||
|
buf.push(
|
||||||
|
(self.qr as u8) << 7
|
||||||
|
| self.opcode << 3
|
||||||
|
| (self.aa as u8) << 2
|
||||||
|
| (self.tc as u8) << 1
|
||||||
|
| self.rd as u8,
|
||||||
|
);
|
||||||
|
buf.push((self.ra as u8) << 7 | self.z << 4 | self.rcode);
|
||||||
|
buf.extend_from_slice(&self.qdcount.to_be_bytes());
|
||||||
|
buf.extend_from_slice(&self.ancount.to_be_bytes());
|
||||||
|
buf.extend_from_slice(&self.nscount.to_be_bytes());
|
||||||
|
buf.extend_from_slice(&self.arcount.to_be_bytes());
|
||||||
|
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize the header from a byte array
|
||||||
|
pub fn from_bytes(buf: &[u8]) -> Result<Header, ErrorCondition> {
|
||||||
|
if buf.len() < Header::DNS_HEADER_LEN {
|
||||||
|
return Err(ErrorCondition::DeserializationErr(
|
||||||
|
"Buffer length is less than header length".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Header {
|
||||||
|
id: u16::from_be_bytes([buf[0], buf[1]]),
|
||||||
|
qr: (buf[2] & 0b1000_0000) != 0,
|
||||||
|
opcode: (buf[2] & 0b0111_1000) >> 3,
|
||||||
|
aa: (buf[2] & 0b0000_0100) != 0,
|
||||||
|
tc: (buf[2] & 0b0000_0010) != 0,
|
||||||
|
rd: (buf[2] & 0b0000_0001) != 0,
|
||||||
|
ra: (buf[3] & 0b1000_0000) != 0,
|
||||||
|
z: (buf[3] & 0b0111_0000) >> 4,
|
||||||
|
rcode: buf[3] & 0b0000_1111,
|
||||||
|
qdcount: u16::from_be_bytes([buf[4], buf[5]]),
|
||||||
|
ancount: u16::from_be_bytes([buf[6], buf[7]]),
|
||||||
|
nscount: u16::from_be_bytes([buf[8], buf[9]]),
|
||||||
|
arcount: u16::from_be_bytes([buf[10], buf[11]]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Question {
|
||||||
|
pub name: Vec<Label>,
|
||||||
|
pub qtype: Type,
|
||||||
|
pub qclass: Class,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Label(String);
|
||||||
|
|
||||||
|
impl Label {
|
||||||
|
pub fn new(label: &[u8]) -> Result<Self, ErrorCondition> {
|
||||||
|
match std::str::from_utf8(label) {
|
||||||
|
Ok(s) => Ok(Label(s.to_string())),
|
||||||
|
Err(_) => Err(ErrorCondition::InvalidLabel),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Type {
|
||||||
|
// Below are Resource Record Types and QTYPES
|
||||||
|
A = 1, // a host address
|
||||||
|
NS = 2, // an authoritative name server
|
||||||
|
MD = 3, // a mail destination (Obsolete - use MX)
|
||||||
|
MF = 4, // a mail forwarder (Obsolete - use MX)
|
||||||
|
CNAME = 5, // the canonical name for an alias
|
||||||
|
SOA = 6, // marks the start of a zone of authority
|
||||||
|
MB = 7, // a mailbox domain name (EXPERIMENTAL)
|
||||||
|
MG = 8, // a mail group member (EXPERIMENTAL)
|
||||||
|
MR = 9, // a mail rename domain name (EXPERIMENTAL)
|
||||||
|
NULL = 10, // a null RR (EXPERIMENTAL)
|
||||||
|
WKS = 11, // a well known service description
|
||||||
|
PTR = 12, // a domain name pointer
|
||||||
|
HINFO = 13, // host information
|
||||||
|
MINFO = 14, // mailbox or mail list information
|
||||||
|
MX = 15, // mail exchange
|
||||||
|
TXT = 16, // text strings
|
||||||
|
|
||||||
|
// Below are only QTYPES
|
||||||
|
AXFR = 252, // A request for a transfer of an entire zone
|
||||||
|
MAILB = 253, // A request for mailbox-related records (MB, MG or MR)
|
||||||
|
MAILA = 254, // A request for mail agent RRs (Obsolete - see MX)
|
||||||
|
_ALL_ = 255, // A request for all records
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Class {
|
||||||
|
// Below are Resource Record Classes and QCLASS
|
||||||
|
IN = 1, // the Internet
|
||||||
|
CS = 2, // the CSNET class (Obsolete - used only for examples in some obsolete RFCs)
|
||||||
|
CH = 3, // the CHAOS class
|
||||||
|
HS = 4, // Hesiod [Dyer 87]
|
||||||
|
|
||||||
|
// Below are only QCLASSES
|
||||||
|
_ALL_ = 255,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Type {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
let msg: &str = match self {
|
||||||
|
Type::A => "a host address",
|
||||||
|
Type::NS => "an authoritative name server",
|
||||||
|
Type::MD => "a mail destination (Obsolete - use MX)",
|
||||||
|
Type::MF => "a mail forwarder (Obsolete - use MX)",
|
||||||
|
Type::CNAME => "the canonical name for an alias",
|
||||||
|
Type::SOA => "marks the start of a zone of authority",
|
||||||
|
Type::MB => "a mailbox domain name (EXPERIMENTAL)",
|
||||||
|
Type::MG => "a mail group member (EXPERIMENTAL)",
|
||||||
|
Type::MR => "a mail rename domain name (EXPERIMENTAL)",
|
||||||
|
Type::NULL => "a null RR (EXPERIMENTAL)",
|
||||||
|
Type::WKS => "a well known service description",
|
||||||
|
Type::PTR => "a domain name pointer",
|
||||||
|
Type::HINFO => "host information",
|
||||||
|
Type::MINFO => "mailbox or mail list information",
|
||||||
|
Type::MX => "mail exchange",
|
||||||
|
Type::TXT => "text strings",
|
||||||
|
Type::AXFR => "A request for a transfer of an entire zone",
|
||||||
|
Type::MAILB => "A request for mailbox-related records (MB, MG or MR)",
|
||||||
|
Type::MAILA => "A request for mail agent RRs (Obsolete - see MX)",
|
||||||
|
Type::_ALL_ => "A request for all records",
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{}", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Type {
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> Result<Type, ErrorCondition> {
|
||||||
|
match u16::from_be_bytes([bytes[0], bytes[1]]) {
|
||||||
|
1 => Ok(Type::A),
|
||||||
|
2 => Ok(Type::NS),
|
||||||
|
3 => Ok(Type::MD),
|
||||||
|
4 => Ok(Type::MF),
|
||||||
|
5 => Ok(Type::CNAME),
|
||||||
|
6 => Ok(Type::SOA),
|
||||||
|
7 => Ok(Type::MB),
|
||||||
|
8 => Ok(Type::MG),
|
||||||
|
9 => Ok(Type::MR),
|
||||||
|
10 => Ok(Type::NULL),
|
||||||
|
11 => Ok(Type::WKS),
|
||||||
|
12 => Ok(Type::PTR),
|
||||||
|
13 => Ok(Type::HINFO),
|
||||||
|
14 => Ok(Type::MINFO),
|
||||||
|
15 => Ok(Type::MX),
|
||||||
|
16 => Ok(Type::TXT),
|
||||||
|
252 => Ok(Type::AXFR),
|
||||||
|
253 => Ok(Type::MAILB),
|
||||||
|
254 => Ok(Type::MAILA),
|
||||||
|
255 => Ok(Type::_ALL_),
|
||||||
|
n => Err(ErrorCondition::DeserializationErr(
|
||||||
|
format!("Unknown Question Type {}", n).to_string(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> [u8; 2] {
|
||||||
|
let num = match self {
|
||||||
|
Type::A => 1,
|
||||||
|
Type::NS => 2,
|
||||||
|
Type::MD => 3,
|
||||||
|
Type::MF => 4,
|
||||||
|
Type::CNAME => 5,
|
||||||
|
Type::SOA => 6,
|
||||||
|
Type::MB => 7,
|
||||||
|
Type::MG => 8,
|
||||||
|
Type::MR => 9,
|
||||||
|
Type::NULL => 10,
|
||||||
|
Type::WKS => 11,
|
||||||
|
Type::PTR => 12,
|
||||||
|
Type::HINFO => 13,
|
||||||
|
Type::MINFO => 14,
|
||||||
|
Type::MX => 15,
|
||||||
|
Type::TXT => 16,
|
||||||
|
Type::AXFR => 252,
|
||||||
|
Type::MAILB => 253,
|
||||||
|
Type::MAILA => 254,
|
||||||
|
Type::_ALL_ => 255,
|
||||||
|
};
|
||||||
|
|
||||||
|
u16::to_be_bytes(num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Class {
|
||||||
|
pub fn from_bytes(buf: &[u8]) -> Result<Self, ErrorCondition> {
|
||||||
|
let num = u16::from_be_bytes([buf[0], buf[1]]);
|
||||||
|
match num {
|
||||||
|
1 => Ok(Class::IN),
|
||||||
|
2 => Ok(Class::CS),
|
||||||
|
3 => Ok(Class::CH),
|
||||||
|
4 => Ok(Class::HS),
|
||||||
|
_ => Err(ErrorCondition::DeserializationErr(
|
||||||
|
format!("Unknown Question Class {}", num).to_string(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> [u8; 2] {
|
||||||
|
let num = match self {
|
||||||
|
Class::IN => 1,
|
||||||
|
Class::CS => 2,
|
||||||
|
Class::CH => 3,
|
||||||
|
Class::HS => 4,
|
||||||
|
Class::_ALL_ => 255,
|
||||||
|
};
|
||||||
|
|
||||||
|
u16::to_be_bytes(num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Question {
|
||||||
|
// The from_bytes() function reconstructs a Question struct by iterating through the buffer, extracting labels,
|
||||||
|
// parsing the query type and class.
|
||||||
|
pub fn from_bytes(buf: &[u8]) -> Result<Self, ErrorCondition> {
|
||||||
|
let mut index = 0;
|
||||||
|
let mut labels: Vec<Label> = Vec::new();
|
||||||
|
|
||||||
|
println!("Labels:");
|
||||||
|
while buf[index] != 0 {
|
||||||
|
let len = buf[index] as usize;
|
||||||
|
index += 1;
|
||||||
|
labels.push(Label::new(&buf[index..index + len])?);
|
||||||
|
println!("{:?}", labels); // For debugging purposes
|
||||||
|
index += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
index += 1;
|
||||||
|
|
||||||
|
let qtype = Type::from_bytes(&buf[index..index + 2])?;
|
||||||
|
index += 2;
|
||||||
|
let qclass = Class::from_bytes(&buf[index..index + 2])?;
|
||||||
|
|
||||||
|
Ok(Question {
|
||||||
|
name: labels,
|
||||||
|
qtype,
|
||||||
|
qclass,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
|
||||||
|
// Write the labels to the buffer and add . inbetween and end with 0
|
||||||
|
for label in &self.name {
|
||||||
|
buf.push(label.len() as u8);
|
||||||
|
buf.extend_from_slice(label.0.as_bytes());
|
||||||
|
}
|
||||||
|
buf.push(0);
|
||||||
|
|
||||||
|
// Write the question type and class to the buffer
|
||||||
|
buf.extend_from_slice(&self.qtype.to_bytes());
|
||||||
|
buf.extend_from_slice(&self.qclass.to_bytes());
|
||||||
|
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
}
|
||||||
52
__network/dns-server-tutorial/step2/src/main.rs
Normal file
52
__network/dns-server-tutorial/step2/src/main.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// src/main.rs
|
||||||
|
use std::net::UdpSocket;
|
||||||
|
|
||||||
|
mod dns;
|
||||||
|
use dns::{Header, Question};
|
||||||
|
|
||||||
|
// Debug print hex bytes of a buffer 16 bytes width followed by the ASCII representation of the bytes
|
||||||
|
fn debug_print_bytes(buf: &[u8]) {
|
||||||
|
for (i, chunk) in buf.chunks(16).enumerate() {
|
||||||
|
print!("{:08x}: ", i * 16);
|
||||||
|
for byte in chunk {
|
||||||
|
print!("{:02x} ", byte);
|
||||||
|
}
|
||||||
|
for _ in 0..(16 - chunk.len()) {
|
||||||
|
print!(" ");
|
||||||
|
}
|
||||||
|
print!(" ");
|
||||||
|
for byte in chunk {
|
||||||
|
if *byte >= 32 && *byte <= 126 {
|
||||||
|
print!("{}", *byte as char);
|
||||||
|
} else {
|
||||||
|
print!(".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let socket = UdpSocket::bind("0.0.0.0:1053").expect("Could not bind to port 1053");
|
||||||
|
let mut buf = [0; 512];
|
||||||
|
|
||||||
|
println!("DNS server is running at port 1053");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (len, addr) = socket.recv_from(&mut buf).expect("Could not receive data");
|
||||||
|
|
||||||
|
println!("\nReceived query from {} with length {} bytes", addr, len);
|
||||||
|
println!("\n### DNS Query: ###");
|
||||||
|
debug_print_bytes(&buf[..len]);
|
||||||
|
|
||||||
|
let header = Header::from_bytes(&buf[..12]).expect("Could not parse DNS header");
|
||||||
|
println!("\n{:?}", header);
|
||||||
|
|
||||||
|
println!("\n### Question: ###");
|
||||||
|
debug_print_bytes(&buf[12..len]);
|
||||||
|
println!();
|
||||||
|
|
||||||
|
let question = Question::from_bytes(&buf[12..len]).expect("Could not parse DNS question");
|
||||||
|
println!("\n{:?}", question);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
__network/dns-server-tutorial/step3/Cargo.toml
Normal file
9
__network/dns-server-tutorial/step3/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "dns-server"
|
||||||
|
authors = ["Bob Peters <contact@rust-trends.com>"]
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.5.29", features = ["derive"] }
|
||||||
|
thiserror = "2.0.11"
|
||||||
349
__network/dns-server-tutorial/step3/src/dns.rs
Normal file
349
__network/dns-server-tutorial/step3/src/dns.rs
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
// src/dns.rs
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ErrorCondition {
|
||||||
|
#[error("Serialization Error: {0}")]
|
||||||
|
SerializationErr(String),
|
||||||
|
|
||||||
|
#[error("Deserialization Error: {0}")]
|
||||||
|
DeserializationErr(String),
|
||||||
|
|
||||||
|
#[error("Invalid Label")]
|
||||||
|
InvalidLabel,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maximum DNS message size without EDNS0
|
||||||
|
const MAX_DNS_MESSAGE_SIZE: usize = 512;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Header {
|
||||||
|
pub id: u16, // identifier
|
||||||
|
pub qr: bool, // 0 for query, 1 for response
|
||||||
|
pub opcode: u8, // 0 for standard query
|
||||||
|
pub aa: bool, // authoritative answer
|
||||||
|
pub tc: bool, // truncated message
|
||||||
|
pub rd: bool, // recursion desired
|
||||||
|
pub ra: bool, // recursion available
|
||||||
|
pub z: u8, // reserved for future use
|
||||||
|
pub rcode: u8, // 0 for no error
|
||||||
|
pub qdcount: u16, // number of entries in the question section
|
||||||
|
pub ancount: u16, // number of resource records in the answer section
|
||||||
|
pub nscount: u16, // number of name server resource records in the authority records section
|
||||||
|
pub arcount: u16, // number of resource records in the additional records section
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header {
|
||||||
|
const DNS_HEADER_LEN: usize = 12;
|
||||||
|
|
||||||
|
// Serialize the header to a byte array
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
let mut buf = Vec::with_capacity(Header::DNS_HEADER_LEN);
|
||||||
|
|
||||||
|
buf.extend_from_slice(&self.id.to_be_bytes());
|
||||||
|
buf.push(
|
||||||
|
(self.qr as u8) << 7
|
||||||
|
| self.opcode << 3
|
||||||
|
| (self.aa as u8) << 2
|
||||||
|
| (self.tc as u8) << 1
|
||||||
|
| self.rd as u8,
|
||||||
|
);
|
||||||
|
buf.push((self.ra as u8) << 7 | self.z << 4 | self.rcode);
|
||||||
|
buf.extend_from_slice(&self.qdcount.to_be_bytes());
|
||||||
|
buf.extend_from_slice(&self.ancount.to_be_bytes());
|
||||||
|
buf.extend_from_slice(&self.nscount.to_be_bytes());
|
||||||
|
buf.extend_from_slice(&self.arcount.to_be_bytes());
|
||||||
|
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize the header from a byte array
|
||||||
|
pub fn from_bytes(buf: &[u8]) -> Result<Header, ErrorCondition> {
|
||||||
|
if buf.len() < Header::DNS_HEADER_LEN {
|
||||||
|
return Err(ErrorCondition::DeserializationErr(
|
||||||
|
"Buffer length is less than header length".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Header {
|
||||||
|
id: u16::from_be_bytes([buf[0], buf[1]]),
|
||||||
|
qr: (buf[2] & 0b1000_0000) != 0,
|
||||||
|
opcode: (buf[2] & 0b0111_1000) >> 3,
|
||||||
|
aa: (buf[2] & 0b0000_0100) != 0,
|
||||||
|
tc: (buf[2] & 0b0000_0010) != 0,
|
||||||
|
rd: (buf[2] & 0b0000_0001) != 0,
|
||||||
|
ra: (buf[3] & 0b1000_0000) != 0,
|
||||||
|
z: (buf[3] & 0b0111_0000) >> 4,
|
||||||
|
rcode: buf[3] & 0b0000_1111,
|
||||||
|
qdcount: u16::from_be_bytes([buf[4], buf[5]]),
|
||||||
|
ancount: u16::from_be_bytes([buf[6], buf[7]]),
|
||||||
|
nscount: u16::from_be_bytes([buf[8], buf[9]]),
|
||||||
|
arcount: u16::from_be_bytes([buf[10], buf[11]]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Question {
|
||||||
|
pub name: Vec<Label>,
|
||||||
|
pub qtype: Type,
|
||||||
|
pub qclass: Class,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Label(String);
|
||||||
|
|
||||||
|
impl Label {
|
||||||
|
pub fn new(label: &[u8]) -> Result<Self, ErrorCondition> {
|
||||||
|
match std::str::from_utf8(label) {
|
||||||
|
Ok(s) => Ok(Label(s.to_string())),
|
||||||
|
Err(_) => Err(ErrorCondition::InvalidLabel),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Type {
|
||||||
|
// Below are Resource Record Types and QTYPES
|
||||||
|
A = 1, // a host address
|
||||||
|
NS = 2, // an authoritative name server
|
||||||
|
MD = 3, // a mail destination (Obsolete - use MX)
|
||||||
|
MF = 4, // a mail forwarder (Obsolete - use MX)
|
||||||
|
CNAME = 5, // the canonical name for an alias
|
||||||
|
SOA = 6, // marks the start of a zone of authority
|
||||||
|
MB = 7, // a mailbox domain name (EXPERIMENTAL)
|
||||||
|
MG = 8, // a mail group member (EXPERIMENTAL)
|
||||||
|
MR = 9, // a mail rename domain name (EXPERIMENTAL)
|
||||||
|
NULL = 10, // a null RR (EXPERIMENTAL)
|
||||||
|
WKS = 11, // a well known service description
|
||||||
|
PTR = 12, // a domain name pointer
|
||||||
|
HINFO = 13, // host information
|
||||||
|
MINFO = 14, // mailbox or mail list information
|
||||||
|
MX = 15, // mail exchange
|
||||||
|
TXT = 16, // text strings
|
||||||
|
|
||||||
|
// Below are only QTYPES
|
||||||
|
AXFR = 252, // A request for a transfer of an entire zone
|
||||||
|
MAILB = 253, // A request for mailbox-related records (MB, MG or MR)
|
||||||
|
MAILA = 254, // A request for mail agent RRs (Obsolete - see MX)
|
||||||
|
_ALL_ = 255, // A request for all records
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Class {
|
||||||
|
// Below are Resource Record Classes and QCLASS
|
||||||
|
IN = 1, // the Internet
|
||||||
|
CS = 2, // the CSNET class (Obsolete - used only for examples in some obsolete RFCs)
|
||||||
|
CH = 3, // the CHAOS class
|
||||||
|
HS = 4, // Hesiod [Dyer 87]
|
||||||
|
|
||||||
|
// Below are only QCLASSES
|
||||||
|
_ALL_ = 255,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Type {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
let msg: &str = match self {
|
||||||
|
Type::A => "a host address",
|
||||||
|
Type::NS => "an authoritative name server",
|
||||||
|
Type::MD => "a mail destination (Obsolete - use MX)",
|
||||||
|
Type::MF => "a mail forwarder (Obsolete - use MX)",
|
||||||
|
Type::CNAME => "the canonical name for an alias",
|
||||||
|
Type::SOA => "marks the start of a zone of authority",
|
||||||
|
Type::MB => "a mailbox domain name (EXPERIMENTAL)",
|
||||||
|
Type::MG => "a mail group member (EXPERIMENTAL)",
|
||||||
|
Type::MR => "a mail rename domain name (EXPERIMENTAL)",
|
||||||
|
Type::NULL => "a null RR (EXPERIMENTAL)",
|
||||||
|
Type::WKS => "a well known service description",
|
||||||
|
Type::PTR => "a domain name pointer",
|
||||||
|
Type::HINFO => "host information",
|
||||||
|
Type::MINFO => "mailbox or mail list information",
|
||||||
|
Type::MX => "mail exchange",
|
||||||
|
Type::TXT => "text strings",
|
||||||
|
Type::AXFR => "A request for a transfer of an entire zone",
|
||||||
|
Type::MAILB => "A request for mailbox-related records (MB, MG or MR)",
|
||||||
|
Type::MAILA => "A request for mail agent RRs (Obsolete - see MX)",
|
||||||
|
Type::_ALL_ => "A request for all records",
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{}", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Type {
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> Result<Type, ErrorCondition> {
|
||||||
|
match u16::from_be_bytes([bytes[0], bytes[1]]) {
|
||||||
|
1 => Ok(Type::A),
|
||||||
|
2 => Ok(Type::NS),
|
||||||
|
3 => Ok(Type::MD),
|
||||||
|
4 => Ok(Type::MF),
|
||||||
|
5 => Ok(Type::CNAME),
|
||||||
|
6 => Ok(Type::SOA),
|
||||||
|
7 => Ok(Type::MB),
|
||||||
|
8 => Ok(Type::MG),
|
||||||
|
9 => Ok(Type::MR),
|
||||||
|
10 => Ok(Type::NULL),
|
||||||
|
11 => Ok(Type::WKS),
|
||||||
|
12 => Ok(Type::PTR),
|
||||||
|
13 => Ok(Type::HINFO),
|
||||||
|
14 => Ok(Type::MINFO),
|
||||||
|
15 => Ok(Type::MX),
|
||||||
|
16 => Ok(Type::TXT),
|
||||||
|
252 => Ok(Type::AXFR),
|
||||||
|
253 => Ok(Type::MAILB),
|
||||||
|
254 => Ok(Type::MAILA),
|
||||||
|
255 => Ok(Type::_ALL_),
|
||||||
|
n => Err(ErrorCondition::DeserializationErr(
|
||||||
|
format!("Unknown Question Type {}", n).to_string(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> [u8; 2] {
|
||||||
|
let num = match self {
|
||||||
|
Type::A => 1,
|
||||||
|
Type::NS => 2,
|
||||||
|
Type::MD => 3,
|
||||||
|
Type::MF => 4,
|
||||||
|
Type::CNAME => 5,
|
||||||
|
Type::SOA => 6,
|
||||||
|
Type::MB => 7,
|
||||||
|
Type::MG => 8,
|
||||||
|
Type::MR => 9,
|
||||||
|
Type::NULL => 10,
|
||||||
|
Type::WKS => 11,
|
||||||
|
Type::PTR => 12,
|
||||||
|
Type::HINFO => 13,
|
||||||
|
Type::MINFO => 14,
|
||||||
|
Type::MX => 15,
|
||||||
|
Type::TXT => 16,
|
||||||
|
Type::AXFR => 252,
|
||||||
|
Type::MAILB => 253,
|
||||||
|
Type::MAILA => 254,
|
||||||
|
Type::_ALL_ => 255,
|
||||||
|
};
|
||||||
|
|
||||||
|
u16::to_be_bytes(num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Class {
|
||||||
|
pub fn from_bytes(buf: &[u8]) -> Result<Self, ErrorCondition> {
|
||||||
|
let num = u16::from_be_bytes([buf[0], buf[1]]);
|
||||||
|
match num {
|
||||||
|
1 => Ok(Class::IN),
|
||||||
|
2 => Ok(Class::CS),
|
||||||
|
3 => Ok(Class::CH),
|
||||||
|
4 => Ok(Class::HS),
|
||||||
|
_ => Err(ErrorCondition::DeserializationErr(
|
||||||
|
format!("Unknown Question Class {}", num).to_string(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> [u8; 2] {
|
||||||
|
let num = match self {
|
||||||
|
Class::IN => 1,
|
||||||
|
Class::CS => 2,
|
||||||
|
Class::CH => 3,
|
||||||
|
Class::HS => 4,
|
||||||
|
Class::_ALL_ => 255,
|
||||||
|
};
|
||||||
|
|
||||||
|
u16::to_be_bytes(num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Question {
|
||||||
|
// The from_bytes() function reconstructs a Question struct by iterating through the buffer, extracting labels,
|
||||||
|
// parsing the query type and class.
|
||||||
|
pub fn from_bytes(buf: &[u8]) -> Result<Self, ErrorCondition> {
|
||||||
|
let mut index = 0;
|
||||||
|
let mut labels: Vec<Label> = Vec::new();
|
||||||
|
|
||||||
|
println!("Labels:");
|
||||||
|
while buf[index] != 0 {
|
||||||
|
let len = buf[index] as usize;
|
||||||
|
index += 1;
|
||||||
|
labels.push(Label::new(&buf[index..index + len])?);
|
||||||
|
println!("{:?}", labels); // For debugging purposes
|
||||||
|
index += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
index += 1;
|
||||||
|
|
||||||
|
let qtype = Type::from_bytes(&buf[index..index + 2])?;
|
||||||
|
index += 2;
|
||||||
|
let qclass = Class::from_bytes(&buf[index..index + 2])?;
|
||||||
|
|
||||||
|
Ok(Question {
|
||||||
|
name: labels,
|
||||||
|
qtype,
|
||||||
|
qclass,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
|
||||||
|
// Write the labels to the buffer and add . inbetween and end with 0
|
||||||
|
for label in &self.name {
|
||||||
|
buf.push(label.len() as u8);
|
||||||
|
buf.extend_from_slice(label.0.as_bytes());
|
||||||
|
}
|
||||||
|
buf.push(0);
|
||||||
|
|
||||||
|
// Write the question type and class to the buffer
|
||||||
|
buf.extend_from_slice(&self.qtype.to_bytes());
|
||||||
|
buf.extend_from_slice(&self.qclass.to_bytes());
|
||||||
|
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ResourceRecord {
|
||||||
|
pub name: String,
|
||||||
|
pub rtype: Type,
|
||||||
|
pub rclass: Class,
|
||||||
|
pub ttl: u32,
|
||||||
|
pub rdlength: u16,
|
||||||
|
pub rdata: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ResourceRecord {
|
||||||
|
fn default() -> Self {
|
||||||
|
ResourceRecord {
|
||||||
|
name: String::from("www.rust-trends.com"),
|
||||||
|
rtype: Type::A,
|
||||||
|
rclass: Class::IN,
|
||||||
|
ttl: 60,
|
||||||
|
rdlength: 4,
|
||||||
|
rdata: Vec::from([172, 67, 221, 148]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResourceRecord {
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
let mut buf = Vec::with_capacity(MAX_DNS_MESSAGE_SIZE);
|
||||||
|
|
||||||
|
self.name.split('.').for_each(|label| {
|
||||||
|
buf.push(label.len() as u8);
|
||||||
|
buf.extend_from_slice(label.as_bytes());
|
||||||
|
});
|
||||||
|
|
||||||
|
buf.push(0);
|
||||||
|
buf.extend_from_slice(&self.rtype.to_bytes());
|
||||||
|
buf.extend_from_slice(&self.rclass.to_bytes());
|
||||||
|
buf.extend_from_slice(&self.ttl.to_be_bytes());
|
||||||
|
buf.extend_from_slice(&self.rdlength.to_be_bytes());
|
||||||
|
buf.extend_from_slice(&self.rdata);
|
||||||
|
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
}
|
||||||
84
__network/dns-server-tutorial/step3/src/main.rs
Normal file
84
__network/dns-server-tutorial/step3/src/main.rs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
// src/main.rs
|
||||||
|
use std::net::UdpSocket;
|
||||||
|
|
||||||
|
mod dns;
|
||||||
|
use dns::{Header, Question, ResourceRecord};
|
||||||
|
|
||||||
|
// Debug print hex bytes of a buffer 16 bytes width followed by the ASCII representation of the bytes
|
||||||
|
fn debug_print_bytes(buf: &[u8]) {
|
||||||
|
for (i, chunk) in buf.chunks(16).enumerate() {
|
||||||
|
print!("{:08x}: ", i * 16);
|
||||||
|
for byte in chunk {
|
||||||
|
print!("{:02x} ", byte);
|
||||||
|
}
|
||||||
|
for _ in 0..(16 - chunk.len()) {
|
||||||
|
print!(" ");
|
||||||
|
}
|
||||||
|
print!(" ");
|
||||||
|
for byte in chunk {
|
||||||
|
if *byte >= 32 && *byte <= 126 {
|
||||||
|
print!("{}", *byte as char);
|
||||||
|
} else {
|
||||||
|
print!(".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let socket = UdpSocket::bind("0.0.0.0:1053").expect("Could not bind to port 1053");
|
||||||
|
let mut buf = [0; 512];
|
||||||
|
|
||||||
|
println!("DNS server is running at port 1053");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (len, addr) = socket.recv_from(&mut buf).expect("Could not receive data");
|
||||||
|
|
||||||
|
println!("\nReceived query from {} with length {} bytes", addr, len);
|
||||||
|
println!("\n### DNS Query: ###");
|
||||||
|
debug_print_bytes(&buf[..len]);
|
||||||
|
|
||||||
|
let header = Header::from_bytes(&buf[..12]).expect("Could not parse DNS header");
|
||||||
|
println!("\n{:?}", header);
|
||||||
|
|
||||||
|
println!("\n### Question: ###");
|
||||||
|
debug_print_bytes(&buf[12..len]);
|
||||||
|
println!();
|
||||||
|
|
||||||
|
let question = Question::from_bytes(&buf[12..len]).expect("Could not parse DNS question");
|
||||||
|
println!("\n{:?}", question);
|
||||||
|
|
||||||
|
// We parsed the DNS query and question, now we can respond to it
|
||||||
|
let answer = ResourceRecord::default();
|
||||||
|
|
||||||
|
println!("{:?}", answer);
|
||||||
|
|
||||||
|
let response_header = Header {
|
||||||
|
id: header.id,
|
||||||
|
qr: true, // It is a query response
|
||||||
|
opcode: header.opcode, // Standard query
|
||||||
|
aa: false, // Not authoritative
|
||||||
|
tc: false, // Not truncated
|
||||||
|
rd: header.rd, // Recursion desired
|
||||||
|
ra: false, // Recursion not available
|
||||||
|
z: 0, // Reserved
|
||||||
|
rcode: if header.opcode == 0 { 0 } else { 4 },
|
||||||
|
qdcount: 1, // Question count we assume is 1
|
||||||
|
ancount: 1, // Answer count is 1
|
||||||
|
nscount: 0, // Name server count is 0
|
||||||
|
arcount: 0, // Additional record count is 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a response message with the header and question
|
||||||
|
let mut response: Vec<u8> = Vec::new();
|
||||||
|
response.extend_from_slice(&response_header.to_bytes());
|
||||||
|
response.extend_from_slice(&question.to_bytes());
|
||||||
|
response.extend_from_slice(&answer.to_bytes());
|
||||||
|
|
||||||
|
// Send the response back to the client
|
||||||
|
socket
|
||||||
|
.send_to(&response, addr)
|
||||||
|
.expect("Could not send response");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,3 +35,6 @@ fn main() -> XResult<()> {
|
|||||||
opt_result!(fs::write("README.md", readme), "Write README.md failed: {}");
|
opt_result!(fs::write("README.md", readme), "Write README.md failed: {}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20250308T212308+08:00.MEQCIBblLBxZYIuyjfsrCbQo
|
||||||
|
// oGiiiUGtdmjOo0p1s9DfPewYAiBZfpz6Nj737qGfejAPmnO03+NGuR0wc51NqXcer2WxKA==
|
||||||
|
|||||||
Reference in New Issue
Block a user