1mod capacity;
15mod limits;
16mod recheck;
17mod wire;
18
19use std::io::{self, ErrorKind, Write as _};
20use std::net::{SocketAddr, TcpListener, TcpStream};
21use std::sync::atomic::{AtomicUsize, Ordering};
22use std::thread;
23use std::time::Instant;
24
25use openmoat_core::{CompiledPolicy, Decision, Verdict};
26use zeroize::Zeroizing;
27
28pub use self::capacity::RULE_CAPACITY;
29use self::capacity::{Full, Refusals};
30pub use self::limits::Limits;
31use self::wire::{read_head, read_hello, refuse, respond, summary};
32use crate::audit::{Connection, Recorder};
33use crate::body::{self, BodyEnd, Framing};
34use crate::broker::{Broker, Watch};
35use crate::decide::{self, RULE_REQUEST, RULE_SNI};
36use crate::local::{self, LocalService};
37use crate::request::{Request, Target};
38use crate::session::{RULE_SESSION, Sessions};
39use crate::slots::{PENDING, Share, Shares};
40use crate::tunnel::{self, Forward};
41use crate::upstream::{self, Refusal, Resolve};
42
43const MALFORMED_CHUNKS: &str = "malformed chunked request body";
45
46#[derive(Clone, Copy)]
48pub struct Proxy<'a> {
49 pub policy: &'a CompiledPolicy<'a>,
51 pub resolver: &'a dyn Resolve,
53 pub recorder: &'a dyn Recorder,
55 pub limits: &'a Limits,
57 pub broker: &'a Broker,
59 pub loopback_ok: &'a [SocketAddr],
62 pub allow_ech: bool,
65 pub sessions: Option<&'a Sessions<'a>>,
70 pub local: Option<&'a dyn LocalService>,
73}
74
75impl std::fmt::Debug for Proxy<'_> {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 f.debug_struct("Proxy")
78 .field("limits", &self.limits)
79 .field("broker", &self.broker)
80 .field("loopback_ok", &self.loopback_ok)
81 .field("allow_ech", &self.allow_ech)
82 .field("sessions", &self.sessions)
83 .field("local", &self.local.is_some())
84 .finish_non_exhaustive()
85 }
86}
87
88struct Served<'s> {
90 addresses: &'s CompiledPolicy<'s>,
91 ports: &'s CompiledPolicy<'s>,
93 shares: Shares,
94}
95
96#[derive(Clone, Copy)]
99struct Turn<'s> {
100 session: Option<&'s str>,
101 started: Instant,
102}
103
104impl Proxy<'_> {
105 pub fn serve(&self, listener: &TcpListener) -> io::Result<()> {
108 self.limits.check()?;
109 let address_policy = decide::address_policy(self.policy.policy());
110 let addresses = CompiledPolicy::compile(&address_policy, self.policy.context())
111 .map_err(io::Error::other)?;
112 let port_policy = decide::port_policy(self.policy.policy());
113 let ports = CompiledPolicy::compile(&port_policy, self.policy.context())
114 .map_err(io::Error::other)?;
115 let served = Served {
116 addresses: &addresses,
117 ports: &ports,
118 shares: Shares::new(self.limits.max_connections),
119 };
120 let served = &served;
121 let active = AtomicUsize::new(0);
122 let refusals = Refusals::default();
123 thread::scope(|scope| {
124 loop {
125 let client = match listener.accept() {
126 Ok((client, _)) => client,
127 Err(e) if is_transient(&e) => continue,
128 Err(e) => return Err(e),
129 };
130 refusals.flush(self.recorder, Instant::now());
131 let _ = client.set_write_timeout(Some(self.limits.handshake_timeout));
132 let full = |kind: Full| {
133 respond(&client, 503, kind.reply());
134 refusals.refused(kind, self.recorder, Instant::now());
135 };
136 let slot = Slot::take(&active, self.limits.max_connections);
137 let Some(slot) = slot else {
138 full(Full::Slots);
139 continue;
140 };
141 let pending = if self.sessions.is_some() {
144 let Some(share) = served.shares.take(PENDING) else {
145 full(Full::Pending);
146 continue;
147 };
148 Some(share)
149 } else {
150 None
151 };
152 let _ = thread::Builder::new().spawn_scoped(scope, move || {
155 let _slot = slot;
156 self.handle(&client, served, pending);
157 });
158 }
159 })
160 }
161
162 fn handle(&self, client: &TcpStream, served: &Served<'_>, pending: Option<Share<'_>>) {
163 let started = Instant::now();
164 let timeout = Some(self.limits.handshake_timeout);
165 if client.set_read_timeout(timeout).is_err() || client.set_write_timeout(timeout).is_err() {
166 return;
167 }
168 let mut buf = Zeroizing::new(Vec::new());
170 let deadline = started + self.limits.handshake_timeout;
171 if let Some(local) = self.local
173 && !local.prefix().is_empty()
174 && local::starts_with(client, local.prefix(), deadline)
175 {
176 local.serve(client);
177 return;
178 }
179 let request = match read_head(client, &mut buf, self.limits.max_head_bytes, deadline) {
180 Ok(request) => request,
181 Err(why) => {
182 let decision = Decision::single(Verdict::Deny, RULE_REQUEST, why);
183 let turn = Turn {
184 session: None,
185 started,
186 };
187 self.record(turn, None, &decision);
188 refuse(client, 400, &decision.reasons[0]);
189 return;
190 }
191 };
192 let Some((turn, _share)) = self.admit(client, &request, served, started) else {
193 return;
194 };
195 drop(pending);
196 let mut watch = Watch::new(self.broker, request.host());
200 if let Some(leak) = watch.next(&buf) {
201 let decision = leak.decision(request.host());
202 self.record(turn, Some(&request), &decision);
203 refuse(client, 403, &summary(&decision));
204 return;
205 }
206 let rest = Zeroizing::new(buf.split_off(request.head_len));
207 let decision = decide::host_port(self.policy, served.ports, request.host(), request.port());
208 if decision.verdict != Verdict::Allow {
209 self.record(turn, Some(&request), &decision);
210 refuse(client, 403, &summary(&decision));
211 return;
212 }
213 if let Some(refusal) = self.tainted(turn, &request, None) {
214 self.record(turn, Some(&request), &refusal);
215 refuse(client, 403, &summary(&refusal));
216 return;
217 }
218 let mut body = BodyEnd::new(match &request.target {
222 Target::Http { head, .. } => body::framing(head),
223 Target::Connect { .. } => Framing::None,
224 });
225 let Ok(body_in_rest) = body.feed(&rest) else {
226 let decision = Decision::single(Verdict::Deny, RULE_REQUEST, MALFORMED_CHUNKS);
227 self.record(turn, Some(&request), &decision);
228 refuse(client, 400, MALFORMED_CHUNKS);
229 return;
230 };
231 let upstream = match upstream::connect(
232 self.resolver,
233 request.host(),
234 request.port(),
235 served.addresses,
236 self.loopback_ok,
237 self.limits.connect_timeout,
238 ) {
239 Ok(upstream) => upstream,
240 Err(refusal) => {
241 let decision = refusal.decision(request.host());
242 self.record(turn, Some(&request), &decision);
243 let status = if matches!(refusal, Refusal::Address(..) | Refusal::Denied(..)) {
244 403
245 } else {
246 502
247 };
248 refuse(client, status, &summary(&decision));
249 return;
250 }
251 };
252 let mut inspect = |chunk: &[u8]| {
254 let Ok(n) = body.feed(chunk) else {
255 let decision = Decision::single(Verdict::Deny, RULE_REQUEST, MALFORMED_CHUNKS);
257 self.record(turn, Some(&request), &decision);
258 return Forward::Stop;
259 };
260 if let Some(leak) = watch.next(&chunk[..n]) {
261 self.record(turn, Some(&request), &leak.decision(request.host()));
262 return Forward::Stop;
263 }
264 if n < chunk.len() || body.done() {
265 Forward::Part(n)
266 } else {
267 Forward::All
268 }
269 };
270 let (first_bytes, inspect) = match &request.target {
271 Target::Connect { host, .. } => {
272 match self.check_tunnel(client, host, rest.to_vec(), &decision, &request, turn) {
273 Some(hello) => (Zeroizing::new(hello), None),
274 None => return,
275 }
276 }
277 Target::Http { head, host, port } => {
278 let mut decision = decision.clone();
279 decision.reasons.extend(self.broker.withheld(host, *port));
280 if !self.record(turn, Some(&request), &decision) {
281 refuse(client, 403, "audit log unavailable");
282 return;
283 }
284 let inspect: tunnel::Inspect<'_> = &mut inspect;
285 let injected = self.broker.inject(host, *port, head);
288 let head = injected.as_deref().map_or(head.as_slice(), Vec::as_slice);
289 let body_bytes = &rest[..body_in_rest];
290 (Zeroizing::new([head, body_bytes].concat()), Some(inspect))
291 }
292 };
293 if (&upstream).write_all(&first_bytes).is_ok() {
294 let still_allowed = || self.still_untainted(turn, &request);
295 let recheck = turn.session.map(|_| tunnel::Recheck {
296 every: self.limits.taint_recheck,
297 still_allowed: &still_allowed,
298 });
299 tunnel::relay(
300 client,
301 &upstream,
302 self.limits.idle_timeout,
303 inspect,
304 recheck,
305 );
306 }
307 }
308
309 fn admit<'s>(
314 &'s self,
315 client: &TcpStream,
316 request: &Request,
317 served: &'s Served<'_>,
318 started: Instant,
319 ) -> Option<(Turn<'s>, Option<Share<'s>>)> {
320 let mut turn = Turn {
321 session: None,
322 started,
323 };
324 let Some(sessions) = self.sessions else {
325 return Some((turn, None));
326 };
327 let session = match sessions.verify(&request.credentials) {
328 Ok(session) => session,
329 Err(why) => {
330 let decision = Decision::single(Verdict::Deny, RULE_SESSION, why.to_string());
331 self.record(turn, Some(request), &decision);
332 refuse(client, 407, &summary(&decision));
333 return None;
334 }
335 };
336 turn.session = Some(session);
337 if let Some(share) = served.shares.take(session) {
338 return Some((turn, Some(share)));
339 }
340 let decision = Decision::single(
341 Verdict::Deny,
342 RULE_SESSION,
343 format!(
344 "the session already holds {} connections, half of the proxy's",
345 served.shares.cap()
346 ),
347 );
348 self.record(turn, Some(request), &decision);
349 refuse(client, 503, &summary(&decision));
350 None
351 }
352
353 fn check_tunnel(
358 &self,
359 client: &TcpStream,
360 host: &str,
361 mut hello: Vec<u8>,
362 allowed: &Decision,
363 request: &Request,
364 turn: Turn<'_>,
365 ) -> Option<Vec<u8>> {
366 let answered = (&*client).write_all(b"HTTP/1.1 200 Connection Established\r\n\r\n");
367 let deadline = Instant::now() + self.limits.handshake_timeout;
368 let read = answered
369 .map_err(|e| format!("answering the CONNECT: {e}"))
370 .and_then(|()| read_hello(client, &mut hello, deadline));
371 let refusal = match read {
372 Ok((_, true)) if !self.allow_ech => Some(format!(
375 "TLS ClientHello for CONNECT {host} carries an encrypted server name (ECH), \
376 so the server may serve another site; `sandbox.proxy_allow_ech: true` \
377 in the policy allows it"
378 )),
379 Ok((Some(name), _)) if name == host => None,
380 Ok((Some(name), _)) => Some(format!(
381 "TLS server name {name} does not match CONNECT {host}"
382 )),
383 Ok((None, _)) if host.parse::<std::net::IpAddr>().is_ok() => None,
386 Ok((None, _)) => Some(format!(
387 "TLS ClientHello for CONNECT {host} has no server name"
388 )),
389 Err(why) => Some(why),
390 };
391 if let Some(why) = refusal {
392 let decision = Decision::single(Verdict::Deny, RULE_SNI, why);
393 self.record(turn, Some(request), &decision);
394 return None;
395 }
396 self.record(turn, Some(request), allowed).then_some(hello)
397 }
398
399 fn record(&self, turn: Turn<'_>, request: Option<&Request>, decision: &Decision) -> bool {
402 let connection = Connection {
403 session: turn.session,
404 method: request.map(|r| r.method.as_str()),
405 destination: request.map(|r| (r.host(), r.port())),
406 decision,
407 latency_us: u64::try_from(turn.started.elapsed().as_micros()).unwrap_or(u64::MAX),
408 };
409 match self.recorder.record(&connection) {
410 Ok(()) => true,
411 Err(error) => {
412 self.recorder.unrecorded(&error);
415 false
416 }
417 }
418 }
419}
420
421struct Slot<'a>(&'a AtomicUsize);
424
425impl<'a> Slot<'a> {
426 fn take(active: &'a AtomicUsize, max: usize) -> Option<Self> {
427 let slot = Self(active);
430 (active.fetch_add(1, Ordering::AcqRel) < max).then_some(slot)
431 }
432}
433
434impl Drop for Slot<'_> {
435 fn drop(&mut self) {
436 self.0.fetch_sub(1, Ordering::AcqRel);
437 }
438}
439
440fn is_transient(e: &io::Error) -> bool {
441 matches!(
442 e.kind(),
443 ErrorKind::Interrupted | ErrorKind::ConnectionAborted | ErrorKind::ConnectionReset
444 )
445}
446
447#[cfg(test)]
448mod tests;