Skip to main content

nym_exit_policy/
lib.rs

1// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4pub 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    // each exit policy rule must begin with 'ExitPolicy' followed by the actual rule
52    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// for each line, ignore everything after the comment
61
62#[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        // ExitPolicy reject 1.2.3.4/32:*#comment
110        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        // ExitPolicy reject 1.2.3.5:* #comment
122        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        // ExitPolicy reject 1.2.3.6/16:*
134        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        // ExitPolicy reject 1.2.3.6/16:123-456
146        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        // ExitPolicy accept *:53
158        expected.push(
159            Accept,
160            AddressPortPattern {
161                ip_pattern: IpPattern::Star,
162                ports: PortRange::new_singleton(53),
163            },
164        );
165
166        // ExitPolicy accept6 *6:119
167        expected.push(
168            Accept6,
169            AddressPortPattern {
170                ip_pattern: IpPattern::V6Star,
171                ports: PortRange::new_singleton(119),
172            },
173        );
174
175        // ExitPolicy accept *4:120
176        expected.push(
177            Accept,
178            AddressPortPattern {
179                ip_pattern: IpPattern::V4Star,
180                ports: PortRange::new_singleton(120),
181            },
182        );
183
184        // ExitPolicy reject6 [FC00::]/7:*
185        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        // ExitPolicy accept *:0
197        expected.push(
198            Accept,
199            AddressPortPattern {
200                ip_pattern: IpPattern::Star,
201                ports: PortRange::new_zero(),
202            },
203        );
204
205        // ExitPolicy accept *4:0
206        expected.push(
207            Accept,
208            AddressPortPattern {
209                ip_pattern: IpPattern::V4Star,
210                ports: PortRange::new_zero(),
211            },
212        );
213
214        // ExitPolicy accept *6:0
215        expected.push(
216            Accept,
217            AddressPortPattern {
218                ip_pattern: IpPattern::V6Star,
219                ports: PortRange::new_zero(),
220            },
221        );
222
223        // ExitPolicy reject *:0
224        expected.push(
225            Reject,
226            AddressPortPattern {
227                ip_pattern: IpPattern::Star,
228                ports: PortRange::new_zero(),
229            },
230        );
231
232        // ExitPolicy reject *4:0
233        expected.push(
234            Reject,
235            AddressPortPattern {
236                ip_pattern: IpPattern::V4Star,
237                ports: PortRange::new_zero(),
238            },
239        );
240
241        // ExitPolicy reject *6:0
242        expected.push(
243            Reject,
244            AddressPortPattern {
245                ip_pattern: IpPattern::V6Star,
246                ports: PortRange::new_zero(),
247            },
248        );
249
250        // ExitPolicy FE80:0000:0000:0000:0202:B3FF:FE1E:8329:*
251        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        // ExitPolicy FE80:0000:0000:0000:0202:B3FF:FE1E:8328:1234
263        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        // ExitPolicy FE80:0000:0000:0000:0202:B3FF:FE1E:8328/64:1235
275        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}