Compare commits

..

2 Commits

24 changed files with 498 additions and 206 deletions

View File

@ -31,4 +31,26 @@ jobs:
- name: Cargo Test - name: Cargo Test
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: test command: test
doc:
runs-on: ubuntu-latest
needs: [ build ] # for ignoring bad builds
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build Docs
run: cargo doc --no-deps --document-private-items
- name: Deploy To Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./target/doc

60
Cargo.lock generated
View File

@ -50,6 +50,12 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.4.18" version = "4.4.18"
@ -122,6 +128,7 @@ dependencies = [
"clap", "clap",
"dnstplib", "dnstplib",
"log", "log",
"rand",
"simplelog", "simplelog",
] ]
@ -133,6 +140,17 @@ dependencies = [
"urlencoding", "urlencoding",
] ]
[[package]]
name = "getrandom"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.4.1" version = "0.4.1"
@ -172,6 +190,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.76" version = "1.0.76"
@ -190,6 +214,36 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.196" version = "1.0.196"
@ -296,6 +350,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@ -1,3 +1,9 @@
# dnstp # dnstp
Transmitting files over dns piece by piece. [![Build Binaries](https://github.com/Sarsoo/dnstp/actions/workflows/build.yml/badge.svg)](https://github.com/Sarsoo/dnstp/actions/workflows/build.yml)
Transmitting files over dns piece by piece. Should be a pretty subtle way of sending files.
I remember I was listening to, I think, [Security This Week with Carl Franklin](https://securitythisweek.com/). One of the hosts mentioned doing data exfiltration from a tight network by breaking the file down and sending it over DNS. I wanted to see how this could work. [Read More](https://www.securityweek.com/multigrain-pos-malware-exfiltrates-card-data-over-dns/).
I also wanted to play with a big rust project for standard targets with threading. Although I had a lot of fun with my browser-based checkers game, [Draught](https://draught.sarsoo.xyz), working against WASM has some restrictions.

View File

@ -9,4 +9,5 @@ edition = "2021"
dnstplib = { path = "../dnstp" } dnstplib = { path = "../dnstp" }
clap = { version = "4.4.18", features = ["derive"] } clap = { version = "4.4.18", features = ["derive"] }
log = "0.4.20" log = "0.4.20"
simplelog = "0.12.1" simplelog = "0.12.1"
rand = "0.8.5"

View File

@ -4,10 +4,14 @@ use std::thread;
use std::time::Duration; use std::time::Duration;
use clap::Parser; use clap::Parser;
use log::{info, LevelFilter}; use log::{info, LevelFilter};
use rand::RngCore;
use simplelog::*; use simplelog::*;
use dnstplib::dns_socket::DNSSocket; use dnstplib::message::header::{Direction, DNSHeader, Opcode, ResponseCode};
use dnstplib::raw_request::NetworkMessage; use dnstplib::message::question::{DNSQuestion, QClass, QType};
use dnstplib::response_processor::ResponseProcesor; use dnstplib::message::request::DNSRequest;
use dnstplib::net::socket::DNSSocket;
use dnstplib::net::raw_request::NetworkMessage;
use dnstplib::processor::ResponseProcesor;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
@ -40,18 +44,41 @@ fn main() {
socket.run_rx(processor.get_message_channel().expect("couldn't get message processing channel")); socket.run_rx(processor.get_message_channel().expect("couldn't get message processing channel"));
let mut rng = rand::thread_rng();
loop { loop {
info!("sending..."); info!("sending...");
let mut send_buf = [0; 512]; let message = DNSRequest {
send_buf[0] = 'a' as u8; header: DNSHeader {
send_buf[1] = 'b' as u8; id: rng.next_u32() as u16,
send_buf[2] = 'c' as u8; direction: Direction::Request,
send_buf[3] = 'd' as u8; opcode: Opcode::Query,
authoritative: false,
truncation: false,
recursion_desired: true,
recursion_available: false,
valid_zeroes: true,
response: ResponseCode::NoError,
question_count: 1,
answer_record_count: 0,
authority_record_count: 0,
additional_record_count: 0
},
questions: vec![
DNSQuestion {
qname: "duck.com".to_string(),
qtype: QType::A,
qclass: QClass::Internet
}
],
peer: address
};
let bytes = message.to_bytes();
tx_channel.send(Box::from(NetworkMessage { tx_channel.send(Box::from(NetworkMessage {
buffer: Box::from(send_buf), buffer: Box::from(bytes),
peer: args.address.parse().unwrap() peer: args.address.parse().unwrap()
})); }));

View File

@ -6,8 +6,8 @@ use simplelog::*;
use std::fs::File; use std::fs::File;
use std::net::SocketAddr; use std::net::SocketAddr;
use dnstplib::dns_socket::DNSSocket; use dnstplib::net::socket::DNSSocket;
use dnstplib::request_processor::RequestProcesor; use dnstplib::processor::RequestProcesor;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]

29
dnstp/src/byte.rs Normal file
View File

@ -0,0 +1,29 @@
const BYTEMASK_32: u32 = 0b11111111;
const BYTEMASK_16: u16 = 0b11111111;
pub fn two_byte_extraction(buffer: &[u8], idx: usize) -> u16
{
((buffer[idx] as u16) << 8) | buffer[idx + 1] as u16
}
pub fn two_byte_split(num: u16) -> (u8, u8)
{
((num >> 8) as u8,
(num & BYTEMASK_16) as u8)
}
pub fn four_byte_split(num: u32) -> (u8, u8, u8, u8)
{
((num >> 24) as u8,
((num >> 16) & BYTEMASK_32) as u8,
((num >> 8) & BYTEMASK_32) as u8,
(num & BYTEMASK_32) as u8)
}
pub fn apply_split_bytes(buffer: &mut [u8], value: u16, index: usize)
{
let val = two_byte_split(value);
buffer[index] = val.0;
buffer[index + 1] = val.1;
}

View File

@ -1,10 +0,0 @@
use std::net::SocketAddr;
use crate::dns_header::DNSHeader;
use crate::dns_question::DNSQuestion;
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug)]
pub struct DNSRequest {
pub header: DNSHeader,
pub questions: Vec<DNSQuestion>,
pub peer: SocketAddr
}

View File

@ -1,8 +1,7 @@
pub mod dns_socket;
pub mod request_parser; pub mod request_parser;
pub mod dns_header;
pub mod request_processor; mod byte;
pub mod response_processor; pub mod processor;
pub mod raw_request; pub mod message;
pub mod dns_question; pub mod net;
pub mod dns_request; mod string;

View File

@ -0,0 +1,43 @@
use crate::byte::{four_byte_split, two_byte_split};
use crate::message::question::{QClass, QType};
use crate::string::encode_domain_name;
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug)]
pub struct DNSAnswer {
pub name: String,
pub answer_type: QType,
pub class: QClass,
pub ttl: u32,
pub rd_length: u16,
pub r_data: Vec<u8>
}
impl DNSAnswer {
pub fn to_bytes(& self) -> Vec<u8>
{
let mut ret = encode_domain_name(&self.name);
let type_split = two_byte_split(self.answer_type as u16);
ret.push(type_split.0);
ret.push(type_split.1);
let class_split = two_byte_split(self.class as u16);
ret.push(class_split.0);
ret.push(class_split.1);
let (ttl_1, ttl_2, ttl_3, ttl_4) = four_byte_split(self.ttl);
ret.push(ttl_1);
ret.push(ttl_2);
ret.push(ttl_3);
ret.push(ttl_4);
let rd_length_split = two_byte_split(self.rd_length);
ret.push(rd_length_split.0);
ret.push(rd_length_split.1);
ret.append(&mut self.r_data.clone());
return ret
}
}

View File

@ -1,4 +1,6 @@
use std::convert::TryFrom; use std::convert::TryFrom;
use crate::byte::apply_split_bytes;
use crate::message::header::Direction::Response;
pub const HEADER_SIZE: usize = 12; pub const HEADER_SIZE: usize = 12;
@ -17,7 +19,7 @@ pub enum Opcode {
} }
impl TryFrom<u16> for Opcode { impl TryFrom<u16> for Opcode {
type Error = (); type Error = u16;
fn try_from(v: u16) -> Result<Self, Self::Error> { fn try_from(v: u16) -> Result<Self, Self::Error> {
match v { match v {
@ -25,7 +27,7 @@ impl TryFrom<u16> for Opcode {
x if x == Opcode::RQuery as u16 => Ok(Opcode::RQuery), x if x == Opcode::RQuery as u16 => Ok(Opcode::RQuery),
x if x == Opcode::Status as u16 => Ok(Opcode::Status), x if x == Opcode::Status as u16 => Ok(Opcode::Status),
x if x == Opcode::Reserved as u16 => Ok(Opcode::Reserved), x if x == Opcode::Reserved as u16 => Ok(Opcode::Reserved),
_ => Err(()), _ => Err(v),
} }
} }
} }
@ -46,7 +48,7 @@ pub enum ResponseCode {
} }
impl TryFrom<u16> for ResponseCode { impl TryFrom<u16> for ResponseCode {
type Error = (); type Error = u16;
fn try_from(v: u16) -> Result<Self, Self::Error> { fn try_from(v: u16) -> Result<Self, Self::Error> {
match v { match v {
@ -61,7 +63,7 @@ impl TryFrom<u16> for ResponseCode {
x if x == ResponseCode::NXRRSet as u16 => Ok(ResponseCode::NXRRSet), x if x == ResponseCode::NXRRSet as u16 => Ok(ResponseCode::NXRRSet),
x if x == ResponseCode::NotAuth as u16 => Ok(ResponseCode::NotAuth), x if x == ResponseCode::NotAuth as u16 => Ok(ResponseCode::NotAuth),
x if x == ResponseCode::NotZone as u16 => Ok(ResponseCode::NotZone), x if x == ResponseCode::NotZone as u16 => Ok(ResponseCode::NotZone),
_ => Err(()), _ => Err(v),
} }
} }
} }
@ -81,4 +83,37 @@ pub struct DNSHeader {
pub answer_record_count: u16, pub answer_record_count: u16,
pub authority_record_count: u16, pub authority_record_count: u16,
pub additional_record_count: u16, pub additional_record_count: u16,
}
impl DNSHeader {
pub fn to_bytes(&self) -> [u8; 12]
{
let mut header_bytes: [u8; 12] = [0; 12];
apply_split_bytes(&mut header_bytes, self.id, crate::request_parser::ID_START);
let mut flags: u16 = 0;
if self.direction == Response {
flags |= 0b1 << crate::request_parser::DIRECTION_SHIFT;
}
flags |= (self.opcode as u16) << crate::request_parser::OPCODE_SHIFT;
flags |= (self.authoritative as u16) << crate::request_parser::AUTHORITATIVE_SHIFT;
flags |= (self.truncation as u16) << crate::request_parser::TRUNCATION_SHIFT;
flags |= (self.recursion_desired as u16) << crate::request_parser::RECURSION_DESIRED_SHIFT;
flags |= (self.recursion_available as u16) << crate::request_parser::RECURSION_AVAILABLE_SHIFT;
flags |= self.response as u16;
apply_split_bytes(&mut header_bytes, flags, crate::request_parser::FLAGS_START);
apply_split_bytes(&mut header_bytes, self.question_count, crate::request_parser::QUESTION_COUNT_START);
apply_split_bytes(&mut header_bytes, self.answer_record_count, crate::request_parser::ANSWER_RECORD_COUNT_START);
apply_split_bytes(&mut header_bytes, self.authority_record_count, crate::request_parser::AUTHORITY_RECORD_COUNT_START);
apply_split_bytes(&mut header_bytes, self.additional_record_count, crate::request_parser::ADDITIONAL_RECORD_COUNT_START);
header_bytes
}
} }

6
dnstp/src/message/mod.rs Normal file
View File

@ -0,0 +1,6 @@
pub mod header;
pub mod question;
pub mod request;
pub mod answer;
pub mod response;

View File

@ -1,5 +1,6 @@
use std::ops::Sub; use std::ops::Sub;
use urlencoding::{encode, decode}; use urlencoding::{encode, decode};
use crate::string::encode_domain_name;
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)] #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)]
pub enum QType { pub enum QType {
@ -19,7 +20,7 @@ pub enum QType {
} }
impl TryFrom<u8> for QType { impl TryFrom<u8> for QType {
type Error = (); type Error = u8;
fn try_from(v: u8) -> Result<Self, Self::Error> { fn try_from(v: u8) -> Result<Self, Self::Error> {
match v { match v {
@ -36,7 +37,7 @@ impl TryFrom<u8> for QType {
x if x == QType::RP as u8 => Ok(QType::RP), x if x == QType::RP as u8 => Ok(QType::RP),
x if x == QType::AAAA as u8 => Ok(QType::AAAA), x if x == QType::AAAA as u8 => Ok(QType::AAAA),
x if x == QType::SRV as u8 => Ok(QType::SRV), x if x == QType::SRV as u8 => Ok(QType::SRV),
_ => Err(()), _ => Err(v),
} }
} }
} }
@ -49,23 +50,23 @@ pub enum QClass {
} }
impl TryFrom<u8> for QClass { impl TryFrom<u8> for QClass {
type Error = (); type Error = u8;
fn try_from(v: u8) -> Result<Self, Self::Error> { fn try_from(v: u8) -> Result<Self, Self::Error> {
match v { match v {
x if x == QClass::Internet as u8 => Ok(QClass::Internet), x if x == QClass::Internet as u8 => Ok(QClass::Internet),
x if x == QClass::Chaos as u8 => Ok(QClass::Chaos), x if x == QClass::Chaos as u8 => Ok(QClass::Chaos),
x if x == QClass::Hesiod as u8 => Ok(QClass::Hesiod), x if x == QClass::Hesiod as u8 => Ok(QClass::Hesiod),
_ => Err(()), _ => Err(v),
} }
} }
} }
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug)] #[derive(Ord, PartialOrd, Eq, PartialEq, Debug)]
pub struct DNSQuestion { pub struct DNSQuestion {
qname: String, pub qname: String,
qtype: QType, pub qtype: QType,
qclass: QClass pub qclass: QClass
} }
impl DNSQuestion { impl DNSQuestion {
@ -80,20 +81,7 @@ impl DNSQuestion {
pub fn to_bytes(&self) -> Vec<u8> pub fn to_bytes(&self) -> Vec<u8>
{ {
let mut ret: Vec<u8> = Vec::with_capacity(self.qname.len() + 2 + 3); let mut ret = encode_domain_name(&self.qname);
for part in self.qname.split(".")
{
let encoded_string = encode(part);
let count = encoded_string.len();
ret.push(count as u8);
for x in encoded_string.bytes() {
ret.push(x);
};
}
ret.push(0);
ret.push(self.qtype as u8); ret.push(self.qtype as u8);
ret.push(self.qclass as u8); ret.push(self.qclass as u8);
@ -102,11 +90,30 @@ impl DNSQuestion {
} }
} }
pub fn questions_from_bytes(bytes: Vec<u8>, total_questions: u16) -> Result<Vec<DNSQuestion>, ()> pub fn questions_to_bytes(questions: &Vec<DNSQuestion>) -> Vec<u8>
{
let mut ret = Vec::with_capacity(20);
for q in questions
{
ret.append(&mut q.to_bytes());
}
ret
}
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug)]
pub enum QuestionParseError {
ShortLength(usize),
QTypeParse(u8),
QClassParse(u8)
}
pub fn questions_from_bytes(bytes: Vec<u8>, total_questions: u16) -> Result<Vec<DNSQuestion>, QuestionParseError>
{ {
if bytes.len() < 4 if bytes.len() < 4
{ {
return Err(()); return Err(QuestionParseError::ShortLength(bytes.len()));
} }
let mut questions: Vec<DNSQuestion> = Vec::with_capacity(total_questions as usize); let mut questions: Vec<DNSQuestion> = Vec::with_capacity(total_questions as usize);
@ -150,7 +157,7 @@ pub fn questions_from_bytes(bytes: Vec<u8>, total_questions: u16) -> Result<Vec<
match (qtype_b.try_into(), byte.try_into()) { match (qtype_b.try_into(), byte.try_into()) {
(Ok(qtype), Ok(qclass)) => { (Ok(qtype), Ok(qclass)) => {
questions.push(DNSQuestion { questions.push(DNSQuestion {
qname: String::from_utf8(current_query.unwrap()).unwrap(), qname: decode(String::from_utf8(current_query.unwrap()).unwrap().as_str()).unwrap().to_string(),
qtype, qtype,
qclass qclass
}); });
@ -162,8 +169,11 @@ pub fn questions_from_bytes(bytes: Vec<u8>, total_questions: u16) -> Result<Vec<
current_qclass = None; current_qclass = None;
trailers_reached = false; trailers_reached = false;
} }
_ => { (Err(qtype_e), _) => {
return Err(()); return Err(QuestionParseError::QTypeParse(qtype_e));
}
(_, Err(qclass_e)) => {
return Err(QuestionParseError::QClassParse(qclass_e));
} }
} }
} }

