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