use std::io;
use std::net::{SocketAddr, TcpListener};
use tracing::{info, warn};
use crate::proxy::{Listener, ListenerOptions, Proxy};
pub async fn proxy_listeners(
proxy: &Proxy,
name: &str,
addrs: &[SocketAddr],
options: ListenerOptions,
) -> io::Result<Vec<Listener>> {
let activated = activate_socket(name)?;
if activated.is_empty() {
let mut listeners = Vec::new();
for (i, addr) in addrs.iter().enumerate() {
match proxy.bind(*addr, options.clone()).await {
Ok(listener) => listeners.push(listener),
Err(err) if i > 0 => warn!("not listening on {addr}: {err}"),
Err(err) => return Err(err),
}
}
return Ok(listeners);
}
activated
.into_iter()
.map(|socket| {
let listener = proxy.listen(socket, options.clone())?;
if let Ok(local) = listener.local_addr() {
info!("listening on {local} (socket {name:?} from launchd)");
}
Ok(listener)
})
.collect()
}
#[cfg(target_os = "macos")]
pub fn activate_socket(name: &str) -> io::Result<Vec<TcpListener>> {
use std::ffi::CString;
use std::os::fd::FromRawFd;
use libc::{c_char, c_int, size_t};
unsafe extern "C" {
fn launch_activate_socket(
name: *const c_char,
fds: *mut *mut c_int,
cnt: *mut size_t,
) -> c_int;
}
let name = CString::new(name).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
let mut fds: *mut c_int = std::ptr::null_mut();
let mut count: size_t = 0;
let err = unsafe { launch_activate_socket(name.as_ptr(), &mut fds, &mut count) };
match err {
0 => {}
libc::ESRCH | libc::ENOENT => return Ok(Vec::new()),
err => return Err(io::Error::from_raw_os_error(err)),
}
if fds.is_null() {
return Ok(Vec::new());
}
let raw = unsafe { std::slice::from_raw_parts(fds, count) }.to_vec();
unsafe { libc::free(fds.cast()) };
Ok(raw
.into_iter()
.map(|fd| unsafe { TcpListener::from_raw_fd(fd) })
.collect())
}
#[cfg(not(target_os = "macos"))]
pub fn activate_socket(_name: &str) -> io::Result<Vec<TcpListener>> {
Ok(Vec::new())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn finds_no_sockets_outside_launchd() {
assert!(activate_socket("http").unwrap().is_empty());
}
}