diff --git a/src/bin/punchnet/main.rs b/src/bin/punchnet/main.rs index a0b1a50..dab3d83 100755 --- a/src/bin/punchnet/main.rs +++ b/src/bin/punchnet/main.rs @@ -1,6 +1,7 @@ use punchnet::get_base_dir; use punchnet::get_edge; use punchnet::get_my_networks; +use punchnet::restore_dns; use punchnet::run_sdlan; use punchnet::set_base_dir; use punchnet::CommandLine; @@ -16,19 +17,12 @@ use structopt::StructOpt; #[tokio::main] async fn main() { - // let _guard = rolling_file::new_log( - // "./.output", - // 7, - // rolling_file::PeriodGap::Daily, - // tracing::Level::DEBUG, - // true, - // true, - // ); set_base_dir("/usr/local/punchnet"); let _guard = log::init_log(&format!("{}/.output", get_base_dir())); let cmd = CommandLineInput::from_args(); + // println!("port is {}", cmd.port); let (tx, rx) = std::sync::mpsc::channel(); @@ -116,16 +110,29 @@ async fn main() { let mut started = true; */ - loop { - tokio::time::sleep(Duration::from_secs(10)).await; - /* - let sig = stream.recv().await; - if started { - edge.stop().await; - } else { - edge.start("0".to_owned()).await; + + match tokio::signal::ctrl_c().await { + Ok(()) => { + restore_dns(); + println!("restoreing dns"); + } + Err(err) => { + eprintln!("failed to listen for shutdown signal: {}", err); } - started = !started; - */ } + + std::process::exit(0); + + // loop { + // tokio::time::sleep(Duration::from_secs(10)).await; + // /* + // let sig = stream.recv().await; + // if started { + // edge.stop().await; + // } else { + // edge.start("0".to_owned()).await; + // } + // started = !started; + // */ + // } } diff --git a/src/lib.rs b/src/lib.rs index ff89854..a082f47 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ use std::net::{SocketAddr, ToSocketAddrs}; pub use network::get_edge; pub use network::get_install_channel; -use network::{async_main, init_arp, init_edge, NodeConfig}; +pub use network::{async_main, init_arp, init_edge, NodeConfig, restore_dns}; use serde::{Deserialize, Serialize}; use tokio::net::UdpSocket; use tokio::sync::mpsc::{channel, Sender}; diff --git a/src/network/async_main.rs b/src/network/async_main.rs index 79237e5..1d0d7f1 100755 --- a/src/network/async_main.rs +++ b/src/network/async_main.rs @@ -83,7 +83,7 @@ async fn handle_tcp_message(msg: SdlanTcp) { .ip .net_bit_len .store(dev.net_bit_len as u8, Ordering::Relaxed); - edge.device.reload_config(&edge.device_config); + edge.device.reload_config(&edge.device_config, &dev.network_domain); edge.network_id.store(dev.network_id, Ordering::Relaxed); edge.set_authorized(true, aes); @@ -578,8 +578,9 @@ async fn loop_tap(eee: &'static Node, cancel: CancellationToken) { packet.push(0x08); packet.push(0x00); packet.extend_from_slice(&reply); - eee.device.handle_packet_from_net(&packet, &Vec::new()).await; - println!("got 15353's reply"); + if let Err(_e) = eee.device.handle_packet_from_net(&packet, &Vec::new()).await { + error!("failed to write dns packet to device"); + } } buf = rx.recv() => { if buf.is_none() { diff --git a/src/network/mod.rs b/src/network/mod.rs index 99ca253..5cd3e33 100755 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -21,6 +21,6 @@ pub use tuntap::*; #[cfg_attr(target_os = "linux", path = "tun_linux.rs")] #[cfg_attr(target_os = "windows", path = "tun_win.rs")] mod tun; -pub use tun::get_install_channel; +pub use tun::{get_install_channel, restore_dns}; mod device; diff --git a/src/network/tun_linux.rs b/src/network/tun_linux.rs index 327e6f2..fb6b66a 100755 --- a/src/network/tun_linux.rs +++ b/src/network/tun_linux.rs @@ -6,12 +6,14 @@ use sdlan_sn_rs::utils::{ }; use std::ffi::CStr; use std::ffi::{c_char, c_int}; -use std::fs::OpenOptions; +use std::fs::{self, OpenOptions}; +use std::os::unix::fs::{MetadataExt, PermissionsExt}; +use std::path::Path; use std::ptr::null_mut; use std::sync::atomic::Ordering; use sdlan_sn_rs::utils::Result; -use std::io::{Read, Write}; +use std::io::{BufRead, BufReader, Read, Write}; use std::os::fd::AsRawFd; use std::process::Command; @@ -27,6 +29,9 @@ use super::TunTapPacketHandler; const DNS_IP: u32 = (100<<24) + (100<<16) + (100<<8) + 100; +const RESOLV_FILE: &'static str = "/etc/resolv.conf"; +const RESOLV_FILE_BACKUP: &'static str = "/etc/resolv.conf.punchnet.bak"; + // #[link(name = "tuntap", kind="static")] #[link(name = "tuntap")] extern "C" { @@ -38,6 +43,7 @@ pub struct Iface { fd: std::fs::File, mode: Mode, name: String, + has_resolvectl: bool, } pub fn new_iface(tunname: &str, mode: Mode) -> Iface { @@ -72,7 +78,7 @@ impl Iface { let mut success = false; let mut _name = Vec::new(); for i in 0..16 { - _name = Vec::new(); + _name.clear(); _name.extend_from_slice(ifname.as_bytes()); _name.extend_from_slice(i.to_string().as_bytes()); _name.extend_from_slice(&[0; 33]); @@ -98,25 +104,30 @@ impl Iface { .to_string_lossy() .into_owned() }; - Ok(Iface { fd: fs, mode, name }) + let has_resolvectl = check_has_resolvectl(); + + Ok(Iface { fd: fs, mode, name, has_resolvectl }) } else { Err(SDLanError::NormalError("failed to setup tun")) } } - pub fn reload_config(&self, device_config: &DeviceConfig) { + pub fn reload_config(&self, device_config: &DeviceConfig, network_domain: &str) { let netbit = device_config.get_net_bit(); let ip = device_config.get_ip(); if netbit == 0 || ip == 0 { error!("reload config's ip is 0"); return; } + let mask = net_bit_len_to_mask(netbit); + let default_gw = (ip & mask) + 1; let ip = ip_to_string(&ip); let netbit = ip_to_string(&net_bit_len_to_mask(netbit)); if cfg!(not(feature = "tun")) { info!("set tap device"); let mac = device_config.get_mac(); + let res = Command::new("ifconfig") .arg(&self.name) .arg(ip) @@ -140,6 +151,13 @@ impl Iface { error!("failed to run ifconfig: {}", e.to_string()); } } + + if let Err(e) = set_dns(self, &self.name, network_domain, &ip_to_string(&default_gw)) { + println!("failed to set dns: {}", e.as_str()); + error!("failed to set dns: {}", e.as_str()); + } else { + println!("set dns ok"); + } } else { info!("set tun device"); let res = Command::new("ifconfig") @@ -159,6 +177,13 @@ impl Iface { error!("failed to run ifconfig: {}", e.to_string()); } } + + if let Err(e) = set_dns(self, &self.name, network_domain, &ip_to_string(&default_gw)) { + println!("failed to set dns: {}", e.as_str()); + error!("failed to set dns: {}", e.as_str()); + } else { + println!("set dns ok"); + } } } @@ -519,3 +544,214 @@ impl TunTapPacketHandler for Iface { pub fn get_install_channel() -> String { "linux".to_owned() } + +fn check_has_resolvectl() -> bool { + return false; + let res = Command::new("resolvectl") + .arg("status") + .output(); + if let Ok(_) = res { + true + } else { + false + } +} + +fn add_dns_route(gw: &str) -> Result<()>{ + Command::new("route") + .arg("add") + .arg("-host") + .arg("100.100.100.100") + .arg("gw") + .arg(gw) + .output()?; + + Ok(()) +} + +fn add_resolvectl( + name: &str, + network_domain: &str, +) -> Result<()>{ + Command::new("resolvectl") + .arg("dns") + .arg(name) + .arg("100.100.100.100") + .output()?; + + Command::new("resolvectl") + .arg("domain") + .arg(name) + .arg(format!("~{}", network_domain)) + .output()?; + + Ok(()) +} + +fn set_dns( + iface: &Iface, + name: &str, + network_domain: &str, + gw: &str +) -> Result<()> { + if iface.has_resolvectl { + add_resolvectl(name, network_domain)?; + } else { + backup_resolv_conf()?; + modify_resolv_conf(&vec!["100.100.100.100".to_owned()], true)?; + } + add_dns_route(gw)?; + Ok(()) +} + +pub fn restore_dns() -> Result<()> { + let eee = get_edge(); + if !eee.device.has_resolvectl { + // should restore /etc/resolv.conf + restore_resolv_conf()?; + } + + Ok(()) +} + +/// 修改 /etc/resolv.conf 中的 nameserver 条目 +/// +/// - `new_nameservers`: 新的 nameserver 列表(IPv4/IPv6 字符串) +/// - `keep_other_ns`: 是否保留原有的 nameserver(true = 追加到新列表后,false = 完全替换) +pub fn modify_resolv_conf(new_nameservers: &[String], keep_other_ns: bool) -> Result<()> { + let path = Path::new(RESOLV_FILE); + if !path.exists() { + return Err(SDLanError::IOError(format!("{} does not exists", RESOLV_FILE))); + } + + // 读取原文件权限和元数据 + let metadata = fs::metadata(path)?; + let perms = fs::Permissions::from_mode(metadata.mode()); + let uid = metadata.uid(); + let gid = metadata.gid(); + + // 读取所有行 + let file = fs::File::open(path)?; + let reader = BufReader::new(file); + let mut lines = Vec::new(); + + let mut inserted = false; + let mut encounted_nameserver = false; + + for line in reader.lines() { + let line = line?; + let trimmed = line.trim(); + if trimmed.starts_with("nameserver ") { + // 提取 IP + encounted_nameserver = true; + if keep_other_ns { + lines.push(line); + } + // 如果不保留原有 nameserver,就不加入 lines + } else { + if encounted_nameserver { + if !inserted { + inserted = true; + for new in new_nameservers { + lines.push(format!("nameserver {}", new.clone())); + } + } + } + // 保留非 nameserver 行(注释、search、options 等) + lines.push(line); + } + } + + // 原子写入:先写临时文件 + let tmp_dir = Path::new("/etc"); + let tmp_path = tmp_dir.join("resolv.conf.tmp"); + let mut tmp_file = fs::File::create(&tmp_path)?; + println!("new resolv.conf: "); + for l in &lines { + writeln!(tmp_file, "{}", l)?; + println!("{}", l); + } + println!(""); + tmp_file.flush()?; + + // 设置权限 + fs::set_permissions(&tmp_path, perms)?; + #[cfg(unix)] + { + use std::os::unix::fs::chown; + chown(&tmp_path, Some(uid), Some(gid))?; + } + + // 原子替换 + fs::rename(&tmp_path, path)?; + + Ok(()) +} + +/// 备份 /etc/resolv.conf 到指定路径 +fn backup_resolv_conf() -> Result<()> { + let src = Path::new(RESOLV_FILE); + let dst = Path::new(RESOLV_FILE_BACKUP); + + if !src.exists() { + return Err(SDLanError::IOError(format!("{} does not exists", RESOLV_FILE))); + // anyhow::bail!("Source /etc/resolv.conf does not exist"); + } + + // 如果目标已存在,可选择覆盖或跳过(这里覆盖) + if dst.exists() { + fs::remove_file(dst)?; + } + + fs::copy(src, dst)?; + + // 保留原权限 + let metadata = fs::metadata(src)?; + let permissions = fs::Permissions::from_mode(metadata.mode()); + fs::set_permissions(dst, permissions)?; + + // 保留所有者(需要 root 权限,否则会失败) + #[cfg(unix)] + { + use std::os::unix::fs::chown; + let uid = metadata.uid(); + let gid = metadata.gid(); + chown(dst, Some(uid), Some(gid))?; + } + + Ok(()) +} + +/// 从备份路径恢复 /etc/resolv.conf +fn restore_resolv_conf() -> Result<()> { + let src = Path::new(RESOLV_FILE_BACKUP); + let dst = Path::new(RESOLV_FILE); + + if !src.exists() { + return Err(SDLanError::IOError(format!("{} does not exists", RESOLV_FILE_BACKUP))); + } + + // 如果目标是符号链接,先删除链接再复制(避免写入到链接指向位置) + if dst.is_symlink() { + fs::remove_file(dst)?; + } else if dst.exists() { + fs::remove_file(dst)?; + } + + fs::copy(src, dst)?; + + // 保留备份文件的权限 + let metadata = fs::metadata(src)?; + let permissions = fs::Permissions::from_mode(metadata.mode()); + fs::set_permissions(dst, permissions)?; + + #[cfg(unix)] + { + use std::os::unix::fs::chown; + let uid = metadata.uid(); + let gid = metadata.gid(); + chown(dst, Some(uid), Some(gid))?; + } + + Ok(()) +} \ No newline at end of file