|
1 | 1 | use anyhow::Result;
|
2 | 2 | use caracat::models::Probe;
|
3 | 3 | use caracat::rate_limiter::RateLimitingMethod;
|
4 |
| -// use log::{info, warn}; |
5 |
| -// use rdkafka::message::BorrowedMessage; |
6 |
| -// use rdkafka::message::{Headers, Message}; |
7 |
| -use std::net::IpAddr; |
| 4 | +use ipnet::IpNet; |
| 5 | +use rand::seq::SliceRandom; |
| 6 | +use rand::thread_rng; |
8 | 7 | use std::net::Ipv4Addr;
|
9 | 8 | use std::net::Ipv6Addr;
|
10 | 9 | use std::time::Duration;
|
@@ -66,68 +65,99 @@ fn create_config() -> Config {
|
66 | 65 | }
|
67 | 66 |
|
68 | 67 | struct Payload {
|
69 |
| - dst_addr: IpAddr, |
| 68 | + prefix: IpNet, |
70 | 69 | min_ttl: u8,
|
71 | 70 | max_ttl: u8,
|
| 71 | + n_flows: u64, |
72 | 72 | }
|
73 | 73 |
|
74 | 74 | fn decode_payload(payload: &str) -> Result<Payload> {
|
75 | 75 | let parts: Vec<&str> = payload.split(',').collect();
|
76 | 76 | Ok(Payload {
|
77 |
| - dst_addr: parts[0].parse()?, |
| 77 | + prefix: parts[0].parse()?, |
78 | 78 | min_ttl: parts[1].parse()?,
|
79 | 79 | max_ttl: parts[2].parse()?,
|
| 80 | + n_flows: parts[3].parse()?, |
80 | 81 | })
|
81 | 82 | }
|
82 | 83 |
|
83 |
| -// pub async fn handle(m: &BorrowedMessage<'_>) -> Result<()> { |
| 84 | +fn generate_probes(payload: &Payload) -> Result<Vec<Probe>> { |
| 85 | + // TODO: We should pass an iterator instead of a vector. |
| 86 | + let mut probes = vec![]; |
| 87 | + |
| 88 | + // First start by dividing the prefix into /24s or /64s, if necessary. |
| 89 | + let subnets = match payload.prefix { |
| 90 | + IpNet::V4(_) => { |
| 91 | + let prefix_len = payload.prefix.prefix_len(); |
| 92 | + let target_len = if prefix_len > 24 { prefix_len } else { 24 }; |
| 93 | + payload.prefix.subnets(target_len) |
| 94 | + } |
| 95 | + IpNet::V6(_) => { |
| 96 | + let prefix_len = payload.prefix.prefix_len(); |
| 97 | + let target_len = if prefix_len > 64 { prefix_len } else { 64 }; |
| 98 | + payload.prefix.subnets(target_len) |
| 99 | + } |
| 100 | + }?; |
| 101 | + |
| 102 | + // Iterate over the subnets and generate the probes. |
| 103 | + for subnet in subnets { |
| 104 | + // Right now the probe generation is simplistic, we just iterate over the hosts. |
| 105 | + // If we need more flows than hosts, we will we explicitely fail. |
| 106 | + // TODO: implement mapper-like generator such as the ones in diamond-miner. |
| 107 | + // https://github.com/dioptra-io/diamond-miner/blob/main/diamond_miner/mappers.py |
| 108 | + let mut prefix_hosts = subnet.hosts(); |
| 109 | + if payload.n_flows > prefix_hosts.count().try_into()? { |
| 110 | + return Err(anyhow::anyhow!("Not enough hosts in the prefix")); |
| 111 | + } |
| 112 | + |
| 113 | + for _ in 0..payload.n_flows { |
| 114 | + let dst_addr = prefix_hosts.next().unwrap(); |
| 115 | + |
| 116 | + // Randomize the probes order within a flow. |
| 117 | + // In YARRP we randomize the probes over the entire probing space. |
| 118 | + // This is of course a very big simplication, but it's not that silly. |
| 119 | + // The rational is to avoid results errors due to path changes. |
| 120 | + // So, for now, probes belonging to the same traceroute flow will be sent close in time. |
| 121 | + // TODO: is this shuffle fast? |
| 122 | + let mut ttls: Vec<u8> = (payload.min_ttl..payload.max_ttl).collect(); |
| 123 | + ttls.shuffle(&mut thread_rng()); |
| 124 | + |
| 125 | + for i in ttls { |
| 126 | + probes.push(Probe { |
| 127 | + dst_addr, |
| 128 | + src_port: 24000, |
| 129 | + dst_port: 33434, |
| 130 | + ttl: i, |
| 131 | + protocol: caracat::models::L4::ICMPv6, |
| 132 | + }); |
| 133 | + } |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + Ok(probes) |
| 138 | +} |
| 139 | + |
84 | 140 | pub async fn handle(
|
85 | 141 | brokers: &str,
|
86 | 142 | _in_group_id: &str,
|
87 | 143 | _in_topics: &[&str],
|
88 | 144 | out_topic: &str,
|
89 | 145 | ) -> Result<()> {
|
90 |
| - // let payload = match m.payload_view::<str>() { |
91 |
| - // None => "", |
92 |
| - // Some(Ok(s)) => s, |
93 |
| - // Some(Err(e)) => { |
94 |
| - // warn!("Error while deserializing message payload: {:?}", e); |
95 |
| - // "" |
96 |
| - // } |
97 |
| - // }; |
98 |
| - |
99 |
| - // info!( |
100 |
| - // "key: '{:?}', payload: '{}', topic: {}, partition: {}, offset: {}, timestamp: {:?}", |
101 |
| - // m.key(), |
102 |
| - // payload, |
103 |
| - // m.topic(), |
104 |
| - // m.partition(), |
105 |
| - // m.offset(), |
106 |
| - // m.timestamp() |
107 |
| - // ); |
108 |
| - |
109 |
| - // if let Some(headers) = m.headers() { |
110 |
| - // for header in headers.iter() { |
111 |
| - // info!(" Header {:#?}: {:?}", header.key, header.value); |
112 |
| - // } |
113 |
| - // } |
114 |
| - |
115 |
| - let payload = "2606:4700:4700::1111,1,32"; |
| 146 | + let payload = "2606:4700:4700::1111/128,1,32,1"; |
116 | 147 |
|
117 | 148 | // Probing
|
118 | 149 | let config = create_config();
|
119 | 150 | let payload = decode_payload(payload)?;
|
120 | 151 |
|
121 |
| - let mut probes_to_send = vec![]; |
122 |
| - for i in payload.min_ttl..=payload.max_ttl { |
123 |
| - probes_to_send.push(Probe { |
124 |
| - dst_addr: payload.dst_addr.clone(), |
125 |
| - src_port: 24000, |
126 |
| - dst_port: 33434, |
127 |
| - ttl: i, |
128 |
| - protocol: caracat::models::L4::ICMPv6, |
129 |
| - }); |
130 |
| - } |
| 152 | + let probes_to_send = generate_probes(&payload); |
| 153 | + let probes_to_send = match probes_to_send { |
| 154 | + Ok(probes) => probes, |
| 155 | + Err(e) => { |
| 156 | + eprintln!("Error: {}", e); |
| 157 | + return Ok(()); |
| 158 | + } |
| 159 | + }; |
| 160 | + |
131 | 161 | let result = task::spawn_blocking(move || probe(config, probes_to_send.into_iter())).await?;
|
132 | 162 |
|
133 | 163 | let (_, _, results) = result?;
|
|
0 commit comments