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> {
70 match self {
71 Self::Tcp(addr) | Self::Udp(addr) => {
72 let mut connect_addr = *addr;
73 if connect_addr.ip().is_unspecified() {
74 let localhost_ip = match connect_addr.is_ipv4() {
75 true => IpAddr::V4(Ipv4Addr::LOCALHOST),
76 false => IpAddr::V6(Ipv6Addr::LOCALHOST),
77 };
78
79 connect_addr.set_ip(localhost_ip);
80 }
81
82 Some(connect_addr)
83 }
84 #[cfg(unix)]
85 Self::Unixgram(_) => None,
86 #[cfg(unix)]
87 Self::Unix(_) => None,
88 }
89 }
90
91 pub fn as_unix_stream_path(&self) -> Option<&Path> {
95 match self {
96 #[cfg(unix)]
97 Self::Unix(path) => Some(path),
98 _ => None,
99 }
100 }
101}
102
103impl fmt::Display for ListenAddress {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 match self {
106 Self::Tcp(addr) => write!(f, "tcp://{}", addr),
107 Self::Udp(addr) => write!(f, "udp://{}", addr),
108 #[cfg(unix)]
109 Self::Unixgram(path) => write!(f, "unixgram://{}", path.display()),
110 #[cfg(unix)]
111 Self::Unix(path) => write!(f, "unix://{}", path.display()),
112 }
113 }
114}
115
116impl TryFrom<String> for ListenAddress {
117 type Error = String;
118
119 fn try_from(value: String) -> Result<Self, Self::Error> {
120 Self::try_from(value.as_str())
121 }
122}
123
124impl<'a> TryFrom<&'a str> for ListenAddress {
125 type Error = String;
126
127 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
128 let url = match Url::parse(value) {
129 Ok(url) => url,
130 Err(e) => match e {
131 url::ParseError::RelativeUrlWithoutBase => {
132 Url::parse(&format!("unixgram://{}", value)).map_err(|e| e.to_string())?
133 }
134 _ => return Err(e.to_string()),
135 },
136 };
137
138 match url.scheme() {
139 "tcp" => {
140 let mut socket_addresses = url.socket_addrs(|| None).map_err(|e| e.to_string())?;
141 if socket_addresses.is_empty() {
142 Err("listen address must resolve to at least one valid IP address/port pair".to_string())
143 } else {
144 Ok(Self::Tcp(socket_addresses.swap_remove(0)))
145 }
146 }
147 "udp" => {
148 let mut socket_addresses = url.socket_addrs(|| None).map_err(|e| e.to_string())?;
149 if socket_addresses.is_empty() {
150 Err("listen address must resolve to at least one valid IP address/port pair".to_string())
151 } else {
152 Ok(Self::Udp(socket_addresses.swap_remove(0)))
153 }
154 }
155 #[cfg(unix)]
156 "unixgram" => {
157 let path = url.path();
158 if path.is_empty() {
159 return Err("socket path cannot be empty".to_string());
160 }
161
162 let path_buf = PathBuf::from(path);
163 if !path_buf.is_absolute() {
164 return Err("socket path must be absolute".to_string());
165 }
166
167 Ok(Self::Unixgram(path_buf))
168 }
169 #[cfg(unix)]
170 "unix" => {
171 let path = url.path();
172 if path.is_empty() {
173 return Err("socket path cannot be empty".to_string());
174 }
175
176 let path_buf = PathBuf::from(path);
177 if !path_buf.is_absolute() {
178 return Err("socket path must be absolute".to_string());
179 }
180
181 Ok(Self::Unix(path_buf))
182 }
183 scheme => Err(format!("unknown/unsupported address scheme '{}'", scheme)),
184 }
185 }
186}
187
188#[cfg(unix)]
197#[derive(Clone)]
198pub struct ProcessCredentials {
199 pub pid: i32,
201
202 pub uid: u32,
204
205 pub gid: u32,
207}
208
209#[derive(Clone)]
214pub enum ConnectionAddress {
215 SocketLike(SocketAddr),
217
218 #[cfg(unix)]
220 ProcessLike(Option<ProcessCredentials>),
221}
222
223impl fmt::Display for ConnectionAddress {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 match self {
226 Self::SocketLike(addr) => write!(f, "{}", addr),
227 #[cfg(unix)]
228 Self::ProcessLike(maybe_creds) => match maybe_creds {
229 None => write!(f, "<unbound>"),
230 Some(creds) => write!(f, "<pid={} uid={} gid={}>", creds.pid, creds.uid, creds.gid),
231 },
232 }
233 }
234}
235
236impl From<SocketAddr> for ConnectionAddress {
237 fn from(value: SocketAddr) -> Self {
238 Self::SocketLike(value)
239 }
240}
241
242#[cfg(unix)]
243impl From<ProcessCredentials> for ConnectionAddress {
244 fn from(creds: ProcessCredentials) -> Self {
245 Self::ProcessLike(Some(creds))
246 }
247}
248
249impl<'a> Connected<&'a Connection> for ConnectionAddress {
250 fn connect_info(target: &'a Connection) -> Self {
251 target.remote_addr()
252 }
253}
254
255pub enum GrpcTargetAddress {
265 Tcp(SocketAddr),
266 Unix(PathBuf),
267}
268
269impl GrpcTargetAddress {
270 pub fn try_from_listen_addr(listen_address: &ListenAddress) -> Option<Self> {
274 match listen_address {
275 ListenAddress::Tcp(addr) => Some(GrpcTargetAddress::Tcp(*addr)),
276 ListenAddress::Unix(path) => Some(GrpcTargetAddress::Unix(path.clone())),
277 _ => None,
278 }
279 }
280}
281
282impl fmt::Display for GrpcTargetAddress {
283 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
284 match self {
285 GrpcTargetAddress::Tcp(addr) => write!(f, "{}", addr),
286 GrpcTargetAddress::Unix(path) => write!(f, "unix://{}", path.display()),
287 }
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294
295 #[test]
296 fn test_as_local_connect_addr() {
297 let tcp_any_addr = ListenAddress::try_from("tcp://0.0.0.0:1234").unwrap();
298 assert_eq!(
299 tcp_any_addr.as_local_connect_addr(),
300 Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 1234)))
301 );
302
303 let tcp_localhost_addr = ListenAddress::try_from("tcp://127.0.0.1:2345").unwrap();
304 assert_eq!(
305 tcp_localhost_addr.as_local_connect_addr(),
306 Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 2345)))
307 );
308
309 let tcp_private_addr = ListenAddress::try_from("tcp://192.168.10.2:3456").unwrap();
310 assert_eq!(
311 tcp_private_addr.as_local_connect_addr(),
312 Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 168, 10, 2), 3456)))
313 );
314
315 let udp_any_addr = ListenAddress::try_from("udp://0.0.0.0:4567").unwrap();
316 assert_eq!(
317 udp_any_addr.as_local_connect_addr(),
318 Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 4567)))
319 );
320
321 let udp_localhost_addr = ListenAddress::try_from("udp://127.0.0.1:5678").unwrap();
322 assert_eq!(
323 udp_localhost_addr.as_local_connect_addr(),
324 Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 5678)))
325 );
326
327 let udp_private_addr = ListenAddress::try_from("udp://192.168.10.2:6789").unwrap();
328 assert_eq!(
329 udp_private_addr.as_local_connect_addr(),
330 Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 168, 10, 2), 6789)))
331 );
332 }
333}