Skip to main content

manycastr/net/
udp.rs

1use crate::custom_module::manycastr::Address;
2use crate::net::packet::DnsProbeId;
3use crate::net::{PacketPayload, PseudoHeader, build_ip_packet, calculate_checksum};
4use crate::{dns_identifier, m_id_of};
5use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
6use prost::bytes::Buf;
7use std::io::{Cursor, Read, Write};
8
9/// An UDPPacket (UDP packet) <https://en.wikipedia.org/wiki/User_Datagram_Protocol>
10#[derive(Debug)]
11pub struct UDPPacket {
12    pub sport: u16,
13    pub dport: u16,
14    pub length: u16,
15    pub checksum: u16,
16    pub body: Vec<u8>,
17}
18
19/// Parsing from bytes into UDPPacket
20impl From<&[u8]> for UDPPacket {
21    fn from(data: &[u8]) -> Self {
22        let mut data = Cursor::new(data);
23        UDPPacket {
24            sport: data.read_u16::<NetworkEndian>().unwrap(),
25            dport: data.read_u16::<NetworkEndian>().unwrap(),
26            length: data.read_u16::<NetworkEndian>().unwrap(),
27            checksum: data.read_u16::<NetworkEndian>().unwrap(),
28            body: data.into_inner()[8..].to_vec(),
29        }
30    }
31}
32
33/// Convert UDPPacket into a vector of bytes
34impl From<&UDPPacket> for Vec<u8> {
35    fn from(packet: &UDPPacket) -> Self {
36        let mut wtr = vec![];
37        wtr.write_u16::<NetworkEndian>(packet.sport)
38            .expect("Unable to write to byte buffer for UDP packet");
39        wtr.write_u16::<NetworkEndian>(packet.dport)
40            .expect("Unable to write to byte buffer for UDP packet");
41        wtr.write_u16::<NetworkEndian>(packet.length)
42            .expect("Unable to write to byte buffer for UDP packet");
43        wtr.write_u16::<NetworkEndian>(packet.checksum)
44            .expect("Unable to write to byte buffer for UDP packet");
45        wtr.write_all(&packet.body)
46            .expect("Unable to write to byte buffer for UDP packet");
47
48        wtr
49    }
50}
51
52/// DNS request body
53#[allow(dead_code)]
54#[derive(Debug)]
55pub struct DNSRecord {
56    pub transaction_id: u16,
57    pub flags: u16,
58    pub questions: u16,
59    pub answer: u16,
60    pub authority: u16,
61    pub additional: u16,
62    pub domain: String,
63    pub record_type: u16,
64    pub class: u16,
65    pub body: Vec<u8>, // Possible answer sections
66}
67
68/// DNS answer body
69#[allow(dead_code)]
70#[derive(Debug)]
71pub struct DNSAnswer {
72    pub domain: String,
73    pub record_type: u16,
74    pub class: u16,
75    pub ttl: u32,
76    pub data_length: u16,
77    pub data: Vec<u8>,
78}
79
80/// DNS TXT data record
81#[allow(dead_code)]
82#[derive(Debug)]
83pub struct TXTRecord {
84    pub txt_length: u8,
85    pub txt: String,
86}
87
88/// Read a DNS name that is contained in a DNS response.
89/// Returns the domain name string of the A record reply.
90#[cfg_attr(test, allow(dead_code))]
91fn read_dns_name(data: &mut Cursor<&[u8]>) -> String {
92    let mut result = String::new();
93    loop {
94        if !data.has_remaining() {
95            break;
96        }
97        let label_len = data.read_u8().unwrap();
98        // If label length is 0, it is the end of the string
99        if label_len == 0 {
100            break;
101        }
102        // If the first two bytes of the label length is set to 11, it points to a different position
103        if label_len & 0xC0 == 0xC0 {
104            // The offset is the pointer to the previous domain name
105            let offset = ((label_len as u16 & 0x3F) << 8) | data.read_u8().unwrap() as u16;
106            data.set_position(offset as u64);
107            result.push_str(&read_dns_name(data));
108            break;
109        }
110        // Read the label
111        let mut label_bytes = vec![0; label_len as usize];
112
113        match data.read_exact(&mut label_bytes) {
114            Ok(()) => {}
115            Err(_) => {
116                return "Invalid domain name".to_string();
117            }
118        }
119
120        let label = String::from_utf8_lossy(&label_bytes).to_string();
121        result.push_str(&label);
122        result.push('.');
123    }
124    // Remove the trailing '.' if there is one
125    if result.ends_with('.') {
126        result.pop();
127    }
128    result
129}
130
131/// Parsing from bytes into a DNS A record
132impl From<&[u8]> for DNSRecord {
133    fn from(data: &[u8]) -> Self {
134        let mut data = Cursor::new(data);
135
136        let transaction_id = data.read_u16::<NetworkEndian>().unwrap();
137        let flags = data.read_u16::<NetworkEndian>().unwrap();
138        let questions = data.read_u16::<NetworkEndian>().unwrap();
139        let answer = data.read_u16::<NetworkEndian>().unwrap();
140        let authority = data.read_u16::<NetworkEndian>().unwrap();
141        let additional = data.read_u16::<NetworkEndian>().unwrap();
142        let domain = read_dns_name(&mut data);
143
144        let (record_type, class, body) = if data.remaining() >= 4 {
145            let record_type = data.read_u16::<NetworkEndian>().unwrap();
146            let class = data.read_u16::<NetworkEndian>().unwrap();
147            let body = data.clone().into_inner()[data.position() as usize..].to_vec();
148            (record_type, class, body)
149        } else {
150            let record_type = 0;
151            let class = 0;
152            let body = vec![];
153            (record_type, class, body)
154        };
155
156        DNSRecord {
157            transaction_id,
158            flags,
159            questions,
160            answer,
161            authority,
162            additional,
163            domain,
164            record_type,
165            class,
166            body,
167        }
168    }
169}
170
171/// Parsing from bytes into a DNS A record
172impl From<&[u8]> for DNSAnswer {
173    fn from(data: &[u8]) -> Self {
174        let mut data = Cursor::new(data);
175
176        // Make sure data has the required length
177        if data.remaining() < 10 {
178            return DNSAnswer {
179                domain: "Invalid DNS record".to_string(),
180                record_type: 0,
181                class: 0,
182                ttl: 0,
183                data_length: 0,
184                data: vec![],
185            };
186        }
187
188        DNSAnswer {
189            domain: data.read_u16::<NetworkEndian>().unwrap().to_string(), //read_dns_name(&mut data), // Two bytes that are a pointer to the domain name of the request record
190            record_type: data.read_u16::<NetworkEndian>().unwrap(),
191            class: data.read_u16::<NetworkEndian>().unwrap(),
192            ttl: data.read_u32::<NetworkEndian>().unwrap(),
193            data_length: data.read_u16::<NetworkEndian>().unwrap(),
194            data: data.clone().into_inner()[data.position() as usize..].to_vec(),
195        }
196    }
197}
198
199/// Parsing from bytes into a DNS TXT record
200impl From<&[u8]> for TXTRecord {
201    fn from(data: &[u8]) -> Self {
202        let mut data = Cursor::new(data);
203        // Make sure txt_length is not out of bounds
204        if data.remaining() < 1 {
205            return TXTRecord {
206                txt_length: 0,
207                txt: "Invalid TXT record".to_string(),
208            };
209        }
210
211        let txt_length = data.read_u8().unwrap();
212
213        // Make sure txt_length is not out of bounds
214        if txt_length as usize > data.remaining() {
215            return TXTRecord {
216                txt_length,
217                txt: "Invalid TXT record".to_string(),
218            };
219        }
220
221        TXTRecord {
222            txt_length,
223            // txt: read_dns_name(&mut data),
224            txt: String::from_utf8_lossy(&data.into_inner()[1..(1 + txt_length as u64) as usize])
225                .to_string(),
226        }
227    }
228}
229
230impl UDPPacket {
231    /// Create a UDP packet with a DNS A record request.
232    /// In the domain of the A record, we encode the transmit time, source and destination addresses, sender worker ID, source port, and measurement ID.
233    pub fn dns_request(
234        src: &Address,
235        dst: &Address,
236        sport: u16,
237        domain_name: &str,
238        tx_time: u64,
239        id: &DnsProbeId,
240    ) -> Vec<u8> {
241        let ttl: u8 = 255;
242        let dns_packet = Self::create_a_record_request(domain_name, tx_time, src, dst, id, sport);
243
244        let udp_length = (8 + dns_packet.len()) as u16;
245
246        let mut udp_packet = Self {
247            sport,
248            dport: 53u16, // DNS port
249            length: udp_length,
250            checksum: 0,
251            body: dns_packet,
252        };
253
254        let udp_bytes: Vec<u8> = (&udp_packet).into();
255
256        let pseudo_header = PseudoHeader::new(src, dst, 17, udp_length as u32);
257        udp_packet.checksum = calculate_checksum(&udp_bytes, &pseudo_header);
258
259        build_ip_packet(
260            src,
261            dst,
262            ttl,
263            15037,
264            PacketPayload::Udp { value: udp_packet },
265        )
266    }
267
268    /// Creating a DNS A Record Request body <http://www.tcpipguide.com/free/t_DNSMessageHeaderandQuestionSectionFormat.htm>
269    fn create_a_record_request(
270        domain_name: &str,
271        tx_time: u64,
272        src: &Address,
273        dst: &Address,
274        id: &DnsProbeId,
275        sport: u16,
276    ) -> Vec<u8> {
277        let src_num = src.as_numeric();
278        let dst_num = dst.as_numeric();
279        let tx_id = id.worker_id;
280        let probe_id = id.probe_id;
281
282        let subdomain =
283            format!("{tx_time}.{src_num}.{dst_num}.{tx_id}.{sport}.{probe_id}.{domain_name}");
284        let mut dns_body: Vec<u8> = Vec::new();
285
286        // Transaction ID (6-bit measurement identifier + 10-bit tx worker ID)
287        let tx_id_raw: u16 = tx_id as u16;
288        let encoded_tx_id =
289            ((dns_identifier(m_id_of(probe_id)) as u16) << 10) | (tx_id_raw & 0x03FF);
290
291        // DNS Header
292        dns_body
293            .write_u16::<byteorder::BigEndian>(encoded_tx_id)
294            .unwrap(); // Transaction ID
295        dns_body.write_u16::<byteorder::BigEndian>(0x0100).unwrap(); // Flags (Standard query, recursion desired)
296        dns_body.write_u16::<byteorder::BigEndian>(0x0001).unwrap(); // Number of questions
297        dns_body.write_u16::<byteorder::BigEndian>(0x0000).unwrap(); // Number of answer RRs
298        dns_body.write_u16::<byteorder::BigEndian>(0x0000).unwrap(); // Number of authority RRs
299        dns_body.write_u16::<byteorder::BigEndian>(0x0000).unwrap(); // Number of additional RRs
300
301        // DNS Question
302        for label in subdomain.split('.') {
303            dns_body.push(label.len() as u8);
304            dns_body.write_all(label.as_bytes()).unwrap();
305        }
306        dns_body.push(0); // Terminate the QNAME
307        dns_body.write_u16::<byteorder::BigEndian>(0x0001).unwrap(); // QTYPE (A record)
308        dns_body.write_u16::<byteorder::BigEndian>(0x0001).unwrap(); // QCLASS (IN)
309
310        dns_body
311    }
312
313    /// Create a UDP packet with a CHAOS TXT record request.
314    pub fn chaos_request(
315        src: &Address,
316        dst: &Address,
317        sport: u16,
318        id: &DnsProbeId,
319        chaos: &str,
320    ) -> Vec<u8> {
321        let dns_body = Self::create_chaos_request(id, chaos);
322
323        let udp_length = 8 + dns_body.len() as u32;
324
325        let mut udp_packet = Self {
326            sport,
327            dport: 53u16,
328            length: udp_length as u16,
329            checksum: 0,
330            body: dns_body,
331        };
332
333        let udp_bytes: Vec<u8> = (&udp_packet).into();
334
335        let pseudo_header = PseudoHeader::new(src, dst, 17, udp_length);
336
337        udp_packet.checksum = calculate_checksum(&udp_bytes, &pseudo_header);
338
339        build_ip_packet(
340            src,
341            dst,
342            255,
343            15037,
344            PacketPayload::Udp { value: udp_packet },
345        )
346    }
347
348    /// Creating a DNS TXT record request for CHAOS
349    fn create_chaos_request(id: &DnsProbeId, chaos: &str) -> Vec<u8> {
350        let mut dns_body: Vec<u8> = Vec::new();
351
352        // Transaction ID (6-bit measurement identifier + 10-bit tx worker ID)
353        let tx_id_raw: u16 = id.worker_id as u16;
354        let encoded_tx_id =
355            ((dns_identifier(m_id_of(id.probe_id)) as u16) << 10) | (tx_id_raw & 0x03FF);
356
357        // DNS Header
358        dns_body
359            .write_u16::<byteorder::BigEndian>(encoded_tx_id)
360            .unwrap(); // Transaction ID
361        dns_body.write_u16::<byteorder::BigEndian>(0x0100).unwrap(); // Flags (Standard query, recursion desired)
362        dns_body.write_u16::<byteorder::BigEndian>(0x0001).unwrap(); // Number of questions
363        dns_body.write_u16::<byteorder::BigEndian>(0x0000).unwrap(); // Number of answer RRs
364        dns_body.write_u16::<byteorder::BigEndian>(0x0000).unwrap(); // Number of authority RRs
365        dns_body.write_u16::<byteorder::BigEndian>(0x0000).unwrap(); // Number of additional RRs
366
367        // DNS Question
368        for label in chaos.split('.') {
369            dns_body.push(label.len() as u8);
370            dns_body.write_all(label.as_bytes()).unwrap();
371        }
372        dns_body.push(0); // Terminate the QNAME
373        dns_body.write_u16::<byteorder::BigEndian>(0x0010).unwrap(); // QTYPE (TXT record)
374        dns_body.write_u16::<byteorder::BigEndian>(0x0003).unwrap(); // QCLASS (CHAOS)
375
376        dns_body
377    }
378}
379
380/// Build a DNS A-query message (header + question) for a UDP (Paris) traceroute probe.
381///
382/// The QNAME encodes the probe identity so that the **destination DNS server's** reply
383/// can be matched to a trace session and terminate it:
384///
385/// `{tx_micros}.{src}.{dst}.{worker_id}.{sport}.{probe_id}.{ttl}.{qname}`
386///
387/// where `tx_micros` is the full microsecond send time (so the destination-hop RTT is a
388/// plain epoch delta, matching the ICMP/discovery convention).
389pub(crate) fn dns_a_trace_body(
390    src: &Address,
391    dst: &Address,
392    sport: u16,
393    id: &crate::net::packet::TraceDnsId,
394) -> Vec<u8> {
395    let &crate::net::packet::TraceDnsId {
396        tx_id,
397        probe_id,
398        tx_micros,
399        ttl,
400        qname,
401    } = id;
402
403    let src_num = src.as_numeric();
404    let dst_num = dst.as_numeric();
405    let subdomain =
406        format!("{tx_micros}.{src_num}.{dst_num}.{tx_id}.{sport}.{probe_id}.{ttl}.{qname}");
407
408    let mut dns_body: Vec<u8> = Vec::new();
409
410    // Transaction ID (6-bit measurement identifier + 10-bit tx worker ID), as in dns_request.
411    let encoded_tx_id =
412        ((dns_identifier(m_id_of(probe_id)) as u16) << 10) | ((tx_id as u16) & 0x03FF);
413    dns_body
414        .write_u16::<byteorder::BigEndian>(encoded_tx_id)
415        .unwrap(); // Transaction ID
416    dns_body.write_u16::<byteorder::BigEndian>(0x0100).unwrap(); // Flags (standard query, RD)
417    dns_body.write_u16::<byteorder::BigEndian>(0x0001).unwrap(); // QDCOUNT
418    dns_body.write_u16::<byteorder::BigEndian>(0x0000).unwrap(); // ANCOUNT
419    dns_body.write_u16::<byteorder::BigEndian>(0x0000).unwrap(); // NSCOUNT
420    dns_body.write_u16::<byteorder::BigEndian>(0x0000).unwrap(); // ARCOUNT
421
422    for label in subdomain.split('.') {
423        dns_body.push(label.len() as u8);
424        dns_body.write_all(label.as_bytes()).unwrap();
425    }
426    dns_body.push(0); // Terminate the QNAME
427    dns_body.write_u16::<byteorder::BigEndian>(0x0001).unwrap(); // QTYPE (A record)
428    dns_body.write_u16::<byteorder::BigEndian>(0x0001).unwrap(); // QCLASS (IN)
429
430    dns_body
431}