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 use super::*;
405
406 #[test]
407 fn pattern_ok() {
408 use ProxyPattern as P;
409 assert_eq!(P::from_str("*").unwrap(), P(1..=65535));
410 assert_eq!(P::from_str("100").unwrap(), P(100..=100));
411 assert_eq!(P::from_str("100-200").unwrap(), P(100..=200));
412 assert_eq!(P::from_str("0-200").unwrap(), P(1..=200));
413 }
414
415 #[test]
416 fn pattern_display() {
417 use ProxyPattern as P;
418 assert_eq!(P::all_ports().to_string(), "*");
419 assert_eq!(P::one_port(100).unwrap().to_string(), "100");
420 assert_eq!(P::port_range(100, 200).unwrap().to_string(), "100-200");
421 }
422
423 #[test]
424 fn pattern_err() {
425 use ProxyConfigError as PCE;
426 use ProxyPattern as P;
427 assert!(matches!(P::from_str("fred"), Err(PCE::InvalidPort(_, _))));
428 assert!(matches!(
429 P::from_str("100-fred"),
430 Err(PCE::InvalidPort(_, _))
431 ));
432 assert!(matches!(P::from_str("100-42"), Err(PCE::EmptyPortRange)));
433 }
434
435 #[test]
436 fn target_ok() {
437 use Encapsulation::Simple;
438 use ProxyAction as T;
439 use TargetAddr as A;
440 assert!(matches!(T::from_str("reject"), Ok(T::RejectStream)));
441 assert!(matches!(T::from_str("ignore"), Ok(T::IgnoreStream)));
442 assert!(matches!(T::from_str("destroy"), Ok(T::DestroyCircuit)));
443 let sa: SocketAddr = "192.168.1.1:50".parse().unwrap();
444 assert!(
445 matches!(T::from_str("192.168.1.1:50"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa)
446 );
447 assert!(
448 matches!(T::from_str("inet:192.168.1.1:50"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa)
449 );
450 let sa: SocketAddr = "[::1]:999".parse().unwrap();
451 assert!(matches!(T::from_str("[::1]:999"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa));
452 assert!(
453 matches!(T::from_str("inet:[::1]:999"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa)
454 );
455 }
462
463 #[test]
464 fn target_display() {
465 use Encapsulation::Simple;
466 use ProxyAction as T;
467 use TargetAddr as A;
468
469 assert_eq!(T::RejectStream.to_string(), "reject");
470 assert_eq!(T::IgnoreStream.to_string(), "ignore");
471 assert_eq!(T::DestroyCircuit.to_string(), "destroy");
472 assert_eq!(
473 T::Forward(Simple, A::Inet("192.168.1.1:50".parse().unwrap())).to_string(),
474 "simple:inet:192.168.1.1:50"
475 );
476 assert_eq!(
477 T::Forward(Simple, A::Inet("[::1]:999".parse().unwrap())).to_string(),
478 "simple:inet:[::1]:999"
479 );
480 }
487
488 #[test]
489 fn target_err() {
490 use ProxyAction as T;
491 use ProxyConfigError as PCE;
492
493 assert!(matches!(
494 T::from_str("sdakljf"),
495 Err(PCE::UnrecognizedTargetType(_))
496 ));
497
498 assert!(matches!(
499 T::from_str("inet:hello"),
500 Err(PCE::InvalidTargetAddr(_, _))
501 ));
502 assert!(matches!(
503 T::from_str("inet:wwww.example.com:80"),
504 Err(PCE::InvalidTargetAddr(_, _))
505 ));
506
507 assert!(matches!(
508 T::from_str("127.1:80"),
509 Err(PCE::InvalidTargetAddr(_, _))
510 ));
511 assert!(matches!(
512 T::from_str("inet:127.1:80"),
513 Err(PCE::InvalidTargetAddr(_, _))
514 ));
515 assert!(matches!(
516 T::from_str("127.1:80"),
517 Err(PCE::InvalidTargetAddr(_, _))
518 ));
519 assert!(matches!(
520 T::from_str("inet:2130706433:80"),
521 Err(PCE::InvalidTargetAddr(_, _))
522 ));
523
524 assert!(matches!(
525 T::from_str("128.256.cats.and.dogs"),
526 Err(PCE::InvalidTargetAddr(_, _))
527 ));
528 }
529
530 #[test]
531 fn deserialize() {
532 use Encapsulation::Simple;
533 use TargetAddr as A;
534 let ex = r#"{
535 "proxy_ports": [
536 [ "443", "127.0.0.1:11443" ],
537 [ "80", "ignore" ],
538 [ "*", "destroy" ]
539 ]
540 }"#;
541 let bld: ProxyConfigBuilder = serde_json::from_str(ex).unwrap();
542 let cfg = bld.build().unwrap();
543 assert_eq!(cfg.proxy_ports.len(), 3);
544 assert_eq!(cfg.proxy_ports[0].source.0, 443..=443);
545 assert_eq!(cfg.proxy_ports[1].source.0, 80..=80);
546 assert_eq!(cfg.proxy_ports[2].source.0, 1..=65535);
547
548 assert_eq!(
549 cfg.proxy_ports[0].target,
550 ProxyAction::Forward(Simple, A::Inet("127.0.0.1:11443".parse().unwrap()))
551 );
552 assert_eq!(cfg.proxy_ports[1].target, ProxyAction::IgnoreStream);
553 assert_eq!(cfg.proxy_ports[2].target, ProxyAction::DestroyCircuit);
554 }
555
556 #[test]
557 fn validation_fail() {
558 let ex = r#"{
560 "proxy_ports": [
561 [ "2-300", "127.0.0.1:11443" ],
562 [ "301-999", "ignore" ],
563 [ "30-310", "destroy" ]
564 ]
565 }"#;
566 let bld: ProxyConfigBuilder = serde_json::from_str(ex).unwrap();
567 match bld.build() {
568 Err(ConfigBuildError::Invalid { field, problem }) => {
569 assert_eq!(field, "proxy_ports");
570 assert_eq!(problem, "Port pattern 30-310 is not reachable");
571 }
572 other => panic!("Expected an Invalid error; got {other:?}"),
573 }
574
575 let ex = r#"{
577 "proxy_ports": [
578 [ "2-300", "127.0.0.1:11443" ],
579 [ "302-999", "ignore" ],
580 [ "30-310", "destroy" ]
581 ]
582 }"#;
583 let bld: ProxyConfigBuilder = serde_json::from_str(ex).unwrap();
584 assert!(bld.build().is_ok());
585 }
586
587 #[test]
588 fn demo() {
589 let b: ProxyConfigBuilder = toml::de::from_str(
590 r#"
591proxy_ports = [
592 [ 80, "127.0.0.1:10080"],
593 ["22", "destroy"],
594 ["265", "ignore"],
595 # ["1-1024", "unix:/var/run/allium-cepa/socket"], # TODO (#1246))
596]
597"#,
598 )
599 .unwrap();
600 let c = b.build().unwrap();
601 assert_eq!(c.proxy_ports.len(), 3);
602 assert_eq!(
603 c.proxy_ports[0],
604 ProxyRule::new(
605 ProxyPattern::one_port(80).unwrap(),
606 ProxyAction::Forward(
607 Encapsulation::Simple,
608 TargetAddr::Inet("127.0.0.1:10080".parse().unwrap())
609 )
610 )
611 );
612 assert_eq!(
613 c.proxy_ports[1],
614 ProxyRule::new(
615 ProxyPattern::one_port(22).unwrap(),
616 ProxyAction::DestroyCircuit
617 )
618 );
619 assert_eq!(
620 c.proxy_ports[2],
621 ProxyRule::new(
622 ProxyPattern::one_port(265).unwrap(),
623 ProxyAction::IgnoreStream
624 )
625 );
626 }
639}