Skip to content

Commit abdb38b

Browse files
committed
Add unix domain socket for telemetry server
1 parent cd7061c commit abdb38b

11 files changed

Lines changed: 309 additions & 46 deletions

File tree

examples/http_server/example_conf.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,11 @@ telemetry:
5757
# Enables telemetry server
5858
enabled: true
5959
# Telemetry server address.
60+
# Can be either a TCP socket address (e.g., "127.0.0.1:8080")
61+
# or a Unix domain socket path (e.g., "/tmp/telemetry.sock") on Unix systems.
6062
addr: "127.0.0.1:0"
63+
# Example Unix socket configuration (uncomment to use):
64+
# addr: "/tmp/telemetry.sock"
6165
# HTTP endpoints configuration.
6266
endpoints:
6367
Example endpoint:

examples/http_server/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ async fn main() -> BootstrapResult<()> {
5656
custom_server_routes: vec![],
5757
})?;
5858

59-
if let Some(tele_serv_addr) = tele_driver.server_addr() {
59+
if let Some(tele_serv_addr) = tele_driver.addr() {
6060
log::info!("Telemetry server is listening on http://{}", tele_serv_addr);
6161
}
6262

foundations/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ mod utils;
6969
#[cfg(feature = "cli")]
7070
pub mod cli;
7171

72-
#[cfg(feature = "settings")]
72+
#[cfg(any(feature = "settings", feature = "telemetry-server"))]
7373
pub mod settings;
7474

7575
#[cfg(any(

foundations/src/settings/addr.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//! Network address types that support both TCP and Unix domain sockets.
2+
//!
3+
//! This module provides the [`ListenAddr`] enum, a flexible address type that can represent
4+
//! either TCP socket addresses or Unix domain socket paths.
5+
6+
use super::net::SocketAddr;
7+
use super::Settings;
8+
use serde::{Deserialize, Serialize};
9+
use std::fmt;
10+
11+
/// Address that can be either TCP socket or Unix domain socket endpoint
12+
#[derive(Clone, Debug, Serialize, Deserialize)]
13+
#[serde(untagged)]
14+
pub enum ListenAddr {
15+
/// TCP network socket address
16+
Tcp(SocketAddr),
17+
/// Unix domain socket path
18+
#[cfg(unix)]
19+
Unix(std::path::PathBuf),
20+
}
21+
22+
impl Default for ListenAddr {
23+
fn default() -> Self {
24+
let addr: std::net::SocketAddr = (std::net::Ipv4Addr::LOCALHOST, 0).into();
25+
ListenAddr::Tcp(addr.into())
26+
}
27+
}
28+
29+
impl From<SocketAddr> for ListenAddr {
30+
fn from(addr: SocketAddr) -> Self {
31+
ListenAddr::Tcp(addr)
32+
}
33+
}
34+
35+
impl From<std::net::SocketAddr> for ListenAddr {
36+
fn from(addr: std::net::SocketAddr) -> Self {
37+
ListenAddr::Tcp(addr.into())
38+
}
39+
}
40+
41+
#[cfg(unix)]
42+
impl From<std::path::PathBuf> for ListenAddr {
43+
fn from(path: std::path::PathBuf) -> Self {
44+
ListenAddr::Unix(path)
45+
}
46+
}
47+
48+
#[cfg(unix)]
49+
impl From<&str> for ListenAddr {
50+
fn from(path: &str) -> Self {
51+
ListenAddr::Unix(std::path::PathBuf::from(path))
52+
}
53+
}
54+
55+
#[cfg(unix)]
56+
impl From<String> for ListenAddr {
57+
fn from(path: String) -> Self {
58+
ListenAddr::Unix(std::path::PathBuf::from(path))
59+
}
60+
}
61+
62+
impl fmt::Display for ListenAddr {
63+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64+
match self {
65+
ListenAddr::Tcp(addr) => write!(f, "{addr}"),
66+
#[cfg(unix)]
67+
ListenAddr::Unix(path) => write!(f, "{}", path.display()),
68+
}
69+
}
70+
}
71+
72+
impl Settings for ListenAddr {}

