1pub mod policy;
5
6#[cfg(feature = "client")]
7pub mod client;
8
9pub use crate::policy::{
10 AddressPolicy, AddressPolicyAction, AddressPolicyRule, AddressPortPattern, PolicyError,
11 PortRange,
12};
13
14pub(crate) const EXIT_POLICY_FIELD_NAME: &str = "ExitPolicy";
15const COMMENT_CHAR: char = '#';
16
17pub type ExitPolicy = AddressPolicy;
18
19pub fn parse_exit_policy<S: AsRef<str>>(exit_policy: S) -> Result<ExitPolicy, PolicyError> {
20 let rules = exit_policy
21 .as_ref()
22 .lines()
23 .map(|maybe_rule| {
24 if let Some(comment_start) = maybe_rule.find(COMMENT_CHAR) {
25 &maybe_rule[..comment_start]
26 } else {
27 maybe_rule
28 }
29 .trim()
30 })
31 .filter(|maybe_rule| !maybe_rule.is_empty())
32 .map(parse_address_policy_rule)
33 .collect::<Result<Vec<_>, _>>()?;
34
35 Ok(AddressPolicy { rules })
36}
37
38pub fn format_exit_policy(policy: &ExitPolicy) -> String {
39 policy
40 .rules
41 .iter()
42 .map(|rule| format!("{EXIT_POLICY_FIELD_NAME} {rule}"))
43 .fold(String::new(), |accumulator, rule| {
44 accumulator + &rule + "\n"
45 })
46 .trim_end()
47 .to_string()
48}
49
50fn parse_address_policy_rule(rule: &str) -> Result<AddressPolicyRule, PolicyError> {
51 rule.strip_prefix(EXIT_POLICY_FIELD_NAME)
53 .ok_or(PolicyError::NoExitPolicyPrefix {
54 entry: rule.to_string(),
55 })?
56 .trim()
57 .parse()
58}
59
60#[cfg(test)]
63mod tests {
64 use super::*;
65 use crate::policy::AddressPolicyAction::{Accept, Accept6, Reject, Reject6};
66 use crate::policy::IpPattern;
67
68 #[test]
69 fn parsing_policy() {
70 let sample = r#"
71ExitPolicy reject 1.2.3.4/32:*#comment
72ExitPolicy reject 1.2.3.5:* #comment
73ExitPolicy reject 1.2.3.6/16:*
74ExitPolicy reject 1.2.3.6/16:123-456 # comment
75
76ExitPolicy accept *:53 # DNS
77
78# random comment
79
80ExitPolicy accept6 *6:119
81ExitPolicy accept *4:120
82ExitPolicy reject6 [FC00::]/7:*
83
84# Portless
85ExitPolicy accept *:0
86ExitPolicy accept *4:0
87ExitPolicy accept *6:0
88
89ExitPolicy reject *:0
90ExitPolicy reject *4:0
91ExitPolicy reject *6:0
92
93#ExitPolicy accept *:8080 #and another comment here
94
95ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8329:*
96ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8328:1234
97ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8328/64:1235
98
99#another comment
100#ExitPolicy accept *:8080
101
102ExitPolicy reject *:*
103 "#;
104
105 let res = parse_exit_policy(sample).unwrap();
106
107 let mut expected = AddressPolicy::new();
108
109 expected.push(
111 Reject,
112 AddressPortPattern {
113 ip_pattern: IpPattern::V4 {
114 addr_prefix: "1.2.3.4".parse().unwrap(),
115 mask: 32,
116 },
117 ports: PortRange::new_all(),
118 },
119 );
120
121 expected.push(
123 Reject,
124 AddressPortPattern {
125 ip_pattern: IpPattern::V4 {
126 addr_prefix: "1.2.3.5".parse().unwrap(),
127 mask: 32,
128 },
129 ports: PortRange::new_all(),
130 },
131 );
132
133 expected.push(
135 Reject,
136 AddressPortPattern {
137 ip_pattern: IpPattern::V4 {
138 addr_prefix: "1.2.3.6".parse().unwrap(),
139 mask: 16,
140 },
141 ports: PortRange::new_all(),
142 },
143 );
144
145 expected.push(
147 Reject,
148 AddressPortPattern {
149 ip_pattern: IpPattern::V4 {
150 addr_prefix: "1.2.3.6".parse().unwrap(),
151 mask: 16,
152 },
153 ports: PortRange::new(123, 456).unwrap(),
154 },
155 );
156
157 expected.push(
159 Accept,
160 AddressPortPattern {
161 ip_pattern: IpPattern::Star,
162 ports: PortRange::new_singleton(53),
163 },
164 );
165
166 expected.push(
168 Accept6,
169 AddressPortPattern {
170 ip_pattern: IpPattern::V6Star,
171 ports: PortRange::new_singleton(119),
172 },
173 );
174
175 expected.push(
177 Accept,
178 AddressPortPattern {
179 ip_pattern: IpPattern::V4Star,
180 ports: PortRange::new_singleton(120),
181 },
182 );
183
184 expected.push(
186 Reject6,
187 AddressPortPattern {
188 ip_pattern: IpPattern::V6 {
189 addr_prefix: "FC00::".parse().unwrap(),
190 mask: 7,
191 },
192 ports: PortRange::new_all(),
193 },
194 );
195
196 expected.push(
198 Accept,
199 AddressPortPattern {
200 ip_pattern: IpPattern::Star,
201 ports: PortRange::new_zero(),
202 },
203 );
204
205 expected.push(
207 Accept,
208 AddressPortPattern {
209 ip_pattern: IpPattern::V4Star,
210 ports: PortRange::new_zero(),
211 },
212 );
213
214 expected.push(
216 Accept,
217 AddressPortPattern {
218 ip_pattern: IpPattern::V6Star,
219 ports: PortRange::new_zero(),
220 },
221 );
222
223 expected.push(
225 Reject,
226 AddressPortPattern {
227 ip_pattern: IpPattern::Star,
228 ports: PortRange::new_zero(),
229 },
230 );
231
232 expected.push(
234 Reject,
235 AddressPortPattern {
236 ip_pattern: IpPattern::V4Star,
237 ports: PortRange::new_zero(),
238 },
239 );
240
241 expected.push(
243 Reject,
244 AddressPortPattern {
245 ip_pattern: IpPattern::V6Star,
246 ports: PortRange::new_zero(),
247 },
248 );
249
250 expected.push(
252 Reject,
253 AddressPortPattern {
254 ip_pattern: IpPattern::V6 {
255 addr_prefix: "FE80:0000:0000:0000:0202:B3FF:FE1E:8329".parse().unwrap(),
256 mask: 128,
257 },
258 ports: PortRange::new_all(),
259 },
260 );
261
262 expected.push(
264 Reject,
265 AddressPortPattern {
266 ip_pattern: IpPattern::V6 {
267 addr_prefix: "FE80:0000:0000:0000:0202:B3FF:FE1E:8328".parse().unwrap(),
268 mask: 128,
269 },
270 ports: PortRange::new_singleton(1234),
271 },
272 );
273
274 expected.push(
276 Reject,
277 AddressPortPattern {
278 ip_pattern: IpPattern::V6 {
279 addr_prefix: "FE80:0000:0000:0000:0202:B3FF:FE1E:8328".parse().unwrap(),
280 mask: 64,
281 },
282 ports: PortRange::new_singleton(1235),
283 },
284 );
285
286 expected.push(
287 Reject,
288 AddressPortPattern {
289 ip_pattern: IpPattern::Star,
290 ports: PortRange::new_all(),
291 },
292 );
293
294 assert_eq!(res, expected)
295 }
296}