microsandbox_server/
port.rs1use microsandbox_utils::PORTAL_PORTS_FILE;
16use once_cell::sync::Lazy;
17use serde::{Deserialize, Serialize};
18use std::{
19 collections::HashMap,
20 net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener},
21 path::{Path, PathBuf},
22};
23use tokio::{fs, sync::Mutex};
24use tracing::{debug, info, warn};
25
26use crate::{MicrosandboxServerError, MicrosandboxServerResult};
27
28pub const LOCALHOST_IP: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST);
34
35static PORT_ASSIGNMENT_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
37
38#[derive(Debug, Clone, Default)]
44pub struct BiPortMapping {
45 sandbox_to_port: HashMap<String, u16>,
47
48 port_to_sandbox: HashMap<u16, String>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, Default)]
54pub struct PortMapping {
55 pub mappings: HashMap<String, u16>,
57}
58
59#[derive(Debug)]
61pub struct PortManager {
62 mappings: BiPortMapping,
64
65 file_path: PathBuf,
67}
68
69impl BiPortMapping {
74 pub fn new() -> Self {
76 Self {
77 sandbox_to_port: HashMap::new(),
78 port_to_sandbox: HashMap::new(),
79 }
80 }
81
82 pub fn insert(&mut self, sandbox_key: String, port: u16) {
84 if let Some(existing_sandbox) = self.port_to_sandbox.get(&port) {
86 if existing_sandbox != &sandbox_key {
87 warn!(
89 "Port {} was already assigned to sandbox {}, reassigning to {}",
90 port, existing_sandbox, sandbox_key
91 );
92 self.sandbox_to_port.remove(existing_sandbox);
93 }
94 }
95
96 if let Some(existing_port) = self.sandbox_to_port.get(&sandbox_key) {
98 if *existing_port != port {
99 self.port_to_sandbox.remove(existing_port);
101 }
102 }
103
104 self.sandbox_to_port.insert(sandbox_key.clone(), port);
106 self.port_to_sandbox.insert(port, sandbox_key);
107 }
108
109 pub fn remove_by_sandbox(&mut self, sandbox_key: &str) -> Option<u16> {
111 if let Some(port) = self.sandbox_to_port.remove(sandbox_key) {
112 self.port_to_sandbox.remove(&port);
113 Some(port)
114 } else {
115 None
116 }
117 }
118
119 pub fn remove_by_port(&mut self, port: u16) -> Option<String> {
121 if let Some(sandbox_key) = self.port_to_sandbox.remove(&port) {
122 self.sandbox_to_port.remove(&sandbox_key);
123 Some(sandbox_key)
124 } else {
125 None
126 }
127 }
128
129 pub fn get_port(&self, sandbox_key: &str) -> Option<u16> {
131 self.sandbox_to_port.get(sandbox_key).copied()
132 }
133
134 pub fn get_sandbox(&self, port: u16) -> Option<&String> {
136 self.port_to_sandbox.get(&port)
137 }
138
139 pub fn to_port_mapping(&self) -> PortMapping {
141 PortMapping {
142 mappings: self.sandbox_to_port.clone(),
143 }
144 }
145
146 pub fn from_port_mapping(mapping: PortMapping) -> Self {
148 let mut result = Self::new();
149
150 for (sandbox_key, port) in mapping.mappings {
151 result.insert(sandbox_key, port);
152 }
153
154 result
155 }
156}
157
158impl PortManager {
159 pub async fn new(namespace_dir: impl AsRef<Path>) -> MicrosandboxServerResult<Self> {
161 let file_path = namespace_dir.as_ref().join(PORTAL_PORTS_FILE);
162 let mappings = Self::load_mappings(&file_path).await?;
163
164 Ok(Self {
165 mappings,
166 file_path,
167 })
168 }
169
170 async fn load_mappings(file_path: &Path) -> MicrosandboxServerResult<BiPortMapping> {
172 if file_path.exists() {
173 let contents = fs::read_to_string(file_path).await.map_err(|e| {
174 MicrosandboxServerError::ConfigError(format!(
175 "Failed to read port mappings file: {}",
176 e
177 ))
178 })?;
179
180 let port_mapping: PortMapping = serde_json::from_str(&contents).map_err(|e| {
181 MicrosandboxServerError::ConfigError(format!(
182 "Failed to parse port mappings file: {}",
183 e
184 ))
185 })?;
186
187 Ok(BiPortMapping::from_port_mapping(port_mapping))
188 } else {
189 debug!("No port mappings file found, creating a new one");
190 Ok(BiPortMapping::new())
191 }
192 }
193
194 async fn save_mappings(&self) -> MicrosandboxServerResult<()> {
196 let port_mapping = self.mappings.to_port_mapping();
197 let contents = serde_json::to_string_pretty(&port_mapping).map_err(|e| {
198 MicrosandboxServerError::ConfigError(format!(
199 "Failed to serialize port mappings: {}",
200 e
201 ))
202 })?;
203
204 if let Some(parent) = self.file_path.parent() {
206 if !parent.exists() {
207 fs::create_dir_all(parent).await.map_err(|e| {
208 MicrosandboxServerError::ConfigError(format!(
209 "Failed to create directory for port mappings file: {}",
210 e
211 ))
212 })?;
213 }
214 }
215
216 fs::write(&self.file_path, contents).await.map_err(|e| {
217 MicrosandboxServerError::ConfigError(format!(
218 "Failed to write port mappings file: {}",
219 e
220 ))
221 })
222 }
223
224 pub async fn assign_port(&mut self, key: &str) -> MicrosandboxServerResult<u16> {
226 if let Some(port) = self.mappings.get_port(key) {
228 if self.verify_port_availability(port) {
230 return Ok(port);
231 } else {
232 warn!("Previously assigned port {} for sandbox {} is no longer available, reassigning", port, key);
234 self.mappings.remove_by_sandbox(key);
235 }
236 }
237
238 let _lock = PORT_ASSIGNMENT_LOCK.lock().await;
240
241 let port = self.get_available_port_from_os()?;
243
244 self.mappings.insert(key.to_string(), port);
246 self.save_mappings().await?;
247
248 info!("Assigned port {} to sandbox {}", port, key);
249 Ok(port)
250 }
251
252 pub async fn release_port(&mut self, key: &str) -> MicrosandboxServerResult<()> {
254 if self.mappings.remove_by_sandbox(key).is_some() {
255 self.save_mappings().await?;
256 info!("Released port for sandbox {}", key);
257 }
258
259 Ok(())
260 }
261
262 pub fn get_port(&self, key: &str) -> Option<u16> {
264 self.mappings.get_port(key)
265 }
266
267 fn verify_port_availability(&self, port: u16) -> bool {
269 let addr = SocketAddr::new(LOCALHOST_IP, port);
270 match TcpListener::bind(addr) {
271 Ok(_) => true, Err(_) => false, }
274 }
275
276 fn get_available_port_from_os(&self) -> MicrosandboxServerResult<u16> {
278 let addr = SocketAddr::new(LOCALHOST_IP, 0);
280 let listener = TcpListener::bind(addr).map_err(|e| {
281 MicrosandboxServerError::ConfigError(format!(
282 "Failed to bind to address to get available port: {}",
283 e
284 ))
285 })?;
286
287 let port = listener
289 .local_addr()
290 .map_err(|e| {
291 MicrosandboxServerError::ConfigError(format!(
292 "Failed to get local address from socket: {}",
293 e
294 ))
295 })?
296 .port();
297
298 debug!("OS assigned port {}", port);
299
300 Ok(port)
304 }
305}