moblink-rust 0.9.4

Use spare devices as extra SRTLA bonding connections
Documentation
use std::collections::HashSet;
use std::net::Ipv4Addr;
use std::time::Duration;

use log::{error, info, warn};
use network_interface::{Addr, NetworkInterface, NetworkInterfaceConfig};
use rand::distr::{Alphanumeric, SampleString};
use tokio::net::lookup_host;
use tokio::process::Command;

pub const MDNS_SERVICE_TYPE: &str = "_moblink._tcp.local.";

pub type AnyError = Box<dyn std::error::Error + Send + Sync>;

pub fn random_string() -> String {
    Alphanumeric.sample_string(&mut rand::rng(), 64)
}

pub async fn execute_command(executable: &str, args: &[&str]) {
    let command = format_command(executable, args);
    match Command::new(executable).args(args).status().await {
        Ok(status) => {
            if status.success() {
                info!("Command '{}' succeeded!", command);
            } else {
                warn!("Command '{}' failed with status {}", command, status);
            }
        }
        Err(error) => {
            error!("Command '{}' failed with error: {}", command, error);
        }
    }
}

pub fn format_command(executable: &str, args: &[&str]) -> String {
    format!("{} {}", executable, args.join(" "))
}

pub async fn resolve_host(address: &str) -> Result<String, AnyError> {
    for _ in 0..50 {
        match lookup_host(format!("{}:9999", address)).await {
            Ok(mut addresses) => {
                if let Some(address) = addresses.next() {
                    return Ok(address.ip().to_string());
                } else {
                    warn!("No address found for {}", address);
                }
            }
            Err(error) => {
                warn!("DNS lookup for '{}' failed with error {}", address, error);
            }
        }
        tokio::time::sleep(Duration::from_secs(2)).await;
    }
    Err("DNS lookup failed after timeout".into())
}

pub fn get_first_ipv4_address(interface: &NetworkInterface) -> Option<Ipv4Addr> {
    for address in &interface.addr {
        if let Addr::V4(address) = address {
            return Some(address.ip);
        }
    }
    None
}

pub fn any_address_belongs_to_this_machine(addresses: &HashSet<&Ipv4Addr>) -> bool {
    let Ok(interfaces) = NetworkInterface::show() else {
        return true;
    };
    for interface in interfaces {
        for addr in interface.addr {
            if let Addr::V4(addr) = addr {
                if addresses.contains(&addr.ip) {
                    return true;
                }
            }
        }
    }
    false
}