1use std::{
2 fmt,
3 net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4},
4 path::{Path, PathBuf},
5};
6
7use axum::extract::connect_info::Connected;
8use serde::Deserialize;
9use url::Url;
10
11use super::Connection;
12
13#[derive(Clone, Debug, Deserialize)]
26#[serde(try_from = "String")]
27pub enum ListenAddress {
28 Tcp(SocketAddr),
30
31 Udp(SocketAddr),
33
34 #[cfg(unix)]
36 Unixgram(PathBuf),
37
38 #[cfg(unix)]
40 Unix(PathBuf),
41}
42
43impl ListenAddress {
44 pub const fn any_tcp(port: u16) -> Self {
46 Self::Tcp(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port)))
47 }
48
49 pub const fn listener_type(&self) -> &'static str {
51 match self {
52 Self::Tcp(_) => "tcp",
53 Self::Udp(_) => "udp",
54 #[cfg(unix)]
55 Self::Unixgram(_) => "unixgram",
56 #[cfg(unix)]
57 Self::Unix(_) => "unix",
58 }
59 }
60
61 pub fn as_local_connect_addr(&self) -> Option<SocketAddr> {
71 match self {
72 Self::Tcp(addr) | Self::Udp(addr) => {
73 let mut connect_addr = *addr;
74 if connect_addr.ip().is_unspecified() {
75 let localhost_ip = match connect_addr.is_ipv4() {
76 true => IpAddr::V4(Ipv4Addr::LOCALHOST),
77 false => IpAddr::V6(Ipv6Addr::LOCALHOST),
78 };
79
80 connect_addr.set_ip(localhost_ip);
81 }
82
83 Some(connect_addr)
84 }
85 #[cfg(unix)]
88 Self::Unixgram(_) => None,
89 #[cfg(unix)]
90 Self::Unix(_) => None,
91 }
92 }
93
94 pub fn as_unix_stream_path(&self) -> Option<&Path> {
98 match self {
99 #[cfg(unix)]
100 Self::Unix(path) => Some(path),
101 _ => None,
102 }
103 }
104}
105
106impl fmt::Display for ListenAddress {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 match self {
109 Self::Tcp(addr) => write!(f, "tcp://{}", addr),
110 Self::Udp(addr) => write!(f, "udp://{}", addr),
111 #[cfg(unix)]
112 Self::Unixgram(path) => write!(f, "unixgram://{}", path.display()),
113 #[cfg(unix)]
114 Self::Unix(path) => write!(f, "unix://{}", path.display()),
115 }
116 }
117}
118
119impl TryFrom<String> for ListenAddress {
120 type Error = String;
121
122 fn try_from(value: String) -> Result<Self, Self::Error> {
123 Self::try_from(value.as_str())
124 }
125}
126
127impl<'a> TryFrom<&'a str> for ListenAddress {
128 type Error = String;
129
130 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
131 let url = match Url::parse(value) {
132 Ok(url) => url,
133 Err(e) => match e {
134 url::ParseError::RelativeUrlWithoutBase => {
135 Url::parse(&format!("unixgram://{}", value)).map_err(|e| e.to_string())?
136 }
137 _ => return Err(e.to_string()),
138 },
139 };
140
141 match url.scheme() {
142 "tcp" => {
143 let mut socket_addresses = url.socket_addrs(|| None).map_err(|e| e.to_string())?;
144 if socket_addresses.is_empty() {
145 Err("listen address must resolve to at least one valid IP address/port pair".to_string())
146 } else {
147 Ok(Self::Tcp(socket_addresses.swap_remove(0)))
148 }
149 }
150 "udp" => {
151 let mut socket_addresses = url.socket_addrs(|| None).map_err(|e| e.to_string())?;
152 if socket_addresses.is_empty() {
153 Err("listen address must resolve to at least one valid IP address/port pair".to_string())
154 } else {
155 Ok(Self::Udp(socket_addresses.swap_remove(0)))
156 }
157 }
158 #[cfg(unix)]
159 "unixgram" => {
160 let path = url.path();
161 if path.is_empty() {
162 return Err("socket path cannot be empty".to_string());
163 }
164
165 let path_buf = PathBuf::from(path);
166 if !path_buf.is_absolute() {
167 return Err("socket path must be absolute".to_string());
168 }
169
170 Ok(Self::Unixgram(path_buf))
171 }
172 #[cfg(unix)]
173 "unix" => {
174 let path = url.path();
175 if path.is_empty() {
176 return Err("socket path cannot be empty".to_string());
177 }
178
179 let path_buf = PathBuf::from(path);
180 if !path_buf.is_absolute() {
181 return Err("socket path must be absolute".to_string());
182 }
183
184 Ok(Self::Unix(path_buf))
185 }
186 scheme => Err(format!("unknown/unsupported address scheme '{}'", scheme)),
187 }
188 }
189}
190
191#[cfg(unix)]
200#[derive(Clone)]
201pub struct ProcessCredentials {
202 pub pid: i32,
204
205 pub uid: u32,
207
208 pub gid: u32,
210}
211
212#[cfg(unix)]
214#[derive(Clone, Copy)]
215pub enum ProcessCredentialsError {
216 InvalidCredentials,
218
219 ZeroPid,
221
222 UnsupportedPlatform,
224}
225
226#[cfg(unix)]
227impl ProcessCredentialsError {
228 pub const fn identifier(&self) -> &'static str {
230 match self {
231 Self::InvalidCredentials => "invalid-credentials",
232 Self::ZeroPid => "zero-pid",
233 Self::UnsupportedPlatform => "unsupported-platform",
234 }
235 }
236}
237
238#[cfg(unix)]
239impl fmt::Display for ProcessCredentialsError {
240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 match self {
242 Self::InvalidCredentials => write!(f, "invalid process credentials"),
243 Self::ZeroPid => write!(f, "process credential PID is zero"),
244 Self::UnsupportedPlatform => write!(f, "process credentials are unsupported on this platform"),
245 }
246 }
247}
248
249#[cfg(unix)]
251#[derive(Clone)]
252pub enum ProcessIdentity {
253 Credentials(ProcessCredentials),
255
256 Error(ProcessCredentialsError),
258
259 Unavailable,
261}
262
263#[cfg(unix)]
264impl ProcessIdentity {
265 pub fn credentials(&self) -> Option<&ProcessCredentials> {
267 match self {
268 Self::Credentials(creds) => Some(creds),
269 Self::Error(_) | Self::Unavailable => None,
270 }
271 }
272
273 pub const fn is_error(&self) -> bool {
275 matches!(self, Self::Error(_))
276 }
277}
278
279#[derive(Clone)]
284pub enum ConnectionAddress {
285 SocketLike(SocketAddr),
287
288 #[cfg(unix)]
290 ProcessLike(ProcessIdentity),
291}
292
293impl fmt::Display for ConnectionAddress {
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 match self {
296 Self::SocketLike(addr) => write!(f, "{}", addr),
297 #[cfg(unix)]
298 Self::ProcessLike(identity) => match identity {
299 ProcessIdentity::Credentials(creds) => {
300 write!(f, "<pid={} uid={} gid={}>", creds.pid, creds.uid, creds.gid)
301 }
302 ProcessIdentity::Error(error) => write!(f, "<origin-detection-error: {}>", error.identifier()),
303 ProcessIdentity::Unavailable => write!(f, "<no-origin>"),
304 },
305 }
306 }
307}
308
309impl ConnectionAddress {
310 #[cfg(unix)]
312 pub fn process_credentials(&self) -> Option<&ProcessCredentials> {
313 match self {
314 Self::ProcessLike(identity) => identity.credentials(),
315 Self::SocketLike(_) => None,
316 }
317 }
318
319 #[cfg(unix)]
321 pub const fn has_process_credential_error(&self) -> bool {
322 match self {
323 Self::ProcessLike(identity) => identity.is_error(),
324 Self::SocketLike(_) => false,
325 }
326 }
327}
328
329impl From<SocketAddr> for ConnectionAddress {
330 fn from(value: SocketAddr) -> Self {
331 Self::SocketLike(value)
332 }
333}
334
335#[cfg(unix)]
336impl From<ProcessCredentials> for ConnectionAddress {
337 fn from(creds: ProcessCredentials) -> Self {
338 Self::ProcessLike(ProcessIdentity::Credentials(creds))
339 }
340}
341
342impl<'a> Connected<&'a Connection> for ConnectionAddress {
343 fn connect_info(target: &'a Connection) -> Self {
344 target.remote_addr()
345 }
346}
347
348pub enum GrpcTargetAddress {
358 Tcp(SocketAddr),
359 Unix(PathBuf),
360}
361
362impl GrpcTargetAddress {
363 pub fn try_from_listen_addr(listen_address: &ListenAddress) -> Option<Self> {
370 match listen_address {
371 ListenAddress::Tcp(_) => {
372 listen_address.as_local_connect_addr().map(GrpcTargetAddress::Tcp)
374 }
375 ListenAddress::Unix(path) => Some(GrpcTargetAddress::Unix(path.clone())),
376 _ => None,
377 }
378 }
379}
380
381impl fmt::Display for GrpcTargetAddress {
382 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
383 match self {
384 GrpcTargetAddress::Tcp(addr) => write!(f, "{}", addr),
385 GrpcTargetAddress::Unix(path) => write!(f, "unix://{}", path.display()),
386 }
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393
394 #[test]
395 fn test_as_local_connect_addr() {
396 let tcp_any_addr = ListenAddress::try_from("tcp://0.0.0.0:1234").unwrap();
397 assert_eq!(
398 tcp_any_addr.as_local_connect_addr(),
399 Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 1234)))
400 );
401
402 let tcp_localhost_addr = ListenAddress::try_from("tcp://127.0.0.1:2345").unwrap();
403 assert_eq!(
404 tcp_localhost_addr.as_local_connect_addr(),
405 Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 2345)))
406 );
407
408 let tcp_private_addr = ListenAddress::try_from("tcp://192.168.10.2:3456").unwrap();
409 assert_eq!(
410 tcp_private_addr.as_local_connect_addr(),
411 Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 168, 10, 2), 3456)))
412 );
413
414 let udp_any_addr = ListenAddress::try_from("udp://0.0.0.0:4567").unwrap();
415 assert_eq!(
416 udp_any_addr.as_local_connect_addr(),
417 Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 4567)))
418 );
419
420 let udp_localhost_addr = ListenAddress::try_from("udp://127.0.0.1:5678").unwrap();
421 assert_eq!(
422 udp_localhost_addr.as_local_connect_addr(),
423 Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 5678)))
424 );
425
426 let udp_private_addr = ListenAddress::try_from("udp://192.168.10.2:6789").unwrap();
427 assert_eq!(
428 udp_private_addr.as_local_connect_addr(),
429 Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 168, 10, 2), 6789)))
430 );
431 }
432}