824 lines
29 KiB
Rust
Executable File
824 lines
29 KiB
Rust
Executable File
use etherparse::{Ethernet2Header};
|
||
use sdlan_sn_rs::config::SDLAN_DEFAULT_TTL;
|
||
use sdlan_sn_rs::utils::{
|
||
aes_encrypt, ip_to_string, is_ipv6_multicast, net_bit_len_to_mask,
|
||
SDLanError,
|
||
};
|
||
use std::ffi::CStr;
|
||
use std::ffi::{c_char, c_int};
|
||
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::{BufRead, BufReader, Read, Write};
|
||
use std::os::fd::AsRawFd;
|
||
use std::process::Command;
|
||
|
||
use tracing::{debug, error, info};
|
||
|
||
use crate::get_edge;
|
||
use crate::network::send_packet_to_net;
|
||
use crate::pb::{encode_to_udp_message, SdlData};
|
||
use crate::tcp::PacketType;
|
||
|
||
use super::device::{DeviceConfig, Mode};
|
||
use super::TunTapPacketHandler;
|
||
|
||
const RESOLV_FILE: &'static str = "/etc/resolv.conf";
|
||
const RESOLV_FILE_BACKUP: &'static str = "/etc/resolv.conf.punchnet.bak";
|
||
use crate::network::DNS_IP;
|
||
|
||
// #[link(name = "tuntap", kind="static")]
|
||
#[link(name = "tuntap")]
|
||
extern "C" {
|
||
fn tuntap_setup(fd: c_int, name: *mut u8, mode: c_int, packet_info: c_int) -> c_int;
|
||
}
|
||
|
||
#[allow(unused)]
|
||
pub struct Iface {
|
||
fd: std::fs::File,
|
||
mode: Mode,
|
||
name: String,
|
||
has_resolvectl: bool,
|
||
}
|
||
|
||
pub fn new_iface(tunname: &str, mode: Mode) -> Iface {
|
||
match Iface::without_packet_info(tunname, mode) {
|
||
Err(e) => {
|
||
panic!("failed to create tun: {}", e.as_str());
|
||
}
|
||
Ok(iface) => iface,
|
||
}
|
||
}
|
||
|
||
impl Iface {
|
||
#[allow(unused)]
|
||
pub fn with_packet_info(ifname: &str, mode: Mode) -> Result<Self> {
|
||
Iface::open_tun(ifname, mode, true)
|
||
}
|
||
|
||
pub fn without_packet_info(ifname: &str, mode: Mode) -> Result<Self> {
|
||
Iface::open_tun(ifname, mode, false)
|
||
}
|
||
|
||
fn open_tun(ifname: &str, mode: Mode, need_packet_info: bool) -> Result<Self> {
|
||
let fs = match OpenOptions::new()
|
||
.read(true)
|
||
.write(true)
|
||
.open("/dev/net/tun")
|
||
{
|
||
Ok(fs) => fs,
|
||
Err(e) => panic!("failed to open tun: {}", e),
|
||
};
|
||
let mut name_ptr: *mut u8 = null_mut();
|
||
let mut success = false;
|
||
let mut _name = Vec::new();
|
||
for i in 0..16 {
|
||
_name.clear();
|
||
_name.extend_from_slice(ifname.as_bytes());
|
||
_name.extend_from_slice(i.to_string().as_bytes());
|
||
_name.extend_from_slice(&[0; 33]);
|
||
|
||
name_ptr = _name.as_mut_ptr();
|
||
|
||
let result = unsafe {
|
||
tuntap_setup(
|
||
fs.as_raw_fd(),
|
||
name_ptr,
|
||
mode as c_int,
|
||
if need_packet_info { 1 } else { 0 },
|
||
)
|
||
};
|
||
if result >= 0 {
|
||
success = true;
|
||
break;
|
||
}
|
||
}
|
||
if success {
|
||
let name = unsafe {
|
||
CStr::from_ptr(name_ptr as *const c_char)
|
||
.to_string_lossy()
|
||
.into_owned()
|
||
};
|
||
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, 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 mut default_gw = (ip & mask) + 1;
|
||
if default_gw == ip {
|
||
default_gw += 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)
|
||
.arg("netmask")
|
||
.arg(&netbit)
|
||
.arg("hw")
|
||
.arg("ether")
|
||
.arg(format!(
|
||
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
|
||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
|
||
))
|
||
.arg("mtu")
|
||
.arg(format!("{}", device_config.mtu))
|
||
.arg("up")
|
||
.output();
|
||
match res {
|
||
Ok(_) => {
|
||
debug!("ifconfig ok");
|
||
}
|
||
Err(e) => {
|
||
error!("failed to run ifconfig: {}", e.to_string());
|
||
}
|
||
}
|
||
|
||
// TODO: set dns should be opened
|
||
/*
|
||
if let Err(e) = set_dns(self, &self.name, network_domain, &ip_to_string(&default_gw)) {
|
||
error!("failed to set dns: {}", e.as_str());
|
||
}
|
||
*/
|
||
} else {
|
||
info!("set tun device");
|
||
let res = Command::new("ifconfig")
|
||
.arg(&self.name)
|
||
.arg(ip)
|
||
.arg("netmask")
|
||
.arg(&netbit)
|
||
.arg("mtu")
|
||
.arg(format!("{}", device_config.mtu))
|
||
.arg("up")
|
||
.output();
|
||
match res {
|
||
Ok(_) => {
|
||
debug!("ifconfig ok");
|
||
}
|
||
Err(e) => {
|
||
error!("failed to run ifconfig: {}", e.to_string());
|
||
}
|
||
}
|
||
|
||
if let Err(e) = set_dns(self, &self.name, network_domain, &ip_to_string(&default_gw)) {
|
||
error!("failed to set dns: {}", e.as_str());
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn recv(&self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||
(&self.fd).read(buf)
|
||
}
|
||
|
||
pub fn send(&self, content: &[u8]) -> std::io::Result<usize> {
|
||
(&self.fd).write(content)
|
||
}
|
||
}
|
||
|
||
#[cfg(not(feature = "tun"))]
|
||
impl TunTapPacketHandler for Iface {
|
||
async fn handle_packet_from_net(&self, data: &[u8], _: &[u8]) -> std::io::Result<()> {
|
||
// debug!("in tap mode, got data: {:?}", data);
|
||
match self.send(data) {
|
||
Err(e) => {
|
||
error!("failed to write to tap: {}", e.to_string());
|
||
return Err(e);
|
||
}
|
||
Ok(_) => return Ok(()),
|
||
}
|
||
}
|
||
async fn handle_packet_from_device(
|
||
&self,
|
||
data: Vec<u8>,
|
||
encrypt_key: &[u8],
|
||
) -> std::io::Result<()> {
|
||
use etherparse::PacketHeaders;
|
||
|
||
debug!("in tap mode2");
|
||
let edge = get_edge();
|
||
|
||
let Ok(headers) = PacketHeaders::from_ethernet_slice(&data) else {
|
||
error!("failed to parse packet");
|
||
return Ok(());
|
||
};
|
||
|
||
|
||
if let Some(eth) = headers.link {
|
||
if let Some(hdr) = eth.ethernet2() {
|
||
use bytes::Bytes;
|
||
|
||
|
||
if let Some(ip) = headers.net {
|
||
match ip {
|
||
etherparse::NetHeaders::Ipv4(ipv4, _) => {
|
||
use etherparse::ip_number::{self, ICMP};
|
||
|
||
|
||
if let Some(transport) = headers.transport {
|
||
if let Some(tcp) = transport.tcp() {
|
||
// is tcp
|
||
}
|
||
}
|
||
|
||
if u32::from_be_bytes(ipv4.destination) == DNS_IP {
|
||
// should send to dns
|
||
if let Err(e) = edge.udp_sock_for_dns.send_to(&data[14..], format!("{}:15353", edge.server_ip)).await {
|
||
error!("failed to send request to 15353: {}", e);
|
||
}
|
||
// edge.udp_sock_for_dns.send_to()
|
||
return Ok(())
|
||
}
|
||
}
|
||
_other => {
|
||
// just ignore
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
let target = hdr.destination;
|
||
if is_ipv6_multicast(&target) {
|
||
return Ok(());
|
||
}
|
||
let size = data.len();
|
||
|
||
let Ok(encrypted) = aes_encrypt(encrypt_key, &data) else {
|
||
error!("failed to encrypt packet request");
|
||
return Ok(());
|
||
};
|
||
let data_bytes = Bytes::from(encrypted);
|
||
let data = SdlData {
|
||
is_p2p: true,
|
||
network_id: edge.network_id.load(Ordering::Relaxed),
|
||
ttl: SDLAN_DEFAULT_TTL as u32,
|
||
src_mac: Vec::from(edge.device_config.get_mac()),
|
||
dst_mac: Vec::from(target),
|
||
data: data_bytes,
|
||
identity_id: edge.identity_id.load(),
|
||
session_token: edge.session_token.get(),
|
||
};
|
||
let msg = encode_to_udp_message(Some(data), PacketType::Data as u8).unwrap();
|
||
|
||
send_packet_to_net(edge, target, &msg, size as u64).await;
|
||
} else {
|
||
println!("erro 2");
|
||
}
|
||
} else {
|
||
println!("erro 1");
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
#[cfg(feature = "tun")]
|
||
impl TunTapPacketHandler for Iface {
|
||
async fn handle_packet_from_net(&self, data: &[u8], key: &[u8]) -> std::io::Result<()> {
|
||
debug!("in tun mode");
|
||
|
||
// got layer 2 frame
|
||
match Ethernet2Header::from_slice(&data) {
|
||
Ok((hdr, rest)) => {
|
||
use etherparse::ether_type::ARP;
|
||
use sdlan_sn_rs::utils::is_multi_broadcast;
|
||
|
||
if rest.len() < 4 {
|
||
error!("payload length error");
|
||
return Ok(());
|
||
}
|
||
// let crc_code = &rest[(rest.len() - 4)..rest.len()];
|
||
// let rest = &rest[..(rest.len() - 4)];
|
||
|
||
// let crc_hash: crc::Crc<u32> = crc::Crc::<u32>::new(&crc::CRC_32_CKSUM);
|
||
// let ck = caculate_crc(&data[..(data.len() - 4)]);
|
||
// let sent_ck = u32::from_be_bytes(crc_code.try_into().unwrap());
|
||
// debug!("ck = {}, sent_ck = {}", ck, sent_ck);
|
||
|
||
debug!("ip size is {}", rest.len());
|
||
let edge = get_edge();
|
||
let self_mac = edge.device_config.get_mac();
|
||
|
||
if hdr.destination != self_mac && !is_multi_broadcast(&hdr.destination) {
|
||
use sdlan_sn_rs::utils::mac_to_string;
|
||
|
||
error!(
|
||
"packet to [{:?}] not direct to us",
|
||
mac_to_string(&hdr.destination)
|
||
);
|
||
return Ok(());
|
||
}
|
||
|
||
if hdr.ether_type == ARP {
|
||
use crate::network::ArpHdr;
|
||
|
||
let mut arp = ArpHdr::from_slice(&data);
|
||
let self_ip = edge.device_config.get_ip();
|
||
|
||
// println!("self_ip: {:?}", self_ip.to_be_bytes());
|
||
let from_ip = ((arp.sipaddr[0] as u32) << 16) + arp.sipaddr[1] as u32;
|
||
// println!("from_ip: {:?}", from_ip.to_be_bytes());
|
||
let dest_ip = ((arp.dipaddr[0] as u32) << 16) + arp.dipaddr[1] as u32;
|
||
// println!("dest_ip: {:?}", dest_ip.to_be_bytes());
|
||
|
||
match arp.opcode {
|
||
ARP_REQUEST => {
|
||
// handle ARP REQUEST
|
||
debug!("got ARP REQUEST");
|
||
if arp.ethhdr.dest != [0xff; 6] {
|
||
debug!("ARP REQUEST not broadcast");
|
||
return Ok(());
|
||
}
|
||
if dest_ip == self_ip {
|
||
use bytes::Bytes;
|
||
use sdlan_sn_rs::utils::mac_to_string;
|
||
|
||
use crate::network::{ARP_REPLY, ArpRequestInfo, send_arp_request};
|
||
|
||
send_arp_request(ArpRequestInfo::Set {
|
||
ip: from_ip,
|
||
mac: arp.shwaddr,
|
||
})
|
||
.await;
|
||
|
||
// target to us
|
||
arp.opcode = ARP_REPLY;
|
||
arp.dhwaddr = arp.shwaddr;
|
||
arp.shwaddr = self_mac;
|
||
arp.ethhdr.src = self_mac;
|
||
arp.ethhdr.dest = arp.dhwaddr;
|
||
|
||
arp.dipaddr = arp.sipaddr;
|
||
|
||
arp.sipaddr =
|
||
[((self_ip >> 16) & 0xffff) as u16, (self_ip & 0xffff) as u16];
|
||
|
||
let data = arp.marshal_to_bytes();
|
||
let Ok(encrypted) = aes_encrypt(key, &data) else {
|
||
error!("failed to encrypt arp reply");
|
||
return Ok(());
|
||
};
|
||
|
||
let data_bytes = Bytes::from(encrypted);
|
||
|
||
let data = SdlData {
|
||
is_p2p: true,
|
||
ttl: 2,
|
||
network_id: edge.network_id.load(Ordering::Relaxed),
|
||
src_mac: Vec::from(self_mac),
|
||
dst_mac: Vec::from(arp.dhwaddr),
|
||
data: data_bytes,
|
||
session_token: edge.session_token.get(),
|
||
identity_id: edge.identity_id.load(),
|
||
};
|
||
|
||
let v = encode_to_udp_message(Some(data), PacketType::Data as u8)
|
||
.unwrap();
|
||
debug!(
|
||
"xxxx send arp reply to [{}], selfmac=[{}]",
|
||
mac_to_string(&arp.dhwaddr),
|
||
mac_to_string(&self_mac)
|
||
);
|
||
send_packet_to_net(edge, arp.dhwaddr, &v, 0).await;
|
||
// send_to_sock(edge, &v, from_sock);
|
||
// edge.sock.send(v).await;
|
||
}
|
||
}
|
||
ARP_REPLY => {
|
||
|
||
debug!("mac {:?} is at {:?}", arp.shwaddr, from_ip.to_be_bytes());
|
||
if dest_ip == self_ip {
|
||
use crate::network::{ArpRequestInfo, arp_arrived, send_arp_request};
|
||
|
||
send_arp_request(ArpRequestInfo::Set {
|
||
ip: from_ip,
|
||
mac: arp.shwaddr,
|
||
})
|
||
.await;
|
||
arp_arrived(from_ip, arp.shwaddr).await;
|
||
}
|
||
}
|
||
_other => {
|
||
error!("unknown arp type info");
|
||
}
|
||
}
|
||
} else {
|
||
use etherparse::IpHeaders;
|
||
|
||
match IpHeaders::from_slice(rest) {
|
||
Ok((iphdr, _)) => {
|
||
let Some(ipv4) = iphdr.ipv4() else {
|
||
error!("not ipv4, dropping");
|
||
return Ok(());
|
||
};
|
||
let ip = u32::from_be_bytes(ipv4.0.source);
|
||
let mac = hdr.source;
|
||
if !is_multi_broadcast(&mac) {
|
||
use crate::network::{ArpRequestInfo, send_arp_request};
|
||
|
||
send_arp_request(ArpRequestInfo::Set { ip, mac }).await;
|
||
}
|
||
}
|
||
Err(_) => {
|
||
error!("failed to parse ip header, dropping");
|
||
return Ok(());
|
||
}
|
||
}
|
||
|
||
// println!("got ip packet");
|
||
// println!("got data: {:?}", rest);
|
||
match edge.device.send(rest) {
|
||
Ok(size) => {
|
||
debug!("send to tun {} bytes", size);
|
||
}
|
||
Err(e) => {
|
||
error!("failed to send to device: {}", e.to_string());
|
||
}
|
||
}
|
||
// edge.tun.send_data_to_tun(Vec::from(hdr.1)).await;
|
||
}
|
||
}
|
||
Err(e) => {
|
||
error!("failed to parse tun packet: {}", e);
|
||
return Ok(());
|
||
}
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
async fn handle_packet_from_device(
|
||
&self,
|
||
data: Vec<u8>,
|
||
encrypt_key: &[u8],
|
||
) -> std::io::Result<()> {
|
||
use etherparse::IpHeaders;
|
||
|
||
let eee = get_edge();
|
||
|
||
let src_mac = eee.device_config.get_mac();
|
||
|
||
match IpHeaders::from_slice(&data) {
|
||
Ok((iphdr, _payload)) => {
|
||
use crate::network::{ArpRequestInfo, ArpResponse, send_arp_request};
|
||
|
||
let Some(ipv4hdr) = iphdr.ipv4() else {
|
||
debug!("ipv6 packet ignored");
|
||
return Ok(());
|
||
};
|
||
let dstip = u32::from_be_bytes(ipv4hdr.0.destination);
|
||
debug!("packet dst ip: {:?}", ipv4hdr.0.destination);
|
||
let src = u32::from_be_bytes(ipv4hdr.0.source);
|
||
debug!("packet src ip: {:?}", ipv4hdr.0.source);
|
||
// packet should be sent to dev
|
||
debug!("got {} bytes from tun", data.len());
|
||
if (!eee.config.allow_routing) && (src != eee.device_config.get_ip()) {
|
||
info!("dropping routed packet");
|
||
return Ok(());
|
||
}
|
||
if !eee.is_authorized() {
|
||
debug!("drop tun packet due to not authed");
|
||
return Ok(());
|
||
}
|
||
if dstip == DNS_IP {
|
||
// should do the dns request
|
||
// println!("request for dns");
|
||
let addr = format!("{}:15353", eee.server_ip);
|
||
// println!("send dns to {}", addr);
|
||
if let Err(e) = eee.udp_sock_for_dns.send_to(&data, &addr).await {
|
||
error!("failed to send request to 15353: {}", e);
|
||
}
|
||
return Ok(());
|
||
}
|
||
match send_arp_request(ArpRequestInfo::Lookup { ip: dstip }).await {
|
||
ArpResponse::LookupResp {
|
||
mac,
|
||
ip,
|
||
do_arp_request,
|
||
} => {
|
||
use bytes::Bytes;
|
||
|
||
use crate::utils::caculate_crc;
|
||
|
||
if do_arp_request {
|
||
use sdlan_sn_rs::utils::BROADCAST_MAC;
|
||
|
||
use crate::network::{add_to_arp_wait_list, generate_arp_request};
|
||
|
||
add_to_arp_wait_list(dstip, data);
|
||
debug!(
|
||
"find ip: {:?} => {:?}",
|
||
src.to_be_bytes(),
|
||
dstip.to_be_bytes()
|
||
);
|
||
let arp_msg =
|
||
generate_arp_request(src_mac, ip, eee.device_config.get_ip());
|
||
let Ok(encrypted) = aes_encrypt(&encrypt_key, &arp_msg) else {
|
||
error!("failed to encrypt arp request");
|
||
return Ok(());
|
||
};
|
||
// println!("arp_msg: {:?}", arp_msg);
|
||
let data = SdlData {
|
||
network_id: eee.network_id.load(Ordering::Relaxed),
|
||
src_mac: Vec::from(src_mac),
|
||
dst_mac: Vec::from([0xff; 6]),
|
||
is_p2p: true,
|
||
ttl: SDLAN_DEFAULT_TTL as u32,
|
||
data: Bytes::from(encrypted),
|
||
session_token: eee.session_token.get(),
|
||
identity_id: eee.identity_id.load(),
|
||
};
|
||
let data =
|
||
encode_to_udp_message(Some(data), PacketType::Data as u8).unwrap();
|
||
debug!("sending arp");
|
||
// let data = marshal_message(&data);
|
||
send_packet_to_net(eee, BROADCAST_MAC, &data, arp_msg.len() as u64)
|
||
.await;
|
||
// edge.sock.send(data).await;
|
||
// println!("should send arp");
|
||
return Ok(());
|
||
}
|
||
|
||
// prepend the ether header
|
||
let mut etherheader = Ethernet2Header::default();
|
||
etherheader.destination = mac;
|
||
etherheader.ether_type = etherparse::EtherType::IPV4;
|
||
etherheader.source = src_mac;
|
||
let mut packet = Vec::with_capacity(14 + data.len() + 4);
|
||
packet.extend_from_slice(ðerheader.to_bytes()[..]);
|
||
packet.extend_from_slice(&data);
|
||
let crc = caculate_crc(&packet);
|
||
packet.extend_from_slice(&crc.to_be_bytes());
|
||
|
||
let pkt_size = packet.len();
|
||
// println!("sending data with mac");
|
||
|
||
let Ok(encrypted) = aes_encrypt(&encrypt_key, &packet) else {
|
||
error!("failed to encrypt packet request");
|
||
return Ok(());
|
||
};
|
||
let data = SdlData {
|
||
is_p2p: true,
|
||
network_id: eee.network_id.load(Ordering::Relaxed),
|
||
ttl: SDLAN_DEFAULT_TTL as u32,
|
||
src_mac: Vec::from(src_mac),
|
||
dst_mac: Vec::from(mac),
|
||
data: Bytes::from(encrypted),
|
||
session_token: eee.session_token.get(),
|
||
identity_id: eee.identity_id.load(),
|
||
};
|
||
let msg =
|
||
encode_to_udp_message(Some(data), PacketType::Data as u8).unwrap();
|
||
let size = msg.len();
|
||
send_packet_to_net(eee, mac, &msg, pkt_size as u64).await;
|
||
// let dstip = u32::from_be_bytes(ipv4hdr.0.destination);
|
||
}
|
||
_ => {}
|
||
}
|
||
}
|
||
Err(e) => {
|
||
error!("failed to parse ip packet: {}", e.to_string());
|
||
}
|
||
}
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
pub fn get_install_channel() -> String {
|
||
"linux".to_owned()
|
||
}
|
||
|
||
fn check_has_resolvectl() -> bool {
|
||
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()], network_domain, 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], search_domain: &str, 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;
|
||
let mut search_added = 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 等)
|
||
if trimmed.starts_with("search ") {
|
||
lines.push(format!("{} {}", trimmed, search_domain));
|
||
search_added = true;
|
||
} else {
|
||
lines.push(line);
|
||
}
|
||
}
|
||
}
|
||
if !search_added {
|
||
lines.push(format!("search {}", search_domain));
|
||
}
|
||
|
||
// 原子写入:先写临时文件
|
||
let tmp_dir = Path::new("/etc");
|
||
let tmp_path = tmp_dir.join("resolv.conf.tmp");
|
||
let mut tmp_file = fs::File::create(&tmp_path)?;
|
||
for l in &lines {
|
||
writeln!(tmp_file, "{}", l)?;
|
||
}
|
||
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(())
|
||
} |