Compare commits
2 Commits
1f6593263a
...
bc367e7814
Author | SHA1 | Date | |
---|---|---|---|
bc367e7814 | |||
8e1d070f85 |
22
.github/workflows/build.yml
vendored
22
.github/workflows/build.yml
vendored
@ -32,3 +32,25 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
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
60
Cargo.lock
generated
@ -50,6 +50,12 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.18"
|
||||
@ -122,6 +128,7 @@ dependencies = [
|
||||
"clap",
|
||||
"dnstplib",
|
||||
"log",
|
||||
"rand",
|
||||
"simplelog",
|
||||
]
|
||||
|
||||
@ -133,6 +140,17 @@ dependencies = [
|
||||
"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]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
@ -172,6 +190,12 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.76"
|
||||
@ -190,6 +214,36 @@ dependencies = [
|
||||
"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]]
|
||||
name = "serde"
|
||||
version = "1.0.196"
|
||||
@ -296,6 +350,12 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
@ -1,3 +1,9 @@
|
||||
# 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.
|
@ -10,3 +10,4 @@ dnstplib = { path = "../dnstp" }
|
||||
clap = { version = "4.4.18", features = ["derive"] }
|
||||
log = "0.4.20"
|
||||
simplelog = "0.12.1"
|
||||
rand = "0.8.5"
|
@ -4,10 +4,14 @@ use std::thread;
|
||||
use std::time::Duration;
|
||||
use clap::Parser;
|
||||
use log::{info, LevelFilter};
|
||||
use rand::RngCore;
|
||||
use simplelog::*;
|
||||
use dnstplib::dns_socket::DNSSocket;
|
||||
use dnstplib::raw_request::NetworkMessage;
|
||||
use dnstplib::response_processor::ResponseProcesor;
|
||||
use dnstplib::message::header::{Direction, DNSHeader, Opcode, ResponseCode};
|
||||
use dnstplib::message::question::{DNSQuestion, QClass, QType};
|
||||
use dnstplib::message::request::DNSRequest;
|
||||
use dnstplib::net::socket::DNSSocket;
|
||||
use dnstplib::net::raw_request::NetworkMessage;
|
||||
use dnstplib::processor::ResponseProcesor;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[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"));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
loop {
|
||||
|
||||
info!("sending...");
|
||||
|
||||
let mut send_buf = [0; 512];
|
||||
send_buf[0] = 'a' as u8;
|
||||
send_buf[1] = 'b' as u8;
|
||||
send_buf[2] = 'c' as u8;
|
||||
send_buf[3] = 'd' as u8;
|
||||
let message = DNSRequest {
|
||||
header: DNSHeader {
|
||||
id: rng.next_u32() as u16,
|
||||
direction: Direction::Request,
|
||||
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 {
|
||||
buffer: Box::from(send_buf),
|
||||
buffer: Box::from(bytes),
|
||||
peer: args.address.parse().unwrap()
|
||||
}));
|
||||
|
||||
|
@ -6,8 +6,8 @@ use simplelog::*;
|
||||
use std::fs::File;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use dnstplib::dns_socket::DNSSocket;
|
||||
use dnstplib::request_processor::RequestProcesor;
|
||||
use dnstplib::net::socket::DNSSocket;
|
||||
use dnstplib::processor::RequestProcesor;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
|
29
dnstp/src/byte.rs
Normal file
29
dnstp/src/byte.rs
Normal 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;
|
||||
}
|
@ -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
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
pub mod dns_socket;
|
||||
pub mod request_parser;
|
||||
pub mod dns_header;
|
||||
pub mod request_processor;
|
||||
pub mod response_processor;
|
||||
pub mod raw_request;
|
||||
pub mod dns_question;
|
||||
pub mod dns_request;
|
||||
|
||||
mod byte;
|
||||
pub mod processor;
|
||||
pub mod message;
|
||||
pub mod net;
|
||||
mod string;
|
43
dnstp/src/message/answer.rs
Normal file
43
dnstp/src/message/answer.rs
Normal 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
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
use std::convert::TryFrom;
|
||||
use crate::byte::apply_split_bytes;
|
||||
use crate::message::header::Direction::Response;
|
||||
|
||||
pub const HEADER_SIZE: usize = 12;
|
||||
|
||||
@ -17,7 +19,7 @@ pub enum Opcode {
|
||||
}
|
||||
|
||||
impl TryFrom<u16> for Opcode {
|
||||
type Error = ();
|
||||
type Error = u16;
|
||||
|
||||
fn try_from(v: u16) -> Result<Self, Self::Error> {
|
||||
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::Status as u16 => Ok(Opcode::Status),
|
||||
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 {
|
||||
type Error = ();
|
||||
type Error = u16;
|
||||
|
||||
fn try_from(v: u16) -> Result<Self, Self::Error> {
|
||||
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::NotAuth as u16 => Ok(ResponseCode::NotAuth),
|
||||
x if x == ResponseCode::NotZone as u16 => Ok(ResponseCode::NotZone),
|
||||
_ => Err(()),
|
||||
_ => Err(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -82,3 +84,36 @@ pub struct DNSHeader {
|
||||
pub authority_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
6
dnstp/src/message/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
pub mod header;
|
||||
pub mod question;
|
||||
pub mod request;
|
||||
pub mod answer;
|
||||
pub mod response;
|
@ -1,5 +1,6 @@
|
||||
use std::ops::Sub;
|
||||
use urlencoding::{encode, decode};
|
||||
use crate::string::encode_domain_name;
|
||||
|
||||
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)]
|
||||
pub enum QType {
|
||||
@ -19,7 +20,7 @@ pub enum QType {
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for QType {
|
||||
type Error = ();
|
||||
type Error = u8;
|
||||
|
||||
fn try_from(v: u8) -> Result<Self, Self::Error> {
|
||||
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::AAAA as u8 => Ok(QType::AAAA),
|
||||
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 {
|
||||
type Error = ();
|
||||
type Error = u8;
|
||||
|
||||
fn try_from(v: u8) -> Result<Self, Self::Error> {
|
||||
match v {
|
||||
x if x == QClass::Internet as u8 => Ok(QClass::Internet),
|
||||
x if x == QClass::Chaos as u8 => Ok(QClass::Chaos),
|
||||
x if x == QClass::Hesiod as u8 => Ok(QClass::Hesiod),
|
||||
_ => Err(()),
|
||||
_ => Err(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug)]
|
||||
pub struct DNSQuestion {
|
||||
qname: String,
|
||||
qtype: QType,
|
||||
qclass: QClass
|
||||
pub qname: String,
|
||||
pub qtype: QType,
|
||||
pub qclass: QClass
|
||||
}
|
||||
|
||||
impl DNSQuestion {
|
||||
@ -80,20 +81,7 @@ impl DNSQuestion {
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8>
|
||||
{
|
||||
let mut ret: Vec<u8> = Vec::with_capacity(self.qname.len() + 2 + 3);
|
||||
|
||||
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);
|
||||
let mut ret = encode_domain_name(&self.qname);
|
||||
|
||||
ret.push(self.qtype 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
|
||||
{
|
||||
return Err(());
|
||||
return Err(QuestionParseError::ShortLength(bytes.len()));
|
||||
}
|
||||
|
||||
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()) {
|
||||
(Ok(qtype), Ok(qclass)) => {
|
||||
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,
|
||||
qclass
|
||||
});
|
||||
@ -162,8 +169,11 @@ pub fn questions_from_bytes(bytes: Vec<u8>, total_questions: u16) -> Result<Vec<
|
||||
current_qclass = None;
|
||||
trailers_reached = false;
|
||||
}
|
||||
_ => {
|
||||
return Err(());
|
||||
(Err(qtype_e), _) => {
|
||||
return Err(QuestionParseError::QTypeParse(qtype_e));
|
||||
}
|
||||
(_, Err(qclass_e)) => {
|
||||
return Err(QuestionParseError::QClassParse(qclass_e));
|
||||
}
|
||||
}
|
||||
}
|
23
dnstp/src/message/request.rs
Normal file
23
dnstp/src/message/request.rs
Normal 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
|
||||
}
|
||||
}
|
25
dnstp/src/message/response.rs
Normal file
25
dnstp/src/message/response.rs
Normal 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
6
dnstp/src/net/mod.rs
Normal 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;
|
@ -3,6 +3,6 @@ use std::net::SocketAddr;
|
||||
pub type NetworkMessagePtr = Box<NetworkMessage>;
|
||||
|
||||
pub struct NetworkMessage {
|
||||
pub buffer: Box<[u8; 512]>,
|
||||
pub buffer: Box<Vec<u8>>,
|
||||
pub peer: SocketAddr
|
||||
}
|
@ -2,12 +2,10 @@ use std::net::{SocketAddr, UdpSocket};
|
||||
use std::thread;
|
||||
use std::thread::{JoinHandle};
|
||||
use log::{debug, error, info};
|
||||
|
||||
use std::str;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::{Receiver, Sender, TryRecvError};
|
||||
use crate::dns_header::HEADER_SIZE;
|
||||
use crate::raw_request::{NetworkMessage, NetworkMessagePtr};
|
||||
use crate::message::header::HEADER_SIZE;
|
||||
use crate::net::raw_request::{NetworkMessage, NetworkMessagePtr};
|
||||
|
||||
pub struct DNSSocket {
|
||||
addresses: Vec<SocketAddr>,
|
||||
@ -77,13 +75,12 @@ impl DNSSocket {
|
||||
Some(s) => {
|
||||
let mut cancelled = false;
|
||||
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));
|
||||
|
||||
match res {
|
||||
Ok((read_count, peer)) => {
|
||||
// let res_str = str::from_utf8(&(*buf)).unwrap();
|
||||
// info!("received [{}] from [{}]", res_str, peer);
|
||||
|
||||
if read_count > HEADER_SIZE {
|
||||
message_sender.send(Box::new(NetworkMessage {
|
||||
@ -131,7 +128,6 @@ impl DNSSocket {
|
||||
while !cancelled {
|
||||
|
||||
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){
|
||||
error!("error sending response to [{}], {}", m.peer, e);
|
||||
}
|
6
dnstp/src/processor/mod.rs
Normal file
6
dnstp/src/processor/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
pub use request::RequestProcesor;
|
||||
pub use response::ResponseProcesor;
|
78
dnstp/src/processor/request.rs
Normal file
78
dnstp/src/processor/request.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ use std::sync::mpsc::{Receiver, Sender};
|
||||
use std::thread;
|
||||
use log::info;
|
||||
use std::str;
|
||||
use crate::raw_request::NetworkMessagePtr;
|
||||
use crate::net::raw_request::NetworkMessagePtr;
|
||||
|
||||
pub struct ResponseProcesor {
|
||||
message_channel: Option<Sender<NetworkMessagePtr>>
|
@ -1,43 +1,40 @@
|
||||
use crate::dns_header::{Direction, DNSHeader, Opcode, ResponseCode};
|
||||
use crate::dns_header::Direction::Response;
|
||||
use crate::dns_question::{DNSQuestion, questions_from_bytes};
|
||||
use crate::dns_request::DNSRequest;
|
||||
use crate::raw_request::NetworkMessage;
|
||||
use crate::byte;
|
||||
use crate::message::header::{Direction, DNSHeader, Opcode, ResponseCode};
|
||||
use crate::message::question::{QuestionParseError, questions_from_bytes};
|
||||
use crate::message::request::DNSRequest;
|
||||
use crate::net::raw_request::NetworkMessage;
|
||||
use crate::request_parser::RequestParseError::{HeaderParse, QuesionsParse};
|
||||
|
||||
fn two_byte_extraction(buffer: &[u8], idx: usize) -> u16
|
||||
{
|
||||
((buffer[idx] as u16) << 8) | buffer[idx + 1] as u16
|
||||
pub const ID_START: usize = 0;
|
||||
pub const FLAGS_START: usize = 2;
|
||||
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;
|
||||
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 flags = byte::two_byte_extraction(header, FLAGS_START);
|
||||
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 {
|
||||
return Err(e);
|
||||
return Err(HeaderParseError::OpcodeParse(e));
|
||||
}
|
||||
|
||||
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 response: Result<ResponseCode, ()> = (flags & 0b1111).try_into();
|
||||
let response: Result<ResponseCode, u16> = (flags & 0b1111).try_into();
|
||||
if let Err(e) = response
|
||||
{
|
||||
return Err(e);
|
||||
return Err(HeaderParseError::ResponseCodeParse(e));
|
||||
}
|
||||
|
||||
let question_count = two_byte_extraction(header, QUESTION_COUNT_START);
|
||||
let answer_record_count = two_byte_extraction(header, ANSWER_RECORD_COUNT_START);
|
||||
let authority_record_count = two_byte_extraction(header, AUTHORITY_RECORD_COUNT_START);
|
||||
let additional_record_count = two_byte_extraction(header, ADDITIONAL_RECORD_COUNT_START);
|
||||
let question_count = byte::two_byte_extraction(header, QUESTION_COUNT_START);
|
||||
let answer_record_count = byte::two_byte_extraction(header, ANSWER_RECORD_COUNT_START);
|
||||
let authority_record_count = byte::two_byte_extraction(header, AUTHORITY_RECORD_COUNT_START);
|
||||
let additional_record_count = byte::two_byte_extraction(header, ADDITIONAL_RECORD_COUNT_START);
|
||||
|
||||
Ok(DNSHeader {
|
||||
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)
|
||||
{
|
||||
let val = two_byte_split(value);
|
||||
buffer[index] = val.0;
|
||||
buffer[index + 1] = val.1;
|
||||
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug)]
|
||||
pub enum RequestParseError {
|
||||
HeaderParse(HeaderParseError),
|
||||
QuesionsParse(QuestionParseError),
|
||||
}
|
||||
|
||||
pub fn parse_header_to_bytes(header: &DNSHeader) -> [u8; 12]
|
||||
{
|
||||
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, ()>
|
||||
pub fn parse_request(msg: NetworkMessage) -> Result<DNSRequest, RequestParseError>
|
||||
{
|
||||
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
|
||||
})
|
||||
}
|
||||
Err(_) => Err(())
|
||||
Err(e) => Err(QuesionsParse(e))
|
||||
}
|
||||
},
|
||||
Err(_) => Err(())
|
||||
Err(e) => Err(HeaderParse(e))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::byte::{two_byte_extraction, two_byte_split};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@ -188,7 +154,7 @@ mod tests {
|
||||
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();
|
||||
|
||||
|
@ -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
21
dnstp/src/string.rs
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user