in linux, use resolvectl or /etc/resolv.conf for dns resolving

This commit is contained in:
alex 2025-12-23 22:23:53 +08:00
parent fad345becb
commit b343124821
5 changed files with 272 additions and 28 deletions

View File

@ -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");
}
started = !started;
*/
Err(err) => {
eprintln!("failed to listen for shutdown signal: {}", err);
}
}
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;
// */
// }
}

View File

@ -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};

View File

@ -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() {

View File

@ -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;

View File

@ -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`: 是否保留原有的 nameservertrue = 追加到新列表后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(())
}