rsubdomain 1.2.10

A high-performance subdomain brute-force tool written in Rust
Documentation
use std::net::{IpAddr, Ipv4Addr, SocketAddrV4, UdpSocket};
use std::process::Command;
use std::str::FromStr;
use std::thread;
use std::time::Duration;

use pnet::datalink;

use crate::model::EthTable;

const DEFAULT_DNS_PROBE_TARGETS: &[&str] = &[
    "223.5.5.5",
    "223.6.6.6",
    "119.29.29.29",
    "114.114.114.114",
    "8.8.8.8",
];

#[derive(Debug, Clone)]
struct RouteResolution {
    interface: Option<String>,
    gateway: Option<Ipv4Addr>,
}

/// 网络设备信息
#[derive(Debug, Clone)]
pub struct NetworkDevice {
    pub name: String,
    pub description: Option<String>,
    pub mac: Option<pnet::util::MacAddr>,
    pub ips: Vec<std::net::IpAddr>,
    pub is_up: bool,
    pub is_loopback: bool,
}

/// 列出所有可用的网络设备
pub fn list_network_devices() -> Vec<NetworkDevice> {
    datalink::interfaces()
        .into_iter()
        .map(|interface| NetworkDevice {
            name: interface.name.clone(),
            description: Some(interface.description.clone()),
            mac: interface.mac,
            ips: interface.ips.iter().map(|ip| ip.ip()).collect(),
            is_up: interface.is_up(),
            is_loopback: interface.is_loopback(),
        })
        .collect()
}

/// 打印网络设备列表
pub fn print_network_devices() {
    let devices = list_network_devices();

    println!(
        "\n{:<20} {:<18} {:<15} {:<8} {:<10} {:<30}",
        "设备名称", "MAC地址", "IP地址", "状态", "类型", "描述"
    );
    println!("{}", "-".repeat(100));

    for device in devices {
        let mac_str = device.mac.map_or("N/A".to_string(), |mac| mac.to_string());
        let status = if device.is_up { "UP" } else { "DOWN" };
        let device_type = if device.is_loopback {
            "LOOPBACK"
        } else {
            "ETHERNET"
        };
        let description = device.description.as_deref().unwrap_or("N/A");

        if device.ips.is_empty() {
            println!(
                "{:<20} {:<18} {:<15} {:<8} {:<10} {:<30}",
                device.name, mac_str, "N/A", status, device_type, description
            );
            continue;
        }

        for (index, ip) in device.ips.iter().enumerate() {
            if index == 0 {
                println!(
                    "{:<20} {:<18} {:<15} {:<8} {:<10} {:<30}",
                    device.name,
                    mac_str,
                    ip.to_string(),
                    status,
                    device_type,
                    description
                );
            } else {
                println!(
                    "{:<20} {:<18} {:<15} {:<8} {:<10} {:<30}",
                    "",
                    "",
                    ip.to_string(),
                    "",
                    "",
                    ""
                );
            }
        }
    }
}

/// 根据设备名称获取设备信息
pub fn get_device_by_name(device_name: &str) -> Result<EthTable, String> {
    get_device_by_name_for_dns(device_name, &[])
}

/// 根据设备名称和DNS服务器获取设备信息
pub fn get_device_by_name_for_dns(
    device_name: &str,
    dns_servers: &[String],
) -> Result<EthTable, String> {
    let probe_target = choose_probe_target(dns_servers)
        .ok_or_else(|| "未找到可用于原始发包的DNS探测目标".to_string())?;
    let route = resolve_route_to_target(probe_target)?;
    let interface = datalink::interfaces()
        .into_iter()
        .find(|iface| iface.name == device_name && !iface.is_loopback())
        .ok_or_else(|| format!("未找到指定网络接口 {}", device_name))?;
    let src_ip = first_ipv4_on_interface(&interface)
        .ok_or_else(|| format!("接口 {} 没有可用的IPv4地址", interface.name))?;
    let src_mac = interface
        .mac
        .ok_or_else(|| format!("接口 {} 缺少MAC地址", interface.name))?;

    if let Some(route_interface) = route.interface.as_ref() {
        if route_interface != &interface.name {
            return Err(format!(
                "指定接口 {} 与系统路由接口 {} 不一致,拒绝使用错误网卡",
                interface.name, route_interface
            ));
        }
    }

    let next_hop_ip = route.gateway.unwrap_or(probe_target);
    let dst_mac = resolve_next_hop_mac(src_ip, probe_target, next_hop_ip, &interface.name)?;

    Ok(EthTable {
        src_ip,
        device: interface.name,
        src_mac,
        dst_mac,
    })
}

/// 尝试获取网关MAC地址(兼容旧接口)
#[allow(dead_code)]
fn get_gateway_mac(interface_name: &str) -> Option<pnet::util::MacAddr> {
    let probe_target = choose_probe_target(&[])?;
    let route = resolve_route_to_target(probe_target).ok()?;
    let next_hop_ip = route.gateway.unwrap_or(probe_target);
    lookup_arp_cache(next_hop_ip, interface_name)
}

/// 自动检测并选择最佳网络设备
pub async fn auto_get_devices() -> Result<EthTable, String> {
    auto_get_devices_for_dns(&[]).await
}

/// 根据DNS服务器自动检测网络设备
pub async fn auto_get_devices_for_dns(dns_servers: &[String]) -> Result<EthTable, String> {
    let probe_target = choose_probe_target(dns_servers).unwrap_or_else(|| {
        Ipv4Addr::from_str(DEFAULT_DNS_PROBE_TARGETS[0]).unwrap_or(Ipv4Addr::new(223, 5, 5, 5))
    });

    resolve_best_device(probe_target)
}

fn resolve_best_device(probe_target: Ipv4Addr) -> Result<EthTable, String> {
    let (interface_name, src_ip, src_mac) = detect_egress_interface(probe_target)?;
    let route = resolve_route_to_target(probe_target)?;

    if let Some(route_interface) = route.interface.as_ref() {
        if route_interface != &interface_name {
            return Err(format!(
                "系统路由接口 {} 与本地出接口 {} 不一致",
                route_interface, interface_name
            ));
        }
    }

    let next_hop_ip = route.gateway.unwrap_or(probe_target);
    let dst_mac = resolve_next_hop_mac(src_ip, probe_target, next_hop_ip, &interface_name)?;

    println!(
        "自动检测网络设备成功: interface={} src_ip={} next_hop={} dst_mac={}",
        interface_name, src_ip, next_hop_ip, dst_mac
    );

    Ok(EthTable {
        src_ip,
        device: interface_name,
        src_mac,
        dst_mac,
    })
}

fn detect_egress_interface(
    probe_target: Ipv4Addr,
) -> Result<(String, Ipv4Addr, pnet::util::MacAddr), String> {
    let socket = UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))
        .map_err(|error| format!("绑定UDP探测socket失败: {}", error))?;
    socket
        .connect(SocketAddrV4::new(probe_target, 53))
        .map_err(|error| format!("连接探测DNS服务器失败: {}", error))?;

    let local_ip = match socket
        .local_addr()
        .map_err(|error| format!("读取本地探测地址失败: {}", error))?
        .ip()
    {
        IpAddr::V4(ip) => ip,
        IpAddr::V6(ip) => {
            return Err(format!("探测得到IPv6地址 {},当前仅支持IPv4原始发包", ip));
        }
    };

    for interface in datalink::interfaces() {
        if interface.is_loopback() {
            continue;
        }

        let has_local_ip = interface
            .ips
            .iter()
            .any(|network| network.ip() == IpAddr::V4(local_ip));
        if !has_local_ip {
            continue;
        }

        let src_mac = interface
            .mac
            .ok_or_else(|| format!("接口 {} 缺少MAC地址", interface.name))?;
        return Ok((interface.name, local_ip, src_mac));
    }

    Err(format!("未找到承载本地地址 {} 的有效网络接口", local_ip))
}

