openmoat_proxy/
upstream.rs1use std::io;
8use std::net::{IpAddr, SocketAddr, TcpStream, ToSocketAddrs as _};
9use std::time::Duration;
10
11use openmoat_core::{CompiledPolicy, Decision, Verdict};
12
13use crate::decide::{self, Forbidden, RULE_ADDRESS, RULE_UPSTREAM};
14
15pub trait Resolve: Send + Sync {
17 fn resolve(&self, host: &str, port: u16) -> io::Result<Vec<SocketAddr>>;
20}
21
22#[derive(Debug, Clone, Copy, Default)]
24pub struct SystemResolver;
25
26impl Resolve for SystemResolver {
27 fn resolve(&self, host: &str, port: u16) -> io::Result<Vec<SocketAddr>> {
28 Ok((host, port).to_socket_addrs()?.collect())
29 }
30}
31
32#[derive(Debug)]
34#[non_exhaustive]
35pub enum Refusal {
36 Address(SocketAddr, Forbidden),
38 Denied(SocketAddr, Decision),
40 Unreachable(String),
42}
43
44impl Refusal {
45 #[must_use]
47 pub fn decision(&self, host: &str) -> Decision {
48 match self {
49 Self::Address(addr, why) => Decision::single(
50 Verdict::Deny,
51 RULE_ADDRESS,
52 format!("{host} resolves to {}, {}", addr.ip(), why.describe()),
53 ),
54 Self::Denied(addr, denied) => {
55 let rules = denied
56 .rules
57 .iter()
58 .zip(&denied.reasons)
59 .map(|(rule, reason)| format!("`{rule}` ({reason})"))
60 .collect::<Vec<_>>()
61 .join(", ");
62 Decision::single(
63 Verdict::Deny,
64 RULE_ADDRESS,
65 format!(
66 "{host} resolves to {}, refused by deny rule {rules}",
67 addr.ip()
68 ),
69 )
70 }
71 Self::Unreachable(why) => Decision::single(Verdict::Deny, RULE_UPSTREAM, why.clone()),
72 }
73 }
74}
75
76pub fn connect(
83 resolver: &dyn Resolve,
84 host: &str,
85 port: u16,
86 addresses: &CompiledPolicy<'_>,
87 loopback_ok: &[SocketAddr],
88 timeout: Duration,
89) -> Result<TcpStream, Refusal> {
90 let addrs = match host.parse::<IpAddr>() {
91 Ok(ip) => vec![SocketAddr::new(ip, port)],
92 Err(_) => resolver
93 .resolve(host, port)
94 .map_err(|e| Refusal::Unreachable(format!("cannot resolve {host}: {e}")))?,
95 };
96 if addrs.is_empty() {
97 return Err(Refusal::Unreachable(format!("{host} has no addresses")));
98 }
99 for addr in &addrs {
102 match decide::forbidden(addr.ip()) {
103 Some(Forbidden::Loopback) if loopback_ok.contains(addr) => {}
104 Some(Forbidden::Private(_)) if decide::named(addresses, addr.ip()) => {}
105 Some(why) => return Err(Refusal::Address(*addr, why)),
106 None => {}
107 }
108 if let Some(denied) = decide::denied(addresses, addr.ip()) {
111 return Err(Refusal::Denied(*addr, denied));
112 }
113 }
114 let mut last = None;
115 for addr in &addrs {
116 match TcpStream::connect_timeout(addr, timeout) {
117 Ok(stream) => return Ok(stream),
118 Err(e) => last = Some(e),
119 }
120 }
121 Err(Refusal::Unreachable(match last {
122 Some(e) => format!("cannot connect to {host}:{port}: {e}"),
123 None => format!("cannot connect to {host}:{port}"),
124 }))
125}