Skip to content

Instantly share code, notes, and snippets.

@neetsdkasu
Last active April 19, 2022 16:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neetsdkasu/3fb7123375d4b5a43289292ccd84015d to your computer and use it in GitHub Desktop.
Save neetsdkasu/3fb7123375d4b5a43289292ccd84015d to your computer and use it in GitHub Desktop.
DNSクエリ見てみる
// ネットワークアダプタの設定で
// DNSのIPを127.7.7.7に設定してPC内からのDNSクエリを盗み見る
// nslookup example.com 127.7.7.7 とかで動くかテストしながら開発
fn main() -> std::io::Result<()> {
eprintln!("START");
let mut bufs = Bufs::new();
let res = bufs.start();
for (i, (req, ans)) in bufs.iter().enumerate() {
println!("QUERY: {i}");
println!("[REQUEST]");
show_packet(req);
println!("[ANSWER]");
show_packet(ans);
}
eprintln!("STOP");
res
}
// DNS照会を受信する最大数
// この数を超えたらプログラムは終了するため、パソコンはDNS照会できなくなる(笑)
// (タイムアウトでも終了するため最大数受信できるとは限らない)
const COUNT: usize = 100;
#[derive(Debug)]
struct Bufs {
req_bufs: Vec<Vec<u8>>,
req_sizes: Vec<usize>,
ans_bufs: Vec<Vec<u8>>,
ans_sizes: Vec<usize>,
count: usize,
}
impl Bufs {
fn new() -> Self {
Self {
req_bufs: vec![vec![0_u8; 512]; COUNT],
req_sizes: vec![0_usize; COUNT],
ans_bufs: vec![vec![0_u8; 512]; COUNT],
ans_sizes: vec![0_usize; COUNT],
count: 0,
}
}
fn start(&mut self) -> std::io::Result<()> {
use std::net::{SocketAddr, UdpSocket};
use std::time::Duration;
// DNS照会を盗み見るためだけの偽のDNSサーバー
// 適当なローカルホストIPアドレスを割り当てる
let fake_dns = SocketAddr::from(([127, 7, 7, 7], 53));
eprintln!("FAKE DNS SERVER: {:?}", fake_dns);
// 実在のDNSサーバ 8.8.8.8:53 など
let dns = SocketAddr::from(([8, 8, 4, 4], 53));
// 実在のDNSサーバとやりとりするためのソケット用のアドレス
// ルータなどLAN内でPCに割り当てられたIPアドレス(192.168.10.3など)と適当な空きポート番号(9876など)
let cli = SocketAddr::from(([192, 168, 10, 18], 9981));
let timeout = Duration::from_secs(1);
let srv_socket = UdpSocket::bind(fake_dns)?;
srv_socket.set_read_timeout(None)?;
srv_socket.set_write_timeout(Some(timeout))?;
let cli_socket = UdpSocket::bind(cli)?;
cli_socket.set_read_timeout(Some(timeout))?;
cli_socket.set_write_timeout(Some(timeout))?;
for i in 0..COUNT {
// DNS照会の受信
let (number_of_bytes, req_addr) = srv_socket.recv_from(&mut self.req_bufs[i])?;
self.req_sizes[i] = number_of_bytes;
// DNS照会の転送
let req = &self.req_bufs[i][..number_of_bytes];
let _number_of_bytes = cli_socket.send_to(req, dns)?;
// DNS照会の応答の受信
let (number_of_bytes, _) = cli_socket.recv_from(&mut self.ans_bufs[i])?;
self.ans_sizes[i] = number_of_bytes;
// DNS照会の応答の転送
let ans = &self.ans_bufs[i][..number_of_bytes];
let _number_of_bytes = srv_socket.send_to(ans, req_addr)?;
self.count += 1;
}
Ok(())
}
fn iter(&self) -> BufsIterator<'_> {
BufsIterator { bufs: self, i: 0 }
}
}
#[derive(Debug)]
struct BufsIterator<'a> {
bufs: &'a Bufs,
i: usize,
}
impl<'a> Iterator for BufsIterator<'a> {
type Item = (&'a [u8], &'a [u8]);
fn next(&mut self) -> Option<Self::Item> {
if self.i >= self.bufs.count {
return None;
}
let i = self.i;
self.i += 1;
let number_of_bytes = self.bufs.req_sizes[i];
let req = &self.bufs.req_bufs[i][..number_of_bytes];
let number_of_bytes = self.bufs.ans_sizes[i];
let ans = &self.bufs.ans_bufs[i][..number_of_bytes];
Some((req, ans))
}
}
const BAR: &str =
"--------------------------------------------------------------------------------";
fn show_packet(buf: &[u8]) -> Option<()> {
let number_of_bytes = buf.len();
println!("number_of_bytes: {number_of_bytes}");
for chunk in buf.chunks(80) {
for ch in chunk {
let ch = *ch as char;
if ch.is_ascii_graphic() {
print!("{ch}");
} else {
print!(".");
}
}
println!();
}
match Message::read(buf) {
Some(message) => show_message(&message),
None => println!("failed to read message"),
}
println!("{}", BAR);
Some(())
}
fn show_message(message: &Message) {
let header = &message.header;
show_header(header);
for (i, question) in message.questions.iter().enumerate() {
println!(" QUESTION SECTION: {} / {}", i + 1, header.qdcount);
show_question(question);
}
for (i, rr) in message.answers.iter().enumerate() {
println!(" ANSWER SECTION: {} / {}", i + 1, header.ancount);
show_resource_record(rr);
}
for (i, rr) in message.authorities.iter().enumerate() {
println!(" AUTHORITY RECORD SECTION: {} / {}", i + 1, header.nscount);
show_resource_record(rr);
}
for (i, rr) in message.additionals.iter().enumerate() {
println!(
" ADDITIONAL RECORD SECTION: {} / {}",
i + 1,
header.arcount
);
show_resource_record(rr);
}
}
fn show_header(header: &Header) {
println!(" ID: {}", header.id);
show_param(&header.param);
println!(" QDCOUNT: {}", header.qdcount);
println!(" ANCOUNT: {}", header.ancount);
println!(" NSCOUNT: {}", header.nscount);
println!(" ARCOUNT: {}", header.arcount);
}
fn show_param(param: &Param) {
let qr_text = get_qr_text(param.qr);
println!(" QR: {} {}", param.qr, qr_text);
let opcode_text = get_opcode_text(param.opcode);
println!(" Opcode: {} {}", param.opcode, opcode_text);
println!(" AA: {}", param.aa);
println!(" TC: {}", param.tc);
println!(" RD: {}", param.rd);
println!(" RA: {}", param.ra);
println!(" Z: {}", param.z);
let rcode_text = get_rcode_text(param.rcode);
println!(" RCODE: {} {}", param.rcode, rcode_text);
}
fn show_question(question: &Question) {
for word in &question.name {
println!(" LENGTH: {:<3} {}", word.len(), word);
}
let qtype_text = get_type_text(question.qtype);
println!(" QTYPE: {} {}", question.qtype, qtype_text);
let qclass_text = get_class_text(question.qclass);
println!(" QCLASS: {} {}", question.qclass, qclass_text);
}
fn show_resource_record(rr: &ResourceRecord) {
for word in &rr.name {
println!(" LENGTH: {:<3} {}", word.len(), word);
}
let rrtype_text = get_type_text(rr.rrtype);
println!(" TYPE: {} {}", rr.rrtype, rrtype_text);
let rrclass_text = get_class_text(rr.rrclass);
println!(" CLASS: {} {}", rr.rrclass, rrclass_text);
println!(" TTL: {}", rr.ttl);
println!(" RDLENGTH: {}", rr.rdlength);
println!(" RDATA: {:?}", rr.rdata);
}
#[derive(Debug)]
struct Message {
header: Header,
questions: Vec<Question>,
answers: Vec<ResourceRecord>,
authorities: Vec<ResourceRecord>,
additionals: Vec<ResourceRecord>,
}
#[derive(Debug)]
struct Header {
id: u16,
param: Param,
qdcount: u16,
ancount: u16,
nscount: u16,
arcount: u16,
}
#[derive(Debug)]
struct Param {
qr: u16,
opcode: u16,
aa: u16,
tc: u16,
rd: u16,
ra: u16,
z: u16,
rcode: u16,
}
#[derive(Debug)]
struct Question {
name: Vec<String>,
qtype: u16,
qclass: u16,
}
#[derive(Debug)]
struct ResourceRecord {
name: Vec<String>,
rrtype: u16,
rrclass: u16,
ttl: u32,
rdlength: u16,
rdata: Box<RData>,
}
#[allow(dead_code)]
#[derive(Debug)]
enum RData {
A {
address: [u8; 4],
},
Cname {
cname: Vec<String>,
},
Mb {
madname: Vec<String>,
},
Md {
madname: Vec<String>,
},
Mf {
madname: Vec<String>,
},
Mg {
mgmname: Vec<String>,
},
Minfo {
rmailbx: Vec<String>,
emailbx: Vec<String>,
},
Mr {
newname: Vec<String>,
},
Mx {
preference: u16,
exchange: Vec<String>,
},
Null {
anything: Vec<u8>,
},
Ns {
nsdname: Vec<String>,
},
Ptr {
ptrdname: Vec<String>,
},
Soa {
nname: Vec<String>,
rname: Vec<String>,
serial: u32,
refresh: u32,
retry: u32,
expire: u32,
minimum: u32,
},
NotImlemented {
raw: Vec<u8>,
},
Unknown {
raw: Vec<u8>,
},
}
impl Message {
fn read(buf: &[u8]) -> Option<Self> {
let orig_buf = buf;
let (buf, header) = Header::read(buf)?;
let mut questions: Vec<Question> = Vec::new();
let mut buf = buf;
for _ in 0..header.qdcount {
let (rest, question) = Question::read(orig_buf, buf)?;
buf = rest;
questions.push(question);
}
let mut answers: Vec<ResourceRecord> = Vec::new();
for _ in 0..header.ancount {
let (rest, answer) = ResourceRecord::read(orig_buf, buf)?;
buf = rest;
answers.push(answer);
}
let mut authorities: Vec<ResourceRecord> = Vec::new();
for _ in 0..header.nscount {
let (rest, authority) = ResourceRecord::read(orig_buf, buf)?;
buf = rest;
authorities.push(authority);
}
let mut additionals: Vec<ResourceRecord> = Vec::new();
for _ in 0..header.arcount {
let (rest, additional) = ResourceRecord::read(orig_buf, buf)?;
buf = rest;
additionals.push(additional);
}
let message = Self {
header,
questions,
answers,
authorities,
additionals,
};
Some(message)
}
}
impl Header {
fn read(buf: &[u8]) -> Option<(&[u8], Self)> {
let (buf, id) = read_u16(buf)?;
let (buf, value) = read_u16(buf)?;
let param = Param::from(value);
let (buf, qdcount) = read_u16(buf)?;
let (buf, ancount) = read_u16(buf)?;
let (buf, nscount) = read_u16(buf)?;
let (buf, arcount) = read_u16(buf)?;
let header = Self {
id,
param,
qdcount,
ancount,
nscount,
arcount,
};
Some((buf, header))
}
}
impl From<u16> for Param {
fn from(value: u16) -> Self {
Self {
qr: (value >> 15) & 0b1,
opcode: (value >> 11) & 0b1111,
aa: (value >> 10) & 0b1,
tc: (value >> 9) & 0b1,
rd: (value >> 8) & 0b1,
ra: (value >> 7) & 0b1,
z: (value >> 4) & 0b111,
rcode: value & 0b1111,
}
}
}
impl Question {
fn read<'a, 'b>(orig_buf: &'a [u8], buf: &'b [u8]) -> Option<(&'b [u8], Self)> {
let (buf, name) = read_domain_name(orig_buf, buf)?;
let (buf, qtype) = read_u16(buf)?;
let (buf, qclass) = read_u16(buf)?;
let question = Self {
name,
qtype,
qclass,
};
Some((buf, question))
}
}
impl ResourceRecord {
fn read<'a, 'b>(orig_buf: &'a [u8], buf: &'b [u8]) -> Option<(&'b [u8], Self)> {
let (buf, name) = read_domain_name(orig_buf, buf)?;
let (buf, rrtype) = read_u16(buf)?;
let (buf, rrclass) = read_u16(buf)?;
let (buf, ttl) = read_u32(buf)?;
let (buf, rdlength) = read_u16(buf)?;
let (rdata_buf, buf) = try_split_at(buf, rdlength as usize)?;
let rr = Self {
name,
rrtype,
rrclass,
ttl,
rdlength,
rdata: read_rdata(orig_buf, rrtype, rdata_buf)?,
};
Some((buf, rr))
}
}
fn read_rdata(orig_buf: &[u8], rrtype: u16, rdata_buf: &[u8]) -> Option<Box<RData>> {
let rdata = match rrtype {
1 => {
// A
if let Ok(address) = rdata_buf.try_into() {
RData::A { address }
} else {
return None;
}
}
2 => {
// NS
let (buf, nsdname) = read_domain_name(orig_buf, rdata_buf)?;
if buf.is_empty() {
RData::Ns { nsdname }
} else {
return None;
}
}
3 => {
// MD
let (buf, madname) = read_domain_name(orig_buf, rdata_buf)?;
if buf.is_empty() {
RData::Md { madname }
} else {
return None;
}
}
4 => {
// MF
let (buf, madname) = read_domain_name(orig_buf, rdata_buf)?;
if buf.is_empty() {
RData::Mf { madname }
} else {
return None;
}
}
5 => {
// CNAME
let (buf, cname) = read_domain_name(orig_buf, rdata_buf)?;
if buf.is_empty() {
RData::Cname { cname }
} else {
return None;
}
}
6 => {
// SOA
let (buf, nname) = read_domain_name(orig_buf, rdata_buf)?;
let (buf, rname) = read_domain_name(orig_buf, buf)?;
let (buf, serial) = read_u32(buf)?;
let (buf, refresh) = read_u32(buf)?;
let (buf, retry) = read_u32(buf)?;
let (buf, expire) = read_u32(buf)?;
let (buf, minimum) = read_u32(buf)?;
if buf.is_empty() {
RData::Soa {
nname,
rname,
serial,
refresh,
retry,
expire,
minimum,
}
} else {
return None;
}
}
7 => {
// MB
let (buf, madname) = read_domain_name(orig_buf, rdata_buf)?;
if buf.is_empty() {
RData::Mb { madname }
} else {
return None;
}
}
8 => {
// MG
let (buf, mgmname) = read_domain_name(orig_buf, rdata_buf)?;
if buf.is_empty() {
RData::Mg { mgmname }
} else {
return None;
}
}
9 => {
// MR
let (buf, newname) = read_domain_name(orig_buf, rdata_buf)?;
if buf.is_empty() {
RData::Mr { newname }
} else {
return None;
}
}
10 => RData::Null {
anything: rdata_buf.to_vec(),
},
12 => {
// PTR
let (buf, ptrdname) = read_domain_name(orig_buf, rdata_buf)?;
if buf.is_empty() {
RData::Ptr { ptrdname }
} else {
return None;
}
}
14 => {
// MINFO
let (buf, rmailbx) = read_domain_name(orig_buf, rdata_buf)?;
let (buf, emailbx) = read_domain_name(orig_buf, buf)?;
if buf.is_empty() {
RData::Minfo { rmailbx, emailbx }
} else {
return None;
}
}
15 => {
// MX
let (buf, preference) = read_u16(rdata_buf)?;
let (buf, exchange) = read_domain_name(orig_buf, buf)?;
if buf.is_empty() {
RData::Mx {
preference,
exchange,
}
} else {
return None;
}
}
11 | 13 | 16 | 28 | 252..=255 => RData::NotImlemented {
raw: rdata_buf.to_vec(),
},
_ => RData::Unknown {
raw: rdata_buf.to_vec(),
},
};
Some(Box::new(rdata))
}
fn read_domain_name<'a, 'b>(
orig_buf: &'a [u8],
mut buf: &'b [u8],
) -> Option<(&'b [u8], Vec<String>)> {
let mut name: Vec<String> = Vec::new();
loop {
let (rest, length) = read_u8(buf)?;
if length == 0 {
return Some((rest, name));
}
if length & 0b1100_0000 == 0b1100_0000 {
// 不適切なオフセットが与えられると無限ループする可能性・・・
let (rest, pointer) = read_u16(buf)?;
let offset = (pointer & 0b0011_1111_1111_1111) as usize;
if orig_buf.len() <= offset {
return None;
}
let new_buf = &orig_buf[offset..];
let (_, expanded) = read_domain_name(orig_buf, new_buf)?;
name.extend(expanded);
return Some((rest, name));
}
buf = rest;
let mut word = String::new();
for _ in 0..length {
let (rest, ch) = read_u8(buf)?;
buf = rest;
word.push(ch as char);
}
name.push(word);
}
}
fn get_qr_text(qr: u16) -> &'static str {
match qr {
0 => "Query",
1 => "Response",
_ => "unknown",
}
}
fn get_opcode_text(opcode: u16) -> &'static str {
match opcode {
0 => "Query",
1 => "IQuery",
2 => "Status",
_ => "reserve (RCF1035)",
}
}
fn get_rcode_text(rcode: u16) -> &'static str {
match rcode {
0 => "NoError (No Error)",
1 => "FormErr (Format Error)",
2 => "ServFail (Server Failure)",
3 => "NXDomain (Non-Existent Domain)",
_ => "unknown",
}
}
fn get_type_text(type_id: u16) -> &'static str {
match type_id {
1 => "A (a host address)",
2 => "NS (an authoritative name server)",
3 => "MD (a mail destination)",
4 => "MF (a mail forwarder)",
5 => "CNAME (the canonical name for an alias)",
6 => "SOA (marks the start of a zone of authority)",
7 => "MB (a mailbox domain name)",
8 => "MG (a mail gropu member)",
9 => "MR (a mail rename domain name)",
10 => "NULL (a null RR)",
11 => "WKS (a well known service description)",
12 => "PTR (a domain name pointer)",
13 => "HINFO (host infomation)",
14 => "MINFO (mailbox or mail list infomation)",
15 => "MX (mail exchange)",
16 => "TXT (text strings)",
28 => "AAAA (IP6 Address)",
252 => "AXFR (A request for a transfer of an entire zone)",
253 => "MAILB (A request for mailbox-related records)",
254 => "MAILA (A request for mail agent RRs)",
255 => "* (A request for all records)",
_ => "unknown",
}
}
fn get_class_text(class_id: u16) -> &'static str {
match class_id {
1 => "IN (the Internet)",
2 => "CS (the CSNET class)",
3 => "CH (the CHAOS class)",
4 => "HS (Hesiod)",
255 => "* (any class)",
_ => "unknown",
}
}
fn try_split_at(buf: &[u8], pos: usize) -> Option<(&[u8], &[u8])> {
if pos <= buf.len() {
Some(buf.split_at(pos))
} else {
None
}
}
fn read_u8(buf: &[u8]) -> Option<(&[u8], u8)> {
let (any_bytes, rest) = try_split_at(buf, std::mem::size_of::<u8>())?;
if let &[value] = any_bytes {
Some((rest, value))
} else {
None
}
}
fn read_u16(buf: &[u8]) -> Option<(&[u8], u16)> {
let (any_bytes, rest) = try_split_at(buf, std::mem::size_of::<u16>())?;
if let Ok(two_bytes) = any_bytes.try_into() {
let value = u16::from_be_bytes(two_bytes);
Some((rest, value))
} else {
None
}
}
fn read_u32(buf: &[u8]) -> Option<(&[u8], u32)> {
let (any_bytes, rest) = try_split_at(buf, std::mem::size_of::<u32>())?;
if let Ok(four_bytes) = any_bytes.try_into() {
let value = u32::from_be_bytes(four_bytes);
Some((rest, value))
} else {
None
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment