1#![warn(missing_docs, clippy::unwrap_used, clippy::expect_used)]
6
7use std::collections::HashMap;
8use std::collections::VecDeque;
9use std::io;
10use std::net::IpAddr;
11use std::net::SocketAddr;
12use std::sync::Arc;
13use std::time::Duration;
14
15use isakmp::v1::definitions::NotifyMessageType;
16use isakmp::v1::generator::MessageBuilder;
17use isakmp::v1::generator::Transform;
18use thiserror::Error;
19use tokio::net::UdpSocket;
20use tokio::select;
21use tokio::sync::mpsc;
22use tokio::time::interval;
23use tokio::time::sleep;
24use tracing::debug;
25use tracing::error;
26use tracing::info;
27use tracing::instrument;
28use tracing::trace;
29use tracing::warn;
30
31use crate::recv::ReceiveError;
32use crate::utils::gen_transforms::gen_v1_transforms;
33use crate::utils::payload_to_transforms::payload_to_transforms;
34
35mod recv;
36pub mod utils;
37
38#[derive(Debug, Clone)]
40pub struct ScanResult {
41 pub valid_transforms: Vec<Transform>,
43}
44
45#[derive(Debug, Clone)]
47pub struct ScanOptions {
48 pub ip: IpAddr,
50 pub port: u16,
52 pub interval: u64,
54 pub transform_no: usize,
56 pub sleep_on_transform_found: Duration,
60}
61
62#[instrument(skip_all)]
64pub async fn scan(opts: ScanOptions) -> Result<ScanResult, ScanError> {
65 let addr = SocketAddr::new(opts.ip, opts.port);
67
68 info!("Binding and starting to scan {addr}");
69 let socket = Arc::new(match addr.ip() {
70 IpAddr::V4(_) => UdpSocket::bind("0.0.0.0:500")
71 .await
72 .map_err(ScanError::CouldNotBind)?,
73 IpAddr::V6(_) => UdpSocket::bind("[::]:500")
74 .await
75 .map_err(ScanError::CouldNotBind)?,
76 });
77 socket.connect(&addr).await.map_err(ScanError::Receive)?;
78
79 let (tx, mut rx) = mpsc::unbounded_channel();
80 let mut interval = interval(Duration::from_millis(opts.interval));
81
82 tokio::spawn(recv::handle_receive(socket.clone(), tx));
83
84 let mut todo: VecDeque<Vec<_>> = gen_v1_transforms(opts.transform_no);
86
87 let mut open: HashMap<u64, Vec<Transform>> = HashMap::new();
89
90 let mut lost_cookies = 0;
92
93 let mut found: Vec<Transform> = vec![];
95
96 let mut do_sleep = false;
98
99 loop {
100 select! {
101 msg_res = rx.recv() => {
103 if let Some(res) = msg_res {
104 match res {
105 Ok(msg) => {
106 trace!("Received message: {msg:?}");
107
108 if !msg.security_associations.is_empty() {
110 for sa in &msg.security_associations {
111 for prop in &sa.proposal_payload {
112 do_sleep = true;
113
114 let Ok(transforms) = payload_to_transforms(prop) else {
115 warn!("Could not retrieve transform from msg: {msg:?}");
116 debug!("{msg:?}");
117 continue;
118 };
119
120 found.extend(transforms.clone());
122
123 let Some(all) = open.get(&msg.header.initiator_cookie) else {
124 warn!("Missing initiator cookie");
125 trace!("{} :: {:#?}", msg.header.initiator_cookie, open);
126 continue;
127 };
128
129 let other: Vec<Transform> = all.clone().into_iter().filter(|x| !transforms.contains(x)).collect();
131
132 let [mut a,mut b] = [vec![], vec![]];
134 for x in other {
135 if a.len() == b.len() {
136 a.push(x);
137 } else {
138 b.push(x);
139 }
140 }
141
142 if !b.is_empty() {
144 todo.push_back(a);
145 todo.push_back(b);
146 } else if !a.is_empty() {
147 todo.push_back(a);
148 }
149 }
150 }
151 let removed = open.remove(&msg.header.initiator_cookie);
152 if removed.is_none() {
153 warn!("Could not find corresponding initiator cookie: {}", msg.header.initiator_cookie);
154 lost_cookies += 1;
155 }
156
157 } else if msg.notification_payloads.iter().any(|x| x.notify_message_type == NotifyMessageType::NoProposalChosen) {
159 let removed = open.remove(&msg.header.initiator_cookie);
160 if removed.is_none() {
161 warn!("Could not find corresponding initiator cookie: {}", msg.header.initiator_cookie);
162 lost_cookies += 1;
163 }
164 } else {
165 warn!("Unknown message: {:?}", msg)
166 }
167
168 }
169 Err(err) => match err {
170 ReceiveError::Io(err) => {
171 error!("Error in receiving side: {err}");
172 return Err(ScanError::Receive(err));
173 }
174 ReceiveError::InvalidMessage(err) => {
175 trace!("Could not parse incoming message: {err}");
176 }
177 }
178 }
179 }
180 }
181
182 _ = interval.tick() => {
184 match todo.pop_front() {
185 None => {
187 debug!("Nothing more to do, waiting some time for more incoming messages");
188 interval.tick().await;
189 if todo.is_empty() && open.len() <= lost_cookies {
190 found.sort();
191 found.dedup();
192
193 return Ok(ScanResult {
194 valid_transforms: found,
195 })
196 }
197 }
198 Some(transforms) => {
199 let mut mb = MessageBuilder::new();
200 for transform in &transforms {
201 mb = mb.add_transform(transform.clone());
202 }
203 let (msg, initiator_cookie) = mb.build();
204 trace!("Send ({initiator_cookie}) transforms: {transforms:?}");
205
206 if do_sleep {
207 info!(
208 "Sleep {} seconds to evade running into timeout due to half-open connections",
209 opts.sleep_on_transform_found.as_secs(),
210 );
211 sleep(opts.sleep_on_transform_found).await;
212 do_sleep = false;
213 }
214
215 open.insert(initiator_cookie, transforms);
216 socket.send(&msg).await.map_err(ScanError::Send)?;
217 }
218 }
219 }
220 }
221 }
222}
223
224#[derive(Debug, Error)]
226#[allow(missing_docs)]
227pub enum ScanError {
228 #[error("Could not bind: {0}")]
229 CouldNotBind(io::Error),
230 #[error("Could not recv: {0}")]
231 Receive(io::Error),
232 #[error("Could not send: {0}")]
233 Send(io::Error),
234}