This commit is contained in:
parent
dee38eb6c2
commit
ba9103685f
83
examples/network/network_connectivity.rhai
Normal file
83
examples/network/network_connectivity.rhai
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Example of using the network modules in SAL
|
||||||
|
// Shows TCP port checking, HTTP URL validation, and SSH command execution
|
||||||
|
|
||||||
|
// Import system module for display
|
||||||
|
import "os" as os;
|
||||||
|
|
||||||
|
// Function to print section header
|
||||||
|
fn section(title) {
|
||||||
|
print("\n");
|
||||||
|
print("==== " + title + " ====");
|
||||||
|
print("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCP connectivity checks
|
||||||
|
section("TCP Connectivity");
|
||||||
|
|
||||||
|
// Create a TCP connector
|
||||||
|
let tcp = sal::net::TcpConnector::new();
|
||||||
|
|
||||||
|
// Check if a port is open
|
||||||
|
let host = "localhost";
|
||||||
|
let port = 22;
|
||||||
|
print(`Checking if port ${port} is open on ${host}...`);
|
||||||
|
let is_open = tcp.check_port(host, port);
|
||||||
|
print(`Port ${port} is ${is_open ? "open" : "closed"}`);
|
||||||
|
|
||||||
|
// Check multiple ports
|
||||||
|
let ports = [22, 80, 443];
|
||||||
|
print(`Checking multiple ports on ${host}...`);
|
||||||
|
let port_results = tcp.check_ports(host, ports);
|
||||||
|
for result in port_results {
|
||||||
|
print(`Port ${result.0} is ${result.1 ? "open" : "closed"}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP connectivity checks
|
||||||
|
section("HTTP Connectivity");
|
||||||
|
|
||||||
|
// Create an HTTP connector
|
||||||
|
let http = sal::net::HttpConnector::new();
|
||||||
|
|
||||||
|
// Check if a URL is reachable
|
||||||
|
let url = "https://www.example.com";
|
||||||
|
print(`Checking if ${url} is reachable...`);
|
||||||
|
let is_reachable = http.check_url(url);
|
||||||
|
print(`${url} is ${is_reachable ? "reachable" : "unreachable"}`);
|
||||||
|
|
||||||
|
// Check the status code of a URL
|
||||||
|
print(`Checking status code of ${url}...`);
|
||||||
|
let status = http.check_status(url);
|
||||||
|
if status {
|
||||||
|
print(`Status code: ${status.unwrap()}`);
|
||||||
|
} else {
|
||||||
|
print("Failed to get status code");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only attempt SSH if port 22 is open
|
||||||
|
if is_open {
|
||||||
|
// SSH connectivity checks
|
||||||
|
section("SSH Connectivity");
|
||||||
|
|
||||||
|
// Create an SSH connection to localhost (if SSH server is running)
|
||||||
|
print("Attempting to connect to SSH server on localhost...");
|
||||||
|
|
||||||
|
// Using the builder pattern
|
||||||
|
let ssh = sal::net::SshConnectionBuilder::new()
|
||||||
|
.host("localhost")
|
||||||
|
.port(22)
|
||||||
|
.user(os::get_env("USER") || "root")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Execute a simple command
|
||||||
|
print("Executing 'uname -a' command...");
|
||||||
|
let result = ssh.execute("uname -a");
|
||||||
|
if result.0 == 0 {
|
||||||
|
print("Command output:");
|
||||||
|
print(result.1);
|
||||||
|
} else {
|
||||||
|
print(`Command failed with exit code: ${result.0}`);
|
||||||
|
print(result.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\nNetwork connectivity checks completed.");
|
82
examples/network/network_rhai.rhai
Normal file
82
examples/network/network_rhai.rhai
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// Example of using the network modules in SAL through Rhai
|
||||||
|
// Shows TCP port checking, HTTP URL validation, and SSH command execution
|
||||||
|
|
||||||
|
// Function to print section header
|
||||||
|
fn section(title) {
|
||||||
|
print("\n");
|
||||||
|
print("==== " + title + " ====");
|
||||||
|
print("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCP connectivity checks
|
||||||
|
section("TCP Connectivity");
|
||||||
|
|
||||||
|
// Create a TCP connector
|
||||||
|
let tcp = net::new_tcp_connector();
|
||||||
|
|
||||||
|
// Check if a port is open
|
||||||
|
let host = "localhost";
|
||||||
|
let port = 22;
|
||||||
|
print(`Checking if port ${port} is open on ${host}...`);
|
||||||
|
let is_open = tcp.check_port(host, port);
|
||||||
|
print(`Port ${port} is ${is_open ? "open" : "closed"}`);
|
||||||
|
|
||||||
|
// Check multiple ports
|
||||||
|
let ports = [22, 80, 443];
|
||||||
|
print(`Checking multiple ports on ${host}...`);
|
||||||
|
let port_results = tcp.check_ports(host, ports);
|
||||||
|
for result in port_results {
|
||||||
|
print(`Port ${result.port} is ${result.is_open ? "open" : "closed"}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP connectivity checks
|
||||||
|
section("HTTP Connectivity");
|
||||||
|
|
||||||
|
// Create an HTTP connector
|
||||||
|
let http = net::new_http_connector();
|
||||||
|
|
||||||
|
// Check if a URL is reachable
|
||||||
|
let url = "https://www.example.com";
|
||||||
|
print(`Checking if ${url} is reachable...`);
|
||||||
|
let is_reachable = http.check_url(url);
|
||||||
|
print(`${url} is ${is_reachable ? "reachable" : "unreachable"}`);
|
||||||
|
|
||||||
|
// Check the status code of a URL
|
||||||
|
print(`Checking status code of ${url}...`);
|
||||||
|
let status = http.check_status(url);
|
||||||
|
if status != () {
|
||||||
|
print(`Status code: ${status}`);
|
||||||
|
} else {
|
||||||
|
print("Failed to get status code");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get content from a URL
|
||||||
|
print(`Getting content from ${url}...`);
|
||||||
|
let content = http.get_content(url);
|
||||||
|
print(`Content length: ${content.len()} characters`);
|
||||||
|
print(`First 100 characters: ${content.substr(0, 100)}...`);
|
||||||
|
|
||||||
|
// Only attempt SSH if port 22 is open
|
||||||
|
if is_open {
|
||||||
|
// SSH connectivity checks
|
||||||
|
section("SSH Connectivity");
|
||||||
|
|
||||||
|
// Create an SSH connection to localhost (if SSH server is running)
|
||||||
|
print("Attempting to connect to SSH server on localhost...");
|
||||||
|
|
||||||
|
// Using the builder pattern
|
||||||
|
let ssh = net::new_ssh_builder()
|
||||||
|
.host("localhost")
|
||||||
|
.port(22)
|
||||||
|
.user(os::get_env("USER") || "root")
|
||||||
|
.timeout(10)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Execute a simple command
|
||||||
|
print("Executing 'uname -a' command...");
|
||||||
|
let result = ssh.execute("uname -a");
|
||||||
|
print(`Command exit code: ${result.code}`);
|
||||||
|
print(`Command output: ${result.output}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\nNetwork connectivity checks completed.");
|
126
src/net/http.rs
126
src/net/http.rs
@ -1,51 +1,93 @@
|
|||||||
use reqwest::Client;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
// HTTP Checker
|
use anyhow::Result;
|
||||||
pub struct HttpChecker {
|
use reqwest::{Client, StatusCode, Url};
|
||||||
|
|
||||||
|
/// HTTP Connectivity module for checking HTTP/HTTPS connections
|
||||||
|
pub struct HttpConnector {
|
||||||
client: Client,
|
client: Client,
|
||||||
url: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpChecker {
|
impl HttpConnector {
|
||||||
pub async fn check_url(&self) -> Result<bool, reqwest::Error> {
|
/// Create a new HTTP connector with the default configuration
|
||||||
let res = self.client.get(&self.url).send().await?;
|
pub fn new() -> Result<Self> {
|
||||||
Ok(res.status().is_success())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP Checker Builder
|
|
||||||
pub struct HttpCheckerBuilder {
|
|
||||||
url: String,
|
|
||||||
timeout: Duration,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HttpCheckerBuilder {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
url: "http://localhost".to_string(),
|
|
||||||
timeout: Duration::from_secs(30),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn url<S: Into<String>>(mut self, url: S) -> Self {
|
|
||||||
self.url = url.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn timeout(mut self, timeout: Duration) -> Self {
|
|
||||||
self.timeout = timeout;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> HttpChecker {
|
|
||||||
let client = Client::builder()
|
let client = Client::builder()
|
||||||
.timeout(self.timeout)
|
.timeout(Duration::from_secs(30))
|
||||||
.build()
|
.build()?;
|
||||||
.expect("Failed to build HTTP client");
|
|
||||||
HttpChecker {
|
Ok(Self { client })
|
||||||
client,
|
}
|
||||||
url: self.url,
|
|
||||||
|
/// Create a new HTTP connector with a custom timeout
|
||||||
|
pub fn with_timeout(timeout: Duration) -> Result<Self> {
|
||||||
|
let client = Client::builder()
|
||||||
|
.timeout(timeout)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
Ok(Self { client })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a URL is reachable
|
||||||
|
pub async fn check_url<U: AsRef<str>>(&self, url: U) -> Result<bool> {
|
||||||
|
let url_str = url.as_ref();
|
||||||
|
let url = Url::parse(url_str)?;
|
||||||
|
|
||||||
|
let result = self.client
|
||||||
|
.head(url)
|
||||||
|
.send()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(result.is_ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check a URL and return the status code if reachable
|
||||||
|
pub async fn check_status<U: AsRef<str>>(&self, url: U) -> Result<Option<StatusCode>> {
|
||||||
|
let url_str = url.as_ref();
|
||||||
|
let url = Url::parse(url_str)?;
|
||||||
|
|
||||||
|
let result = self.client
|
||||||
|
.head(url)
|
||||||
|
.send()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(response) => Ok(Some(response.status())),
|
||||||
|
Err(_) => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the content of a URL
|
||||||
|
pub async fn get_content<U: AsRef<str>>(&self, url: U) -> Result<String> {
|
||||||
|
let url_str = url.as_ref();
|
||||||
|
let url = Url::parse(url_str)?;
|
||||||
|
|
||||||
|
let response = self.client
|
||||||
|
.get(url)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"HTTP request failed with status: {}",
|
||||||
|
response.status()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = response.text().await?;
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify that a URL responds with a specific status code
|
||||||
|
pub async fn verify_status<U: AsRef<str>>(&self, url: U, expected_status: StatusCode) -> Result<bool> {
|
||||||
|
match self.check_status(url).await? {
|
||||||
|
Some(status) => Ok(status == expected_status),
|
||||||
|
None => Ok(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HttpConnector {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new().expect("Failed to create default HttpConnector")
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,3 +1,8 @@
|
|||||||
pub mod ssh;
|
pub mod ssh;
|
||||||
pub mod tcp;
|
pub mod tcp;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
|
|
||||||
|
// Re-export main types for a cleaner API
|
||||||
|
pub use ssh::{SshConnection, SshConnectionBuilder};
|
||||||
|
pub use tcp::TcpConnector;
|
||||||
|
pub use http::HttpConnector;
|
174
src/net/ssh.rs
174
src/net/ssh.rs
@ -1,31 +1,99 @@
|
|||||||
use russh::client;
|
|
||||||
use russh_keys::key;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use std::process::Stdio;
|
||||||
|
|
||||||
// SSH Connection
|
use anyhow::Result;
|
||||||
#[derive(Clone)]
|
use tokio::io::{AsyncReadExt, BufReader};
|
||||||
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
/// SSH Connection that uses the system's SSH client
|
||||||
pub struct SshConnection {
|
pub struct SshConnection {
|
||||||
session: Arc<client::Handle<Client>>,
|
host: String,
|
||||||
|
port: u16,
|
||||||
|
user: String,
|
||||||
|
identity_file: Option<PathBuf>,
|
||||||
|
timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SshConnection {
|
impl SshConnection {
|
||||||
pub async fn ping(&self) -> Result<(), anyhow::Error> {
|
/// Execute a command over SSH and return its output
|
||||||
let mut channel = self.session.channel_open_session().await?;
|
pub async fn execute(&self, command: &str) -> Result<(i32, String)> {
|
||||||
channel.exec(true, "ping -c 1 127.0.0.1").await?;
|
let mut args = Vec::new();
|
||||||
Ok(())
|
|
||||||
|
// Add SSH options
|
||||||
|
args.push("-o".to_string());
|
||||||
|
args.push(format!("ConnectTimeout={}", self.timeout.as_secs()));
|
||||||
|
|
||||||
|
// Don't check host key to avoid prompts
|
||||||
|
args.push("-o".to_string());
|
||||||
|
args.push("StrictHostKeyChecking=no".to_string());
|
||||||
|
|
||||||
|
// Specify port if not default
|
||||||
|
if self.port != 22 {
|
||||||
|
args.push("-p".to_string());
|
||||||
|
args.push(self.port.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add identity file if provided
|
||||||
|
if let Some(identity) = &self.identity_file {
|
||||||
|
args.push("-i".to_string());
|
||||||
|
args.push(identity.to_string_lossy().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user and host
|
||||||
|
args.push(format!("{}@{}", self.user, self.host));
|
||||||
|
|
||||||
|
// Add the command to execute
|
||||||
|
args.push(command.to_string());
|
||||||
|
|
||||||
|
// Run the SSH command
|
||||||
|
let mut child = Command::new("ssh")
|
||||||
|
.args(&args)
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
// Collect stdout and stderr
|
||||||
|
let stdout = child.stdout.take().unwrap();
|
||||||
|
let stderr = child.stderr.take().unwrap();
|
||||||
|
|
||||||
|
let mut stdout_reader = BufReader::new(stdout);
|
||||||
|
let mut stderr_reader = BufReader::new(stderr);
|
||||||
|
|
||||||
|
let mut output = String::new();
|
||||||
|
stdout_reader.read_to_string(&mut output).await?;
|
||||||
|
|
||||||
|
let mut error_output = String::new();
|
||||||
|
stderr_reader.read_to_string(&mut error_output).await?;
|
||||||
|
|
||||||
|
// If there's error output, append it to the regular output
|
||||||
|
if !error_output.is_empty() {
|
||||||
|
if !output.is_empty() {
|
||||||
|
output.push_str("\n");
|
||||||
|
}
|
||||||
|
output.push_str(&error_output);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the command to complete and get exit status
|
||||||
|
let status = child.wait().await?;
|
||||||
|
let code = status.code().unwrap_or(-1);
|
||||||
|
|
||||||
|
Ok((code, output))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the host is reachable via SSH
|
||||||
|
pub async fn ping(&self) -> Result<bool> {
|
||||||
|
let result = self.execute("echo 'Connection successful'").await?;
|
||||||
|
Ok(result.0 == 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSH Connection Builder
|
/// Builder for SSH connections
|
||||||
pub struct SshConnectionBuilder {
|
pub struct SshConnectionBuilder {
|
||||||
host: String,
|
host: String,
|
||||||
port: u16,
|
port: u16,
|
||||||
user: String,
|
user: String,
|
||||||
password: Option<String>,
|
identity_file: Option<PathBuf>,
|
||||||
key_path: Option<PathBuf>,
|
|
||||||
use_agent: bool,
|
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,10 +103,8 @@ impl SshConnectionBuilder {
|
|||||||
host: "localhost".to_string(),
|
host: "localhost".to_string(),
|
||||||
port: 22,
|
port: 22,
|
||||||
user: "root".to_string(),
|
user: "root".to_string(),
|
||||||
password: None,
|
identity_file: None,
|
||||||
key_path: None,
|
timeout: Duration::from_secs(10),
|
||||||
use_agent: true,
|
|
||||||
timeout: Duration::from_secs(30),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,18 +123,8 @@ impl SshConnectionBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn password<S: Into<String>>(mut self, password: S) -> Self {
|
pub fn identity_file(mut self, path: PathBuf) -> Self {
|
||||||
self.password = Some(password.into());
|
self.identity_file = Some(path);
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn key_path(mut self, key_path: PathBuf) -> Self {
|
|
||||||
self.key_path = Some(key_path);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn use_agent(mut self, use_agent: bool) -> Self {
|
|
||||||
self.use_agent = use_agent;
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,57 +133,13 @@ impl SshConnectionBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn build(self) -> Result<SshConnection, anyhow::Error> {
|
pub fn build(self) -> SshConnection {
|
||||||
let config = Arc::new(client::Config::default());
|
SshConnection {
|
||||||
let sh = Client;
|
host: self.host,
|
||||||
|
port: self.port,
|
||||||
let mut session = client::connect(config, (self.host.as_str(), self.port), sh).await?;
|
user: self.user,
|
||||||
|
identity_file: self.identity_file,
|
||||||
let auth_res = if self.use_agent {
|
timeout: self.timeout,
|
||||||
let mut agent = russh_keys::agent::client::AgentClient::connect_env().await?;
|
|
||||||
let mut keys = agent.request_identities().await?;
|
|
||||||
if keys.is_empty() {
|
|
||||||
return Err(anyhow::anyhow!("No identities found in ssh-agent"));
|
|
||||||
}
|
|
||||||
let key = keys.remove(0);
|
|
||||||
let (_agent, authed) = session
|
|
||||||
.authenticate_future(self.user.as_str(), Arc::new(key), agent)
|
|
||||||
.await;
|
|
||||||
authed?
|
|
||||||
} else if let Some(password) = self.password {
|
|
||||||
session
|
|
||||||
.authenticate_password(self.user.as_str(), &password)
|
|
||||||
.await?
|
|
||||||
} else if let Some(key_path) = self.key_path {
|
|
||||||
let key_pair = russh_keys::load_secret_key(key_path, None)?;
|
|
||||||
session
|
|
||||||
.authenticate_publickey(self.user.as_str(), Arc::new(key_pair))
|
|
||||||
.await?
|
|
||||||
} else {
|
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"No authentication method specified"
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
if !auth_res {
|
|
||||||
return Err(anyhow::anyhow!("Authentication failed"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(SshConnection {
|
|
||||||
session: Arc::new(session),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Client;
|
|
||||||
|
|
||||||
impl client::Handler for Client {
|
|
||||||
type Error = russh::Error;
|
|
||||||
|
|
||||||
fn check_server_key(
|
|
||||||
&mut self,
|
|
||||||
_server_public_key: &key::PublicKey,
|
|
||||||
) -> std::future::Ready<Result<bool, Self::Error>> {
|
|
||||||
std::future::ready(Ok(true))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
112
src/net/tcp.rs
112
src/net/tcp.rs
@ -1,64 +1,74 @@
|
|||||||
use std::net::{SocketAddr, TcpStream};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
// TCP Checker
|
use anyhow::Result;
|
||||||
pub struct TcpChecker {
|
use tokio::net::TcpStream;
|
||||||
host: String,
|
use tokio::time::timeout;
|
||||||
port: u16,
|
|
||||||
|
/// TCP Connectivity module for checking TCP connections
|
||||||
|
pub struct TcpConnector {
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TcpChecker {
|
impl TcpConnector {
|
||||||
pub fn ping(&self) -> Result<(), std::io::Error> {
|
/// Create a new TCP connector with the default timeout (5 seconds)
|
||||||
let addr = format!("{}:{}", self.host, self.port);
|
|
||||||
let socket_addr: SocketAddr = addr.parse().expect("Failed to parse socket address");
|
|
||||||
TcpStream::connect_timeout(&socket_addr, self.timeout)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_port(&self) -> bool {
|
|
||||||
let addr = format!("{}:{}", self.host, self.port);
|
|
||||||
let socket_addr: SocketAddr = addr.parse().expect("Failed to parse socket address");
|
|
||||||
TcpStream::connect_timeout(&socket_addr, self.timeout).is_ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCP Checker Builder
|
|
||||||
pub struct TcpCheckerBuilder {
|
|
||||||
host: String,
|
|
||||||
port: u16,
|
|
||||||
timeout: Duration,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TcpCheckerBuilder {
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
host: "localhost".to_string(),
|
timeout: Duration::from_secs(5),
|
||||||
port: 80,
|
}
|
||||||
timeout: Duration::from_secs(1),
|
}
|
||||||
|
|
||||||
|
/// Create a new TCP connector with a custom timeout
|
||||||
|
pub fn with_timeout(timeout: Duration) -> Self {
|
||||||
|
Self { timeout }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a TCP port is open on a host
|
||||||
|
pub async fn check_port<A: Into<IpAddr>>(&self, host: A, port: u16) -> Result<bool> {
|
||||||
|
let addr = SocketAddr::new(host.into(), port);
|
||||||
|
let connect_future = TcpStream::connect(addr);
|
||||||
|
|
||||||
|
match timeout(self.timeout, connect_future).await {
|
||||||
|
Ok(Ok(_)) => Ok(true),
|
||||||
|
Ok(Err(_)) => Ok(false),
|
||||||
|
Err(_) => Ok(false), // Timeout occurred
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn host<S: Into<String>>(mut self, host: S) -> Self {
|
/// Check if multiple TCP ports are open on a host
|
||||||
self.host = host.into();
|
pub async fn check_ports<A: Into<IpAddr> + Clone>(&self, host: A, ports: &[u16]) -> Result<Vec<(u16, bool)>> {
|
||||||
self
|
let mut results = Vec::with_capacity(ports.len());
|
||||||
}
|
|
||||||
|
for &port in ports {
|
||||||
pub fn port(mut self, port: u16) -> Self {
|
let is_open = self.check_port(host.clone(), port).await?;
|
||||||
self.port = port;
|
results.push((port, is_open));
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn timeout(mut self, timeout: Duration) -> Self {
|
|
||||||
self.timeout = timeout;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> TcpChecker {
|
|
||||||
TcpChecker {
|
|
||||||
host: self.host,
|
|
||||||
port: self.port,
|
|
||||||
timeout: self.timeout,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a host is reachable on the network using ICMP ping
|
||||||
|
pub async fn ping<S: AsRef<str>>(&self, host: S) -> Result<bool> {
|
||||||
|
// Convert to owned strings to avoid borrowing issues
|
||||||
|
let host_str = host.as_ref().to_string();
|
||||||
|
let timeout_secs = self.timeout.as_secs().to_string();
|
||||||
|
|
||||||
|
// Run the ping command with explicit arguments
|
||||||
|
let status = tokio::process::Command::new("ping")
|
||||||
|
.arg("-c")
|
||||||
|
.arg("1") // Just one ping
|
||||||
|
.arg("-W")
|
||||||
|
.arg(timeout_secs) // Timeout in seconds
|
||||||
|
.arg(host_str) // Host to ping
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(status.status.success())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TcpConnector {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
89
src/rhai/net.rs
Normal file
89
src/rhai/net.rs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
//! Rhai wrappers for network module functions
|
||||||
|
//!
|
||||||
|
//! This module provides Rhai wrappers for network connectivity functions.
|
||||||
|
|
||||||
|
use rhai::{Engine, EvalAltResult};
|
||||||
|
use crate::net::TcpConnector;
|
||||||
|
use super::error::register_error_types;
|
||||||
|
|
||||||
|
/// Register network module functions with the Rhai engine
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `engine` - The Rhai engine to register the functions with
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
|
||||||
|
pub fn create_module() -> rhai::Module {
|
||||||
|
let mut module = rhai::Module::new();
|
||||||
|
|
||||||
|
// Register basic TCP functions
|
||||||
|
module.set_native_fn("tcp_check", tcp_check);
|
||||||
|
module.set_native_fn("tcp_ping", tcp_ping);
|
||||||
|
|
||||||
|
module
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register network module functions with the Rhai engine
|
||||||
|
pub fn register_net_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||||
|
// Register error types
|
||||||
|
register_error_types(engine)?;
|
||||||
|
|
||||||
|
// TCP functions
|
||||||
|
engine.register_fn("tcp_check", tcp_check);
|
||||||
|
engine.register_fn("tcp_ping", tcp_ping);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a TCP port is open
|
||||||
|
pub fn tcp_check(host: &str, port: i64) -> bool {
|
||||||
|
let connector = TcpConnector::new();
|
||||||
|
|
||||||
|
// Create a simple runtime to run the async function
|
||||||
|
match tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build() {
|
||||||
|
Ok(rt) => {
|
||||||
|
rt.block_on(async {
|
||||||
|
// Resolve host name first
|
||||||
|
let sock_addr = format!("{}:{}", host, port);
|
||||||
|
match tokio::net::lookup_host(sock_addr).await {
|
||||||
|
Ok(mut addrs) => {
|
||||||
|
if let Some(addr) = addrs.next() {
|
||||||
|
match connector.check_port(addr.ip(), port as u16).await {
|
||||||
|
Ok(is_open) => is_open,
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ping a host using ICMP
|
||||||
|
pub fn tcp_ping(host: &str) -> bool {
|
||||||
|
let connector = TcpConnector::new();
|
||||||
|
|
||||||
|
// Create a simple runtime to run the async function
|
||||||
|
match tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build() {
|
||||||
|
Ok(rt) => {
|
||||||
|
rt.block_on(async {
|
||||||
|
match connector.ping(host).await {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user