feat: Add sal-net package to workspace
Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run

- Add new sal-net package to the workspace.
- Update MONOREPO_CONVERSION_PLAN.md to reflect the
  addition of the sal-net package and mark it as
  production-ready.
- Add Cargo.toml and README.md for the sal-net package.
This commit is contained in:
Mahmoud-Emad
2025-06-22 09:52:20 +03:00
parent d22fd686b7
commit 74217364fa
23 changed files with 2540 additions and 158 deletions

View File

@@ -39,7 +39,7 @@ pub type Result<T> = std::result::Result<T, Error>;
// Re-export modules
pub mod cmd;
pub use sal_mycelium as mycelium;
pub mod net;
pub use sal_net as net;
pub use sal_os as os;
pub mod postgresclient;
pub mod process;

View File

@@ -1,93 +0,0 @@
use std::time::Duration;
use anyhow::Result;
use reqwest::{Client, StatusCode, Url};
/// HTTP Connectivity module for checking HTTP/HTTPS connections
pub struct HttpConnector {
client: Client,
}
impl HttpConnector {
/// Create a new HTTP connector with the default configuration
pub fn new() -> Result<Self> {
let client = Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
Ok(Self { client })
}
/// 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")
}
}

View File

@@ -1,8 +0,0 @@
pub mod ssh;
pub mod tcp;
pub mod http;
// Re-export main types for a cleaner API
pub use ssh::{SshConnection, SshConnectionBuilder};
pub use tcp::TcpConnector;
pub use http::HttpConnector;

View File

@@ -1,145 +0,0 @@
use std::path::PathBuf;
use std::time::Duration;
use std::process::Stdio;
use anyhow::Result;
use tokio::io::{AsyncReadExt, BufReader};
use tokio::process::Command;
/// SSH Connection that uses the system's SSH client
pub struct SshConnection {
host: String,
port: u16,
user: String,
identity_file: Option<PathBuf>,
timeout: Duration,
}
impl SshConnection {
/// Execute a command over SSH and return its output
pub async fn execute(&self, command: &str) -> Result<(i32, String)> {
let mut args = Vec::new();
// 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)
}
}
/// Builder for SSH connections
pub struct SshConnectionBuilder {
host: String,
port: u16,
user: String,
identity_file: Option<PathBuf>,
timeout: Duration,
}
impl SshConnectionBuilder {
pub fn new() -> Self {
Self {
host: "localhost".to_string(),
port: 22,
user: "root".to_string(),
identity_file: None,
timeout: Duration::from_secs(10),
}
}
pub fn host<S: Into<String>>(mut self, host: S) -> Self {
self.host = host.into();
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub fn user<S: Into<String>>(mut self, user: S) -> Self {
self.user = user.into();
self
}
pub fn identity_file(mut self, path: PathBuf) -> Self {
self.identity_file = Some(path);
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn build(self) -> SshConnection {
SshConnection {
host: self.host,
port: self.port,
user: self.user,
identity_file: self.identity_file,
timeout: self.timeout,
}
}
}

View File

@@ -1,74 +0,0 @@
use std::net::{IpAddr, SocketAddr};
use std::time::Duration;
use anyhow::Result;
use tokio::net::TcpStream;
use tokio::time::timeout;
/// TCP Connectivity module for checking TCP connections
pub struct TcpConnector {
timeout: Duration,
}
impl TcpConnector {
/// Create a new TCP connector with the default timeout (5 seconds)
pub fn new() -> Self {
Self {
timeout: Duration::from_secs(5),
}
}
/// 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
}
}
/// Check if multiple TCP ports are open on a host
pub async fn check_ports<A: Into<IpAddr> + Clone>(&self, host: A, ports: &[u16]) -> Result<Vec<(u16, bool)>> {
let mut results = Vec::with_capacity(ports.len());
for &port in ports {
let is_open = self.check_port(host.clone(), port).await?;
results.push((port, is_open));
}
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()
}
}

View File

@@ -102,6 +102,9 @@ pub use sal_mycelium::rhai::register_mycelium_module;
// Re-export text module
pub use sal_text::rhai::register_text_module;
// Re-export net module
pub use sal_net::rhai::register_net_module;
// Re-export crypto module
pub use vault::register_crypto_module;
@@ -155,6 +158,9 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
// Register Text module functions
sal_text::rhai::register_text_module(engine)?;
// Register Net module functions
sal_net::rhai::register_net_module(engine)?;
// Register RFS module functions
rfs::register(engine)?;

View File

@@ -1,89 +0,0 @@
//! 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,
}
}