1use derive_builder::Builder;
4use derive_deftly::Deftly;
5use serde::{Deserialize, Serialize};
6use std::{net::SocketAddr, ops::RangeInclusive, str::FromStr};
7use tracing::warn;
8use tor_config::{define_list_builder_accessors, define_list_builder_helper, ConfigBuildError};
10
11#[derive(Clone, Debug, Builder, Eq, PartialEq)]
13#[builder(build_fn(error = "ConfigBuildError", validate = "Self::validate"))]
14#[builder(derive(Debug, Serialize, Deserialize, Deftly, Eq, PartialEq))]
15#[builder_struct_attr(derive_deftly(tor_config::Flattenable))]
16pub struct ProxyConfig {
17 #[builder(sub_builder, setter(custom))]
20 pub(crate) proxy_ports: ProxyRuleList,
21 }
25
26impl ProxyConfigBuilder {
27 fn validate(&self) -> Result<(), ConfigBuildError> {
29 let mut covered = rangemap::RangeInclusiveSet::<u16>::new();
31 for rule in self.proxy_ports.access_opt().iter().flatten() {
32 let range = &rule.source.0;
33 if covered.gaps(range).next().is_none() {
34 return Err(ConfigBuildError::Invalid {
35 field: "proxy_ports".into(),
36 problem: format!("Port pattern {} is not reachable", rule.source),
37 });
38 }
39 covered.insert(range.clone());
40 }
41
42 let mut any_forward = false;
44 for rule in self.proxy_ports.access_opt().iter().flatten() {
45 if let ProxyAction::Forward(_, target) = &rule.target {
46 any_forward = true;
47 if !target.is_sufficiently_private() {
48 warn!(
54 "Onion service target {} does not look like a private address. \
55 Do you really mean to send connections onto the public internet?",
56 target
57 );
58 }
59 }
60 }
61
62 if !any_forward {
63 warn!("Onion service is not configured to accept any connections.");
64 }
65
66 Ok(())
67 }
68}
69
70define_list_builder_accessors! {
71 struct ProxyConfigBuilder {
72 pub proxy_ports: [ProxyRule],
73 }
74}
75
76type ProxyRuleList = Vec<ProxyRule>;
78
79define_list_builder_helper! {
80 #[derive(Eq, PartialEq)]
81 pub struct ProxyRuleListBuilder {
82 pub(crate) values: [ProxyRule],
83 }
84 built: ProxyRuleList = values;
85 default = vec![];
86 item_build: |value| Ok(value.clone());
87}
88
89impl ProxyConfig {
90 pub(crate) fn resolve_port_for_begin(&self, port: u16) -> Option<&ProxyAction> {
93 self.proxy_ports
94 .iter()
95 .find(|rule| rule.source.matches_port(port))
96 .map(|rule| &rule.target)
97 }
98}
99
100#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
104#[serde(from = "ProxyRuleAsTuple", into = "ProxyRuleAsTuple")]
108pub struct ProxyRule {
109 source: ProxyPattern,
111 target: ProxyAction,
113}
114
115type ProxyRuleAsTuple = (ProxyPattern, ProxyAction);
117impl From<ProxyRuleAsTuple> for ProxyRule {
118 fn from(value: ProxyRuleAsTuple) -> Self {
119 Self {
120 source: value.0,
121 target: value.1,
122 }
123 }
124}
125impl From<ProxyRule> for ProxyRuleAsTuple {
126 fn from(value: ProxyRule) -> Self {
127 (value.source, value.target)
128 }
129}
130impl ProxyRule {
131 pub fn new(source: ProxyPattern, target: ProxyAction) -> Self {
133 Self { source, target }
134 }
135}
136
137#[derive(Clone, Debug, serde::Deserialize, serde_with::SerializeDisplay, Eq, PartialEq)]
139#[serde(try_from = "ProxyPatternAsEnum")]
140pub struct ProxyPattern(RangeInclusive<u16>);
141
142#[derive(serde::Deserialize)]
144#[serde(untagged)]
145enum ProxyPatternAsEnum {
146 Number(u16),
148 String(String),
150}
151
152impl TryFrom<ProxyPatternAsEnum> for ProxyPattern {
153 type Error = ProxyConfigError;
154
155 fn try_from(value: ProxyPatternAsEnum) -> Result<Self, Self::Error> {
156 match value {
157 ProxyPatternAsEnum::Number(port) => Self::one_port(port),
158 ProxyPatternAsEnum::String(s) => Self::from_str(&s),
159 }
160 }
161}
162
163impl FromStr for ProxyPattern {
164 type Err = ProxyConfigError;
165
166 fn from_str(s: &str) -> Result<Self, Self::Err> {
167 use ProxyConfigError as PCE;
168 if s == "*" {
169 Ok(Self::all_ports())
170 } else if let Some((left, right)) = s.split_once('-') {
171 let left: u16 = left
172 .parse()
173 .map_err(|e| PCE::InvalidPort(left.to_string(), e))?;
174 let right: u16 = right
175 .parse()
176 .map_err(|e| PCE::InvalidPort(right.to_string(), e))?;
177 Self::port_range(left, right)
178 } else {
179 let port = s.parse().map_err(|e| PCE::InvalidPort(s.to_string(), e))?;
180 Self::one_port(port)
181 }
182 }
183}
184impl std::fmt::Display for ProxyPattern {
185 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186 match self.0.clone().into_inner() {
187 (start, end) if start == end => write!(f, "{}", start),
188 (1, 65535) => write!(f, "*"),
189 (start, end) => write!(f, "{}-{}", start, end),
190 }
191 }
192}
193
194impl ProxyPattern {
195 pub fn all_ports() -> Self {
197 Self::check(1, 65535).expect("Somehow, 1-65535 was not a valid pattern")
198 }
199 pub fn one_port(port: u16) -> Result<Self, ProxyConfigError> {
203 Self::check(port, port)
204 }
205 pub fn port_range(low: u16, high: u16) -> Result<Self, ProxyConfigError> {
209 Self::check(low, high)
210 }
211
212 pub(crate) fn matches_port(&self, port: u16) -> bool {
214 self.0.contains(&port)
215 }
216
217 fn check(start: u16, end: u16) -> Result<ProxyPattern, ProxyConfigError> {
220 use ProxyConfigError as PCE;
221 match (start, end) {
222 (_, 0) => Err(PCE::ZeroPort),
223 (0, n) => Ok(Self(1..=n)),
224 (low, high) if low > high => Err(PCE::EmptyPortRange),
225 (low, high) => Ok(Self(low..=high)),
226 }
227 }
228}
229
230#[derive(
235 Clone,
236 Debug,
237 Default,
238 serde_with::DeserializeFromStr,
239 serde_with::SerializeDisplay,
240 Eq,
241 PartialEq,
242 strum::EnumDiscriminants,
243)]
244#[strum_discriminants(derive(Hash, strum::EnumIter))] #[strum_discriminants(derive(strum::IntoStaticStr), strum(serialize_all = "snake_case"))]
246#[strum_discriminants(vis(pub(crate)))]
247#[non_exhaustive]
248pub enum ProxyAction {
249 #[default]
251 DestroyCircuit,
252 Forward(Encapsulation, TargetAddr),
255 RejectStream,
257 IgnoreStream,
259}
260
261#[derive(Clone, Debug, Eq, PartialEq)]
263#[non_exhaustive]
264pub enum TargetAddr {
265 Inet(SocketAddr),
267 }
272
273impl TargetAddr {
274 fn is_sufficiently_private(&self) -> bool {
278 use std::net::IpAddr;
279 match self {
280 TargetAddr::Inet(sa) => match sa.ip() {
289 IpAddr::V4(ip) => ip.is_loopback() || ip.is_unspecified() || ip.is_private(),
290 IpAddr::V6(ip) => ip.is_loopback() || ip.is_unspecified(),
291 },
292 }
293 }
294}
295
296impl FromStr for TargetAddr {
297 type Err = ProxyConfigError;
298
299 fn from_str(s: &str) -> Result<Self, Self::Err> {
300 use ProxyConfigError as PCE;
301
302 fn looks_like_attempted_addr(s: &str) -> bool {
304 s.starts_with(|c: char| c.is_ascii_digit())
305 || s.strip_prefix('[')
306 .map(|rhs| rhs.starts_with(|c: char| c.is_ascii_hexdigit() || c == ':'))
307 .unwrap_or(false)
308 }
309 if let Some(addr) = s.strip_prefix("inet:") {
315 Ok(Self::Inet(addr.parse().map_err(|e| {
316 PCE::InvalidTargetAddr(addr.to_string(), e)
317 })?))
318 } else if looks_like_attempted_addr(s) {
319 Ok(Self::Inet(
321 s.parse()
322 .map_err(|e| PCE::InvalidTargetAddr(s.to_string(), e))?,
323 ))
324 } else {
325 Err(PCE::UnrecognizedTargetType(s.to_string()))
326 }
327 }
328}
329
330impl std::fmt::Display for TargetAddr {
331 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
332 match self {
333 TargetAddr::Inet(a) => write!(f, "inet:{}", a),
334 }
337 }
338}
339
340#[derive(Clone, Debug, Default, Eq, PartialEq)]
345#[non_exhaustive]
346pub enum Encapsulation {
347 #[default]
353 Simple,
354}
355
356impl FromStr for ProxyAction {
357 type Err = ProxyConfigError;
358
359 fn from_str(s: &str) -> Result<Self, Self::Err> {
360 if s == "destroy" {
361 Ok(Self::DestroyCircuit)
362 } else if s == "reject" {
363 Ok(Self::RejectStream)
364 } else if s == "ignore" {
365 Ok(Self::IgnoreStream)
366 } else if let Some(addr) = s.strip_prefix("simple:") {
367 Ok(Self::Forward(Encapsulation::Simple, addr.parse()?))
368 } else {
369 Ok(Self::Forward(Encapsulation::Simple, s.parse()?))
370 }
371 }
372}
373
374impl std::fmt::Display for ProxyAction {
375 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376 match self {
377 ProxyAction::DestroyCircuit => write!(f, "destroy"),
378 ProxyAction::Forward(Encapsulation::Simple, addr) => write!(f, "simple:{}", addr),
379 ProxyAction::RejectStream => write!(f, "reject"),
380 ProxyAction::IgnoreStream => write!(f, "ignore"),
381 }
382 }
383}
384
385#[derive(Debug, Clone, thiserror::Error)]
387#[non_exhaustive]
388pub enum ProxyConfigError {
389 #[error("Could not parse onion service target type {0:?}")]
391 UnrecognizedTargetType(String),
392
393 #[error("Could not parse onion service target address {0:?}")]
395 InvalidTargetAddr(String, #[source] std::net::AddrParseError),
396
397 #[error("Could not parse onion service source port {0:?}")]
399 InvalidPort(String, #[source] std::num::ParseIntError),
400
401 #[error("Zero is not a valid port.")]
403 ZeroPort,
404
405 #[error("Port range is empty.")]
407 EmptyPortRange,
408}
409
410#[cfg(test)]
411mod test {
412 #![allow(clippy::bool_assert_comparison)]
414 #![allow(clippy::clone_on_copy)]
415 #![allow(clippy::dbg_macro)]
416 #![allow(clippy::mixed_attributes_style)]
417 #![allow(clippy::print_stderr)]
418 #![allow(clippy::print_stdout)]
419 #![allow(clippy::single_char_pattern)]
420 #![allow(clippy::unwrap_used)]
421 #![allow(clippy::unchecked_duration_subtraction)]
422 #![allow(clippy::useless_vec)]
423 #![allow(clippy::needless_pass_by_value)]
424 use super::*;
426
427 #[test]
428 fn pattern_ok() {
429 use ProxyPattern as P;
430 assert_eq!(P::from_str("*").unwrap(), P(1..=65535));
431 assert_eq!(P::from_str("100").unwrap(), P(100..=100));
432 assert_eq!(P::from_str("100-200").unwrap(), P(100..=200));
433 assert_eq!(P::from_str("0-200").unwrap(), P(1..=200));
434 }
435
436 #[test]
437 fn pattern_display() {
438 use ProxyPattern as P;
439 assert_eq!(P::all_ports().to_string(), "*");
440 assert_eq!(P::one_port(100).unwrap().to_string(), "100");
441 assert_eq!(P::port_range(100, 200).unwrap().to_string(), "100-200");
442 }
443
444 #[test]
445 fn pattern_err() {
446 use ProxyConfigError as PCE;
447 use ProxyPattern as P;
448 assert!(matches!(P::from_str("fred"), Err(PCE::InvalidPort(_, _))));
449 assert!(matches!(
450 P::from_str("100-fred"),
451 Err(PCE::InvalidPort(_, _))
452 ));
453 assert!(matches!(P::from_str("100-42"), Err(PCE::EmptyPortRange)));
454 }
455
456 #[test]
457 fn target_ok() {
458 use Encapsulation::Simple;
459 use ProxyAction as T;
460 use TargetAddr as A;
461 assert!(matches!(T::from_str("reject"), Ok(T::RejectStream)));
462 assert!(matches!(T::from_str("ignore"), Ok(T::IgnoreStream)));
463 assert!(matches!(T::from_str("destroy"), Ok(T::DestroyCircuit)));
464 let sa: SocketAddr = "192.168.1.1:50".parse().unwrap();
465 assert!(
466 matches!(T::from_str("192.168.1.1:50"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa)
467 );
468 assert!(
469 matches!(T::from_str("inet:192.168.1.1:50"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa)
470 );
471 let sa: SocketAddr = "[::1]:999".parse().unwrap();
472 assert!(matches!(T::from_str("[::1]:999"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa));
473 assert!(
474 matches!(T::from_str("inet:[::1]:999"), Ok(T::Forward(Simple, A::Inet(a))) if a == sa)
475 );
476 }
483
484 #[test]
485 fn target_display() {
486 use Encapsulation::Simple;
487 use ProxyAction as T;
488 use TargetAddr as A;
489
490 assert_eq!(T::RejectStream.to_string(), "reject");
491 assert_eq!(T::IgnoreStream.to_string(), "ignore");
492 assert_eq!(T::DestroyCircuit.to_string(), "destroy");
493 assert_eq!(
494 T::Forward(Simple, A::Inet("192.168.1.1:50".parse().unwrap())).to_string(),
495 "simple:inet:192.168.1.1:50"
496 );
497 assert_eq!(
498 T::Forward(Simple, A::Inet("[::1]:999".parse().unwrap())).to_string(),
499 "simple:inet:[::1]:999"
500 );
501 }
508
509 #[test]
510 fn target_err() {
511 use ProxyAction as T;
512 use ProxyConfigError as PCE;
513
514 assert!(matches!(
515 T::from_str("sdakljf"),
516 Err(PCE::UnrecognizedTargetType(_))
517 ));
518
519 assert!(matches!(
520 T::from_str("inet:hello"),
521 Err(PCE::InvalidTargetAddr(_, _))
522 ));
523 assert!(matches!(
524 T::from_str("inet:wwww.example.com:80"),
525 Err(PCE::InvalidTargetAddr(_, _))
526 ));
527
528 assert!(matches!(
529 T::from_str("127.1:80"),
530 Err(PCE::InvalidTargetAddr(_, _))
531 ));
532 assert!(matches!(
533 T::from_str("inet:127.1:80"),
534 Err(PCE::InvalidTargetAddr(_, _))
535 ));
536 assert!(matches!(
537 T::from_str("127.1:80"),
538 Err(PCE::InvalidTargetAddr(_, _))
539 ));
540 assert!(matches!(
541 T::from_str("inet:2130706433:80"),
542 Err(PCE::InvalidTargetAddr(_, _))
543 ));
544
545 assert!(matches!(
546 T::from_str("128.256.cats.and.dogs"),
547 Err(PCE::InvalidTargetAddr(_, _))
548 ));
549 }
550
551 #[test]
552 fn deserialize() {
553 use Encapsulation::Simple;
554 use TargetAddr as A;
555 let ex = r#"{
556 "proxy_ports": [
557 [ "443", "127.0.0.1:11443" ],
558 [ "80", "ignore" ],
559 [ "*", "destroy" ]
560 ]
561 }"#;
562 let bld: ProxyConfigBuilder = serde_json::from_str(ex).unwrap();
563 let cfg = bld.build().unwrap();
564 assert_eq!(cfg.proxy_ports.len(), 3);
565 assert_eq!(cfg.proxy_ports[0].source.0, 443..=443);
566 assert_eq!(cfg.proxy_ports[1].source.0, 80..=80);
567 assert_eq!(cfg.proxy_ports[2].source.0, 1..=65535);
568
569 assert_eq!(
570 cfg.proxy_ports[0].target,
571 ProxyAction::Forward(Simple, A::Inet("127.0.0.1:11443".parse().unwrap()))
572 );
573 assert_eq!(cfg.proxy_ports[1].target, ProxyAction::IgnoreStream);
574 assert_eq!(cfg.proxy_ports[2].target, ProxyAction::DestroyCircuit);
575 }
576
577 #[test]
578 fn validation_fail() {
579 let ex = r#"{
581 "proxy_ports": [
582 [ "2-300", "127.0.0.1:11443" ],
583 [ "301-999", "ignore" ],
584 [ "30-310", "destroy" ]
585 ]
586 }"#;
587 let bld: ProxyConfigBuilder = serde_json::from_str(ex).unwrap();
588 match bld.build() {
589 Err(ConfigBuildError::Invalid { field, problem }) => {
590 assert_eq!(field, "proxy_ports");
591 assert_eq!(problem, "Port pattern 30-310 is not reachable");
592 }
593 other => panic!("Expected an Invalid error; got {other:?}"),
594 }
595
596 let ex = r#"{
598 "proxy_ports": [
599 [ "2-300", "127.0.0.1:11443" ],
600 [ "302-999", "ignore" ],
601 [ "30-310", "destroy" ]
602 ]
603 }"#;
604 let bld: ProxyConfigBuilder = serde_json::from_str(ex).unwrap();
605 assert!(bld.build().is_ok());
606 }
607
608 #[test]
609 fn demo() {
610 let b: ProxyConfigBuilder = toml::de::from_str(
611 r#"
612proxy_ports = [
613 [ 80, "127.0.0.1:10080"],
614 ["22", "destroy"],
615 ["265", "ignore"],
616 # ["1-1024", "unix:/var/run/allium-cepa/socket"], # TODO (#1246))
617]
618"#,
619 )
620 .unwrap();
621 let c = b.build().unwrap();
622 assert_eq!(c.proxy_ports.len(), 3);
623 assert_eq!(
624 c.proxy_ports[0],
625 ProxyRule::new(
626 ProxyPattern::one_port(80).unwrap(),
627 ProxyAction::Forward(
628 Encapsulation::Simple,
629 TargetAddr::Inet("127.0.0.1:10080".parse().unwrap())
630 )
631 )
632 );
633 assert_eq!(
634 c.proxy_ports[1],
635 ProxyRule::new(
636 ProxyPattern::one_port(22).unwrap(),
637 ProxyAction::DestroyCircuit
638 )
639 );
640 assert_eq!(
641 c.proxy_ports[2],
642 ProxyRule::new(
643 ProxyPattern::one_port(265).unwrap(),
644 ProxyAction::IgnoreStream
645 )
646 );
647 }
660}