fn resolve_route_to_target(probe_target: Ipv4Addr) -> Result<RouteResolution, String> {
    let output = Command::new("route")
        .arg("-n")
        .arg("get")
        .arg(probe_target.to_string())
        .output()
        .map_err(|error| format!("执行 route -n get 失败: {}", error))?;

    if !output.status.success() {
        return Err(format!(
            "route -n get 返回失败: {}",
            String::from_utf8_lossy(&output.stderr).trim()
        ));
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    let interface = parse_route_field(&stdout, "interface");
    let gateway = parse_route_field(&stdout, "gateway")
        .and_then(|value| parse_gateway_field(&value, probe_target));

    Ok(RouteResolution { interface, gateway })
}

fn parse_route_field(output: &str, key: &str) -> Option<String> {
    for line in output.lines() {
        let trimmed = line.trim();
        let prefix = format!("{}:", key);
        if let Some(rest) = trimmed.strip_prefix(&prefix) {
            return Some(rest.trim().to_string());
        }
    }
    None
}

fn parse_gateway_field(value: &str, probe_target: Ipv4Addr) -> Option<Ipv4Addr> {
    if value.starts_with("link#") {
        return Some(probe_target);
    }

    Ipv4Addr::from_str(value).ok()
}

fn resolve_next_hop_mac(
    src_ip: Ipv4Addr,
    probe_target: Ipv4Addr,
    next_hop_ip: Ipv4Addr,
    interface_name: &str,
) -> Result<pnet::util::MacAddr, String> {
    if let Some(mac) = lookup_arp_cache(next_hop_ip, interface_name) {
        return Ok(mac);
    }

    for attempt in 0..5 {
        warm_up_neighbor_cache(src_ip, probe_target)
            .map_err(|error| format!("预热ARP缓存失败: {}", error))?;
        thread::sleep(Duration::from_millis(200 * (attempt + 1) as u64));

        if let Some(mac) = lookup_arp_cache(next_hop_ip, interface_name) {
            return Ok(mac);
        }
    }

    Err(format!(
        "无法解析下一跳 {} 在接口 {} 上的MAC地址",
        next_hop_ip, interface_name
    ))
}

fn warm_up_neighbor_cache(src_ip: Ipv4Addr, probe_target: Ipv4Addr) -> Result<(), std::io::Error> {
    let socket = UdpSocket::bind(SocketAddrV4::new(src_ip, 0))?;
    socket.set_write_timeout(Some(Duration::from_millis(500)))?;
    let _ = socket.send_to(&[0u8], SocketAddrV4::new(probe_target, 53));
    Ok(())
}

fn lookup_arp_cache(target_ip: Ipv4Addr, interface_name: &str) -> Option<pnet::util::MacAddr> {
    let output = Command::new("arp")
        .arg("-n")
        .arg(target_ip.to_string())
        .output()
        .ok()?;

    if !output.status.success() {
        return None;
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    parse_arp_cache_output(&stdout, interface_name)
}

fn parse_arp_cache_output(output: &str, interface_name: &str) -> Option<pnet::util::MacAddr> {
    for line in output.lines() {
        let trimmed = line.trim();
        if trimmed.is_empty() || trimmed.contains("no entry") || trimmed.contains("incomplete") {
            continue;
        }

        if !trimmed.contains(&format!(" on {}", interface_name)) {
            continue;
        }

        let marker = " at ";
        let start = trimmed.find(marker)?;
        let rest = &trimmed[start + marker.len()..];
        let mac_text = rest.split_whitespace().next()?;
        if mac_text == "(incomplete)" {
            continue;
        }

        if let Some(mac) = parse_mac_addr(mac_text) {
            return Some(mac);
        }
    }

    None
}

fn parse_mac_addr(value: &str) -> Option<pnet::util::MacAddr> {
    let normalized = value.trim().replace('-', ":");
    let parts: Vec<&str> = normalized.split(':').collect();
    if parts.len() != 6 {
        return None;
    }

    let mut octets = [0u8; 6];
    for (index, part) in parts.iter().enumerate() {
        octets[index] = u8::from_str_radix(part, 16).ok()?;
    }

    Some(pnet::util::MacAddr::new(
        octets[0], octets[1], octets[2], octets[3], octets[4], octets[5],
    ))
}

fn choose_probe_target(dns_servers: &[String]) -> Option<Ipv4Addr> {
    for server in dns_servers {
        if let Ok(ip) = Ipv4Addr::from_str(server.trim()) {
            return Some(ip);
        }
    }

    DEFAULT_DNS_PROBE_TARGETS
        .iter()
        .find_map(|value| Ipv4Addr::from_str(value).ok())
}

fn first_ipv4_on_interface(interface: &datalink::NetworkInterface) -> Option<Ipv4Addr> {
    interface.ips.iter().find_map(|network| match network.ip() {
        IpAddr::V4(ip) => Some(ip),
        IpAddr::V6(_) => None,
    })
}