Skip to main content

manycastr/net/
packet.rs

1use crate::custom_module::manycastr::Address;
2use crate::net::{
3    ICMPPacket, PacketPayload, PseudoHeader, TCPPacket, UDPPacket, build_ip_packet,
4    calculate_checksum,
5};
6use std::time::{SystemTime, UNIX_EPOCH};
7
8/// ICMP arguments to encode in the payload.
9#[derive(Debug)]
10pub struct ProbePayload<'a> {
11    /// Sender worker ID
12    pub worker_id: u32,
13    /// On-wire probe ID: 16-bit measurement ID + 16-bit session ID (to verify and attribute the reply)
14    pub probe_id: u32,
15    /// Optional TTL value of the IP header (for traceroute)
16    pub trace_ttl: Option<u8>,
17    /// Optional URL (e.g., opt-out information)
18    pub info_url: Option<&'a str>,
19}
20
21/// Creates a ping packet to send.
22///
23/// # Arguments
24/// * `src` - the source address for the ping packet
25/// * `dst` - the destination address for the ping packet
26/// * `identifier` - the identifier to use in the ICMP header
27/// * `seq` - the sequence number to use in the ICMP header
28/// * `payload` - information to encode in the payload
29/// * `ttl` - the time-to-live (TTL) value to set in the IP header
30///
31/// # Returns
32/// A ping packet (including the IP header) as a byte vector.
33pub fn create_icmp(
34    src: &Address,
35    dst: &Address,
36    identifier: u16,
37    seq: u16,
38    payload: &ProbePayload,
39    ttl: u8,
40) -> Vec<u8> {
41    let tx_time = SystemTime::now()
42        .duration_since(UNIX_EPOCH)
43        .unwrap()
44        .as_micros() as u64;
45
46    // Create the ping payload bytes
47    let mut payload_bytes: Vec<u8> = Vec::new();
48    payload_bytes.extend_from_slice(&payload.probe_id.to_be_bytes()); // Bytes 0 - 3
49    payload_bytes.extend_from_slice(&tx_time.to_be_bytes()); // Bytes 4 - 11
50    payload_bytes.extend_from_slice(&payload.worker_id.to_be_bytes()); // Bytes 12 - 15
51
52    // Add addresses to payload (used for spoofing detection)
53    payload_bytes.extend_from_slice(&src.to_be_bytes()); // Bytes 16 - 33 (v6) or 16 - 19 (v4)
54    payload_bytes.extend_from_slice(&dst.to_be_bytes()); // Bytes 34 - 51 (v6) or 20 - 23 (v4)
55
56    // Optional, add trace TTL (traceroute measurements)
57    if let Some(trace_ttl) = payload.trace_ttl {
58        payload_bytes.extend_from_slice(&trace_ttl.to_be_bytes()); // Byte 52 (v6) or 24 (v4)
59    }
60
61    // Add info URL to payload
62    if let Some(info_url) = &payload.info_url {
63        payload_bytes.extend_from_slice(info_url.as_bytes());
64    }
65
66    ICMPPacket::echo_request(identifier, seq, payload_bytes, src, dst, ttl)
67}
68
69pub struct DnsProbeId {
70    pub worker_id: u32,
71    pub probe_id: u32,
72}
73
74/// Creates a DNS packet.
75///
76/// # Arguments
77/// * `origin` - the source address and port values we use for our probes
78/// * `worker_id` - the unique worker ID of this worker
79/// * `dst` - the destination address for the DNS packet
80/// * `is_chaos` - whether this is a CHAOS measurement
81/// * `qname` - the DNS record to request
82///
83/// # Returns
84/// A DNS packet (including the IP header) as a byte vector.
85/// DNS probe identity (worker + measurement).
86pub fn create_dns(
87    src: &Address,
88    dst: &Address,
89    sport: u16,
90    id: &DnsProbeId,
91    is_chaos: bool,
92    qname: &str,
93) -> Vec<u8> {
94    let tx_time = SystemTime::now()
95        .duration_since(UNIX_EPOCH)
96        .unwrap()
97        .as_micros() as u64;
98
99    if !is_chaos {
100        UDPPacket::dns_request(src, dst, sport, qname, tx_time, id)
101    } else {
102        UDPPacket::chaos_request(src, dst, sport, id, qname)
103    }
104}
105
106/// Creates a TCP packet.
107///
108/// # Arguments
109/// * `origin` - the source address and port values we use for our probes
110/// * `dst` - the destination address for the TCP packet
111/// * `worker_id` - the unique worker ID of this worker
112/// * `is_discovery` - whether this is a measurement (False) or discovery (True) probe
113/// * `info_url` - Optional URL to encode in packet payload (e.g., opt-out URL)
114///
115/// # Returns
116/// A TCP packet (including the IP header) as a byte vector.
117pub fn create_tcp(
118    src: &Address,
119    dst: &Address,
120    sport: u16,
121    dport: u16,
122    worker_id: u32,
123    is_discovery: bool,
124    info_url: Option<&str>,
125) -> Vec<u8> {
126    let tx_time = SystemTime::now()
127        .duration_since(UNIX_EPOCH)
128        .unwrap()
129        .as_micros() as u32;
130
131    let timestamp_21b = tx_time & 0x1FFFFF;
132    let worker_10b = worker_id & 0x3FF;
133
134    let discovery_bit = if is_discovery { 1u32 << 31 } else { 0 };
135    let ack = discovery_bit | (worker_10b << 21) | timestamp_21b;
136
137    TCPPacket::tcp_syn_ack(src, dst, sport, dport, ack, 255, info_url)
138}
139
140/// Identity of a UDP/DNS trace probe, encoded in the QNAME so the **destination
141/// DNS server's** reply can be matched back to the trace session.
142pub struct TraceDnsId<'a> {
143    /// Sending worker id
144    pub tx_id: u32,
145    /// On-wire probe ID: 16-bit measurement ID + 16-bit session ID (traces always use session 0)
146    pub probe_id: u32,
147    /// Full microsecond send time (for the destination-hop RTT)
148    pub tx_micros: u64,
149    /// Time-to-live / hop limit of the probe (recovered as hop_count)
150    pub ttl: u8,
151    /// The DNS name to query (e.g. `example.org`)
152    pub qname: &'a str,
153}
154
155/// Creates a UDP (Paris) traceroute probe packet with a DNS payload
156///
157/// When the probe reaches its destination DNS server, the server replies to the DNS query,
158/// resulting in the traceroute being terminated (destination reached).
159///
160/// Encoding scheme:
161/// - IPv4 IP identification (16b) / IPv6 flow label (16b of 20b):
162///   `(worker_hi_2 << 14) | timestamp_14b`
163/// - UDP checksum (16b): `(ttl << 8) | worker_lo_8`
164///
165/// # Arguments
166/// * `src` / `dst` - source / destination address
167/// * `sport` / `dport` - configured ports (constant across probes for Paris)
168/// * `identifier` - IP identification / flow label (worker_hi + timestamp)
169/// * `desired_checksum` - value forced into the UDP checksum (ttl + worker_lo)
170/// * `id` - probe identity encoded in the QNAME (worker, measurement, send time, TTL)
171pub fn create_udp_trace(
172    src: &Address,
173    dst: &Address,
174    sport: u16,
175    dport: u16,
176    identifier: u16,
177    desired_checksum: u16,
178    id: &TraceDnsId,
179) -> Vec<u8> {
180    let ttl = id.ttl;
181
182    // Create a valid DNS query with traceroute encodings and the desired UDP checksum
183    let mut body = crate::net::udp::dns_a_trace_body(src, dst, sport, id);
184    let corr_off = body.len(); // correction word appended after the DNS message
185    body.extend_from_slice(&[0u8, 0u8]);
186
187    let udp_length = (8 + body.len()) as u16;
188
189    // Compute the actual checksum with the correction placeholder as 0x0000
190    let tmp_udp = UDPPacket {
191        sport,
192        dport,
193        length: udp_length,
194        checksum: 0,
195        body: body.clone(),
196    };
197    let udp_bytes: Vec<u8> = (&tmp_udp).into();
198    let pseudo_header = PseudoHeader::new(src, dst, 17, udp_length as u32);
199    let actual_checksum = calculate_checksum(&udp_bytes, &pseudo_header);
200
201    let correction = checksum_correction(actual_checksum, desired_checksum);
202    let (b0, b1) = if corr_off.is_multiple_of(2) {
203        ((correction >> 8) as u8, (correction & 0xFF) as u8)
204    } else {
205        ((correction & 0xFF) as u8, (correction >> 8) as u8)
206    };
207    body[corr_off] = b0;
208    body[corr_off + 1] = b1;
209
210    let udp_packet = UDPPacket {
211        sport,
212        dport,
213        length: udp_length,
214        checksum: desired_checksum,
215        body,
216    };
217
218    build_ip_packet(
219        src,
220        dst,
221        ttl,
222        identifier,
223        PacketPayload::Udp { value: udp_packet },
224    )
225}
226
227/// Compute a 2-byte correction word that, when placed in the payload (replacing
228/// the zero placeholder), forces the UDP checksum to `desired`.
229fn checksum_correction(actual: u16, desired: u16) -> u16 {
230    let c: u32 = (!desired as u32) + (actual as u32);
231    let mut c = (c & 0xFFFF) + (c >> 16);
232    c = (c & 0xFFFF) + (c >> 16); // handle second carry
233    c as u16
234}
235
236/// Creates a TCP (Paris) traceroute probe packet.
237///
238/// Encodes the following information in the seq and ack fields.
239/// - Bits 31-22: worker_id (10 bits, up to 1024 workers)
240/// - Bits 21-14: TTL (8 bits)
241/// - Bits 13-0:  timestamp in milliseconds (14 bits)
242///
243/// # Arguments
244/// * `src` - source address
245/// * `dst` - destination address
246/// * `sport` - configured source port
247/// * `dport` - configured destination port
248/// * `seq` - encoded identity (worker_id + ttl + timestamp); written to both seq and ack
249/// * `ttl` - time-to-live / hop limit
250/// * `info_url` - optional URL encoded in payload
251pub fn create_tcp_trace(
252    src: &Address,
253    dst: &Address,
254    sport: u16,
255    dport: u16,
256    seq: u32,
257    ttl: u8,
258    info_url: Option<&str>,
259) -> Vec<u8> {
260    let body: Vec<u8> = if let Some(url) = info_url {
261        url.bytes().collect()
262    } else {
263        vec![]
264    };
265
266    let mut tcp_packet = TCPPacket {
267        sport,
268        dport,
269        seq,
270        ack: seq, // same identity in ack: the destination RST echoes ack (RST.seq = ack + 1)
271        offset: 0b01010000, // Data offset 5 (20 bytes)
272        flags: 0b00010010, // SYN + ACK (unsolicited → elicits RST from the target)
273        checksum: 0,
274        pointer: 0,
275        body,
276        window_size: 65535,
277    };
278
279    let tcp_bytes: Vec<u8> = (&tcp_packet).into();
280    let pseudo_header = PseudoHeader::new(src, dst, 6, tcp_bytes.len() as u32);
281    tcp_packet.checksum = calculate_checksum(&tcp_bytes, &pseudo_header);
282
283    build_ip_packet(
284        src,
285        dst,
286        ttl,
287        15037,
288        PacketPayload::Tcp { value: tcp_packet },
289    )
290}