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((resolver_name(host).as_str(), port)
29 .to_socket_addrs()?
30 .collect())
31 }
32}
33
34#[cfg(not(windows))]
44pub(crate) fn resolver_name(host: &str) -> String {
45 format!("{host}.")
46}
47
48#[cfg(windows)]
50pub(crate) fn resolver_name(host: &str) -> String {
51 host.to_owned()
52}
53
54#[derive(Debug)]
56#[non_exhaustive]
57pub enum Refusal {
58 Address(SocketAddr, Forbidden),
60 Denied(SocketAddr, Decision),
62 Unreachable(String),
64}
65
66impl Refusal {
67 #[must_use]
69 pub fn decision(&self, host: &str) -> Decision {
70 match self {
71 Self::Address(addr, why) => Decision::single(
72 Verdict::Deny,
73 RULE_ADDRESS,
74 format!("{host} resolves to {}, {}", addr.ip(), why.describe()),
75 ),
76 Self::Denied(addr, denied) => {
77 let rules = denied
78 .rules
79 .iter()
80 .zip(&denied.reasons)
81 .map(|(rule, reason)| format!("`{rule}` ({reason})"))
82 .collect::<Vec<_>>()
83 .join(", ");
84 Decision::single(
85 Verdict::Deny,
86 RULE_ADDRESS,
87 format!(
88 "{host} resolves to {}, refused by deny rule {rules}",
89 addr.ip()
90 ),
91 )
92 }
93 Self::Unreachable(why) => Decision::single(Verdict::Deny, RULE_UPSTREAM, why.clone()),
94 }
95 }
96}
97
98pub fn connect(
105 resolver: &dyn Resolve,
106 host: &str,
107 port: u16,
108 addresses: &CompiledPolicy<'_>,
109 loopback_ok: &[SocketAddr],
110 timeout: Duration,
111) -> Result<TcpStream, Refusal> {
112 let addrs = match host.parse::<IpAddr>() {
113 Ok(ip) => vec![SocketAddr::new(ip, port)],
114 Err(_) => resolver
115 .resolve(host, port)
116 .map_err(|e| Refusal::Unreachable(format!("cannot resolve {host}: {e}")))?,
117 };
118 if addrs.is_empty() {
119 return Err(Refusal::Unreachable(format!("{host} has no addresses")));
120 }
121 for addr in &addrs {
124 match decide::forbidden(addr.ip()) {
125 Some(Forbidden::Loopback) if loopback_ok.contains(addr) => {}
126 Some(Forbidden::Private(_)) if decide::named_on(addresses, *addr) => {}
127 Some(why) => return Err(Refusal::Address(*addr, why)),
128 None => {}
129 }
130 if let Some(denied) = decide::denied(addresses, addr.ip()) {
133 return Err(Refusal::Denied(*addr, denied));
134 }
135 }
136 let mut last = None;
137 for addr in &addrs {
138 match TcpStream::connect_timeout(addr, timeout) {
139 Ok(stream) if !loopback_ok.contains(addr) && to_this_machine(&stream, addr.ip()) => {
143 return Err(Refusal::Address(*addr, Forbidden::ThisMachine));
144 }
145 Ok(stream) => return Ok(stream),
146 Err(e) => last = Some(e),
147 }
148 }
149 Err(Refusal::Unreachable(match last {
150 Some(e) => format!("cannot connect to {host}:{port}: {e}"),
151 None => format!("cannot connect to {host}:{port}"),
152 }))
153}
154
155fn to_this_machine(stream: &TcpStream, peer: IpAddr) -> bool {
158 stream
159 .local_addr()
160 .map_or(true, |own| own.ip().to_canonical() == peer.to_canonical())
161}