fix machine uid and easytier-web panic (#2215)

1. fix(web-client): persist and migrate machine id
2. fix panic when easytier-web session receive malformat packet
This commit is contained in:
KKRainbow
2026-05-07 00:57:42 +08:00
committed by GitHub
parent 4342c8d7a2
commit baeee40b79
13 changed files with 725 additions and 100 deletions
+7
View File
@@ -9,6 +9,7 @@ use crate::{
pub struct Controller {
token: String,
machine_id: uuid::Uuid,
hostname: String,
device_os: DeviceOsInfo,
manager: Arc<NetworkInstanceManager>,
@@ -18,6 +19,7 @@ pub struct Controller {
impl Controller {
pub fn new(
token: String,
machine_id: uuid::Uuid,
hostname: String,
device_os: DeviceOsInfo,
manager: Arc<NetworkInstanceManager>,
@@ -25,6 +27,7 @@ impl Controller {
) -> Self {
Controller {
token,
machine_id,
hostname,
device_os,
manager,
@@ -44,6 +47,10 @@ impl Controller {
self.hostname.clone()
}
pub fn machine_id(&self) -> uuid::Uuid {
self.machine_id
}
pub fn device_os(&self) -> DeviceOsInfo {
self.device_os.clone()
}
+19 -6
View File
@@ -2,11 +2,12 @@ use std::sync::Arc;
use crate::{
common::{
MachineIdOptions,
config::TomlConfigLoader,
global_ctx::{ArcGlobalCtx, GlobalCtx},
log,
os_info::collect_device_os_info,
set_default_machine_id,
resolve_machine_id,
stun::MockStunInfoCollector,
},
connector::create_connector_by_url,
@@ -81,6 +82,7 @@ impl WebClient {
pub fn new<T: TunnelConnector + 'static, S: ToString, H: ToString>(
connector: T,
token: S,
machine_id: Uuid,
hostname: H,
secure_mode: bool,
manager: Arc<NetworkInstanceManager>,
@@ -90,6 +92,7 @@ impl WebClient {
let hooks = hooks.unwrap_or_else(|| Arc::new(DefaultHooks));
let controller = Arc::new(controller::Controller::new(
token.to_string(),
machine_id,
hostname.to_string(),
collect_device_os_info(),
manager,
@@ -229,13 +232,14 @@ impl WebClient {
pub async fn run_web_client(
config_server_url_s: &str,
machine_id: Option<String>,
machine_id_opts: MachineIdOptions,
hostname: Option<String>,
secure_mode: bool,
manager: Arc<NetworkInstanceManager>,
hooks: Option<Arc<dyn WebClientHooks>>,
) -> Result<WebClient> {
set_default_machine_id(machine_id);
let machine_id = resolve_machine_id(&machine_id_opts)
.with_context(|| "failed to resolve machine id for web client")?;
let config_server_url = match Url::parse(config_server_url_s) {
Ok(u) => u,
Err(_) => format!(
@@ -289,6 +293,7 @@ pub async fn run_web_client(
global_ctx,
},
token.to_string(),
machine_id,
hostname,
secure_mode,
manager,
@@ -300,14 +305,18 @@ pub async fn run_web_client(
mod tests {
use std::sync::{Arc, atomic::AtomicBool};
use crate::instance_manager::NetworkInstanceManager;
use crate::{common::MachineIdOptions, instance_manager::NetworkInstanceManager};
#[tokio::test]
async fn test_manager_wait() {
let manager = Arc::new(NetworkInstanceManager::new());
let temp_dir = tempfile::tempdir().unwrap();
let client = super::run_web_client(
format!("ring://{}/test", uuid::Uuid::new_v4()).as_str(),
None,
MachineIdOptions {
explicit_machine_id: None,
state_dir: Some(temp_dir.path().to_path_buf()),
},
None,
false,
manager.clone(),
@@ -335,9 +344,13 @@ mod tests {
#[tokio::test]
async fn test_run_web_client_with_unreachable_config_server() {
let manager = Arc::new(NetworkInstanceManager::new());
let temp_dir = tempfile::tempdir().unwrap();
let client = super::run_web_client(
"udp://config-server.invalid:22020/test",
None,
MachineIdOptions {
explicit_machine_id: None,
state_dir: Some(temp_dir.path().to_path_buf()),
},
None,
false,
manager,
+48 -7
View File
@@ -101,7 +101,11 @@ impl TunnelFilter for SecureDatagramTunnelFilter {
Err(e) => return Some(Err(e)),
};
let mut cipher = ZCPacket::new_with_payload(packet.payload());
let payload = match checked_payload(&packet, "secure packet") {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let mut cipher = ZCPacket::new_with_payload(payload);
cipher.fill_peer_manager_hdr(0, 0, PacketType::Data as u8);
cipher
.mut_peer_manager_header()
@@ -116,15 +120,27 @@ impl TunnelFilter for SecureDatagramTunnelFilter {
))));
}
Some(Ok(ZCPacket::new_from_buf(
cipher.payload_bytes(),
ZCPacketType::DummyTunnel,
)))
let packet = ZCPacket::new_from_buf(cipher.payload_bytes(), ZCPacketType::DummyTunnel);
if packet.peer_manager_header().is_none() {
return Some(Err(TunnelError::InvalidPacket(
"decrypted secure packet too short".to_string(),
)));
}
Some(Ok(packet))
}
fn filter_output(&self) {}
}
fn checked_payload<'a>(packet: &'a ZCPacket, context: &str) -> Result<&'a [u8], TunnelError> {
if packet.peer_manager_header().is_none() {
return Err(TunnelError::InvalidPacket(format!("{context} too short")));
}
Ok(packet.payload())
}
fn pack_control_packet(payload: &[u8]) -> ZCPacket {
let mut packet = ZCPacket::new_with_payload(payload);
packet.fill_peer_manager_hdr(0, 0, PacketType::Data as u8);
@@ -214,7 +230,8 @@ pub async fn upgrade_client_tunnel(
Ok(None) => return Err(TunnelError::Shutdown),
Err(error) => return Err(error.into()),
};
let msg2_cipher = decode_noise_payload(msg2_packet.payload())
let msg2_payload = checked_payload(&msg2_packet, "noise msg2 packet")?;
let msg2_cipher = decode_noise_payload(msg2_payload)
.ok_or_else(|| TunnelError::InvalidPacket("invalid noise msg2 magic".to_string()))?;
let mut root_key_buf = [0u8; 32];
let root_key_len = state
@@ -254,7 +271,8 @@ pub async fn accept_or_upgrade_server_tunnel(
));
}
};
let Some(msg1_cipher) = decode_noise_payload(first_packet.payload()) else {
let first_payload = checked_payload(&first_packet, "first packet")?;
let Some(msg1_cipher) = decode_noise_payload(first_payload) else {
let stream = Box::pin(futures::stream::once(async move { Ok(first_packet) }).chain(stream));
return Ok((
Box::new(RawSplitTunnel::new(info, stream, sink)) as Box<dyn Tunnel>,
@@ -303,6 +321,7 @@ pub async fn accept_or_upgrade_server_tunnel(
mod tests {
use super::*;
use crate::tunnel::ring::create_ring_tunnel_pair;
use bytes::BytesMut;
#[test]
fn web_secure_cipher_algorithm_matches_support_flag() {
@@ -338,6 +357,28 @@ mod tests {
assert!(matches!(err, TunnelError::Timeout(_)));
}
#[tokio::test]
async fn accept_secure_tunnel_rejects_short_first_packet() {
let (server_tunnel, client_tunnel) = create_ring_tunnel_pair();
let server_task =
tokio::spawn(async move { accept_or_upgrade_server_tunnel(server_tunnel).await });
let (_stream, mut sink) = client_tunnel.split();
sink.send(ZCPacket::new_from_buf(
BytesMut::from(&b"\x01"[..]),
ZCPacketType::TCP,
))
.await
.unwrap();
let err = server_task.await.unwrap().unwrap_err();
assert!(matches!(
err,
TunnelError::InvalidPacket(msg) if msg == "first packet too short"
));
}
#[tokio::test]
async fn accept_secure_tunnel_after_short_client_delay() {
let (server_tunnel, client_tunnel) = create_ring_tunnel_pair();
+7 -5
View File
@@ -7,7 +7,7 @@ use tokio::{
};
use crate::{
common::{constants::EASYTIER_VERSION, get_machine_id},
common::constants::EASYTIER_VERSION,
proto::{
rpc_impl::bidirect::BidirectRpcManager,
rpc_types::controller::BaseController,
@@ -65,11 +65,13 @@ impl Session {
tasks: &mut JoinSet<()>,
ctx: HeartbeatCtx,
) {
let mid = get_machine_id();
let controller = controller.upgrade().unwrap();
let mid = controller.machine_id();
let inst_id = uuid::Uuid::new_v4();
let token = controller.upgrade().unwrap().token();
let hostname = controller.upgrade().unwrap().hostname();
let device_os = controller.upgrade().unwrap().device_os();
let token = controller.token();
let hostname = controller.hostname();
let device_os = controller.device_os();
let controller = Arc::downgrade(&controller);
let ctx_clone = ctx.clone();
let mut tick = interval(std::time::Duration::from_secs(1));