Skip to main content

pistol/
route.rs

1use pnet::datalink::MacAddr;
2use pnet::datalink::NetworkInterface;
3use pnet::datalink::interfaces;
4use pnet::ipnetwork::IpNetwork;
5use pnet::packet::ethernet::EtherTypes;
6use pnet::packet::ethernet::EthernetPacket;
7use pnet::packet::icmpv6;
8use pnet::packet::icmpv6::Icmpv6Code;
9use pnet::packet::icmpv6::Icmpv6Type;
10use pnet::packet::icmpv6::Icmpv6Types;
11use pnet::packet::icmpv6::MutableIcmpv6Packet;
12use pnet::packet::icmpv6::ndp::MutableRouterSolicitPacket;
13use pnet::packet::icmpv6::ndp::NdpOption;
14use pnet::packet::icmpv6::ndp::NdpOptionTypes;
15use pnet::packet::ip::IpNextHeaderProtocols;
16use pnet::packet::ipv6::MutableIpv6Packet;
17use regex::Regex;
18use std::collections::HashMap;
19use std::fmt;
20use std::net::IpAddr;
21use std::net::Ipv4Addr;
22use std::net::Ipv6Addr;
23use std::panic::Location;
24use std::process::Command;
25use std::str::FromStr;
26use std::sync::Arc;
27use std::sync::Mutex;
28use std::time::Duration;
29use tracing::debug;
30use tracing::warn;
31
32use crate::DST_CACHE;
33use crate::LayerMatch;
34use crate::NEIGHBOR_SCAN_STATUS;
35use crate::SYSTEM_NET_CACHE;
36use crate::error::PistolError;
37use crate::layer::ICMPV6_RS_HEADER_SIZE;
38use crate::layer::IPV6_HEADER_SIZE;
39use crate::layer::Layer3Match;
40use crate::layer::Layer4MatchIcmpv6;
41use crate::layer::PayloadMatch;
42use crate::layer::PayloadMatchIcmpv6;
43use crate::layer::PayloadMatchIp;
44use crate::layer::find_interface_by_index;
45use crate::layer::find_interface_by_src;
46use crate::layer::layer2_work;
47use crate::scan::arp::send_arp_scan_packet;
48use crate::scan::ndp_ns::send_ndp_ns_scan_packet;
49use crate::utils::neigh_cache_update;
50
51#[cfg(any(
52    target_os = "linux",
53    target_os = "freebsd",
54    target_os = "openbsd",
55    target_os = "netbsd",
56    target_os = "macos"
57))]
58fn find_interface_by_name(name: &str) -> Option<NetworkInterface> {
59    for interface in interfaces() {
60        if interface.name == name {
61            return Some(interface);
62        }
63    }
64    None
65}
66
67fn ipv6_addr_bsd_fix(dst_str: &str) -> Result<String, PistolError> {
68    // Remove the %em0 .etc
69    // fe80::%lo0/10 => fe80::/10
70    // fe80::20c:29ff:fe1f:6f71%lo0 => fe80::20c:29ff:fe1f:6f71
71    if dst_str.contains("%") {
72        let bsd_fix_re = Regex::new(r"(?P<subnet>[^\s^%^/]+)(%(?P<dev>\w+))?(/(?P<mask>\d+))?")?;
73        match bsd_fix_re.captures(dst_str) {
74            Some(caps) => {
75                let addr = caps.name("subnet").map_or("", |m| m.as_str());
76                let mask = caps.name("mask").map_or("", |m| m.as_str());
77                if dst_str.contains("/") {
78                    let output = addr.to_string() + "/" + mask;
79                    return Ok(output);
80                } else {
81                    return Ok(addr.to_string());
82                }
83            }
84            None => {
85                warn!("line: [{}] bsd_fix_re no match", dst_str);
86                Ok(String::new())
87            }
88        }
89    } else {
90        Ok(dst_str.to_string())
91    }
92}
93
94#[derive(Debug, Clone)]
95pub struct DefaultRoute {
96    pub via: IpAddr,           // Next hop gateway address
97    pub dev: NetworkInterface, // Device interface name
98}
99
100impl fmt::Display for DefaultRoute {
101    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102        let default_routes_string = format!(
103            "default dst: {}, default interface: {}",
104            self.via, self.dev.name
105        );
106        write!(f, "{}", default_routes_string)
107    }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Hash)]
111pub enum RouteAddr {
112    IpNetwork(IpNetwork),
113    IpAddr(IpAddr),
114}
115
116impl fmt::Display for RouteAddr {
117    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118        let output_str = match self {
119            RouteAddr::IpNetwork(ipn) => format!("{}", ipn),
120            RouteAddr::IpAddr(ip) => format!("{}", ip),
121        };
122        write!(f, "{}", output_str)
123    }
124}
125
126impl RouteAddr {
127    pub fn contains(&self, ip: IpAddr) -> bool {
128        match self {
129            RouteAddr::IpNetwork(ip_network) => ip_network.contains(ip),
130            RouteAddr::IpAddr(ip_addr) => {
131                if *ip_addr == ip {
132                    true
133                } else {
134                    false
135                }
136            }
137        }
138    }
139    pub fn is_unspecified(&self) -> bool {
140        match self {
141            RouteAddr::IpNetwork(ip_network) => ip_network.ip().is_unspecified(),
142            RouteAddr::IpAddr(ip_addr) => ip_addr.is_unspecified(),
143        }
144    }
145}
146
147#[derive(Debug, Clone)]
148struct InnerDefaultRoute {
149    via: IpAddr,
150    // only the name is stored here, not converted into a formal NetworkInterface struct
151    #[cfg(any(
152        target_os = "linux",
153        target_os = "freebsd",
154        target_os = "openbsd",
155        target_os = "netbsd",
156        target_os = "macos"
157    ))]
158    dev: String,
159    #[cfg(target_os = "windows")]
160    if_index: u32,
161}
162
163impl fmt::Display for InnerDefaultRoute {
164    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
165        #[cfg(any(
166            target_os = "linux",
167            target_os = "freebsd",
168            target_os = "openbsd",
169            target_os = "netbsd",
170            target_os = "macos"
171        ))]
172        let default_routes_string =
173            format!("default via: {}, default interface: {}", self.via, self.dev);
174        #[cfg(target_os = "windows")]
175        let default_routes_string = format!(
176            "default via: {}, default interface index: {}",
177            self.via, self.if_index
178        );
179        write!(f, "{}", default_routes_string)
180    }
181}
182
183#[derive(Debug, Clone)]
184struct InnerRouteInfo {
185    /// linux and unix interface name
186    #[cfg(any(
187        target_os = "linux",
188        target_os = "freebsd",
189        target_os = "openbsd",
190        target_os = "netbsd",
191        target_os = "macos"
192    ))]
193    dev: String,
194    /// windows interface ids
195    #[cfg(target_os = "windows")]
196    dev: u32,
197    via: Option<String>,
198}
199
200// intermediate layer representation, used for testing
201#[derive(Debug, Clone)]
202struct InnerRouteTable {
203    default_route: Option<InnerDefaultRoute>,
204    default_route6: Option<InnerDefaultRoute>,
205    /// (192.168.1.0/24, (dev_name, via_ip)) linux and unix
206    /// (192.168.1.0/24, (if_index, via_ip)) windows
207    routes: HashMap<RouteAddr, InnerRouteInfo>,
208}
209
210impl fmt::Display for InnerRouteTable {
211    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
212        let mut new_routes = HashMap::new();
213        for (r, n) in &self.routes {
214            let addr = match r {
215                RouteAddr::IpAddr(i) => i.to_string(),
216                RouteAddr::IpNetwork(i) => i.to_string(),
217            };
218            new_routes.insert(addr, n);
219        }
220        let mut output = String::new();
221        match &self.default_route {
222            Some(r) => {
223                output += &format!("ipv4: {}, ", r);
224            }
225            None => (),
226        }
227        match &self.default_route6 {
228            Some(r) => {
229                output += &format!("ipv6: {}, ", r);
230            }
231            None => (),
232        }
233        let routes_string = format!("routes: {:?}", new_routes);
234        output += &routes_string;
235        match output.strip_suffix(", ") {
236            Some(o) => write!(f, "{}", o),
237            None => write!(f, "{}", output),
238        }
239    }
240}
241
242impl InnerRouteTable {
243    #[cfg(target_os = "linux")]
244    fn parser(system_route_lines: &[String]) -> Result<InnerRouteTable, PistolError> {
245        let mut default_route = None;
246        let mut default_route6 = None;
247        let mut routes = HashMap::new();
248
249        for line in system_route_lines {
250            let line = line.trim();
251            if line.len() == 0 {
252                continue;
253            }
254            // default via 192.168.72.2 dev ens33
255            // default via 192.168.72.2 dev ens33 proto dhcp metric 100
256            let default_route_judge = |line: &str| -> bool { line.starts_with("default") };
257            if default_route_judge(&line) {
258                let default_route_re =
259                    Regex::new(r"^default\s+via\s+(?P<via>[^\s]+)\s+dev\s+(?P<dev>[^\s]+)(.+)?")?;
260                match default_route_re.captures(&line) {
261                    Some(caps) => {
262                        let via_str = caps.name("via").map_or("", |m| m.as_str());
263                        let via: IpAddr = match via_str.parse() {
264                            Ok(v) => v,
265                            Err(e) => {
266                                warn!(
267                                    "parse route table 'via' [{}] into IpAddr error: {}",
268                                    via_str, e
269                                );
270                                continue;
271                            }
272                        };
273                        let dev = caps.name("dev").map_or("", |m| m.as_str()).to_string();
274                        let inner_default_route = InnerDefaultRoute { via, dev };
275
276                        let mut is_ipv4 = true;
277                        if via_str.contains(":") {
278                            is_ipv4 = false;
279                        }
280
281                        if is_ipv4 {
282                            default_route = Some(inner_default_route);
283                        } else {
284                            default_route6 = Some(inner_default_route);
285                        }
286                    }
287                    None => warn!("line: [{}] default_route_re no match", line),
288                }
289            } else {
290                // 192.168.1.0/24 dev ens36 proto kernel scope link src 192.168.1.132
291                // 192.168.72.0/24 dev ens33 proto kernel scope link src 192.168.72.128
292                // fe80::/64 dev ens33 proto kernel metric 256 pref medium
293                // 192.168.72.0/24 dev ens33 proto kernel scope link src 192.168.72.138 metric 100
294                // 10.179.252.0/24 via 10.179.141.129 dev eth0 proto static metric 100
295                let route_re_1 = Regex::new(r"^(?P<subnet>[^\s]+)\s+dev\s+(?P<dev>[^\s]+)(.+)?")?;
296                let route_re_2 = Regex::new(
297                    r"^(?P<subnet>[^\s]+)\s+via\s+(?P<via>[^\s]+)\s+dev\s+(?P<dev>[^\s]+)(.+)?",
298                )?;
299                // there are many regex to match different output
300                let caps = if let Some(caps) = route_re_1.captures(&line) {
301                    Some(caps)
302                } else if let Some(caps) = route_re_2.captures(&line) {
303                    Some(caps)
304                } else {
305                    None
306                };
307
308                if let Some(caps) = caps {
309                    let dst_str = caps.name("subnet").map_or("", |m| m.as_str());
310                    let dst = if dst_str.contains("/") {
311                        let dst = match IpNetwork::from_str(dst_str) {
312                            Ok(d) => d,
313                            Err(e) => {
314                                warn!("parse route table 'dst' [{}] error: {}", dst_str, e);
315                                continue;
316                            }
317                        };
318                        let dst = RouteAddr::IpNetwork(dst);
319                        dst
320                    } else {
321                        let dst: IpAddr = match dst_str.parse() {
322                            Ok(d) => d,
323                            Err(e) => {
324                                warn!("parse route table 'dst' [{}] error: {}", dst_str, e);
325                                continue;
326                            }
327                        };
328                        let dst = RouteAddr::IpAddr(dst);
329                        dst
330                    };
331                    let dev = caps.name("dev").map_or("", |m| m.as_str()).to_string();
332                    let via = caps.name("via").map_or(None, |m| Some(m.as_str().into()));
333                    let inner_route_info = InnerRouteInfo { dev, via };
334                    routes.insert(dst, inner_route_info);
335                } else {
336                    warn!("line: [{}] route_re_1 and route_re_2 both no match", line);
337                }
338            }
339        }
340
341        Ok(InnerRouteTable {
342            default_route,
343            default_route6,
344            routes,
345        })
346    }
347    #[cfg(any(
348        target_os = "freebsd",
349        target_os = "openbsd",
350        target_os = "netbsd",
351        target_os = "macos"
352    ))]
353    fn parser(system_route_lines: &[String]) -> Result<InnerRouteTable, PistolError> {
354        let mut default_route = None;
355        let mut default_route6 = None;
356        let mut routes = HashMap::new();
357
358        for line in system_route_lines {
359            if line.len() == 0
360                || line.starts_with("R")
361                || line.starts_with("I")
362                || line.starts_with("D")
363            {
364                continue;
365            }
366            let default_route_judge = |line: &str| -> bool { line.starts_with("default") };
367            if default_route_judge(&line) {
368                let line = line.trim();
369                // default 192.168.72.2 UGS em0
370                // default fe80::4a5f:8ff:fee0:1394%em1 UG em1
371                let line_split: Vec<&str> = line
372                    .split(" ")
373                    .map(|x| x.trim())
374                    .filter(|x| x.len() > 0)
375                    .collect();
376                if line_split.len() >= 2 {
377                    let via_str = line_split[1];
378                    let via_str = ipv6_addr_bsd_fix(via_str)?;
379                    let via: IpAddr = match via_str.parse() {
380                        Ok(v) => v,
381                        Err(e) => {
382                            warn!(
383                                "parse route table 'via' [{}] into IpAddr error: {}",
384                                via_str, e
385                            );
386                            continue;
387                        }
388                    };
389                    let dev = line_split[line_split.len() - 1].to_string();
390
391                    let mut is_ipv4 = true;
392                    if via_str.contains(":") {
393                        is_ipv4 = false;
394                    }
395
396                    let inner_default_route = InnerDefaultRoute { via, dev };
397
398                    if is_ipv4 {
399                        default_route = Some(inner_default_route);
400                    } else {
401                        default_route6 = Some(inner_default_route);
402                    }
403                } else {
404                    warn!("line: [{}] default route split no match", line);
405                }
406            } else {
407                // 127.0.0.1          link#2             UH          lo0
408                let line_split: Vec<&str> = line
409                    .split(" ")
410                    .map(|x| x.trim())
411                    .filter(|x| x.len() > 0)
412                    .collect();
413                if line_split.len() >= 3 {
414                    let dst_str = line_split[0];
415                    let dst_str = ipv6_addr_bsd_fix(dst_str)?;
416                    let dst = if dst_str.contains("/") {
417                        let dst = match IpNetwork::from_str(&dst_str) {
418                            Ok(d) => d,
419                            Err(e) => {
420                                warn!("parse route table 'dst' [{}] error: {}", dst_str, e);
421                                continue;
422                            }
423                        };
424                        let dst = RouteAddr::IpNetwork(dst);
425                        dst
426                    } else {
427                        let dst: IpAddr = match dst_str.parse() {
428                            Ok(d) => d,
429                            Err(e) => {
430                                warn!("parse route table 'dst' [{}] error: {}", dst_str, e);
431                                continue;
432                            }
433                        };
434                        let dst = RouteAddr::IpAddr(dst);
435                        dst
436                    };
437                    let dev = line_split[line_split.len() - 1].to_string();
438                    let via = Some(line_split[1].to_string());
439                    let inner_route_info = InnerRouteInfo { dev, via };
440                    routes.insert(dst, inner_route_info);
441                } else {
442                    warn!("line: [{}] route split no match", line);
443                }
444            }
445        }
446        Ok(InnerRouteTable {
447            default_route,
448            default_route6,
449            routes,
450        })
451    }
452    #[cfg(target_os = "windows")]
453    fn parser(system_route_lines: &[String]) -> Result<InnerRouteTable, PistolError> {
454        let mut default_route = None;
455        let mut default_route6 = None;
456        let mut routes = HashMap::new();
457
458        for line in system_route_lines {
459            let line = line.trim();
460            if line.len() == 0 || line.starts_with("-") || line.starts_with("i") {
461                continue;
462            }
463            let default_route_judge =
464                |line: &str| -> bool { line.contains("0.0.0.0/0") || line.contains("::/0") };
465            if default_route_judge(&line) {
466                // 19 0.0.0.0/0 192.168.1.4 256 35 ActiveStore
467                let default_route_re =
468                    Regex::new(r"^(?P<index>[^\s]+)\s+[^\s]+\s+(?P<via>[^\s]+)(.+)?")?;
469                match default_route_re.captures(&line) {
470                    Some(caps) => {
471                        let if_index = caps.name("index").map_or("", |m| m.as_str());
472                        let if_index: u32 = match if_index.parse() {
473                            Ok(i) => i,
474                            Err(e) => {
475                                warn!("parse route table 'if_index' [{}] error: {}", if_index, e);
476                                continue;
477                            }
478                        };
479
480                        let via_str = caps.name("via").map_or("", |m| m.as_str());
481                        let via: IpAddr = match via_str.parse() {
482                            Ok(v) => v,
483                            Err(e) => {
484                                warn!(
485                                    "parse route table 'via' [{}] into IpAddr error: {}",
486                                    via_str, e
487                                );
488                                continue;
489                            }
490                        };
491
492                        let mut is_ipv4 = true;
493                        if via_str.contains(":") {
494                            is_ipv4 = false;
495                        }
496
497                        let inner_default_route = InnerDefaultRoute { via, if_index };
498
499                        if is_ipv4 {
500                            default_route = Some(inner_default_route);
501                        } else {
502                            default_route6 = Some(inner_default_route);
503                        }
504                    }
505                    None => warn!("line: [{}] default_route_re no match", line),
506                }
507            } else {
508                // 17 255.255.255.255/32 0.0.0.0 256 25 ActiveStore
509                // 17 fe80::d547:79a9:84eb:767d/128 :: 256 25 ActiveStore
510                let route_re =
511                    Regex::new(r"^(?P<index>[^\s]+)\s+(?P<dst>[^\s]+)\s+(?P<via>[^\s]+)(.+)?")?;
512                match route_re.captures(&line) {
513                    Some(caps) => {
514                        let if_index = caps.name("index").map_or("", |m| m.as_str());
515                        let if_index: u32 = match if_index.parse() {
516                            Ok(i) => i,
517                            Err(e) => {
518                                warn!("parse route table 'if_index' [{}] error: {}", if_index, e);
519                                continue;
520                            }
521                        };
522
523                        let dst_str = caps.name("dst").map_or("", |m| m.as_str());
524                        let dst = match IpNetwork::from_str(dst_str) {
525                            Ok(d) => d,
526                            Err(e) => {
527                                warn!("parse route table 'dst' [{}] error: {}", dst_str, e);
528                                continue;
529                            }
530                        };
531                        let dst = RouteAddr::IpNetwork(dst);
532                        let via = caps.name("via").map_or(None, |m| Some(m.as_str().into()));
533                        let inner_route_info = InnerRouteInfo { dev: if_index, via };
534                        routes.insert(dst, inner_route_info);
535                    }
536                    None => warn!("line: [{}] default_route_re no match", line),
537                }
538            }
539        }
540
541        Ok(InnerRouteTable {
542            default_route,
543            default_route6,
544            routes,
545        })
546    }
547}
548
549/// Destination mac address and interface cache
550pub struct DstCache {
551    pub dst_addr: IpAddr,
552    pub src_addr: IpAddr,
553    pub mac: MacAddr,
554    pub interface: NetworkInterface,
555}
556
557impl DstCache {
558    // the same target address does not need to be searched again
559    pub fn update(
560        dst_addr: IpAddr,
561        src_addr: IpAddr,
562        mac: MacAddr,
563        interface: NetworkInterface,
564    ) -> Result<(), PistolError> {
565        match DST_CACHE.lock() {
566            Ok(mut dst_cache) => {
567                if !dst_cache.contains_key(&dst_addr) {
568                    let dc = DstCache {
569                        dst_addr,
570                        src_addr,
571                        mac,
572                        interface,
573                    };
574                    let _ = dst_cache.insert(dst_addr, dc);
575                }
576                Ok(())
577            }
578            Err(e) => Err(PistolError::TryLockGlobalVarFailed {
579                var_name: String::from("DST_CACHE"),
580                e: e.to_string(),
581            }),
582        }
583    }
584    pub fn get(dst_addr: IpAddr) -> Result<Option<(MacAddr, NetworkInterface)>, PistolError> {
585        match DST_CACHE.lock() {
586            Ok(dst_cache) => {
587                let ret = dst_cache.get(&dst_addr);
588                if let Some(dc) = ret {
589                    debug!("dst {} found in DST_CACHE", dst_addr);
590                    Ok(Some((dc.mac, dc.interface.clone())))
591                } else {
592                    debug!("dst {} not found in DST_CACHE", dst_addr);
593                    Ok(None)
594                }
595            }
596            Err(e) => Err(PistolError::TryLockGlobalVarFailed {
597                var_name: String::from("DST_CACHE"),
598                e: e.to_string(),
599            }),
600        }
601    }
602}
603
604fn find_loopback_interface() -> Option<NetworkInterface> {
605    for interface in interfaces() {
606        if interface.is_loopback() {
607            return Some(interface);
608        }
609    }
610    None
611}
612
613/// Check if the target IP address is one of the system IP address.
614fn addr_is_my_ip(ip: IpAddr) -> bool {
615    for interface in interfaces() {
616        for ipn in interface.ips {
617            if ipn.ip() == ip {
618                return true;
619            }
620        }
621    }
622    debug!("dst {} is not in host", ip);
623    false
624}
625
626/// Check if the target IP address is in the local.
627fn addr_in_local_net(ip: IpAddr) -> bool {
628    for interface in interfaces() {
629        for ipn in interface.ips {
630            if ipn.contains(ip) {
631                return true;
632            }
633        }
634    }
635    // all data for other addresses are sent to the default route
636    debug!("dst {} is not in local net", ip);
637    false
638}
639
640/// return ipv4 and ipv6 default route
641pub fn get_default_route() -> Result<(Option<DefaultRoute>, Option<DefaultRoute>), PistolError> {
642    // release the lock when leaving the function
643    let snc = match SYSTEM_NET_CACHE.lock() {
644        Ok(snc) => snc.clone(),
645        Err(e) => {
646            return Err(PistolError::TryLockGlobalVarFailed {
647                var_name: String::from("SYSTEM_NET_CACHE"),
648                e: e.to_string(),
649            });
650        }
651    };
652    Ok((snc.default_route, snc.default_route6))
653}
654
655/// Get the send route info from the system route table
656pub fn search_route_table(dst_addr: IpAddr) -> Result<Option<RouteInfo>, PistolError> {
657    let snc = match SYSTEM_NET_CACHE.lock() {
658        Ok(snc) => snc,
659        Err(e) => {
660            return Err(PistolError::TryLockGlobalVarFailed {
661                var_name: String::from("SYSTEM_NET_CACHE"),
662                e: e.to_string(),
663            });
664        }
665    };
666    Ok(snc.search_route_table(dst_addr))
667}
668
669/// Get the target mac address through arp table
670pub fn search_mac(dst_addr: IpAddr) -> Result<Option<MacAddr>, PistolError> {
671    let snc = match SYSTEM_NET_CACHE.lock() {
672        Ok(snc) => snc,
673        Err(e) => {
674            return Err(PistolError::TryLockGlobalVarFailed {
675                var_name: String::from("SYSTEM_NET_CACHE"),
676                e: e.to_string(),
677            });
678        }
679    };
680    Ok(snc.search_mac(dst_addr))
681}
682
683fn get_mac_from_ndp_rs(buff: &[u8]) -> Option<MacAddr> {
684    // return mac address from ndp
685    match EthernetPacket::new(buff) {
686        Some(ethernet_packet) => {
687            let mac = ethernet_packet.get_source();
688            Some(mac)
689        }
690        None => None,
691    }
692}
693
694fn send_ndp_rs_packet(
695    src_ipv6: Ipv6Addr,
696    timeout: Option<Duration>,
697) -> Result<(Option<MacAddr>, Duration), PistolError> {
698    // router solicitation
699    let route_addr_2 = Ipv6Addr::new(0xFF02, 0, 0, 0, 0, 0, 0, 0x0002);
700    // let route_addr_1 = Ipv6Addr::new(0xFF02, 0, 0, 0, 0, 0, 0, 0x0001);
701    let interface = match find_interface_by_src(src_ipv6.into()) {
702        Some(i) => i,
703        None => return Err(PistolError::CanNotFoundInterface),
704    };
705    let src_mac = match interface.mac {
706        Some(m) => m,
707        None => return Err(PistolError::CanNotFoundMacAddress),
708    };
709
710    // ipv6
711    let mut ipv6_buff = [0u8; IPV6_HEADER_SIZE + ICMPV6_RS_HEADER_SIZE];
712    let mut ipv6_header = match MutableIpv6Packet::new(&mut ipv6_buff) {
713        Some(p) => p,
714        None => {
715            return Err(PistolError::BuildPacketError {
716                location: format!("{}", Location::caller()),
717            });
718        }
719    };
720    ipv6_header.set_version(6);
721    ipv6_header.set_traffic_class(0);
722    ipv6_header.set_flow_label(0);
723    ipv6_header.set_payload_length(ICMPV6_RS_HEADER_SIZE as u16);
724    ipv6_header.set_next_header(IpNextHeaderProtocols::Icmpv6);
725    ipv6_header.set_hop_limit(255);
726    ipv6_header.set_source(src_ipv6);
727    ipv6_header.set_destination(route_addr_2);
728
729    // icmpv6
730    let mut icmpv6_header =
731        match MutableRouterSolicitPacket::new(&mut ipv6_buff[IPV6_HEADER_SIZE..]) {
732            Some(p) => p,
733            None => {
734                return Err(PistolError::BuildPacketError {
735                    location: format!("{}", Location::caller()),
736                });
737            }
738        };
739    // Neighbor Solicitation
740    icmpv6_header.set_icmpv6_type(Icmpv6Type(133));
741    icmpv6_header.set_icmpv6_code(Icmpv6Code(0));
742    icmpv6_header.set_reserved(0);
743    let ndp_option = NdpOption {
744        option_type: NdpOptionTypes::SourceLLAddr,
745        length: 1,
746        data: src_mac.octets().to_vec(),
747    };
748    icmpv6_header.set_options(&vec![ndp_option]);
749
750    let mut icmpv6_header = match MutableIcmpv6Packet::new(&mut ipv6_buff[IPV6_HEADER_SIZE..]) {
751        Some(p) => p,
752        None => {
753            return Err(PistolError::BuildPacketError {
754                location: format!("{}", Location::caller()),
755            });
756        }
757    };
758    let checksum = icmpv6::checksum(&icmpv6_header.to_immutable(), &src_ipv6, &route_addr_2);
759    icmpv6_header.set_checksum(checksum);
760
761    // let layer3 = Layer3Match {
762    //     layer2: None,
763    //     src_addr: None,
764    //     dst_addr: Some(route_addr_1.into()),
765    // };
766    let layer3 = Layer3Match {
767        name: "ndp_ns layer3",
768        layer2: None,
769        src_addr: None,
770        dst_addr: None,
771    };
772    // set the icmp payload matchs
773    let payload_ip = PayloadMatchIp {
774        src_addr: None,
775        dst_addr: None,
776    };
777    let payload_icmpv6 = PayloadMatchIcmpv6 {
778        layer3: Some(payload_ip),
779        icmpv6_type: Some(Icmpv6Types::RouterSolicit),
780        icmpv6_code: None,
781    };
782    let payload = PayloadMatch::PayloadMatchIcmpv6(payload_icmpv6);
783    let layer4_icmpv6 = Layer4MatchIcmpv6 {
784        name: "ndp_ns icmpv6",
785        layer3: Some(layer3),
786        icmpv6_type: Some(Icmpv6Types::RouterAdvert), // Type: Router Advertisement (134)
787        icmpv6_code: None,
788        payload: Some(payload),
789    };
790    let layer_match = LayerMatch::Layer4MatchIcmpv6(layer4_icmpv6);
791
792    let dst_mac = MacAddr(33, 33, 00, 00, 00, 02);
793    let ethernet_type = EtherTypes::Ipv6;
794    let (r, rtt) = layer2_work(
795        dst_mac,
796        interface.clone(),
797        &ipv6_buff,
798        IPV6_HEADER_SIZE + ICMPV6_RS_HEADER_SIZE,
799        ethernet_type,
800        vec![layer_match],
801        timeout,
802        true,
803    )?;
804
805    let mac = get_mac_from_ndp_rs(&r);
806    Ok((mac, rtt))
807}
808
809#[derive(Debug, Clone, Copy)]
810pub enum RouteVia {
811    // linux
812    IpAddr(IpAddr),
813    // windows and unix
814    IfIndex(u32),
815    // unix
816    MacAddr(MacAddr),
817}
818
819impl fmt::Display for RouteVia {
820    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
821        let output = match self {
822            RouteVia::IpAddr(ip_addr) => format!("via ip_addr {}", ip_addr),
823            RouteVia::IfIndex(if_index) => format!("via if_index {}", if_index),
824            RouteVia::MacAddr(mac_addr) => format!("via mac_addr {}", mac_addr),
825        };
826        write!(f, "{}", output)
827    }
828}
829
830impl RouteVia {
831    fn standard_process(
832        dst_addr: IpAddr,
833        src_addr: IpAddr,
834        src_interface: NetworkInterface,
835        timeout: Option<Duration>,
836    ) -> Result<MacAddr, PistolError> {
837        if dst_addr.is_loopback() || addr_is_my_ip(dst_addr) {
838            // target is your own machine, such as when using localhost or 127.0.0.1 as the target
839            match src_interface.mac {
840                Some(dst_mac) => Ok(dst_mac),
841                None => Err(PistolError::CanNotFoundMacAddress),
842            }
843        } else if addr_in_local_net(dst_addr) {
844            match search_mac(dst_addr)? {
845                Some(dst_mac) => {
846                    debug!("found {} in arp cache", dst_addr);
847                    Ok(dst_mac)
848                }
849                None => {
850                    // if addr is local address and in local net
851                    // use the arp or ndp_ns to ask the mac address
852                    let src_mac = match src_interface.mac {
853                        Some(m) => m,
854                        None => return Err(PistolError::CanNotFoundMacAddress),
855                    };
856
857                    match dst_addr {
858                        IpAddr::V4(via_ipv4) => {
859                            if let IpAddr::V4(src_ipv4) = src_addr {
860                                let dst_mac = match send_arp_scan_packet(
861                                    via_ipv4,
862                                    MacAddr::broadcast(),
863                                    src_ipv4,
864                                    src_mac,
865                                    src_interface,
866                                    timeout,
867                                )? {
868                                    (Some(m), _rtt) => m,
869                                    (None, _rtt) => {
870                                        return Err(PistolError::CanNotFoundMacAddress);
871                                    }
872                                };
873                                neigh_cache_update(via_ipv4.into(), dst_mac)?;
874                                Ok(dst_mac)
875                            } else {
876                                Err(PistolError::CanNotFoundMacAddress)
877                            }
878                        }
879                        IpAddr::V6(via_ipv6) => {
880                            if let IpAddr::V6(src_ipv6) = src_addr {
881                                let dst_mac = match send_ndp_ns_scan_packet(
882                                    via_ipv6,
883                                    src_ipv6,
884                                    src_mac,
885                                    src_interface,
886                                    timeout,
887                                )? {
888                                    (Some(m), _rtt) => m,
889                                    (None, _rtt) => {
890                                        return Err(PistolError::CanNotFoundMacAddress);
891                                    }
892                                };
893                                neigh_cache_update(via_ipv6.into(), dst_mac)?;
894                                Ok(dst_mac)
895                            } else {
896                                Err(PistolError::CanNotFoundMacAddress)
897                            }
898                        }
899                    }
900                }
901            }
902        } else {
903            // finally, send it to the the default route address in layer2
904            let (default_route, default_route6) = get_default_route()?;
905            let dr = if dst_addr.is_ipv4() {
906                if let Some(dr_ipv4) = default_route {
907                    dr_ipv4
908                } else {
909                    return Err(PistolError::CanNotFoundRouterAddress);
910                }
911            } else {
912                if let Some(dr_ipv6) = default_route6 {
913                    dr_ipv6
914                } else {
915                    return Err(PistolError::CanNotFoundRouterAddress);
916                }
917            };
918
919            let dst_mac = match search_mac(dr.via)? {
920                Some(m) => m,
921                None => {
922                    let dst_mac = MacAddr::broadcast();
923                    let src_mac = match src_interface.mac {
924                        Some(m) => m,
925                        None => return Err(PistolError::CanNotFoundMacAddress),
926                    };
927                    match dr.via {
928                        IpAddr::V4(dr_ipv4) => {
929                            debug!("try to found the dst mac by arp scan");
930                            if let IpAddr::V4(src_ipv4) = src_addr {
931                                match send_arp_scan_packet(
932                                    dr_ipv4,
933                                    dst_mac,
934                                    src_ipv4,
935                                    src_mac,
936                                    src_interface,
937                                    timeout,
938                                )? {
939                                    (Some(m), _rtt) => {
940                                        debug!("update the neigh cache: {} - {}", dr_ipv4, m);
941                                        neigh_cache_update(dr_ipv4.into(), m)?;
942                                        m
943                                    }
944                                    (None, _rtt) => {
945                                        return Err(PistolError::CanNotFoundRouteMacAddress);
946                                    }
947                                }
948                            } else {
949                                return Err(PistolError::CanNotFoundRouteMacAddress);
950                            }
951                        }
952                        IpAddr::V6(dr_ipv6) => {
953                            debug!("try to found the dst mac by ndp_ns scan");
954                            if let IpAddr::V6(src_ipv6) = src_addr {
955                                // this is not ndp_ns scan packet
956                                match send_ndp_rs_packet(src_ipv6, timeout)? {
957                                    (Some(m), _rtt) => {
958                                        debug!("update the neigh cache: {} - {}", dr_ipv6, m);
959                                        neigh_cache_update(dr_ipv6.into(), m)?;
960                                        m
961                                    }
962                                    (None, _rtt) => {
963                                        return Err(PistolError::CanNotFoundRouteMacAddress);
964                                    }
965                                }
966                            } else {
967                                return Err(PistolError::CanNotFoundRouteMacAddress);
968                            }
969                        }
970                    }
971                }
972            };
973            Ok(dst_mac)
974        }
975    }
976    pub fn get_dst_mac_and_src_if_with_lock(
977        dst_addr: IpAddr,
978        src_addr: IpAddr,
979        timeout: Option<Duration>,
980    ) -> Result<(MacAddr, NetworkInterface), PistolError> {
981        // search in the program cache
982        if let Some((dst_mac, src_interface)) = DstCache::get(dst_addr)? {
983            return Ok((dst_mac, src_interface));
984        }
985
986        let (src_interface, route_via) = if src_addr == dst_addr {
987            let dev = match find_loopback_interface() {
988                Some(i) => i,
989                None => return Err(PistolError::CanNotFoundInterface),
990            };
991            if dst_addr.is_ipv4() {
992                let via = Some(RouteVia::IpAddr(IpAddr::V4(Ipv4Addr::LOCALHOST)));
993                (dev, via)
994            } else {
995                let via = Some(RouteVia::IpAddr(IpAddr::V6(Ipv6Addr::LOCALHOST)));
996                (dev, via)
997            }
998        } else {
999            let route_info = match search_route_table(dst_addr)? {
1000                Some(route_info) => route_info,
1001                None => {
1002                    // send it to the default route address
1003                    let (default_route, default_route6) = get_default_route()?;
1004                    let dr = if dst_addr.is_ipv4() {
1005                        if let Some(dr_ipv4) = default_route {
1006                            dr_ipv4
1007                        } else {
1008                            return Err(PistolError::CanNotFoundRouterAddress);
1009                        }
1010                    } else {
1011                        if let Some(dr_ipv6) = default_route6 {
1012                            dr_ipv6
1013                        } else {
1014                            return Err(PistolError::CanNotFoundRouterAddress);
1015                        }
1016                    };
1017                    match search_route_table(dr.via)? {
1018                        Some(route_info) => route_info,
1019                        None => return Err(PistolError::CanNotFoundInterface),
1020                    }
1021                }
1022            };
1023            // debug!(
1024            //     "search route table done, dev {} via {:?}",
1025            //     route_info.dev.name, route_info.via
1026            // );
1027            (route_info.dev, route_info.via)
1028        };
1029        debug!(
1030            "src interface: {}, via addr: {:?}",
1031            src_interface.name, route_via
1032        );
1033
1034        match route_via {
1035            Some(route_via) => {
1036                match route_via {
1037                    RouteVia::IfIndex(if_index) => {
1038                        let src_interface = match find_interface_by_index(if_index) {
1039                            Some(i) => i,
1040                            None => return Err(PistolError::CanNotFoundInterface),
1041                        };
1042                        // no via_addr, use dst_addr here
1043                        let dst_mac = match search_mac(dst_addr)? {
1044                            Some(dst_mac) => dst_mac,
1045                            None => Self::standard_process(
1046                                dst_addr,
1047                                src_addr,
1048                                src_interface.clone(),
1049                                timeout,
1050                            )?,
1051                        };
1052                        DstCache::update(dst_addr, src_addr, dst_mac, src_interface.clone())?;
1053                        Ok((dst_mac, src_interface))
1054                    }
1055                    RouteVia::MacAddr(dst_mac) => {
1056                        DstCache::update(dst_addr, src_addr, dst_mac, src_interface.clone())?;
1057                        Ok((dst_mac, src_interface))
1058                    }
1059                    RouteVia::IpAddr(via_addr) => {
1060                        // we need to fix 0.0.0.0
1061                        let via_addr = if via_addr.is_unspecified() {
1062                            dst_addr
1063                        } else {
1064                            via_addr
1065                        };
1066                        let dst_mac = match search_mac(via_addr)? {
1067                            Some(m) => m,
1068                            None => Self::standard_process(
1069                                via_addr,
1070                                src_addr,
1071                                src_interface.clone(),
1072                                timeout,
1073                            )?,
1074                        };
1075                        DstCache::update(dst_addr, src_addr, dst_mac, src_interface.clone())?;
1076                        Ok((dst_mac, src_interface))
1077                    }
1078                }
1079            }
1080            None => {
1081                debug!("route_via is none");
1082                let dst_mac =
1083                    Self::standard_process(dst_addr, src_addr, src_interface.clone(), timeout)?;
1084                DstCache::update(dst_addr, src_addr, dst_mac, src_interface.clone())?;
1085                Ok((dst_mac, src_interface))
1086            }
1087        }
1088    }
1089    pub fn get_dst_mac_and_src_if(
1090        dst_addr: IpAddr,
1091        src_addr: IpAddr,
1092        timeout: Option<Duration>,
1093    ) -> Result<(MacAddr, NetworkInterface), PistolError> {
1094        let mut neighbor_scan_status = match NEIGHBOR_SCAN_STATUS.lock() {
1095            Ok(n) => n,
1096            Err(e) => {
1097                return Err(PistolError::TryLockGlobalVarFailed {
1098                    var_name: String::from("NEIGHBOR_SCAN_STATUS"),
1099                    e: e.to_string(),
1100                });
1101            }
1102        };
1103        let mutex = match neighbor_scan_status.get(&dst_addr) {
1104            Some(x) => (*x).clone(),
1105            None => {
1106                let x = Arc::new(Mutex::new(()));
1107                neighbor_scan_status.insert(dst_addr, x.clone());
1108                x
1109            }
1110        };
1111
1112        match mutex.lock() {
1113            // lock and send arp scan or ndp_ns scan packet only once
1114            Ok(_) => Self::get_dst_mac_and_src_if_with_lock(dst_addr, src_addr, timeout),
1115            Err(e) => {
1116                return Err(PistolError::TryLockGlobalVarFailed {
1117                    var_name: format!("{}", dst_addr),
1118                    e: e.to_string(),
1119                });
1120            }
1121        }
1122    }
1123    pub fn parser(via_str: &str) -> Result<Option<RouteVia>, PistolError> {
1124        if !via_str.contains("link") {
1125            let ipv6_re = Regex::new(r"^[\w\d:]+(\/\d+)?")?;
1126            let ipv4_re = Regex::new(r"^[\d\.]+(\/\d+)?")?;
1127            let mac_re = Regex::new(
1128                r"^[\w\d]{1,2}:[\w\d]{1,2}:[\w\d]{1,2}:[\w\d]{1,2}:[\w\d]{1,2}:[\w\d]{1,2}",
1129            )?;
1130            let windows_index_re = Regex::new(r"^\d+")?;
1131            if ipv6_re.is_match(via_str) {
1132                // ipv6 address
1133                let ipv6_addr = ipv6_addr_bsd_fix(via_str)?;
1134                let via: IpAddr = match ipv6_addr.parse() {
1135                    Ok(d) => d,
1136                    Err(e) => {
1137                        warn!("parse 'via' [{}] into IpAddr(V6) error: {}", via_str, e);
1138                        return Ok(None);
1139                    }
1140                };
1141                Ok(Some(RouteVia::IpAddr(via.into())))
1142            } else if ipv4_re.is_match(via_str) {
1143                // ipv4 address
1144                let via: IpAddr = match via_str.parse() {
1145                    Ok(d) => d,
1146                    Err(e) => {
1147                        warn!("parse 'via' [{}] into IpAddr(V4) error: {}", via_str, e);
1148                        return Ok(None);
1149                    }
1150                };
1151                if !via.is_unspecified() {
1152                    Ok(Some(RouteVia::IpAddr(via.into())))
1153                } else {
1154                    Ok(None)
1155                }
1156            } else if mac_re.is_match(via_str) {
1157                // mac address
1158                let mac: MacAddr = match via_str.parse() {
1159                    Ok(m) => m,
1160                    Err(e) => {
1161                        warn!("parse 'via' [{}] into MacAddr error: {}", via_str, e);
1162                        return Ok(None);
1163                    }
1164                };
1165                Ok(Some(RouteVia::MacAddr(mac)))
1166            } else if windows_index_re.is_match(via_str) {
1167                let if_index: u32 = match via_str.parse() {
1168                    Ok(i) => i,
1169                    Err(e) => {
1170                        warn!("parse route table 'if_index' [{}] error: {}", via_str, e);
1171                        return Ok(None);
1172                    }
1173                };
1174                Ok(Some(RouteVia::IfIndex(if_index)))
1175            } else {
1176                debug!("via string [{}] no match any regex rules", via_str);
1177                Ok(None)
1178            }
1179        } else {
1180            // link#12
1181            let unix_index_re = Regex::new(r"^link#\d+")?;
1182            if unix_index_re.is_match(via_str) {
1183                // if_index
1184                let via_str_split: Vec<&str> = via_str
1185                    .split("#")
1186                    .map(|x| x.trim())
1187                    .filter(|x| x.len() > 0)
1188                    .collect();
1189                if via_str_split.len() > 1 {
1190                    let if_index = via_str_split[1];
1191                    let if_index: u32 = match if_index.parse() {
1192                        Ok(i) => i,
1193                        Err(e) => {
1194                            warn!("parse route table 'if_index' [{}] error: {}", if_index, e);
1195                            return Ok(None);
1196                        }
1197                    };
1198                    Ok(Some(RouteVia::IfIndex(if_index)))
1199                } else {
1200                    Ok(None)
1201                }
1202            } else {
1203                debug!("via string [{}] not match unix_index_re", via_str);
1204                Ok(None)
1205            }
1206        }
1207    }
1208}
1209
1210#[derive(Debug, Clone)]
1211pub struct RouteInfo {
1212    pub dev: NetworkInterface,
1213    pub via: Option<RouteVia>,
1214}
1215
1216#[derive(Debug, Clone)]
1217pub struct RouteTable {
1218    pub default_route: Option<DefaultRoute>,
1219    pub default_route6: Option<DefaultRoute>,
1220    pub routes: HashMap<RouteAddr, RouteInfo>,
1221}
1222
1223impl fmt::Display for RouteTable {
1224    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1225        let mut new_routes = HashMap::new();
1226        for (r, n) in &self.routes {
1227            let addr = match r {
1228                RouteAddr::IpAddr(i) => i.to_string(),
1229                RouteAddr::IpNetwork(i) => i.to_string(),
1230            };
1231            let interface_name = n.dev.name.clone();
1232            new_routes.insert(addr, interface_name);
1233        }
1234        let mut output = String::new();
1235        match &self.default_route {
1236            Some(r) => {
1237                output += &format!("ipv4 {}, ", r);
1238            }
1239            None => (),
1240        }
1241        match &self.default_route6 {
1242            Some(r) => {
1243                output += &format!("ipv6 {}, ", r);
1244            }
1245            None => (),
1246        }
1247        let routes_string = format!("routes: {:?}", new_routes);
1248        output += &routes_string;
1249        match output.strip_suffix(", ") {
1250            Some(o) => write!(f, "{}", o),
1251            None => write!(f, "{}", output),
1252        }
1253    }
1254}
1255
1256impl RouteTable {
1257    fn exec_system_command() -> Result<Vec<String>, PistolError> {
1258        #[cfg(target_os = "linux")]
1259        let c = Command::new("sh").args(["-c", "ip -4 route"]).output()?;
1260        #[cfg(target_os = "linux")]
1261        let ipv4_output = String::from_utf8_lossy(&c.stdout);
1262        #[cfg(target_os = "linux")]
1263        let c = Command::new("sh").args(["-c", "ip -6 route"]).output()?;
1264        #[cfg(target_os = "linux")]
1265        let ipv6_output = String::from_utf8_lossy(&c.stdout);
1266        #[cfg(target_os = "linux")]
1267        let output = ipv4_output.to_string() + &ipv6_output;
1268
1269        #[cfg(any(
1270            target_os = "freebsd",
1271            target_os = "openbsd",
1272            target_os = "netbsd",
1273            target_os = "macos"
1274        ))]
1275        let c = Command::new("sh").args(["-c", "netstat -rn"]).output()?;
1276        #[cfg(any(
1277            target_os = "freebsd",
1278            target_os = "openbsd",
1279            target_os = "netbsd",
1280            target_os = "macos"
1281        ))]
1282        let output = String::from_utf8_lossy(&c.stdout);
1283
1284        // 17 255.255.255.255/32 0.0.0.0 256 25 ActiveStore
1285        // 17 fe80::d547:79a9:84eb:767d/128 :: 256 25 ActiveStore
1286        #[cfg(target_os = "windows")]
1287        let c = Command::new("powershell").args(["Get-NetRoute"]).output()?;
1288        #[cfg(target_os = "windows")]
1289        let output = String::from_utf8_lossy(&c.stdout);
1290
1291        let system_route_lines: Vec<String> = output
1292            .lines()
1293            .map(|x| x.trim().to_string())
1294            .filter(|v| v.len() > 0)
1295            .collect();
1296        Ok(system_route_lines)
1297    }
1298    pub fn init() -> Result<RouteTable, PistolError> {
1299        let system_route_lines = Self::exec_system_command()?;
1300        let inner_route_table = InnerRouteTable::parser(&system_route_lines)?;
1301        debug!("inner route table: {}", inner_route_table);
1302        let default_route = match inner_route_table.default_route {
1303            Some(inner_default_route) => {
1304                #[cfg(any(
1305                    target_os = "linux",
1306                    target_os = "freebsd",
1307                    target_os = "openbsd",
1308                    target_os = "netbsd",
1309                    target_os = "macos"
1310                ))]
1311                let dev_name = inner_default_route.dev;
1312                #[cfg(any(
1313                    target_os = "linux",
1314                    target_os = "freebsd",
1315                    target_os = "openbsd",
1316                    target_os = "netbsd",
1317                    target_os = "macos"
1318                ))]
1319                match find_interface_by_name(&dev_name) {
1320                    Some(dev) => Some(DefaultRoute {
1321                        via: inner_default_route.via,
1322                        dev,
1323                    }),
1324                    None => {
1325                        warn!("can not found interface by name [{}]", dev_name);
1326                        None
1327                    }
1328                }
1329                #[cfg(target_os = "windows")]
1330                let if_index = inner_default_route.if_index;
1331                #[cfg(target_os = "windows")]
1332                match find_interface_by_index(if_index) {
1333                    Some(dev) => Some(DefaultRoute {
1334                        via: inner_default_route.via,
1335                        dev,
1336                    }),
1337                    None => {
1338                        warn!("can not found interface by if_index [{}]", if_index);
1339                        None
1340                    }
1341                }
1342            }
1343            None => None,
1344        };
1345        let default_route6 = match inner_route_table.default_route6 {
1346            Some(inner_default_route6) => {
1347                #[cfg(any(
1348                    target_os = "linux",
1349                    target_os = "freebsd",
1350                    target_os = "openbsd",
1351                    target_os = "netbsd",
1352                    target_os = "macos"
1353                ))]
1354                let dev_name = inner_default_route6.dev;
1355                #[cfg(any(
1356                    target_os = "linux",
1357                    target_os = "freebsd",
1358                    target_os = "openbsd",
1359                    target_os = "netbsd",
1360                    target_os = "macos"
1361                ))]
1362                match find_interface_by_name(&dev_name) {
1363                    Some(dev) => Some(DefaultRoute {
1364                        via: inner_default_route6.via,
1365                        dev,
1366                    }),
1367                    None => {
1368                        warn!("can not found interface by name [{}]", dev_name);
1369                        None
1370                    }
1371                }
1372                #[cfg(target_os = "windows")]
1373                let if_index = inner_default_route6.if_index;
1374                #[cfg(target_os = "windows")]
1375                match find_interface_by_index(if_index) {
1376                    Some(dev) => Some(DefaultRoute {
1377                        via: inner_default_route6.via,
1378                        dev,
1379                    }),
1380                    None => {
1381                        warn!("can not found interface by if_index [{}]", if_index);
1382                        None
1383                    }
1384                }
1385            }
1386            None => None,
1387        };
1388
1389        let mut routes = HashMap::new();
1390        for (r, inner_route_info) in inner_route_table.routes {
1391            let dev_str = inner_route_info.dev;
1392            let via_str = inner_route_info.via;
1393
1394            #[cfg(any(
1395                target_os = "linux",
1396                target_os = "freebsd",
1397                target_os = "openbsd",
1398                target_os = "netbsd",
1399                target_os = "macos"
1400            ))]
1401            let dev = match find_interface_by_name(&dev_str) {
1402                Some(dev) => dev,
1403                None => {
1404                    warn!("can not found interface by name [{}]", dev_str);
1405                    continue;
1406                }
1407            };
1408
1409            #[cfg(target_os = "windows")]
1410            let dev = match find_interface_by_index(dev_str) {
1411                Some(dev) => dev,
1412                None => {
1413                    warn!("can not found interface by if_index [{}]", dev_str);
1414                    continue;
1415                }
1416            };
1417
1418            match via_str {
1419                Some(via_str) => match RouteVia::parser(&via_str)? {
1420                    Some(via) => {
1421                        let route_info = RouteInfo {
1422                            dev,
1423                            via: Some(via),
1424                        };
1425                        routes.insert(r, route_info);
1426                    }
1427                    None => {
1428                        warn!("parse route table 'via' [{}] into RouteVia failed", via_str);
1429                        continue;
1430                    }
1431                },
1432                None => {
1433                    let route_info = RouteInfo { dev, via: None };
1434                    routes.insert(r, route_info);
1435                }
1436            }
1437        }
1438
1439        Ok(RouteTable {
1440            default_route,
1441            default_route6,
1442            routes,
1443        })
1444    }
1445}
1446
1447#[derive(Debug, Clone)]
1448pub struct NeighborCache {}
1449
1450impl NeighborCache {
1451    #[cfg(target_os = "linux")]
1452    pub fn init() -> Result<HashMap<IpAddr, MacAddr>, PistolError> {
1453        // Debian 12:
1454        // 192.168.72.2 dev ens33 lladdr 00:50:56:fb:1d:74 STALE
1455        // 192.168.1.107 dev ens36 lladdr 74:05:a5:53:69:bb STALE
1456        // 192.168.1.1 dev ens36 lladdr 48:5f:08:e0:13:94 STALE
1457        // 192.168.1.128 dev ens36 lladdr a8:9c:ed:d5:00:4c STALE
1458        // 192.168.72.1 dev ens33 lladdr 00:50:56:c0:00:08 REACHABLE
1459        // fe80::4a5f:8ff:fee0:1394 dev ens36 lladdr 48:5f:08:e0:13:94 router STALE
1460        // fe80::250:56ff:fec0:2222 dev ens33 router FAILED
1461        // CentOS 7:
1462        // 192.168.3.456 dev em2 lladdr fc:e3:3c:a6:a9:8c REACHABLE
1463        let c = Command::new("sh").args(["-c", "ip neigh show"]).output()?;
1464        let output = String::from_utf8_lossy(&c.stdout);
1465        let lines: Vec<&str> = output
1466            .lines()
1467            .map(|x| x.trim())
1468            .filter(|v| v.len() > 0)
1469            .collect();
1470
1471        // regex
1472        let neighbor_re =
1473            Regex::new(r"(?P<addr>[\d\w\.:]+)\s+dev[\w\s]+lladdr\s+(?P<mac>[\d\w:]+).+")?;
1474
1475        let mut ret = HashMap::new();
1476        for line in lines {
1477            match neighbor_re.captures(line) {
1478                Some(caps) => {
1479                    let addr = caps.name("addr").map_or("", |m| m.as_str());
1480                    let addr: IpAddr = match addr.parse() {
1481                        Ok(a) => a,
1482                        Err(e) => {
1483                            warn!("parse neighbor 'addr' error:  {e}");
1484                            continue;
1485                        }
1486                    };
1487                    let mac = caps.name("mac").map_or("", |m| m.as_str());
1488                    let mac: MacAddr = match mac.parse() {
1489                        Ok(m) => m,
1490                        Err(e) => {
1491                            warn!("parse neighbor 'mac' error:  {e}");
1492                            continue;
1493                        }
1494                    };
1495                    if !mac.is_zero() {
1496                        ret.insert(addr, mac);
1497                    }
1498                }
1499                None => warn!("line: [{}] neighbor_re no match", line),
1500            }
1501        }
1502        Ok(ret)
1503    }
1504    #[cfg(any(
1505        target_os = "freebsd",
1506        target_os = "openbsd",
1507        target_os = "netbsd",
1508        target_os = "macos"
1509    ))]
1510    pub fn init() -> Result<HashMap<IpAddr, MacAddr>, PistolError> {
1511        // Examples:
1512        // # arp -a
1513        // ? (192.168.72.1) at 00:50:56:c0:00:08 on em0 expires in 1139 seconds [ethernet]
1514        // ? (192.168.72.129) at 00:0c:29:88:20:d2 on em0 permanent [ethernet]
1515        // ? (192.168.72.2) at 00:50:56:fb:1d:74 on em0 expires in 1168 seconds [ethernet]
1516        // MacOS
1517        // ? (192.168.50.2) at (incomplete) on en0 ifscope [ethernet]
1518        // # ndp -a
1519        // Neighbor                             Linklayer Address  Netif Expire    1s 5s
1520        // fe80::20c:29ff:fe88:20d2%em0         00:0c:29:88:20:d2    em0 permanent R
1521        // fe80::20c:29ff:feb8:a41%em0          00:0c:29:b8:0a:41    em0 permanent R
1522        let c = Command::new("sh").args(["-c", "arp -a"]).output()?;
1523        let arp_output = String::from_utf8_lossy(&c.stdout);
1524        let c = Command::new("sh").args(["-c", "ndp -a"]).output()?;
1525        let ndp_output = String::from_utf8_lossy(&c.stdout);
1526        let output = arp_output.to_string() + &ndp_output;
1527        let lines: Vec<&str> = output
1528            .lines()
1529            .map(|x| x.trim())
1530            .filter(|v| v.len() > 0 && !v.contains("Neighbor"))
1531            .collect();
1532
1533        // regex
1534        let neighbor_re = Regex::new(r"\?\s+\((?P<addr>[^\s]+)\)\s+at\s+(?P<mac>[\w\d:]+).+")?;
1535        let neighbor_re6 = Regex::new(r"(?P<addr>[^\s]+)\s+(?P<mac>[^\s]+).+")?;
1536
1537        let mut ret = HashMap::new();
1538        for line in lines {
1539            match neighbor_re.captures(line) {
1540                Some(caps) => {
1541                    let addr_str = caps.name("addr").map_or("", |m| m.as_str());
1542                    let addr_str = ipv6_addr_bsd_fix(addr_str)?;
1543                    let addr: IpAddr = match addr_str.parse() {
1544                        Ok(a) => a,
1545                        Err(e) => {
1546                            warn!("parse neighbor 'addr' error:  {e}");
1547                            continue;
1548                        }
1549                    };
1550                    let mac = caps.name("mac").map_or("", |m| m.as_str());
1551                    let mac: MacAddr = match mac.parse() {
1552                        Ok(m) => m,
1553                        Err(e) => {
1554                            warn!("parse neighbor 'mac' error:  {e}");
1555                            continue;
1556                        }
1557                    };
1558                    ret.insert(addr, mac);
1559                }
1560                // it maybe the ipv6 addr
1561                None => match neighbor_re6.captures(line) {
1562                    Some(caps) => {
1563                        let addr_str = caps.name("addr").map_or("", |m| m.as_str());
1564                        let addr_str = ipv6_addr_bsd_fix(addr_str)?;
1565                        let addr: IpAddr = match addr_str.parse() {
1566                            Ok(a) => a,
1567                            Err(e) => {
1568                                warn!("parse neighbor 'addr' error:  {e}");
1569                                continue;
1570                            }
1571                        };
1572                        let mac = caps.name("mac").map_or("", |m| m.as_str());
1573                        let mac: MacAddr = match mac.parse() {
1574                            Ok(m) => m,
1575                            Err(e) => {
1576                                warn!("parse neighbor 'mac' error:  {e}");
1577                                continue;
1578                            }
1579                        };
1580                        if !mac.is_zero() {
1581                            ret.insert(addr, mac);
1582                        }
1583                    }
1584                    None => warn!(
1585                        "line: [{}] neighbor_re and neighbor_re6 both no match",
1586                        line
1587                    ),
1588                },
1589            }
1590        }
1591        Ok(ret)
1592    }
1593    #[cfg(target_os = "windows")]
1594    pub fn init() -> Result<HashMap<IpAddr, MacAddr>, PistolError> {
1595        // Examples:
1596        // 58 ff02::1:ff73:3ff4 33-33-FF-73-3F-F4 Permanent ActiveStore
1597        // 58 ff02::1:2  33-33-00-01-00-02 Permanent ActiveStore
1598        // 12 ff05::c Permanent ActiveStore
1599        let c = Command::new("powershell")
1600            .args(["Get-NetNeighbor"])
1601            .output()?;
1602        let output = String::from_utf8_lossy(&c.stdout);
1603        let lines: Vec<&str> = output
1604            .lines()
1605            .map(|x| x.trim())
1606            .filter(|v| v.len() > 0 && !v.contains("ifIndex") && !v.contains("--"))
1607            .collect();
1608
1609        // regex
1610        let neighbor_re =
1611            Regex::new(r"^\d+\s+(?P<addr>[\w\d\.:]+)\s+((?P<mac>[\w\d-]+)\s+)?\w+\s+\w+")?;
1612
1613        let mut ret = HashMap::new();
1614        for line in lines {
1615            match neighbor_re.captures(line) {
1616                Some(caps) => {
1617                    let addr = caps.name("addr").map_or("", |m| m.as_str());
1618                    let addr: IpAddr = match addr.parse() {
1619                        Ok(a) => a,
1620                        Err(e) => {
1621                            warn!("parse neighbor 'addr' error:  {e}");
1622                            continue;
1623                        }
1624                    };
1625                    let mac: Option<String> =
1626                        caps.name("mac").map_or(None, |m| Some(m.as_str().into()));
1627                    // 33-33-00-01-00-02 => 33:33:00:01:00:02
1628                    match mac {
1629                        Some(mac) => {
1630                            let mac = mac.replace("-", ":");
1631                            let mac: MacAddr = match mac.parse() {
1632                                Ok(m) => m,
1633                                Err(e) => {
1634                                    warn!("parse neighbor 'mac' error:  {e}");
1635                                    continue;
1636                                }
1637                            };
1638                            if !mac.is_zero() {
1639                                // do not insert 00-00-00-00-00-00
1640                                ret.insert(addr, mac);
1641                            }
1642                        }
1643                        None => (),
1644                    }
1645                }
1646                None => warn!("line: [{}] neighbor_re no match", line),
1647            }
1648        }
1649        Ok(ret)
1650    }
1651}
1652
1653#[derive(Debug, Clone)]
1654pub struct SystemNetCache {
1655    pub default_route: Option<DefaultRoute>,
1656    pub default_route6: Option<DefaultRoute>,
1657    pub routes: HashMap<RouteAddr, RouteInfo>,
1658    pub neighbor: HashMap<IpAddr, MacAddr>,
1659}
1660
1661impl SystemNetCache {
1662    pub fn init() -> Result<SystemNetCache, PistolError> {
1663        let route_table = RouteTable::init()?;
1664        debug!("route table: {}", route_table);
1665        let neighbor_cache = NeighborCache::init()?;
1666        debug!("neighbor cache: {:?}", neighbor_cache);
1667        let snc = SystemNetCache {
1668            default_route: route_table.default_route,
1669            default_route6: route_table.default_route6,
1670            routes: route_table.routes,
1671            neighbor: neighbor_cache,
1672        };
1673        Ok(snc)
1674    }
1675    pub fn search_mac(&self, ip: IpAddr) -> Option<MacAddr> {
1676        match self.neighbor.get(&ip) {
1677            Some(&m) => Some(m),
1678            None => None,
1679        }
1680    }
1681    pub fn update_neighbor_cache(&mut self, ip: IpAddr, mac: MacAddr) {
1682        self.neighbor.insert(ip, mac);
1683        debug!("update the neigh cache done: {:?}", self.neighbor);
1684    }
1685    pub fn search_route_table(&self, ip: IpAddr) -> Option<RouteInfo> {
1686        for (dst, route_info) in &self.routes {
1687            if !dst.is_unspecified() && dst.contains(ip) {
1688                debug!(
1689                    "route table {} contains target ip, route info dev: {}, via: {:?}",
1690                    dst, route_info.dev.name, route_info.via
1691                );
1692                return Some(route_info.clone());
1693            }
1694        }
1695        None
1696    }
1697}
1698
1699#[cfg(test)]
1700mod tests {
1701    use super::*;
1702    use crate::PistolLogger;
1703    use crate::PistolRunner;
1704    use pnet::datalink::interfaces;
1705    use std::fs::read_to_string;
1706    #[test]
1707    fn test_network_cache() {
1708        let _pr = PistolRunner::init(
1709            PistolLogger::Debug,
1710            None,
1711            None, // use default value
1712        )
1713        .unwrap();
1714
1715        let snc = SystemNetCache::init().unwrap();
1716        for (route_addr, route_info) in snc.routes {
1717            println!(
1718                "route_addr: {:?}, route_info.dev: {}, route_info.via: {:?}",
1719                route_addr, route_info.dev.name, route_info.via
1720            );
1721        }
1722    }
1723    #[test]
1724    fn test_details() {
1725        let n = NeighborCache::init().unwrap();
1726        println!("{:?}", n);
1727
1728        let r = RouteTable::init().unwrap();
1729        println!("{:?}", r);
1730    }
1731    #[test]
1732    fn test_mac() {
1733        let mac = MacAddr::new(0, 0, 0, 0, 0, 0);
1734        println!("{}", mac);
1735        assert_eq!(mac.is_zero(), true)
1736    }
1737    #[test]
1738    fn test_windows_interface() {
1739        println!("TEST!!!!");
1740        for interface in interfaces() {
1741            // can not found ipv6 address in windows
1742            println!("{}", interface);
1743            println!("{}", interface.index);
1744        }
1745    }
1746    #[test]
1747    fn test_unix() {
1748        let input = "fe80::%em0/64";
1749        let input_split: Vec<&str> = input
1750            .split("%")
1751            .map(|x| x.trim())
1752            .filter(|v| v.len() > 0)
1753            .collect();
1754        let _ip: IpAddr = input_split[0].parse().unwrap();
1755        let ipnetwork = IpNetwork::from_str("fe80::").unwrap();
1756        let test_ipv6: IpAddr = "fe80::20c:29ff:feb6:8d99".parse().unwrap();
1757        println!("{}", ipnetwork.contains(test_ipv6));
1758        let ipnetwork = IpNetwork::from_str("fe80::/64").unwrap();
1759        let test_ipv6: IpAddr = "fe80::20c:29ff:feb6:8d99".parse().unwrap();
1760        println!("{}", ipnetwork.contains(test_ipv6));
1761        let ipnetwork = IpNetwork::from_str("::/96").unwrap();
1762        let test_ipv6: IpAddr = "fe80::20c:29ff:feb6:8d99".parse().unwrap();
1763        println!("{}", ipnetwork.contains(test_ipv6));
1764    }
1765    #[test]
1766    fn test_all() {
1767        let _pr = PistolRunner::init(
1768            PistolLogger::Debug,
1769            None,
1770            None, // use default value
1771        )
1772        .unwrap();
1773
1774        #[cfg(target_os = "linux")]
1775        let routetable_str = read_to_string("./tests/linux_routetable.txt").unwrap();
1776
1777        #[cfg(any(
1778            target_os = "freebsd",
1779            target_os = "openbsd",
1780            target_os = "netbsd",
1781            target_os = "macos"
1782        ))]
1783        let routetable_str = read_to_string("./tests/unix_routetable.txt").unwrap();
1784
1785        #[cfg(target_os = "windows")]
1786        let routetable_str = read_to_string("./tests/windows_routetable.txt").unwrap();
1787
1788        let routetable_fix: Vec<String> = routetable_str
1789            .lines()
1790            .map(|x| x.trim().to_string())
1791            .collect();
1792
1793        let mut routetables = Vec::new();
1794        let mut tmp = Vec::new();
1795        for r in &routetable_fix {
1796            if r != "+++" {
1797                // split line
1798                tmp.push(r.clone());
1799            } else {
1800                routetables.push(tmp.clone());
1801                tmp.clear();
1802            }
1803        }
1804
1805        for t in routetables {
1806            let ret = InnerRouteTable::parser(&t).unwrap();
1807            println!("{}", ret);
1808            println!("-------------------------------------------------------------------------");
1809        }
1810    }
1811}