1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
use std::net::{IpAddr, SocketAddr};

use ipnet::{IpAddrRange, IpNet};
use tokio::net::TcpStream;
use tokio::task::JoinHandle;

use crate::*;

#[derive(Debug, Clone)]
pub(crate) struct Service {
  ip_range: IpAddrRange,
  timeout: std::time::Duration,
  modbus_port: u16,
}

impl service::Service for Service {
  fn new(config: config::Values) -> Self {
    Self {
      ip_range: config.network.ip_range,
      timeout: std::time::Duration::from_millis(
        config.network.timeout.num_milliseconds() as u64,
      ),
      modbus_port: config.network.modbus_port,
    }
  }
}

impl Service {
  #[tracing::instrument(skip(self))]
  pub(crate) async fn scan_modbus(&self) -> Vec<SocketAddr> {
    let default_interface_ranges =
      match netdev::interface::get_default_interface() {
        Ok(interface) => interface
          .ipv4
          .iter()
          .map(|addr| IpNet::V4(*addr).hosts())
          .collect::<Vec<_>>(),
        Err(_) => Vec::new(),
      };

    let timeout = self.timeout;
    let mut matched_ips = Vec::new();
    let ip_scans = self
      .ip_range
      .into_iter()
      .chain(default_interface_ranges.into_iter().flatten())
      .map(|ip| {
        let socket_address = self.to_socket(ip);
        (
          socket_address,
          tokio::spawn(async move {
            let timeout = tokio::time::timeout(
              timeout,
              TcpStream::connect(&socket_address),
            )
            .await;
            matches!(timeout, Ok(Ok(_)))
          }),
        )
      })
      .collect::<Vec<(SocketAddr, JoinHandle<bool>)>>();

    tracing::trace!("Matching {:?}", self.ip_range);
    for (ip, scan) in ip_scans {
      if let Ok(true) = scan.await {
        matched_ips.push(ip)
      }
    }
    tracing::trace!("Found {:?} ips", matched_ips.len());

    matched_ips
  }

  pub(crate) fn to_socket(&self, ip: IpAddr) -> SocketAddr {
    SocketAddr::new(ip, self.modbus_port)
  }
}