1use derive_deftly::Deftly;
4use serde::{Deserialize, Serialize};
5use std::{net::SocketAddr, ops::RangeInclusive, str::FromStr};
6use tor_config::ConfigBuildError;
7use tor_config::derive::prelude::*;
8use tracing::warn;
9
10#[derive(Clone, Debug, Deftly, Eq, PartialEq)]
12#[derive_deftly(TorConfig)]
13#[deftly(tor_config(no_default_trait, pre_build = "Self::validate"))]
14pub struct ProxyConfig {
15 #[deftly(tor_config(list(element(clone), listtype = "ProxyRuleList"), default = "vec![]"))]
18 pub(crate) proxy_ports: Vec<ProxyRule>,
19 }
23
24impl ProxyConfigBuilder {
25 fn validate(&self) -> Result<(), ConfigBuildError> {
27 let mut covered = rangemap::RangeInclusiveSet::<u16>::new();
29 for rule in self.proxy_ports.access_opt().iter().flatten() {
30 let range = &rule.source.0;
31 if covered.gaps(range).next().is_none() {
32 return Err(ConfigBuildError::Invalid {
33 field: "proxy_ports".into(),
34 problem: format!("Port pattern {} is not reachable", rule.source),
35 });
36 }
37 covered.insert(range.clone());
38 }
39
40 let mut any_forward = false;
42 for rule in self.proxy_ports.access_opt().iter().flatten() {
43 if let ProxyAction::Forward(_, target) = &rule.target {
44 any_forward = true;
45 if !target.is_sufficiently_private() {
46 warn!(
52 "Onion service target {} does not look like a private address. \
53 Do you really mean to send connections onto the public internet?",
54 target
55 );
56 }
57 }
58 }
59
60 if !any_forward {
61 warn!("Onion service is not configured to accept any connections.");
62 }
63
64 Ok(())
65 }
66}
67
68impl ProxyConfig {
69 pub(crate) fn resolve_port_for_begin(&self, port: u16) -> Option<&ProxyAction> {
72 self.proxy_ports
73 .iter()
74 .find(|rule| rule.source.matches_port(port))
75 .map(|rule| &rule.target)
76 }
77}
78
79#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
83#[serde(from = "ProxyRuleAsTuple", into = "ProxyRuleAsTuple")]
87pub struct ProxyRule {
88 source: ProxyPattern,
90 target: ProxyAction,
92}
93
94type ProxyRuleAsTuple = (ProxyPattern, ProxyAction);
96impl From<ProxyRuleAsTuple> for ProxyRule {
97 fn from(value: ProxyRuleAsTuple) -> Self {
98 Self {
99 source: value.0,
100 target: value.1,
101 }
102 }
103}
104impl From<ProxyRule> for ProxyRuleAsTuple {
105 fn from(value: ProxyRule) -> Self {
106 (value.source, value.target)
107 }
108}
109impl ProxyRule {
110 pub fn new(source: ProxyPattern, target: ProxyAction) -> Self {
112 Self { source, target }
113 }
114}
115
116#[derive(Clone, Debug, serde::Deserialize, serde_with::SerializeDisplay, Eq, PartialEq)]
118#[serde(try_from = "ProxyPatternAsEnum")]
119pub struct ProxyPattern(RangeInclusive<u16>);
120
121#[derive(serde::Deserialize)]
123#[serde(untagged)]
124enum ProxyPatternAsEnum {
125 Number(u16),
127 String(String),
129}
130
131impl TryFrom<ProxyPatternAsEnum> for ProxyPattern {
132 type Error = ProxyConfigError;
133
134 fn try_from(value: ProxyPatternAsEnum) -> Result<Self, Self::Error> {
135 match value {
136 ProxyPatternAsEnum::Number(port) => Self::one_port(port),
137 ProxyPatternAsEnum::String(s) => Self::from_str(&s),
138 }
139 }
140}
141
142impl FromStr for ProxyPattern {
143 type Err = ProxyConfigError;
144
145 fn from_str(s: &str) -> Result<Self, Self::Err> {
146 use ProxyConfigError as PCE;
147 if s == "*" {
148 Ok(Self::all_ports())
149 } else if let Some((left, right)) = s.split_once('-') {
150 let left: u16 = left
151 .parse()
152 .map_err(|e| PCE::InvalidPort(left.to_string(), e))?;
153 let right: u16 = right
154 .parse()
155 .map_err(|e| PCE::InvalidPort(right.to_string(), e))?;
156 Self::port_range(left, right)
157 } else {
158 let port = s.parse().map_err(|e| PCE::InvalidPort(s.to_string(), e))?;
159 Self::one_port(port)
160 }
161 }
162}
163impl std::fmt::Display for ProxyPattern {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 match self.0.clone().into_inner() {
166 (start, end) if start == end => write!(f, "{}", start),
167 (1, 65535) => write!(f, "*"),
168 (start, end) => write!(f, "{}-{}", start, end),
169 }
170 }
171}
172
173impl ProxyPattern {
174 pub fn all_ports() -> Self {
176 Self::check(1, 65535).expect("Somehow, 1-65535 was not a valid pattern")
177 }
178 pub fn one_port(port: u16) -> Result<Self, ProxyConfigError> {
182 Self::check(port, port)
183 }
184 pub fn port_range(low: u16, high: u16) -> Result<Self, ProxyConfigError> {
188 Self::check(low, high)
189 }
190
191 pub(crate) fn matches_port(&self, port: u16) -> bool {
193 self.0.contains(&port)
194 }
195
196 fn check(start: u16, end: u16) -> Result<ProxyPattern, ProxyConfigError> {
199 use ProxyConfigError as PCE;
200 match (start, end) {
201 (_, 0) => Err(PCE::ZeroPort),
202 (0, n) => Ok(Self(1..=n)),
203 (low, high) if low > high => Err(PCE::EmptyPortRange),
204 (low, high) => Ok(Self(low..=high)),
205 }
206 }
207}
208
209#[derive(
214 Clone,
215 Debug,
216 Default,
217 serde_with::DeserializeFromStr,
218 serde_with::SerializeDisplay,
219 Eq,
220 PartialEq,
221 strum::EnumDiscriminants,
222)]
223#[strum_discriminants(derive(Hash, strum::EnumIter))] #[strum_discriminants(derive(strum::IntoStaticStr), strum(serialize_all = "snake_case"))]
225#[strum_discriminants(vis(pub(crate)))]
226#[non_exhaustive]
227pub enum ProxyAction {
228 #[default]
230 DestroyCircuit,
231 Forward(Encapsulation, TargetAddr),
234 RejectStream,
236 IgnoreStream,
238}
239
240#[derive(Clone, Debug, Eq, PartialEq)]
242#[non_exhaustive]
243pub enum TargetAddr {
244 Inet(SocketAddr),
246 }
251
252impl TargetAddr {
253 fn is_sufficiently_private(&self) -> bool {
257 use std::net::IpAddr;
258 match self {
259 TargetAddr::Inet(sa) => match sa.ip() {
268 IpAddr::V4(ip) => ip.is_loopback() || ip.is_unspecified() || ip.is_private(),
269 IpAddr::V6(ip) => ip.is_loopback() || ip.is_unspecified(),
270 },
271 }
272 }
273}
274
275impl FromStr for TargetAddr {
276 type Err = ProxyConfigError;
277
278 fn from_str(s: &str) -> Result<Self, Self::Err> {
279 use ProxyConfigError as PCE;
280
281 fn looks_like_attempted_addr(s: &str) -> bool {
283 s.starts_with(|c: char| c.is_ascii_digit())
284 || s.strip_prefix('[')
285 .map(|rhs| rhs.starts_with(|c: char| c.is_ascii_hexdigit() || c == ':'))
286 .unwrap_or(false)
287 }
288 if let Some(addr) = s.strip_prefix("inet:") {
294 Ok(Self::Inet(addr.parse().map_err(|e| {
295 PCE::InvalidTargetAddr(addr.to_string(), e)
296 })?))
297 } else if looks_like_attempted_addr(s) {
298 Ok(Self::Inet(
300 s.parse()
301 .map_err(|e| PCE::InvalidTargetAddr(s.to_string(), e))?,
302 ))
303 } else {
304 Err(PCE::UnrecognizedTargetType(s.to_string()))
305 }
306 }
307}
308
309impl std::fmt::Display for TargetAddr {
310 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311 match self {
312 TargetAddr::Inet(a) => write!(f, "inet:{}", a),
313 }
316 }
317}
318
319#[derive(Clone, Debug, Default, Eq, PartialEq)]
324#[non_exhaustive]
325pub enum Encapsulation {
326 #[default]
332 Simple,
333}
334
335impl FromStr for ProxyAction {
336 type Err = ProxyConfigError;
337
338 fn from_str(s: &str) -> Result<Self, Self::Err> {
339 if s == "destroy" {
340 Ok(Self::DestroyCircuit)
341 } else if s == "reject" {
342 Ok(Self::RejectStream)
343 } else if s == "ignore" {
344 Ok(Self::IgnoreStream)
345 } else if let Some(addr) = s.strip_prefix("simple:") {
346 Ok(Self::Forward(Encapsulation::Simple, addr.parse()?))
347 } else {
348 Ok(Self::Forward(Encapsulation::Simple, s.parse()?))
349 }
350 }
351}
352
353impl std::fmt::Display for ProxyAction {
354 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
355 match self {
356 ProxyAction::DestroyCircuit => write!(f, "destroy"),
357 ProxyAction::Forward(Encapsulation::Simple, addr) => write!(f, "simple:{}", addr),
358 ProxyAction::RejectStream => write!(f, "reject"),
359 ProxyAction::IgnoreStream => write!(f, "ignore"),
360 }
361 }
362}
363
364#[derive(Debug, Clone, thiserror::Error)]
366#[non_exhaustive]
367pub enum ProxyConfigError {
368 #[error("Could not parse onion service target type {0:?}")]
370 UnrecognizedTargetType(String),
371
372 #[error("Could not parse onion service target address {0:?}")]
374 InvalidTargetAddr(String, #[source] std::net::AddrParseError),
375
376 #[error("Could not parse onion service source port {0:?}")]
378 InvalidPort(String, #[source] std::num::ParseIntError),
379
380 #[error("Zero is not a valid port.")]
382 ZeroPort,
383
384 #[error("Port range is empty.")]
386 EmptyPortRange,
387}
388
389#[cfg(test)]
390mod test {
391 #![allow(clippy::bool_assert_comparison)]
393 #![allow(clippy::clone_on_copy)]
394 #![allow(clippy::dbg_macro)]
395 #![allow(clippy::mixed_attributes_style)]
396 #![allow(clippy::print_stderr)]
397 #![allow(clippy::print_stdout)]
398 #![allow(clippy::single_char_pattern)]
399 #![allow(clippy::unwrap_used)]
400 #![allow(clippy::unchecked_time_subtraction)]
401 #![allow(clippy::useless_vec)]
402 #![allow(clippy::needless_pass_by_value)]
403 #![allow(clippy::string_slice)] use super::*;
406
407 #[test]
408 fn pattern_ok() {
409 use ProxyPattern as P;
410 assert_eq!(P::from_str("*").unwrap(), P(1..=65535));
411 assert_eq!(P::from_str("100").unwrap(), P(100..=100));
412 assert_eq!(P::from_str("100-200").unwrap(), P(100..=200));
413 assert_eq!(P::from_str("0-200").unwrap(), P(1..=200));
414 }
415
416 #[test]
417 fn pattern_display() {
418 use ProxyPattern as P;
419 assert_eq!(P::all_ports().to_string(), "*");
420 assert_eq!(P::one_port(100).unwrap().to_string(), "100");
421 assert_eq!(P::port_range(100, 200).unwrap().to_string(), "100-200");
422 }
423
424 #[test]
425 fn pattern_err() {
426 use ProxyConfigError as PCE;
427 use ProxyPattern as P;
428 assert!(matches!(P::from_str("fred"), Err(PCE::InvalidPort(_, _))));
429 assert!(matches!(
430 P::from_str("100-fred"),
431 Err(PCE::InvalidPort(_, _))
432 ));
433 assert!(matches!(P::from_str("100-42"), Err(PCE::EmptyPortRange)));
434 }
435
436 #[test]
437 fn target_ok() {
438 use Encapsulation::Simple;
439 use ProxyAction as T;
440 use TargetAddr as A;
441 assert!(matches!(T::from_str("reject"), Ok(T::RejectStream)));
442 assert!(matches!(T::from_str("ignore"), Ok(T::IgnoreStream)));
443 assert!(matches!(T::from_str("destroy"), Ok(T::DestroyCircuit)));
444 let sa: SocketAddr = "192.168.1.1:50".parse().unwrap();
445 assert!(
446 matches!(T::from_str("192.168.1.1:50"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa)
447 );
448 assert!(
449 matches!(T::from_str("inet:192.168.1.1:50"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa)
450 );
451 let sa: SocketAddr = "[::1]:999".parse().unwrap();
452 assert!(matches!(T::from_str("[::1]:999"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa));
453 assert!(
454 matches!(T::from_str("inet:[::1]:999"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa)
455 );
456 }
463
464 #[test]
465 fn target_display() {
466 use Encapsulation::Simple;
467 use ProxyAction as T;
468 use TargetAddr as A;
469
470 assert_eq!(T::RejectStream.to_string(), "reject");
471 assert_eq!(T::IgnoreStream.to_string(), "ignore");
472 assert_eq!(T::DestroyCircuit.to_string(), "destroy");
473 assert_eq!(
474 T::Forward(Simple, A::Inet("192.168.1.1:50".parse().unwrap())).to_string(),
475 "simple:inet:192.168.1.1:50"
476 );
477 assert_eq!(
478 T::Forward(Simple, A::Inet("[::1]:999".parse().unwrap())).to_string(),
479 "simple:inet:[::1]:999"
480 );
481 }
488
489 #[test]
490 fn target_err() {
491 use ProxyAction as T;
492 use ProxyConfigError as PCE;
493
494 assert!(matches!(
495 T::from_str("sdakljf"),
496 Err(PCE::UnrecognizedTargetType(_))
497 ));
498
499 assert!(matches!(
500 T::from_str("inet:hello"),
501 Err(PCE::InvalidTargetAddr(_, _))
502 ));
503 assert!(matches!(
504 T::from_str("inet:wwww.example.com:80"),
505 Err(PCE::InvalidTargetAddr(_, _))
506 ));
507
508 assert!(matches!(
509 T::from_str("127.1:80"),
510 Err(PCE::InvalidTargetAddr(_, _))
511 ));
512 assert!(matches!(
513 T::from_str("inet:127.1:80"),
514 Err(PCE::InvalidTargetAddr(_, _))
515 ));
516 assert!(matches!(
517 T::from_str("127.1:80"),
518 Err(PCE::InvalidTargetAddr(_, _))
519 ));
520 assert!(matches!(
521 T::from_str("inet:2130706433:80"),
522 Err(PCE::InvalidTargetAddr(_, _))
523 ));
524
525 assert!(matches!(
526 T::from_str("128.256.cats.and.dogs"),
527 Err(PCE::InvalidTargetAddr(_, _))
528 ));
529 }
530
531 #[test]
532 fn deserialize() {
533 use Encapsulation::Simple;
534 use TargetAddr as A;
535 let ex = r#"{
536 "proxy_ports": [
537 [ "443", "127.0.0.1:11443" ],
538 [ "80", "ignore" ],
539 [ "*", "destroy" ]
540 ]
541 }"#;
542 let bld: ProxyConfigBuilder = serde_json::from_str(ex).unwrap();
543 let cfg = bld.build().unwrap();
544 assert_eq!(cfg.proxy_ports.len(), 3);
545 assert_eq!(cfg.proxy_ports[0].source.0, 443..=443);
546 assert_eq!(cfg.proxy_ports[1].source.0, 80..=80);
547 assert_eq!(cfg.proxy_ports[2].source.0, 1..=65535);
548
549 assert_eq!(
550 cfg.proxy_ports[0].target,
551 ProxyAction::Forward(Simple, A::Inet("127.0.0.1:11443".parse().unwrap()))
552 );
553 assert_eq!(cfg.proxy_ports[1].target, ProxyAction::IgnoreStream);
554 assert_eq!(cfg.proxy_ports[2].target, ProxyAction::DestroyCircuit);
555 }
556
557 #[test]
558 fn validation_fail() {
559 let ex = r#"{
561 "proxy_ports": [
562 [ "2-300", "127.0.0.1:11443" ],
563 [ "301-999", "ignore" ],
564 [ "30-310", "destroy" ]
565 ]
566 }"#;
567 let bld: ProxyConfigBuilder = serde_json::from_str(ex).unwrap();
568 match bld.build() {
569 Err(ConfigBuildError::Invalid { field, problem }) => {
570 assert_eq!(field, "proxy_ports");
571 assert_eq!(problem, "Port pattern 30-310 is not reachable");
572 }
573 other => panic!("Expected an Invalid error; got {other:?}"),
574 }
575
576 let ex = r#"{
578 "proxy_ports": [
579 [ "2-300", "127.0.0.1:11443" ],
580 [ "302-999", "ignore" ],
581 [ "30-310", "destroy" ]
582 ]
583 }"#;
584 let bld: ProxyConfigBuilder = serde_json::from_str(ex).unwrap();
585 assert!(bld.build().is_ok());
586 }
587
588 #[test]
589 fn demo() {
590 let b: ProxyConfigBuilder = toml::de::from_str(
591 r#"
592proxy_ports = [
593 [ 80, "127.0.0.1:10080"],
594 ["22", "destroy"],
595 ["265", "ignore"],
596 # ["1-1024", "unix:/var/run/allium-cepa/socket"], # TODO (#1246))
597]
598"#,
599 )
600 .unwrap();
601 let c = b.build().unwrap();
602 assert_eq!(c.proxy_ports.len(), 3);
603 assert_eq!(
604 c.proxy_ports[0],
605 ProxyRule::new(
606 ProxyPattern::one_port(80).unwrap(),
607 ProxyAction::Forward(
608 Encapsulation::Simple,
609 TargetAddr::Inet("127.0.0.1:10080".parse().unwrap())
610 )
611 )
612 );
613 assert_eq!(
614 c.proxy_ports[1],
615 ProxyRule::new(
616 ProxyPattern::one_port(22).unwrap(),
617 ProxyAction::DestroyCircuit
618 )
619 );
620 assert_eq!(
621 c.proxy_ports[2],
622 ProxyRule::new(
623 ProxyPattern::one_port(265).unwrap(),
624 ProxyAction::IgnoreStream
625 )
626 );
627 }
640}