1#[cfg(feature = "os")]
3use chrono::DateTime;
4#[cfg(feature = "os")]
5use chrono::Local;
6#[cfg(feature = "os")]
7use prettytable::row;
8#[cfg(feature = "os")]
9use prettytable::Cell;
10#[cfg(feature = "os")]
11use prettytable::Row;
12#[cfg(feature = "os")]
13use prettytable::Table;
14#[cfg(feature = "os")]
15use serde::Deserialize;
16#[cfg(feature = "os")]
17use serde::Serialize;
18#[cfg(feature = "os")]
19use std::collections::BTreeMap;
20#[cfg(feature = "os")]
21use std::fmt;
22#[cfg(feature = "os")]
23use std::io::Cursor;
24#[cfg(feature = "os")]
25use std::io::Read;
26#[cfg(feature = "os")]
27use std::net::IpAddr;
28#[cfg(feature = "os")]
29use std::sync::mpsc::channel;
30#[cfg(feature = "os")]
31use std::time::Duration;
32#[cfg(feature = "os")]
33use std::time::Instant;
34#[cfg(feature = "os")]
35use tracing::debug;
36#[cfg(feature = "os")]
37use tracing::warn;
38#[cfg(feature = "os")]
39use zip::ZipArchive;
40
41#[cfg(feature = "os")]
42use crate::error::PistolError;
43#[cfg(feature = "os")]
44use crate::layer::infer_addr;
45#[cfg(feature = "os")]
46use crate::os::dbparser::NmapOSDB;
47#[cfg(feature = "os")]
48use crate::os::osscan::os_probe_thread;
49#[cfg(feature = "os")]
50use crate::os::osscan::Fingerprint;
51#[cfg(feature = "os")]
52use crate::os::osscan6::os_probe_thread6;
53#[cfg(feature = "os")]
54use crate::os::osscan6::Fingerprint6;
55#[cfg(feature = "os")]
56use crate::utils::get_threads_pool;
57#[cfg(feature = "os")]
58use crate::utils::num_threads_check;
59#[cfg(feature = "os")]
60use crate::utils::time_sec_to_string;
61#[cfg(feature = "os")]
62use crate::Target;
63
64#[cfg(feature = "os")]
65pub mod dbparser;
66#[cfg(feature = "os")]
67pub mod operator;
68#[cfg(feature = "os")]
69pub mod operator6;
70#[cfg(feature = "os")]
71pub mod osscan;
72#[cfg(feature = "os")]
73pub mod osscan6;
74#[cfg(feature = "os")]
75pub mod packet;
76#[cfg(feature = "os")]
77pub mod packet6;
78#[cfg(feature = "os")]
79pub mod rr;
80
81#[cfg(feature = "os")]
82#[derive(Debug, Clone)]
83pub struct OsInfo {
84 pub name: String,
85 pub class: Vec<String>,
86 pub cpe: Vec<String>,
87 pub score: usize,
88 pub total: usize,
89 pub db: NmapOSDB,
90}
91
92#[cfg(feature = "os")]
93#[derive(Debug, Clone)]
94pub struct OsInfo6 {
95 pub name: String,
96 pub class: String,
97 pub cpe: String,
98 pub score: f64,
99 pub label: usize,
100}
101
102#[cfg(feature = "os")]
103#[derive(Debug, Clone)]
104pub struct OsDetect4 {
105 pub addr: IpAddr,
106 pub origin: Option<String>,
107 pub alive: bool,
108 pub fingerprint: Fingerprint,
109 pub detects: Vec<OsInfo>,
110 pub cost: Duration,
111}
112
113#[cfg(feature = "os")]
114#[derive(Debug, Clone)]
115pub struct OsDetect6 {
116 pub addr: IpAddr,
117 pub origin: Option<String>,
118 pub alive: bool,
119 pub fingerprint: Fingerprint6,
120 pub detects: Vec<OsInfo6>,
121 pub cost: Duration,
122}
123
124#[cfg(feature = "os")]
125#[derive(Debug, Clone)]
126pub enum OsDetect {
127 V4(OsDetect4),
128 V6(OsDetect6),
129}
130
131#[cfg(feature = "os")]
132impl OsDetect {
133 pub fn addr(&self) -> IpAddr {
134 match self {
135 OsDetect::V4(ipv4) => ipv4.addr,
136 OsDetect::V6(ipv6) => ipv6.addr,
137 }
138 }
139}
140
141#[cfg(feature = "os")]
142#[derive(Debug, Clone)]
143pub struct PistolOsDetects {
144 pub os_detects: Vec<OsDetect>,
145 pub start_time: DateTime<Local>,
146 pub end_time: DateTime<Local>,
147}
148
149#[cfg(feature = "os")]
150impl PistolOsDetects {
151 pub fn new() -> PistolOsDetects {
152 PistolOsDetects {
153 os_detects: Vec::new(),
154 start_time: Local::now(),
155 end_time: Local::now(),
156 }
157 }
158 pub fn value(&self) -> Vec<OsDetect> {
159 self.os_detects.clone()
160 }
161 pub fn finish(&mut self, os_detects: Vec<OsDetect>) {
162 self.end_time = Local::now();
163 self.os_detects = os_detects;
164 }
165}
166
167#[cfg(feature = "os")]
168impl fmt::Display for PistolOsDetects {
169 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170 let mut table = Table::new();
171 table.add_row(Row::new(vec![Cell::new("OS Detect Results")
172 .style_spec("c")
173 .with_hspan(7)]));
174
175 table.add_row(
176 row![c -> "id", c -> "addr", c -> "rank", c -> "score", c -> "os", c -> "cpe", c -> "time cost"],
177 );
178
179 let mut btm_addr: BTreeMap<IpAddr, OsDetect> = BTreeMap::new();
181 for detect in &self.os_detects {
182 btm_addr.insert(detect.addr(), detect.clone());
183 }
184
185 let mut total_cost = 0.0;
186 let mut id = 1;
187 for (addr, detect) in btm_addr {
188 match detect {
189 OsDetect::V4(o) => {
190 let time_cost_str = time_sec_to_string(o.cost);
191 total_cost += o.cost.as_secs_f64();
192 if o.alive {
193 if o.detects.len() > 0 {
194 for (i, os_info) in o.detects.iter().enumerate() {
195 let addr_str = match &o.origin {
196 Some(origin) => format!("{}({})", addr, origin),
197 None => format!("{}", addr),
198 };
199
200 let rank_str = format!("#{}", i + 1);
201 let score_str = format!("{}/{}", os_info.score, os_info.total);
202 let os_details = &os_info.name;
203 let os_cpe = os_info.cpe.join("|");
204 table.add_row(row![c -> id, c -> addr_str, c -> rank_str, c -> score_str, c -> os_details, c -> os_cpe, c -> time_cost_str]);
205 id += 1;
206 }
207 } else {
208 table.add_row(Row::new(vec![
209 Cell::new("there are no matching results, which is very uncommon and is maybe a bug").with_hspan(7),
210 ]));
211 }
212 } else {
213 let addr_str = match o.origin {
214 Some(origin) => format!("{}({})", addr, origin),
215 None => format!("{}", addr),
216 };
217
218 let rank_str = format!("#{}", 1);
219 table.add_row(row![c -> id, c -> addr_str, c -> rank_str, c -> "0/0", c -> "target dead", c -> "", c -> time_cost_str]);
220 id += 1;
221 }
222 }
223 OsDetect::V6(o) => {
224 let time_cost_str = time_sec_to_string(o.cost);
225 total_cost += o.cost.as_secs_f64();
226 if o.alive {
227 if o.detects.len() > 0 {
228 for (i, os_info6) in o.detects.iter().enumerate() {
229 let addr_str = match &o.origin {
230 Some(origin) => format!("{}({})", addr, origin),
231 None => format!("{}", addr),
232 };
233
234 let number_str = format!("#{}", i + 1);
235 let score_str = format!("{:.1}", os_info6.score);
236 let os_str = &os_info6.name;
237 let os_cpe = &os_info6.cpe;
238 table.add_row(row![c -> id, c -> addr_str, c -> number_str, c -> score_str, c -> os_str, c -> os_cpe, c -> time_cost_str]);
239 id += 1;
240 }
241 } else {
242 table.add_row(Row::new(vec![Cell::new(
243 "no results, usually caused by a novelty value greater than 15.0",
244 )
245 .with_hspan(7)]));
246 }
247 } else {
248 let addr_str = match o.origin {
249 Some(origin) => format!("{}({})", addr, origin),
250 None => format!("{}", addr),
251 };
252
253 let rank_str = format!("#{}", 1);
254 table.add_row(row![c -> id, c -> addr_str, c -> rank_str, c -> "0.0", c -> "target dead", c -> "", c -> time_cost_str]);
255 id += 1;
256 }
257 }
258 }
259 }
260 let avg_cost = total_cost / self.os_detects.len() as f64;
261 let summary = format!(
262 "total used time: {:.3}s, avg time cost: {:.3}s",
263 total_cost, avg_cost,
264 );
265 table.add_row(Row::new(vec![Cell::new(&summary).with_hspan(7)]));
266 write!(f, "{}", table)
267 }
268}
269
270#[cfg(feature = "os")]
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct NmapJsonParameters {
273 pub name: String,
274 pub value: Vec<f64>,
275}
276
277#[cfg(feature = "os")]
278#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct CPE {
280 pub name: String,
281 pub osclass: Vec<Vec<String>>,
282 pub cpe: Vec<String>,
283}
284
285#[cfg(feature = "os")]
286#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct Linear {
288 pub infolist: Vec<String>,
289 pub w: Vec<Vec<f64>>,
290 pub scale: Vec<Vec<f64>>,
291 pub mean: Vec<Vec<f64>>,
292 pub variance: Vec<Vec<f64>>,
293 pub cpe: Vec<CPE>,
294}
295
296#[cfg(feature = "os")]
297fn gen_linear() -> Result<Linear, PistolError> {
298 let variance_json_data = include_str!("./db/nmap-os-db-ipv6/variance.json");
299 let variance_json: Vec<NmapJsonParameters> = serde_json::from_str(variance_json_data)?;
300
301 let mut infolist = Vec::new();
302 let mut variance = Vec::new();
303 for v in variance_json {
304 variance.push(v.value);
305 infolist.push(v.name);
306 }
307 assert_eq!(infolist.len(), 92);
308 assert_eq!(variance.len(), 92);
309
310 let mean_json_data = include_str!("./db/nmap-os-db-ipv6/mean.json");
311 let mean_json: Vec<NmapJsonParameters> = serde_json::from_str(mean_json_data)?;
312 let mut mean = Vec::new();
313 for m in mean_json {
314 mean.push(m.value);
315 }
316 assert_eq!(mean.len(), 92);
317
318 let scale_json_data = include_str!("./db/nmap-os-db-ipv6/scale.json"); let scale_json: Vec<NmapJsonParameters> = serde_json::from_str(scale_json_data)?;
320 let mut scale: Vec<Vec<f64>> = Vec::new();
321 for s in scale_json {
322 scale.push(s.value)
323 }
324 assert_eq!(scale.len(), 695);
325
326 let w_json_data = include_str!("./db/nmap-os-db-ipv6/w.json"); let w_json: Vec<NmapJsonParameters> = serde_json::from_str(w_json_data)?;
328 assert_eq!(w_json.len(), 695);
329
330 let mut w = Vec::new();
331 for i in 0..w_json[0].value.len() {
333 let mut tmp = Vec::new();
334 for x in &w_json {
335 tmp.push(x.value[i]);
336 }
337 w.push(tmp);
338 }
339
340 let cpe_json_data = include_str!("./db/nmap-os-db-ipv6/cpe.json"); let cpe: Vec<CPE> = serde_json::from_str(cpe_json_data)?;
342 assert_eq!(cpe.len(), 92);
343
344 let linear = Linear {
345 infolist,
346 scale,
347 w,
348 mean,
349 variance,
350 cpe,
351 };
352 Ok(linear)
353}
354
355#[cfg(feature = "os")]
356fn get_nmap_os_db() -> Result<Vec<NmapOSDB>, PistolError> {
357 let data = include_bytes!("./db/nmap-os-db.zip");
358 let reader = Cursor::new(data);
359 let mut archive = ZipArchive::new(reader)?;
360
361 if archive.len() > 0 {
362 let mut file = archive.by_index(0)?;
363 let mut contents = String::new();
364 file.read_to_string(&mut contents)?;
365 let ret: Vec<NmapOSDB> = serde_json::from_str(&contents)?;
366 Ok(ret)
367 } else {
368 Err(PistolError::ZipEmptyError)
369 }
370}
371
372#[cfg(feature = "os")]
374pub fn os_detect(
375 targets: &[Target],
376 num_threads: Option<usize>,
377 src_addr: Option<IpAddr>,
378 top_k: usize,
379 timeout: Option<Duration>,
380) -> Result<PistolOsDetects, PistolError> {
381 let num_threads = match num_threads {
382 Some(t) => t,
383 None => {
384 let num_threads = targets.len();
385 let num_threads = num_threads_check(num_threads);
386 num_threads
387 }
388 };
389
390 let (tx, rx) = channel();
391 let pool = get_threads_pool(num_threads);
392 let mut recv_size = 0;
393 let mut ret = PistolOsDetects::new();
394 for target in targets {
395 let dst_addr = target.addr;
396 let tx = tx.clone();
397 recv_size += 1;
398 let ia = match infer_addr(dst_addr, src_addr)? {
399 Some(ia) => ia,
400 None => return Err(PistolError::CanNotFoundSourceAddress),
401 };
402 match dst_addr {
403 IpAddr::V4(_) => {
404 let dst_ports = target.ports.clone();
405 let (dst_ipv4, src_ipv4) = ia.ipv4_addr()?;
406 let nmap_os_db = get_nmap_os_db()?;
407 debug!("ipv4 nmap os db parse finish");
408 let origin = target.origin.clone();
409 pool.execute(move || {
410 let start_time = Instant::now();
411 let detect_rets = if dst_ports.len() >= 3 {
412 let dst_open_tcp_port = dst_ports[0];
413 let dst_closed_tcp_port = dst_ports[1];
414 let dst_closed_udp_port = dst_ports[2];
415 let os_detect_ret = os_probe_thread(
416 dst_ipv4,
417 dst_open_tcp_port,
418 dst_closed_tcp_port,
419 dst_closed_udp_port,
420 src_ipv4,
421 nmap_os_db,
422 top_k,
423 timeout,
424 );
425 os_detect_ret
426 } else {
427 Err(PistolError::OSDetectPortsNotEnough)
428 };
429 let od = match detect_rets {
430 Ok((fingerprint, detects)) => {
431 let o = OsDetect4 {
432 addr: dst_addr,
433 origin: origin.clone(),
434 alive: true,
435 fingerprint,
436 detects,
437 cost: start_time.elapsed(),
438 };
439 let od = OsDetect::V4(o);
440 Ok(od)
441 }
442 Err(e) => Err(e),
443 };
444 let _ = tx.send((dst_addr, origin.clone(), od, start_time));
445 });
446 }
447 IpAddr::V6(_) => {
448 let dst_ports = target.ports.clone();
449 let (dst_ipv6, src_ipv6) = ia.ipv6_addr()?;
450 let linear = gen_linear()?;
451 debug!("ipv6 gen linear parse finish");
452 let origin = target.origin.clone();
453 pool.execute(move || {
454 let start_time = Instant::now();
455 let detect_rets = if dst_ports.len() >= 3 {
456 let dst_open_tcp_port = dst_ports[0];
457 let dst_closed_tcp_port = dst_ports[1];
458 let dst_closed_udp_port = dst_ports[2];
459
460 let os_detect_ret = os_probe_thread6(
461 dst_ipv6,
462 dst_open_tcp_port,
463 dst_closed_tcp_port,
464 dst_closed_udp_port,
465 src_ipv6,
466 top_k,
467 linear,
468 timeout,
469 );
470 os_detect_ret
471 } else {
472 Err(PistolError::OSDetectPortsNotEnough)
473 };
474 let od = match detect_rets {
475 Ok((fingerprint, detects)) => {
476 let o = OsDetect6 {
477 addr: dst_addr,
478 origin: origin.clone(),
479 alive: true,
480 fingerprint,
481 detects,
482 cost: start_time.elapsed(),
483 };
484 let od = OsDetect::V6(o);
485 Ok(od)
486 }
487 Err(e) => Err(e),
488 };
489 let _ = tx.send((dst_addr, origin.clone(), od, start_time));
490 });
491 }
492 }
493 }
494
495 let iter = rx.into_iter().take(recv_size);
496 let mut os_detects = Vec::new();
497 for (addr, origin, r, start_time) in iter {
498 match r {
499 Ok(od) => {
500 os_detects.push(od);
501 }
502 Err(e) => {
503 warn!("os probe error: {}", e);
504 match addr {
505 IpAddr::V4(_) => {
506 let o = OsDetect4 {
507 addr,
508 origin,
509 alive: false,
510 fingerprint: Fingerprint::empty(),
511 detects: Vec::new(),
512 cost: start_time.elapsed(),
513 };
514 let od = OsDetect::V4(o);
515 os_detects.push(od);
516 }
517 IpAddr::V6(_) => {
518 let o = OsDetect6 {
519 addr,
520 origin,
521 alive: false,
522 fingerprint: Fingerprint6::empty(),
523 detects: Vec::new(),
524 cost: start_time.elapsed(),
525 };
526 let od = OsDetect::V6(o);
527 os_detects.push(od);
528 }
529 }
530 }
531 }
532 }
533 ret.finish(os_detects);
534 Ok(ret)
535}
536
537#[cfg(feature = "os")]
538pub fn os_detect_raw(
539 dst_addr: IpAddr,
540 dst_open_tcp_port: u16,
541 dst_closed_tcp_port: u16,
542 dst_closed_udp_port: u16,
543 src_addr: Option<IpAddr>,
544 top_k: usize,
545 timeout: Option<Duration>,
546) -> Result<OsDetect, PistolError> {
547 let start_time = Instant::now();
548 let ia = match infer_addr(dst_addr, src_addr)? {
549 Some(ia) => ia,
550 None => return Err(PistolError::CanNotFoundSourceAddress),
551 };
552 match dst_addr {
553 IpAddr::V4(_) => {
554 let (dst_ipv4, src_ipv4) = ia.ipv4_addr()?;
555 let nmap_os_db = get_nmap_os_db()?;
556 debug!("ipv4 nmap os db parse finish");
557 let nmap_os_db = nmap_os_db.to_vec();
558 match os_probe_thread(
559 dst_ipv4,
560 dst_open_tcp_port,
561 dst_closed_tcp_port,
562 dst_closed_udp_port,
563 src_ipv4,
564 nmap_os_db,
565 top_k,
566 timeout,
567 ) {
568 Ok((fingerprint, detects)) => {
569 let o = OsDetect4 {
570 addr: dst_addr,
571 origin: None,
572 alive: true,
573 fingerprint,
574 detects,
575 cost: start_time.elapsed(),
576 };
577 let od = OsDetect::V4(o);
578 Ok(od)
579 }
580 Err(e) => {
581 warn!("os probe error: {}", e);
582 let o = OsDetect4 {
583 addr: dst_addr,
584 origin: None,
585 alive: false,
586 fingerprint: Fingerprint::empty(),
587 detects: Vec::new(),
588 cost: start_time.elapsed(),
589 };
590 let od = OsDetect::V4(o);
591 Ok(od)
592 }
593 }
594 }
595 IpAddr::V6(_) => {
596 let (dst_ipv6, src_ipv6) = ia.ipv6_addr()?;
597 let linear = gen_linear()?;
598 debug!("ipv6 gen linear parse finish");
599 match os_probe_thread6(
600 dst_ipv6,
601 dst_open_tcp_port,
602 dst_closed_tcp_port,
603 dst_closed_udp_port,
604 src_ipv6,
605 top_k,
606 linear,
607 timeout,
608 ) {
609 Ok((fingerprint, detects)) => {
610 let o = OsDetect6 {
611 addr: dst_addr,
612 origin: None,
613 alive: true,
614 fingerprint,
615 detects,
616 cost: start_time.elapsed(),
617 };
618 let od = OsDetect::V6(o);
619 Ok(od)
620 }
621 Err(e) => {
622 warn!("os probe error: {}", e);
623 let o = OsDetect6 {
624 addr: dst_addr,
625 origin: None,
626 alive: false,
627 fingerprint: Fingerprint6::empty(),
628 detects: Vec::new(),
629 cost: start_time.elapsed(),
630 };
631 let od = OsDetect::V6(o);
632 Ok(od)
633 }
634 }
635 }
636 }
637}
638
639#[cfg(feature = "os")]
640#[cfg(test)]
641mod tests {
642 use super::*;
643 use crate::PistolLogger;
644 use crate::PistolRunner;
645 use crate::Target;
646 use std::net::Ipv4Addr;
647 use std::net::Ipv6Addr;
648 use std::str::FromStr;
649 #[test]
650 fn test_os_detect_windows_server_2025() {
651 let _pr = PistolRunner::init(
683 PistolLogger::Debug,
684 Some(String::from("os_detect.pcapng")),
685 None, )
687 .unwrap();
688
689 let src_addr = None;
690 let dst_closed_tcp_port = 8765;
691 let dst_closed_udp_port = 9876;
692
693 let addr1 = IpAddr::V4(Ipv4Addr::new(192, 168, 5, 128));
698 let dst_open_tcp_port = 3389;
699 let target1 = Target::new(
700 addr1,
701 Some(vec![
702 dst_open_tcp_port,
703 dst_closed_tcp_port,
704 dst_closed_udp_port,
705 ]),
706 );
707
708 let timeout = Some(Duration::from_secs_f64(1.0));
709 let top_k = 3;
710 let num_threads = Some(8);
711 let ret = os_detect(&[target1], num_threads, src_addr, top_k, timeout).unwrap();
712 println!("{}", ret);
713
714 let detects = ret.value();
715 for od in detects {
716 match od {
717 OsDetect::V4(r) => {
718 println!("{}", r.fingerprint);
719 }
720 _ => (),
721 }
722 }
723 }
724
725 #[test]
726 fn test_os_detect_centos_7() {
727 let _pr = PistolRunner::init(
760 PistolLogger::Debug,
761 Some(String::from("os_detect.pcapng")),
762 None, )
764 .unwrap();
765
766 let src_addr = None;
767 let dst_closed_tcp_port = 8765;
768 let dst_closed_udp_port = 9876;
769
770 let addr1 = IpAddr::V4(Ipv4Addr::new(192, 168, 5, 129));
771 let dst_open_tcp_port = 22;
772 let target1 = Target::new(
773 addr1,
774 Some(vec![
775 dst_open_tcp_port,
776 dst_closed_tcp_port,
777 dst_closed_udp_port,
778 ]),
779 );
780
781 let timeout = Some(Duration::from_secs_f64(1.0));
782 let top_k = 3;
783 let num_threads = Some(8);
784 let ret = os_detect(&[target1], num_threads, src_addr, top_k, timeout).unwrap();
785 println!("{}", ret);
786
787 let detects = ret.value();
788 for od in detects {
789 match od {
790 OsDetect::V4(r) => {
791 println!("{}", r.fingerprint);
792 }
793 _ => (),
794 }
795 }
796 }
797 #[test]
798 fn test_os_detect6_centos_7() {
799 let _pr = PistolRunner::init(
833 PistolLogger::Debug,
834 Some(String::from("os_detect6.pcapng")),
835 None, )
837 .unwrap();
838
839 let src_addr = None;
840 let dst_open_tcp_port = 22;
841 let dst_closed_tcp_port = 8765;
842 let dst_closed_udp_port = 9876;
843 let addr1 = Ipv6Addr::from_str("fe80::78e8:37a7:7371:778c").unwrap();
844 let target1 = Target::new(
845 addr1.into(),
846 Some(vec![
847 dst_open_tcp_port,
848 dst_closed_tcp_port,
849 dst_closed_udp_port,
850 ]),
851 );
852
853 let timeout = Some(Duration::from_secs_f64(0.5));
854 let top_k = 3;
855 let num_threads = Some(8);
856 let ret = os_detect(&[target1], num_threads, src_addr, top_k, timeout).unwrap();
857 println!("{}", ret);
858 }
860
861 #[test]
862 fn test_os_detect6_windows_server_2025() {
863 let _pr = PistolRunner::init(
890 PistolLogger::Debug,
891 Some(String::from("os_detect6.pcapng")),
892 None, )
894 .unwrap();
895
896 let src_addr = None;
897 let dst_open_tcp_port = 3389;
898 let dst_closed_tcp_port = 8765;
899 let dst_closed_udp_port = 9876;
900 let addr1 = Ipv6Addr::from_str("fe80::5dd9:4cd2:82ac:d35").unwrap();
901 let target1 = Target::new(
902 addr1.into(),
903 Some(vec![
904 dst_open_tcp_port,
905 dst_closed_tcp_port,
906 dst_closed_udp_port,
907 ]),
908 );
909
910 let timeout = Some(Duration::from_secs_f64(0.5));
911 let top_k = 3;
912 let num_threads = Some(8);
913 let ret = os_detect(&[target1], num_threads, src_addr, top_k, timeout).unwrap();
914 println!("{}", ret);
915 }
917 #[test]
918 fn test_os_detect_raw() {
919 let addr1 = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2));
920 let src_addr = None;
921 let dst_open_tcp_port = 22;
922 let dst_closed_tcp_port = 8765;
923 let dst_closed_udp_port = 9876;
924 let timeout = Some(Duration::new(1, 0));
925 let top_k = 3;
926 let ret = os_detect_raw(
927 addr1,
928 dst_open_tcp_port,
929 dst_closed_tcp_port,
930 dst_closed_udp_port,
931 src_addr,
932 top_k,
933 timeout,
934 )
935 .unwrap();
936 println!("{:?}", ret);
937 }
938 #[test]
939 fn test_os_detect6_raw() {
940 let addr1 = IpAddr::V6(Ipv6Addr::new(
941 0xfe80, 0, 0, 0, 0x0020c, 0x29ff, 0xfe2c, 0x09e4,
942 ));
943 let src_addr = None;
944 let dst_open_tcp_port = 22;
945 let dst_closed_tcp_port = 8765;
946 let dst_closed_udp_port = 9876;
947 let timeout = Some(Duration::new(1, 0));
948 let top_k = 3;
949 let ret = os_detect_raw(
950 addr1,
951 dst_open_tcp_port,
952 dst_closed_tcp_port,
953 dst_closed_udp_port,
954 src_addr,
955 top_k,
956 timeout,
957 )
958 .unwrap();
959 println!("{:?}", ret);
960 }
961 #[test]
962 fn test_compare_with_nmap() {
963 let nmap_fingerprint_format = |input: &str| -> String {
964 let output = input.replace("OS:", "");
965 let output = output.replace("\n", "");
966 let output = output.replace(")", ")\n");
967 output
968 };
969
970 let nmap_output = "
971OS:SCAN(V=7.93%E=4%D=11/13%OT=22%CT=1%CU=39775%PV=Y%DS=1%DC=D%G=Y%M=000C29%
972OS:TM=67341E99%P=x86_64-pc-linux-gnu)SEQ(SP=108%GCD=1%ISR=10A%TI=Z%CI=Z%II=
973OS:I%TS=A)OPS(O1=M5B4ST11NW7%O2=M5B4ST11NW7%O3=M5B4NNT11NW7%O4=M5B4ST11NW7%
974OS:O5=M5B4ST11NW7%O6=M5B4ST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W
975OS:6=FE88)ECN(R=Y%DF=Y%T=40%W=FAF0%O=M5B4NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=
976OS:O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD
977OS:=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0
978OS:%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1
979OS:(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI
980OS:=N%T=40%CD=S)";
981
982 let p = nmap_fingerprint_format(&nmap_output);
983 println!("{}", p);
984 }
985}