1use std::ffi::OsString;
29use std::io;
30use std::os::fd::AsRawFd;
31use std::os::fd::OwnedFd;
32use std::os::unix::ffi::OsStringExt;
33use std::path::Path;
34use std::path::PathBuf;
35use std::sync::Mutex;
36use std::time::Duration;
37
38use nix::sys::socket::AddressFamily;
39use nix::sys::socket::Backlog;
40use nix::sys::socket::Shutdown;
41use nix::sys::socket::SockFlag;
42use nix::sys::socket::SockType;
43use nix::sys::socket::SockaddrIn;
44use nix::sys::socket::sockopt::ReuseAddr;
45use reverie_ptrace::GdbConnection;
46use tokio::net::TcpListener;
47use tokio::net::TcpStream;
48use tokio::net::UnixStream;
49
50use crate::Context;
51use crate::Error;
52
53const GDBSERVER_RETRY: Duration = Duration::from_millis(5);
56
57#[derive(Debug)]
60pub struct GdbPort {
61 socket: OwnedFd,
62 port: u16,
63}
64
65static NEXT_GDBSERVER_PORT: Mutex<Option<GdbPort>> = Mutex::new(None);
67
68impl GdbPort {
69 pub fn bind(port: u16) -> Result<Self, Error> {
76 let socket = nix::sys::socket::socket(
77 AddressFamily::Inet,
78 SockType::Stream,
79 SockFlag::SOCK_CLOEXEC,
80 None,
81 )
82 .context("Failed to create the gdbserver's TCP socket")?;
83 nix::sys::socket::setsockopt(&socket, ReuseAddr, &true)
86 .context("Failed to set SO_REUSEADDR on the gdbserver's TCP socket")?;
87 nix::sys::socket::bind(socket.as_raw_fd(), &SockaddrIn::new(127, 0, 0, 1, port))
88 .with_context(|| format!("Failed to bind 127.0.0.1:{port} for the gdbserver"))?;
89 let port = nix::sys::socket::getsockname::<SockaddrIn>(socket.as_raw_fd())
90 .context("Failed to read the gdbserver's bound port")?
91 .port();
92 Ok(Self { socket, port })
93 }
94
95 pub fn port(&self) -> u16 {
97 self.port
98 }
99
100 pub fn serve_next_gdbserver(self) {
104 *NEXT_GDBSERVER_PORT
105 .lock()
106 .unwrap_or_else(|poisoned| poisoned.into_inner()) = Some(self);
107 }
108
109 fn relay(self) -> Result<GdbConnection, Error> {
114 nix::sys::socket::listen(&self.socket, Backlog::new(1)?).with_context(|| {
117 format!(
118 "The gdbserver cannot listen on 127.0.0.1:{}, which another process took after \
119 Hermit bound it; pass another --gdbserver-port",
120 self.port
121 )
122 })?;
123 let mut path = vec![0];
126 path.extend(format!("hermit-gdbserver-{}", self.port).into_bytes());
127 let path = PathBuf::from(OsString::from_vec(path));
128 let listener = std::net::TcpListener::from(self.socket);
129 listener.set_nonblocking(true)?;
130 let listener = TcpListener::from_std(listener)?;
131 tokio::spawn(serve_one_client(listener, self.port, path.clone()));
132 Ok(GdbConnection::Path(path))
133 }
134}
135
136pub(crate) fn connection(port: u16) -> Result<GdbConnection, Error> {
146 let served = NEXT_GDBSERVER_PORT
147 .lock()
148 .unwrap_or_else(|poisoned| poisoned.into_inner())
149 .take();
150 match served {
151 Some(bound) => bound.relay(),
152 None => Ok(port.into()),
153 }
154}
155
156async fn serve_one_client(listener: TcpListener, port: u16, gdbserver: PathBuf) {
159 let (mut client, mut server) = match accept_client(listener, &gdbserver).await {
160 Ok(connected) => connected,
161 Err(error) => {
162 tracing::error!("The gdbserver relay on 127.0.0.1:{port} failed: {error:#}");
163 return;
164 }
165 };
166 if let Err(error) = tokio::io::copy_bidirectional(&mut client, &mut server).await {
169 tracing::debug!("The gdbserver session on 127.0.0.1:{port} ended: {error}");
170 }
171}
172
173async fn accept_client(
176 listener: TcpListener,
177 gdbserver: &Path,
178) -> Result<(TcpStream, UnixStream), Error> {
179 let server = loop {
180 match UnixStream::connect(gdbserver).await {
181 Ok(server) => break server,
182 Err(error) if error.kind() == io::ErrorKind::ConnectionRefused => {
183 tokio::time::sleep(GDBSERVER_RETRY).await;
184 }
185 Err(error) => return Err(error).context("Failed to connect to the gdbserver"),
186 }
187 };
188 let (client, address) = listener.accept().await?;
189 tracing::info!("Relaying gdb client {address} to the gdbserver");
190 if let Err(error) = nix::sys::socket::shutdown(listener.as_raw_fd(), Shutdown::Read) {
198 tracing::warn!(
199 "The gdbserver port still listens after its one client connected; a later client \
200 will wait unanswered: {error}"
201 );
202 }
203 Ok((client, server))
204}
205
206#[cfg(test)]
207mod tests {
208 use std::io::Read;
209 use std::io::Write;
210
211 use super::*;
212
213 fn fake_gdbserver(path: &Path) -> tokio::task::JoinHandle<()> {
216 use tokio::io::AsyncReadExt;
217 use tokio::io::AsyncWriteExt;
218
219 let listener = tokio::net::UnixListener::bind(path).unwrap();
220 tokio::spawn(async move {
221 let (mut stream, _) = listener.accept().await.unwrap();
222 drop(listener);
223 let mut buffer = [0; 64];
224 loop {
225 let read = stream.read(&mut buffer).await.unwrap();
226 if read == 0 {
227 break;
228 }
229 stream
230 .write_all(&buffer[..read].to_ascii_uppercase())
231 .await
232 .unwrap();
233 }
234 })
235 }
236
237 fn listening(port: u16) -> bool {
238 let hex = format!(":{port:04X} ");
239 std::fs::read_to_string("/proc/net/tcp")
240 .unwrap()
241 .lines()
242 .any(|line| line.contains(&hex) && line.split_whitespace().nth(3) == Some("0A"))
243 }
244
245 fn runtime() -> tokio::runtime::Runtime {
246 tokio::runtime::Builder::new_current_thread()
247 .enable_all()
248 .build()
249 .unwrap()
250 }
251
252 #[test]
253 fn a_bound_port_is_closed_on_exec_and_not_yet_listening() {
254 let port = GdbPort::bind(0).unwrap();
255 assert_ne!(port.port(), 0);
256 let flags = nix::fcntl::fcntl(&port.socket, nix::fcntl::FcntlArg::F_GETFD).unwrap();
257 assert_ne!(flags & libc::FD_CLOEXEC, 0);
258 assert!(!listening(port.port()));
261 let error = std::net::TcpStream::connect(("127.0.0.1", port.port())).unwrap_err();
262 assert_eq!(error.kind(), io::ErrorKind::ConnectionRefused);
263 }
264
265 #[test]
266 fn a_port_in_use_is_refused_naming_it() {
267 let taken = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
268 let port = taken.local_addr().unwrap().port();
269 let error = format!("{:#}", GdbPort::bind(port).unwrap_err());
270 assert!(
271 error.contains(&format!("127.0.0.1:{port}")) && error.contains("EADDRINUSE"),
272 "{error}"
273 );
274 }
275
276 #[test]
281 fn a_port_another_socket_listened_on_first_fails_before_the_guest_spawns() {
282 let runtime = runtime();
283 let _guard = runtime.enter();
284 let port = GdbPort::bind(0).unwrap();
285 let other = std::net::TcpListener::bind(("127.0.0.1", port.port())).unwrap();
286 let Err(error) = port.relay() else {
287 panic!("the relay listened on a port another socket listens on");
288 };
289 let error = format!("{error:#}");
290 assert!(
291 error.contains("--gdbserver-port") && error.contains("EADDRINUSE"),
292 "{error}"
293 );
294 drop(other);
295 }
296
297 #[test]
300 fn a_served_port_is_relayed_once_and_then_the_gdbserver_binds_its_own() {
301 let runtime = runtime();
302 let _guard = runtime.enter();
303 let port = GdbPort::bind(0).unwrap();
304 let number = port.port();
305 port.serve_next_gdbserver();
306 match connection(4321).unwrap() {
307 GdbConnection::Path(path) => assert_eq!(
308 path.as_os_str().as_encoded_bytes(),
309 format!("\0hermit-gdbserver-{number}").as_bytes()
310 ),
311 GdbConnection::Addr(address) => panic!("{address} bound with a served port"),
312 }
313 match connection(4321).unwrap() {
314 GdbConnection::Addr(address) => assert_eq!(address.to_string(), "127.0.0.1:4321"),
315 GdbConnection::Path(path) => panic!("relayed to {path:?} with no served port"),
316 }
317 }
318
319 #[test]
320 fn the_relay_serves_one_client_once_the_gdbserver_listens() {
321 if crate::run_in_fresh_process(
322 "gdbserver::tests::the_relay_serves_one_client_once_the_gdbserver_listens",
323 ) {
324 return;
325 }
326 let runtime = runtime();
327 let port = GdbPort::bind(0).unwrap();
328 let number = port.port();
329 let binders_copy = port.socket.try_clone().unwrap();
332 runtime.block_on(async move {
333 let GdbConnection::Path(path) = port.relay().unwrap() else {
334 panic!("a relay serves the gdbserver on a Unix socket");
335 };
336 assert!(listening(number));
337 let client = tokio::task::spawn_blocking(move || {
340 let mut client = std::net::TcpStream::connect(("127.0.0.1", number)).unwrap();
341 client.write_all(b"$qsupported#00").unwrap();
342 let mut reply = [0; 14];
343 client.read_exact(&mut reply).unwrap();
344 let second = std::net::TcpStream::connect(("127.0.0.1", number)).unwrap_err();
346 drop(client);
347 (reply, second.kind())
348 });
349 tokio::time::sleep(GDBSERVER_RETRY * 4).await;
350 let server = fake_gdbserver(&path);
351 let (reply, second) = client.await.unwrap();
352 assert_eq!(&reply, b"$QSUPPORTED#00");
353 assert_eq!(second, io::ErrorKind::ConnectionRefused);
354 assert!(!listening(number));
355 server.await.unwrap();
357 });
358 drop(binders_copy);
359 }
360}