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