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 Unreachable(String),
40}
41
42impl Refusal {
43 #[must_use]
45 pub fn decision(&self, host: &str) -> Decision {
46 match self {
47 Self::Address(addr, why) => Decision::single(
48 Verdict::Deny,
49 RULE_ADDRESS,
50 format!("{host} resolves to {}, {}", addr.ip(), why.describe()),
51 ),
52 Self::Unreachable(why) => Decision::single(Verdict::Deny, RULE_UPSTREAM, why.clone()),
53 }
54 }
55}
56
57pub fn connect(
63 resolver: &dyn Resolve,
64 host: &str,
65 port: u16,
66 addresses: &CompiledPolicy<'_>,
67 loopback_ok: &[SocketAddr],
68 timeout: Duration,
69) -> Result<TcpStream, Refusal> {
70 let addrs = match host.parse::<IpAddr>() {
71 Ok(ip) => vec![SocketAddr::new(ip, port)],
72 Err(_) => resolver
73 .resolve(host, port)
74 .map_err(|e| Refusal::Unreachable(format!("cannot resolve {host}: {e}")))?,
75 };
76 if addrs.is_empty() {
77 return Err(Refusal::Unreachable(format!("{host} has no addresses")));
78 }
79 for addr in &addrs {
82 match decide::forbidden(addr.ip()) {
83 Some(Forbidden::Loopback) if loopback_ok.contains(addr) => {}
84 Some(Forbidden::Private(_)) if decide::named(addresses, addr.ip()) => {}
85 Some(why) => return Err(Refusal::Address(*addr, why)),
86 None => {}
87 }
88 }
89 let mut last = None;
90 for addr in &addrs {
91 match TcpStream::connect_timeout(addr, timeout) {
92 Ok(stream) => return Ok(stream),
93 Err(e) => last = Some(e),
94 }
95 }
96 Err(Refusal::Unreachable(match last {
97 Some(e) => format!("cannot connect to {host}:{port}: {e}"),
98 None => format!("cannot connect to {host}:{port}"),
99 }))
100}