use wgcloak::control; use std::os::unix::fs::PermissionsExt; use tempfile::tempdir; #[tokio::test] async fn control_commands_and_socket_permissions() { let dir = tempdir().unwrap(); let runtime_dir = dir.path().join("runtime"); let path = runtime_dir.join("control.sock"); let state = control::ControlState::default(); state.enable_reconnect(); state.set_status("ready").await; state.set_stats_json("{\"packets\":3}").await; let server = control::ControlServer::bind(&path, state.clone()) .await .unwrap(); assert_eq!( std::fs::metadata(&runtime_dir) .unwrap() .permissions() .mode() & 0o777, 0o700 ); assert_eq!( std::fs::metadata(&path).unwrap().permissions().mode() & 0o777, 0o600 ); let task = tokio::spawn(server.run()); let second_bind = control::ControlServer::bind(&path, control::ControlState::default()).await; assert!(matches!( second_bind, Err(error) if error.kind() == std::io::ErrorKind::AddrInUse )); assert!(control::send_command(&path, "status") .await .unwrap() .contains("ready")); assert!(control::send_command(&path, "stats") .await .unwrap() .contains("packets")); assert!(control::send_command(&path, "reconnect") .await .unwrap() .contains("true")); let reload = control::send_command(&path, "reload").await.unwrap(); assert!(reload.contains("\"ok\":false")); assert!(reload.contains("not supported")); assert!(control::send_command(&path, "shutdown") .await .unwrap() .contains("true")); task.await.unwrap().unwrap(); } #[tokio::test] async fn replaces_only_a_verified_stale_socket() { let dir = tempdir().unwrap(); let private_dir = dir.path().join("runtime"); std::fs::create_dir(&private_dir).unwrap(); std::fs::set_permissions(&private_dir, std::fs::Permissions::from_mode(0o700)).unwrap(); let path = private_dir.join("stale.sock"); let stale = std::os::unix::net::UnixListener::bind(&path).unwrap(); drop(stale); let server = control::ControlServer::bind(&path, control::ControlState::default()) .await .expect("verified stale socket should be replaced"); assert_eq!( std::fs::metadata(&path).unwrap().permissions().mode() & 0o777, 0o600 ); drop(server); assert!(!path.exists()); } #[tokio::test] async fn refuses_to_replace_a_non_socket_path() { let dir = tempdir().unwrap(); let private_dir = dir.path().join("runtime"); std::fs::create_dir(&private_dir).unwrap(); std::fs::set_permissions(&private_dir, std::fs::Permissions::from_mode(0o700)).unwrap(); let path = private_dir.join("control.sock"); std::fs::write(&path, b"keep this file").unwrap(); let result = control::ControlServer::bind(&path, control::ControlState::default()).await; assert!(matches!( result, Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists )); assert_eq!(std::fs::read(&path).unwrap(), b"keep this file"); } #[test] fn default_socket_path_never_targets_the_shared_tmp_socket() { let path = control::default_socket_path(); assert_eq!(path.file_name().unwrap(), "wgcloak.sock"); assert_ne!(path.parent().unwrap(), std::path::Path::new("/tmp")); }