View File

@ -0,0 +1,23 @@
use std::net::SocketAddr;
use crate::message::header::DNSHeader;
use crate::message::question::{DNSQuestion, questions_to_bytes};
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug)]
pub struct DNSRequest {
pub header: DNSHeader,
pub questions: Vec<DNSQuestion>,
pub peer: SocketAddr
}
impl DNSRequest {
pub fn to_bytes(& self) -> Vec<u8>
{
let mut header_bytes = self.header.to_bytes().to_vec();
let mut body_bytes = questions_to_bytes(&self.questions);
header_bytes.append(&mut body_bytes);
return header_bytes
}
}

View File

@ -0,0 +1,25 @@
use std::net::SocketAddr;
use crate::message::answer::DNSAnswer;
use crate::message::header::DNSHeader;
use crate::message::question::{DNSQuestion, questions_to_bytes};
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug)]
pub struct DNSResponse {
pub header: DNSHeader,
pub questions: Vec<DNSQuestion>,
pub answers: Vec<DNSAnswer>,
pub peer: SocketAddr
}
impl DNSResponse {
pub fn to_bytes(& self) -> Vec<u8>
{
let mut header_bytes = self.header.to_bytes().to_vec();
let mut body_bytes = questions_to_bytes(&self.questions);
header_bytes.append(&mut body_bytes);
return header_bytes
}
}

