Skip to main content

manycastr/worker/inbound/
tcp.rs

1use crate::custom_module::manycastr::reply::ReplyData;
2use crate::custom_module::manycastr::{DiscoveryReply, MeasurementReply, Reply, TraceReply};
3use crate::net::TCPPacket;
4use crate::worker::inbound::ReplyMeta;
5use crate::worker::trace_codec::TraceTag;
6
7/// Parse TCP packets into a Reply result.
8/// Only accepts packets with the RST flag set.
9///
10/// # Arguments
11/// * `packet_bytes` - the bytes of the packet to parse
12/// * `sport` - Source port used for outgoing packets (destination port of replies)
13/// * `is_traceroute` - If true, check for traceroute destination replies
14/// * `meta` - received packet metadata (source address, TTL, kernel receive time)
15///
16/// # Returns
17/// * `Option<ResultData>` - the received TCP reply
18///
19/// # Remarks
20/// The function returns None if the packet is too short to contain a TCP header or if the RST flag is not set.
21pub fn parse_tcp(
22    packet_bytes: &[u8],
23    sport: u16,
24    is_traceroute: bool,
25    meta: ReplyMeta,
26) -> Option<Reply> {
27    let ReplyMeta { src, ttl, rx_time } = meta;
28    // Verify RST flag is set
29    if (src.is_v6() && (packet_bytes[13] & 0x04) == 0)
30        || (!src.is_v6() && (packet_bytes[33] & 0x04) == 0)
31    {
32        return None;
33    }
34
35    let tcp_packet = if src.is_v6() {
36        TCPPacket::from(packet_bytes)
37    } else {
38        TCPPacket::from(&packet_bytes[20..])
39    };
40
41    // Verify destination port matches our source port
42    if tcp_packet.dport != sport {
43        return None;
44    }
45
46    let identifier = tcp_packet.seq.wrapping_sub(1); // RST.seq = our ack + 1
47    let is_discovery = (identifier >> 31) & 1 == 1;
48
49    if is_discovery {
50        // Discovery probe (regular layout sets bit 31); identifies the catching worker.
51        Some(Reply {
52            reply_data: Some(ReplyData::Discovery(DiscoveryReply {
53                src: Some(src),
54                session_id: 0, // Does not fit in TCP probes
55            })),
56        })
57    } else if is_traceroute {
58        // Trace probe
59        let tag = TraceTag::decode_tcp_seq(identifier);
60        Some(Reply {
61            reply_data: Some(ReplyData::Trace(TraceReply {
62                hop_addr: Some(src),
63                ttl,
64                rtt: super::rtt_ms(rx_time, tag.ts14 as u64, super::TxEncoding::Trace14),
65                tx_id: tag.worker_id,
66                trace_dst: Some(src),
67                hop_count: tag.ttl as u32,
68            })),
69        })
70    } else {
71        let tx_id = (identifier >> 21) & 0x3FF;
72        let tx_time_21b = identifier & 0x1FFFFF;
73        Some(Reply {
74            reply_data: Some(ReplyData::Measurement(MeasurementReply {
75                src: Some(src),
76                ttl,
77                rtt: super::rtt_ms(rx_time, tx_time_21b as u64, super::TxEncoding::Tcp21),
78                tx_id,
79                chaos: None,
80                session_id: 0, // Does not fit in TCP probes
81            })),
82        })
83    }
84}