surrealism_runtime/
net_allow.rs1use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
7use std::sync::Arc;
8
9use ipnet::IpNet;
10
11#[derive(Debug, Clone)]
13pub enum ResolvedNetAllow {
14 Net(IpNet),
16 IpPort(IpAddr, u16),
18}
19
20impl ResolvedNetAllow {
21 pub fn matches_socket_addr(&self, addr: &SocketAddr) -> bool {
23 match self {
24 Self::Net(net) => net.contains(&addr.ip()),
25 Self::IpPort(ip, port) => addr.ip() == *ip && addr.port() == *port,
26 }
27 }
28
29 fn push_from_socket_addr(port: Option<u16>, addr: SocketAddr, out: &mut Vec<Self>) {
30 if let Some(port) = port {
31 out.push(Self::IpPort(addr.ip(), port));
32 } else {
33 out.push(Self::Net(IpNet::from(addr.ip())));
34 }
35 }
36}
37
38pub fn resolve_allow_net(entries: &[String]) -> anyhow::Result<Arc<Vec<ResolvedNetAllow>>> {
47 let mut out = Vec::new();
48 for entry in entries {
49 resolve_one(entry, &mut out)?;
50 }
51 Ok(Arc::new(out))
52}
53
54fn resolve_one(entry: &str, out: &mut Vec<ResolvedNetAllow>) -> anyhow::Result<()> {
55 if let Ok(net) = entry.parse::<IpNet>() {
56 out.push(ResolvedNetAllow::Net(net));
57 return Ok(());
58 }
59 if let Ok(ip) = entry.parse::<IpAddr>() {
60 out.push(ResolvedNetAllow::Net(IpNet::from(ip)));
61 return Ok(());
62 }
63 let url = url::Url::parse(&format!("http://{entry}"))
64 .map_err(|e| anyhow::anyhow!("failed to parse allow_net entry '{entry}': {e}"))?;
65 let host =
66 url.host().ok_or_else(|| anyhow::anyhow!("allow_net entry '{entry}' has no host"))?;
67
68 let port: Option<u16> = entry.rsplit_once(':').and_then(|(_, p)| p.parse::<u16>().ok());
69
70 match host {
71 url::Host::Ipv4(ip) => {
72 let ip: IpAddr = ip.into();
73 if let Some(port) = port {
74 out.push(ResolvedNetAllow::IpPort(ip, port));
75 } else {
76 out.push(ResolvedNetAllow::Net(IpNet::from(ip)));
77 }
78 }
79 url::Host::Ipv6(ip) => {
80 let ip: IpAddr = ip.into();
81 if let Some(port) = port {
82 out.push(ResolvedNetAllow::IpPort(ip, port));
83 } else {
84 out.push(ResolvedNetAllow::Net(IpNet::from(ip)));
85 }
86 }
87 url::Host::Domain(domain) => {
88 resolve_hostname(domain, port, out)?;
89 }
90 }
91 Ok(())
92}
93
94fn resolve_hostname(
96 hostname: &str,
97 port: Option<u16>,
98 out: &mut Vec<ResolvedNetAllow>,
99) -> anyhow::Result<()> {
100 let addrs = (hostname, port.unwrap_or(80))
101 .to_socket_addrs()
102 .map_err(|e| anyhow::anyhow!("failed to resolve allow_net hostname '{hostname}': {e}"))?;
103 for addr in addrs {
104 ResolvedNetAllow::push_from_socket_addr(port, addr, out);
105 }
106 Ok(())
107}
108
109#[cfg(test)]
110mod tests {
111 use std::net::SocketAddr;
112
113 use super::*;
114
115 #[test]
116 fn parses_ip_and_cidr() {
117 let r = resolve_allow_net(&["192.168.1.1".into(), "10.0.0.0/8".into()]).unwrap();
118 assert_eq!(r.len(), 2);
119 let a: SocketAddr = "192.168.1.1:8080".parse().unwrap();
120 assert!(r[0].matches_socket_addr(&a));
121 let inside: SocketAddr = "10.1.2.3:443".parse().unwrap();
122 assert!(r[1].matches_socket_addr(&inside));
123 }
124
125 #[test]
126 fn parses_ip_with_port() {
127 let r = resolve_allow_net(&["192.168.1.1:80".into()]).unwrap();
128 assert_eq!(r.len(), 1);
129 let ok: SocketAddr = "192.168.1.1:80".parse().unwrap();
130 assert!(r[0].matches_socket_addr(&ok));
131 let wrong: SocketAddr = "192.168.1.1:443".parse().unwrap();
132 assert!(!r[0].matches_socket_addr(&wrong));
133 }
134}