6
dnstp/src/net/mod.rs Normal file
View File

@ -0,0 +1,6 @@
pub mod socket;
// pub mod processor;
//
// pub mod processor::request_processor;
// pub mod processor::response_processor;
pub mod raw_request;

View File

@ -3,6 +3,6 @@ use std::net::SocketAddr;
pub type NetworkMessagePtr = Box<NetworkMessage>; pub type NetworkMessagePtr = Box<NetworkMessage>;
pub struct NetworkMessage { pub struct NetworkMessage {
pub buffer: Box<[u8; 512]>, pub buffer: Box<Vec<u8>>,
pub peer: SocketAddr pub peer: SocketAddr
} }

View File

@ -2,12 +2,10 @@ use std::net::{SocketAddr, UdpSocket};
use std::thread; use std::thread;
use std::thread::{JoinHandle}; use std::thread::{JoinHandle};
use log::{debug, error, info}; use log::{debug, error, info};
use std::str;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender, TryRecvError}; use std::sync::mpsc::{Receiver, Sender, TryRecvError};
use crate::dns_header::HEADER_SIZE; use crate::message::header::HEADER_SIZE;
use crate::raw_request::{NetworkMessage, NetworkMessagePtr}; use crate::net::raw_request::{NetworkMessage, NetworkMessagePtr};
pub struct DNSSocket { pub struct DNSSocket {
addresses: Vec<SocketAddr>, addresses: Vec<SocketAddr>,
@ -77,13 +75,12 @@ impl DNSSocket {
Some(s) => { Some(s) => {
let mut cancelled = false; let mut cancelled = false;
while !cancelled { while !cancelled {
let mut buf = Box::new([0; 512]); let mut buf = Box::new(Vec::with_capacity(512));
buf.resize(512, 0);
let res = s.recv_from(&mut (*buf)); let res = s.recv_from(&mut (*buf));
match res { match res {
Ok((read_count, peer)) => { Ok((read_count, peer)) => {
// let res_str = str::from_utf8(&(*buf)).unwrap();
// info!("received [{}] from [{}]", res_str, peer);
if read_count > HEADER_SIZE { if read_count > HEADER_SIZE {
message_sender.send(Box::new(NetworkMessage { message_sender.send(Box::new(NetworkMessage {
@ -131,7 +128,6 @@ impl DNSSocket {
while !cancelled { while !cancelled {
for m in &msg_rx { for m in &msg_rx {
info!("sending [{}] to [{}]", str::from_utf8(&(*(*m).buffer)).unwrap(), (*m).peer);
if let Err(e) = s.send_to(&(*m.buffer), m.peer){ if let Err(e) = s.send_to(&(*m.buffer), m.peer){
error!("error sending response to [{}], {}", m.peer, e); error!("error sending response to [{}], {}", m.peer, e);
} }

View File

@ -0,0 +1,6 @@
pub mod request;
pub mod response;
pub use request::RequestProcesor;
pub use response::ResponseProcesor;

View File

@ -0,0 +1,78 @@
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
use std::thread;
use log::{error, info};
use crate::message::question::QuestionParseError;
use crate::net::raw_request::NetworkMessagePtr;
use crate::request_parser::{HeaderParseError, parse_request, RequestParseError};
pub struct RequestProcesor {
message_channel: Option<Sender<NetworkMessagePtr>>
}
impl RequestProcesor {
pub fn new() -> RequestProcesor {
RequestProcesor{
message_channel: None
}
}
pub fn run(&mut self, sending_channel: Sender<NetworkMessagePtr>)
{
let (tx, rx): (Sender<NetworkMessagePtr>, Receiver<NetworkMessagePtr>) = mpsc::channel();
self.message_channel = Some(tx);
thread::spawn(move || {
for m in rx
{
let peer = m.peer.clone();
match parse_request(*m) {
Ok(r) => {
info!("received dns message: {:?}", r);
}
Err(e) => {
match e {
RequestParseError::HeaderParse(he) => {
match he {
HeaderParseError::OpcodeParse(oe) => {
error!("[{}] failed to parse opcode from received message: [{}]", peer, oe);
}
HeaderParseError::ResponseCodeParse(rce) => {
error!("[{}] failed to parse response code error from received message: [{}]", peer, rce);
}
}
}
RequestParseError::QuesionsParse(qe) => {
match qe {
QuestionParseError::ShortLength(sl) => {
error!("[{}] failed to parse questions of received message, too short: [{} bytes]", peer, sl);
}
QuestionParseError::QTypeParse(te) => {
error!("[{}] failed to parse questions of received message, qtype error: [{}]", peer, te);
}
QuestionParseError::QClassParse(ce) => {
error!("[{}] failed to parse questions of received message, qclass error: [{}]", peer, ce);
}
}
}
}
}
}
// match sending_channel.send(m) {
// Ok(_) => {}
// Err(_) => {}
// }
}
info!("message processing thread finishing")
});
}
pub fn get_message_channel(&mut self) -> Option<Sender<NetworkMessagePtr>>
{
self.message_channel.clone()
}
}

View File

@ -3,7 +3,7 @@ use std::sync::mpsc::{Receiver, Sender};
use std::thread; use std::thread;
use log::info; use log::info;
use std::str; use std::str;
use crate::raw_request::NetworkMessagePtr; use crate::net::raw_request::NetworkMessagePtr;
pub struct ResponseProcesor { pub struct ResponseProcesor {
message_channel: Option<Sender<NetworkMessagePtr>> message_channel: Option<Sender<NetworkMessagePtr>>

View File

@ -1,43 +1,40 @@
use crate::dns_header::{Direction, DNSHeader, Opcode, ResponseCode}; use crate::byte;
use crate::dns_header::Direction::Response; use crate::message::header::{Direction, DNSHeader, Opcode, ResponseCode};
use crate::dns_question::{DNSQuestion, questions_from_bytes}; use crate::message::question::{QuestionParseError, questions_from_bytes};
use crate::dns_request::DNSRequest; use crate::message::request::DNSRequest;
use crate::raw_request::NetworkMessage; use crate::net::raw_request::NetworkMessage;
use crate::request_parser::RequestParseError::{HeaderParse, QuesionsParse};
fn two_byte_extraction(buffer: &[u8], idx: usize) -> u16 pub const ID_START: usize = 0;
{ pub const FLAGS_START: usize = 2;
((buffer[idx] as u16) << 8) | buffer[idx + 1] as u16 pub const DIRECTION_SHIFT: usize = 15;
pub const OPCODE_SHIFT: usize = 11;
pub const AUTHORITATIVE_SHIFT: usize = 10;
pub const TRUNCATION_SHIFT: usize = 9;
pub const RECURSION_DESIRED_SHIFT: usize = 8;
pub const RECURSION_AVAILABLE_SHIFT: usize = 7;
pub const ZEROES_SHIFT: usize = 4;
pub const QUESTION_COUNT_START: usize = 4;
pub const ANSWER_RECORD_COUNT_START: usize = 6;
pub const AUTHORITY_RECORD_COUNT_START: usize = 8;
pub const ADDITIONAL_RECORD_COUNT_START: usize = 10;
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug)]
pub enum HeaderParseError {
OpcodeParse(u16),
ResponseCodeParse(u16),
} }
fn two_byte_split(num: u16) -> (u8, u8) pub fn parse_header(header: &[u8; 12]) -> Result<DNSHeader, HeaderParseError>
{ {
((num >> 8) as u8, (num & 0b0000000011111111) as u8) let id = byte::two_byte_extraction(header, ID_START);
}
const ID_START: usize = 0; let flags = byte::two_byte_extraction(header, FLAGS_START);
const FLAGS_START: usize = 2;
const DIRECTION_SHIFT: usize = 15;
const OPCODE_SHIFT: usize = 11;
const AUTHORITATIVE_SHIFT: usize = 10;
const TRUNCATION_SHIFT: usize = 9;
const RECURSION_DESIRED_SHIFT: usize = 8;
const RECURSION_AVAILABLE_SHIFT: usize = 7;
const ZEROES_SHIFT: usize = 4;
const QUESTION_COUNT_START: usize = 4;
const ANSWER_RECORD_COUNT_START: usize = 6;
const AUTHORITY_RECORD_COUNT_START: usize = 8;
const ADDITIONAL_RECORD_COUNT_START: usize = 10;
pub fn parse_header(header: &[u8; 12]) -> Result<DNSHeader, ()>
{
let id = two_byte_extraction(header, ID_START);
let flags = two_byte_extraction(header, FLAGS_START);
let direction = if flags & (0b1 << DIRECTION_SHIFT) == 0 {Direction::Request} else { Direction::Response }; let direction = if flags & (0b1 << DIRECTION_SHIFT) == 0 {Direction::Request} else { Direction::Response };
let opcode: Result<Opcode, ()> = ((flags & (0b1111 << OPCODE_SHIFT)) >> OPCODE_SHIFT).try_into(); let opcode: Result<Opcode, u16> = ((flags & (0b1111 << OPCODE_SHIFT)) >> OPCODE_SHIFT).try_into();
if let Err(e) = opcode { if let Err(e) = opcode {
return Err(e); return Err(HeaderParseError::OpcodeParse(e));
} }
let authoritative = (flags & (0b1 << AUTHORITATIVE_SHIFT)) != 0; let authoritative = (flags & (0b1 << AUTHORITATIVE_SHIFT)) != 0;
@ -47,16 +44,16 @@ pub fn parse_header(header: &[u8; 12]) -> Result<DNSHeader, ()>
let zeroes = (flags & (0b111 << ZEROES_SHIFT)) == 0; let zeroes = (flags & (0b111 << ZEROES_SHIFT)) == 0;
let response: Result<ResponseCode, ()> = (flags & 0b1111).try_into(); let response: Result<ResponseCode, u16> = (flags & 0b1111).try_into();
if let Err(e) = response if let Err(e) = response
{ {
return Err(e); return Err(HeaderParseError::ResponseCodeParse(e));
} }
let question_count = two_byte_extraction(header, QUESTION_COUNT_START); let question_count = byte::two_byte_extraction(header, QUESTION_COUNT_START);
let answer_record_count = two_byte_extraction(header, ANSWER_RECORD_COUNT_START); let answer_record_count = byte::two_byte_extraction(header, ANSWER_RECORD_COUNT_START);
let authority_record_count = two_byte_extraction(header, AUTHORITY_RECORD_COUNT_START); let authority_record_count = byte::two_byte_extraction(header, AUTHORITY_RECORD_COUNT_START);
let additional_record_count = two_byte_extraction(header, ADDITIONAL_RECORD_COUNT_START); let additional_record_count = byte::two_byte_extraction(header, ADDITIONAL_RECORD_COUNT_START);
Ok(DNSHeader { Ok(DNSHeader {
id, id,
@ -79,45 +76,13 @@ pub fn parse_header(header: &[u8; 12]) -> Result<DNSHeader, ()>
}) })
} }
fn apply_split_bytes(buffer: &mut [u8], value: u16, index: usize) #[derive(Ord, PartialOrd, Eq, PartialEq, Debug)]
{ pub enum RequestParseError {
let val = two_byte_split(value); HeaderParse(HeaderParseError),
buffer[index] = val.0; QuesionsParse(QuestionParseError),
buffer[index + 1] = val.1;
} }
pub fn parse_header_to_bytes(header: &DNSHeader) -> [u8; 12] pub fn parse_request(msg: NetworkMessage) -> Result<DNSRequest, RequestParseError>
{
let mut header_bytes: [u8; 12] = [0; 12];
apply_split_bytes(&mut header_bytes, header.id, ID_START);
let mut flags: u16 = 0;
if header.direction == Response {
flags |= 0b1 << DIRECTION_SHIFT;
}
flags |= (header.opcode as u16) << OPCODE_SHIFT;
flags |= (header.authoritative as u16) << AUTHORITATIVE_SHIFT;
flags |= (header.truncation as u16) << TRUNCATION_SHIFT;
flags |= (header.recursion_desired as u16) << RECURSION_DESIRED_SHIFT;
flags |= (header.recursion_available as u16) << RECURSION_AVAILABLE_SHIFT;
flags |= header.response as u16;
apply_split_bytes(&mut header_bytes, flags, FLAGS_START);
apply_split_bytes(&mut header_bytes, header.question_count, QUESTION_COUNT_START);
apply_split_bytes(&mut header_bytes, header.answer_record_count, ANSWER_RECORD_COUNT_START);
apply_split_bytes(&mut header_bytes, header.authority_record_count, AUTHORITY_RECORD_COUNT_START);
apply_split_bytes(&mut header_bytes, header.additional_record_count, ADDITIONAL_RECORD_COUNT_START);
header_bytes
}
pub fn parse_request(msg: NetworkMessage) -> Result<DNSRequest, ()>
{ {
let header = parse_header(msg.buffer[0..12].try_into().unwrap()); let header = parse_header(msg.buffer[0..12].try_into().unwrap());
@ -134,15 +99,16 @@ pub fn parse_request(msg: NetworkMessage) -> Result<DNSRequest, ()>
peer: msg.peer peer: msg.peer
}) })
} }
Err(_) => Err(()) Err(e) => Err(QuesionsParse(e))
} }
}, },
Err(_) => Err(()) Err(e) => Err(HeaderParse(e))
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::byte::{two_byte_extraction, two_byte_split};
use super::*; use super::*;
#[test] #[test]
@ -188,7 +154,7 @@ mod tests {
additional_record_count: 4 additional_record_count: 4
}; };
let parsed_bytes = parse_header_to_bytes(&header); let parsed_bytes = header.to_bytes();
let header_again = parse_header(&parsed_bytes).unwrap(); let header_again = parse_header(&parsed_bytes).unwrap();

View File

@ -1,57 +0,0 @@
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
use std::thread;
use log::{error, info};
use std::str;
use crate::dns_request::DNSRequest;
use crate::raw_request::NetworkMessagePtr;
use crate::request_parser::parse_request;
pub struct RequestProcesor {
message_channel: Option<Sender<NetworkMessagePtr>>
}
impl RequestProcesor {
pub fn new() -> RequestProcesor {
RequestProcesor{
message_channel: None
}
}
pub fn run(&mut self, sending_channel: Sender<NetworkMessagePtr>)
{
let (tx, rx): (Sender<NetworkMessagePtr>, Receiver<NetworkMessagePtr>) = mpsc::channel();
self.message_channel = Some(tx);
thread::spawn(move || {
for mut m in rx
{
// info!("processing: {}", str::from_utf8(&(*(*m).buffer)).unwrap());
let request = parse_request(*m);
match request {
Ok(r) => {
info!("received dns message: {:?}", r);
}
Err(_) => {
error!("failed to parse message");
}
}
// match sending_channel.send(m) {
// Ok(_) => {}
// Err(_) => {}
// }
}
info!("message processing thread finishing")
});
}
pub fn get_message_channel(&mut self) -> Option<Sender<NetworkMessagePtr>>
{
self.message_channel.clone()
}
}

21
dnstp/src/string.rs Normal file
View File

@ -0,0 +1,21 @@
use urlencoding::encode;
pub fn encode_domain_name(name: &String) -> Vec<u8>
{
let mut ret: Vec<u8> = Vec::with_capacity(name.len() + 3);
for part in name.split(".")
{
let encoded_string = encode(part);
let count = encoded_string.len();
ret.push(count as u8);
for x in encoded_string.bytes() {
ret.push(x);
};
}
ret.push(0);
ret
}