pub mod policy;
#[cfg(feature = "client")]
pub mod client;
pub use crate::policy::{
AddressPolicy, AddressPolicyAction, AddressPolicyRule, AddressPortPattern, PolicyError,
PortRange,
};
pub(crate) const EXIT_POLICY_FIELD_NAME: &str = "ExitPolicy";
const COMMENT_CHAR: char = '#';
pub type ExitPolicy = AddressPolicy;
pub fn parse_exit_policy<S: AsRef<str>>(exit_policy: S) -> Result<ExitPolicy, PolicyError> {
let rules = exit_policy
.as_ref()
.lines()
.map(|maybe_rule| {
if let Some(comment_start) = maybe_rule.find(COMMENT_CHAR) {
&maybe_rule[..comment_start]
} else {
maybe_rule
}
.trim()
})
.filter(|maybe_rule| !maybe_rule.is_empty())
.map(parse_address_policy_rule)
.collect::<Result<Vec<_>, _>>()?;
Ok(AddressPolicy { rules })
}
pub fn format_exit_policy(policy: &ExitPolicy) -> String {
policy
.rules
.iter()
.map(|rule| format!("{EXIT_POLICY_FIELD_NAME} {rule}"))
.fold(String::new(), |accumulator, rule| {
accumulator + &rule + "\n"
})
.trim_end()
.to_string()
}
fn parse_address_policy_rule(rule: &str) -> Result<AddressPolicyRule, PolicyError> {
rule.strip_prefix(EXIT_POLICY_FIELD_NAME)
.ok_or(PolicyError::NoExitPolicyPrefix {
entry: rule.to_string(),
})?
.trim()
.parse()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::policy::AddressPolicyAction::{Accept, Accept6, Reject, Reject6};
use crate::policy::IpPattern;
#[test]
fn parsing_policy() {
let sample = r#"
ExitPolicy reject 1.2.3.4/32:*#comment
ExitPolicy reject 1.2.3.5:* #comment
ExitPolicy reject 1.2.3.6/16:*
ExitPolicy reject 1.2.3.6/16:123-456 # comment
ExitPolicy accept *:53 # DNS
# random comment
ExitPolicy accept6 *6:119
ExitPolicy accept *4:120
ExitPolicy reject6 [FC00::]/7:*
# Portless
ExitPolicy accept *:0
ExitPolicy accept *4:0
ExitPolicy accept *6:0
ExitPolicy reject *:0
ExitPolicy reject *4:0
ExitPolicy reject *6:0
#ExitPolicy accept *:8080 #and another comment here
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8329:*
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8328:1234
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8328/64:1235
#another comment
#ExitPolicy accept *:8080
ExitPolicy reject *:*
"#;
let res = parse_exit_policy(sample).unwrap();
let mut expected = AddressPolicy::new();
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V4 {
addr_prefix: "1.2.3.4".parse().unwrap(),
mask: 32,
},
ports: PortRange::new_all(),
},
);
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V4 {
addr_prefix: "1.2.3.5".parse().unwrap(),
mask: 32,
},
ports: PortRange::new_all(),
},
);
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V4 {
addr_prefix: "1.2.3.6".parse().unwrap(),
mask: 16,
},
ports: PortRange::new_all(),
},
);
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V4 {
addr_prefix: "1.2.3.6".parse().unwrap(),
mask: 16,
},
ports: PortRange::new(123, 456).unwrap(),
},
);
expected.push(
Accept,
AddressPortPattern {
ip_pattern: IpPattern::Star,
ports: PortRange::new_singleton(53),
},
);
expected.push(
Accept6,
AddressPortPattern {
ip_pattern: IpPattern::V6Star,
ports: PortRange::new_singleton(119),
},
);
expected.push(
Accept,
AddressPortPattern {
ip_pattern: IpPattern::V4Star,
ports: PortRange::new_singleton(120),
},
);
expected.push(
Reject6,
AddressPortPattern {
ip_pattern: IpPattern::V6 {
addr_prefix: "FC00::".parse().unwrap(),
mask: 7,
},
ports: PortRange::new_all(),
},
);
expected.push(
Accept,
AddressPortPattern {
ip_pattern: IpPattern::Star,
ports: PortRange::new_zero(),
},
);
expected.push(
Accept,
AddressPortPattern {
ip_pattern: IpPattern::V4Star,
ports: PortRange::new_zero(),
},
);
expected.push(
Accept,
AddressPortPattern {
ip_pattern: IpPattern::V6Star,
ports: PortRange::new_zero(),
},
);
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::Star,
ports: PortRange::new_zero(),
},
);
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V4Star,
ports: PortRange::new_zero(),
},
);
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V6Star,
ports: PortRange::new_zero(),
},
);
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V6 {
addr_prefix: "FE80:0000:0000:0000:0202:B3FF:FE1E:8329".parse().unwrap(),
mask: 128,
},
ports: PortRange::new_all(),
},
);
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V6 {
addr_prefix: "FE80:0000:0000:0000:0202:B3FF:FE1E:8328".parse().unwrap(),
mask: 128,
},
ports: PortRange::new_singleton(1234),
},
);
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V6 {
addr_prefix: "FE80:0000:0000:0000:0202:B3FF:FE1E:8328".parse().unwrap(),
mask: 64,
},
ports: PortRange::new_singleton(1235),
},
);
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::Star,
ports: PortRange::new_all(),
},
);
assert_eq!(res, expected)
}
}