foundations/src/settings/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@
142142
143143
mod basic_impls;
144144

145+
pub mod addr;
145146
pub mod collections;
146147
pub mod net;
147148

foundations/src/telemetry/driver.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use std::task::{Context, Poll};
99

1010
feature_use!(cfg(feature = "telemetry-server"), {
1111
use super::server::TelemetryServerFuture;
12-
use std::net::SocketAddr;
12+
use crate::settings::addr::ListenAddr;
13+
use crate::settings::net::SocketAddr;
1314
});
1415

1516
/// A future that drives async telemetry functionality and that is returned
@@ -21,7 +22,7 @@ feature_use!(cfg(feature = "telemetry-server"), {
2122
/// [security syscall-related]: `crate::security`
2223
pub struct TelemetryDriver {
2324
#[cfg(feature = "telemetry-server")]
24-
server_addr: Option<SocketAddr>,
25+
addr: Option<ListenAddr>,
2526

2627
#[cfg(feature = "telemetry-server")]
2728
server_fut: Option<TelemetryServerFuture>,
@@ -36,7 +37,7 @@ impl TelemetryDriver {
3637
) -> Self {
3738
Self {
3839
#[cfg(feature = "telemetry-server")]
39-
server_addr: server_fut.as_ref().map(|fut| fut.local_addr()),
40+
addr: server_fut.as_ref().and_then(|fut| fut.local_addr().ok()),
4041

4142
#[cfg(feature = "telemetry-server")]
4243
server_fut,
@@ -47,10 +48,23 @@ impl TelemetryDriver {
4748

4849
/// Address of the telemetry server.
4950
///
50-
/// Returns `None` if the server wasn't spawned.
51+
/// Returns `None` if the server wasn't spawned or if it was spawned with a Unix socket.
5152
#[cfg(feature = "telemetry-server")]
53+
#[deprecated(since = "4.6.0", note = "addr should be used instead")]
5254
pub fn server_addr(&self) -> Option<SocketAddr> {
53-
self.server_addr
55+
match self.addr.as_ref()? {
56+
ListenAddr::Tcp(addr) => Some(*addr),
57+
#[cfg(unix)]
58+
ListenAddr::Unix(_) => None,
59+
}
60+
}
61+
62+
/// Address of the temeletry server.
63+
///
64+
/// Returns `None` if the server weasn't spawned.
65+
#[cfg(feature = "telemetry-server")]
66+
pub fn addr(&self) -> Option<&ListenAddr> {
67+
self.addr.as_ref()
5468
}
5569

5670
/// Instructs the telemetry driver and server to perform an orderly shutdown when the given

foundations/src/telemetry/server/mod.rs

Lines changed: 143 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#[cfg(feature = "metrics")]
22
use super::metrics;
33
use super::settings::TelemetrySettings;
4+
use crate::settings::addr::ListenAddr;
45
use crate::telemetry::log;
56
use crate::BootstrapResult;
67
use anyhow::Context as _;
@@ -14,18 +15,127 @@ use std::net::SocketAddr;
1415
use std::pin::Pin;
1516
use std::sync::Arc;
1617
use std::task::{Context, Poll};
18+
use tokio::io::{AsyncRead, AsyncWrite};
1719
use tokio::net::TcpListener;
20+
#[cfg(unix)]
21+
use tokio::net::{TcpStream, UnixListener, UnixStream};
1822
use tokio::sync::watch;
1923

2024
mod router;
2125

2226
use router::Router;
27+
28+
enum TelemetryStream {
29+
Tcp(TcpStream),
30+
#[cfg(unix)]
31+
Unix(UnixStream),
32+
}
33+
34+
impl AsyncRead for TelemetryStream {
35+
fn poll_read(
36+
self: Pin<&mut Self>,
37+
cx: &mut Context<'_>,
38+
buf: &mut tokio::io::ReadBuf<'_>,
39+
) -> Poll<std::io::Result<()>> {
40+
match self.get_mut() {
41+
TelemetryStream::Tcp(stream) => Pin::new(stream).poll_read(cx, buf),
42+
#[cfg(unix)]
43+
TelemetryStream::Unix(stream) => Pin::new(stream).poll_read(cx, buf),
44+
}
45+
}
46+
}
47+
48+
impl AsyncWrite for TelemetryStream {
49+
fn poll_write(
50+
self: Pin<&mut Self>,
51+
cx: &mut Context<'_>,
52+
buf: &[u8],
53+
) -> Poll<Result<usize, std::io::Error>> {
54+
match self.get_mut() {
55+
TelemetryStream::Tcp(stream) => Pin::new(stream).poll_write(cx, buf),
56+
#[cfg(unix)]
57+
TelemetryStream::Unix(stream) => Pin::new(stream).poll_write(cx, buf),
58+
}
59+
}
60+
61+
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {
62+
match self.get_mut() {
63+
TelemetryStream::Tcp(stream) => Pin::new(stream).poll_flush(cx),
64+
#[cfg(unix)]
65+
TelemetryStream::Unix(stream) => Pin::new(stream).poll_flush(cx),
66+
}
67+
}
68+
69+
fn poll_shutdown(
70+
self: Pin<&mut Self>,
71+
cx: &mut Context<'_>,
72+
) -> Poll<Result<(), std::io::Error>> {
73+
match self.get_mut() {
74+
TelemetryStream::Tcp(stream) => Pin::new(stream).poll_shutdown(cx),
75+
#[cfg(unix)]
76+
TelemetryStream::Unix(stream) => Pin::new(stream).poll_shutdown(cx),
77+
}
78+
}
79+
}
80+
81+
enum TelemetryListener {
82+
Tcp(TcpListener),
83+
#[cfg(unix)]
84+
Unix(UnixListener),
85+
}
86+
87+
impl TelemetryListener {
88+
pub(crate) fn local_addr(&self) -> BootstrapResult<ListenAddr> {
89+
match self {
90+
TelemetryListener::Tcp(listener) => Ok(listener.local_addr()?.into()),
91+
#[cfg(unix)]
92+
TelemetryListener::Unix(listener) => match listener.local_addr()?.as_pathname() {
93+
Some(path) => Ok(path.to_path_buf().into()),
94+
None => Err(anyhow::anyhow!("unix socket listener has no pathname")),
95+
},
96+
}
97+
}
98+
99+
pub(crate) async fn accept(&self) -> std::io::Result<TelemetryStream> {
100+
match self {
101+
TelemetryListener::Tcp(listener) => listener
102+
.accept()
103+
.await
104+
.map(|(conn, _)| TelemetryStream::Tcp(conn)),
105+
#[cfg(unix)]
106+
TelemetryListener::Unix(listener) => listener
107+
.accept()
108+
.await
109+
.map(|(conn, _)| TelemetryStream::Unix(conn)),
110+
}
111+
}
112+
113+
pub(crate) fn poll_accept(
114+
&mut self,
115+
cx: &mut std::task::Context<'_>,
116+
) -> std::task::Poll<std::io::Result<TelemetryStream>> {
117+
match self {
118+
TelemetryListener::Tcp(listener) => match std::task::ready!(listener.poll_accept(cx)) {
119+
Ok((conn, _)) => std::task::Poll::Ready(Ok(TelemetryStream::Tcp(conn))),
120+
Err(e) => std::task::Poll::Ready(Err(e)),
121+
},
122+
#[cfg(unix)]
123+
TelemetryListener::Unix(listener) => {
124+
match std::task::ready!(listener.poll_accept(cx)) {
125+
Ok((conn, _)) => std::task::Poll::Ready(Ok(TelemetryStream::Unix(conn))),
126+
Err(e) => std::task::Poll::Ready(Err(e)),
127+
}
128+
}
129+
}
130+
}
131+
}
132+
23133
pub use router::{
24134
BoxError, TelemetryRouteHandler, TelemetryRouteHandlerFuture, TelemetryServerRoute,
25135
};
26136

27137
pub(super) struct TelemetryServerFuture {
28-
listener: TcpListener,
138+
listener: TelemetryListener,
29139
router: Router,
30140
}
31141

@@ -47,27 +157,40 @@ impl TelemetryServerFuture {
47157
.map_err(|err| anyhow::anyhow!(err))?;
48158
}
49159

50-
let addr = settings.server.addr;
51-
52-
#[cfg(feature = "settings")]
53-
let addr = SocketAddr::from(addr);
54-
55-
let router = Router::new(custom_routes, settings);
56-
57-
let listener = {
58-
let std_listener = std::net::TcpListener::from(
59-
bind_socket(addr).with_context(|| format!("binding to socket {addr:?}"))?,
60-
);
61-
62-
std_listener.set_nonblocking(true)?;
160+
let router = Router::new(custom_routes, Arc::clone(&settings));
161+
162+
let listener = match &settings.server.addr {
163+
ListenAddr::Tcp(addr) => {
164+
#[cfg(feature = "settings")]
165+
let addr = SocketAddr::from(*addr);
166+
167+
let std_listener = std::net::TcpListener::from(
168+
bind_socket(addr).with_context(|| format!("binding to TCP socket {addr:?}"))?,
169+
);
170+
std_listener.set_nonblocking(true)?;
171+
let tokio_listener = tokio::net::TcpListener::from_std(std_listener)?;
172+
TelemetryListener::Tcp(tokio_listener)
173+
}
174+
#[cfg(unix)]
175+
ListenAddr::Unix(path) => {
176+
// Remove existing socket file if it exists to avoid bind errors
177+
if path.exists() {
178+
if let Err(e) = std::fs::remove_file(path) {
179+
log::warn!("failed to remove existing Unix socket file"; "path" => %path.display(), "error" => e);
180+
}
181+
}
63182

64-
tokio::net::TcpListener::from_std(std_listener)?
183+
let unix_listener = UnixListener::bind(path)
184+
.with_context(|| format!("binding to Unix socket {path:?}"))?;
185+
TelemetryListener::Unix(unix_listener)
186+
}
65187
};
66188

67189
Ok(Some(TelemetryServerFuture { listener, router }))
68190
}
69-
pub(super) fn local_addr(&self) -> SocketAddr {
70-
self.listener.local_addr().unwrap()
191+
192+
pub(super) fn local_addr(&self) -> BootstrapResult<ListenAddr> {
193+
self.listener.local_addr()
71194
}
72195

73196
// Adapted from Hyper 0.14 Server stuff and axum::serve::serve.
@@ -87,15 +210,12 @@ impl TelemetryServerFuture {
87210
let (close_tx, close_rx) = watch::channel(());
88211
let listener = self.listener;
89212

90-
pin_mut!(listener);
91-
92213
loop {
93214
let socket = tokio::select! {
94215
conn = listener.accept() => match conn {
95-
Ok((conn, _)) => TokioIo::new(conn),
216+
Ok(conn) => TokioIo::new(conn),
96217
Err(e) => {
97218
log::warn!("failed to accept connection"; "error" => e);
98-
99219
continue;
100220
}
101221
},
@@ -140,11 +260,10 @@ impl Future for TelemetryServerFuture {
140260
let this = &mut *self;
141261

142262
loop {
143-
let socket = match ready!(Pin::new(&mut this.listener).poll_accept(cx)) {
144-
Ok((conn, _)) => TokioIo::new(conn),
263+
let socket = match ready!(this.listener.poll_accept(cx)) {
264+
Ok(conn) => TokioIo::new(conn),
145265
Err(e) => {
146266
log::warn!("failed to accept connection"; "error" => e);
147-
148267
continue;
149268
}
150269
};

0 commit comments

Comments
 (0)