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}