1use std::str::FromStr;
2
3use crate::utils::ip_collection::IpCollection;
4use crate::utils::port_collection::PortCollection;
5use crate::{Fields, FirewallError};
6
7#[derive(Debug, Eq, PartialEq)]
9pub(crate) enum FirewallOption {
10 Dest(IpCollection),
12 Dport(PortCollection),
14 IcmpType(u8),
16 Proto(u8),
18 Source(IpCollection),
20 Sport(PortCollection),
22}
23
24impl FirewallOption {
25 pub(crate) const DEST: &'static str = "--dest";
26 pub(crate) const DPORT: &'static str = "--dport";
27 pub(crate) const ICMPTYPE: &'static str = "--icmp-type";
28 pub(crate) const PROTO: &'static str = "--proto";
29 pub(crate) const SOURCE: &'static str = "--source";
30 pub(crate) const SPORT: &'static str = "--sport";
31
32 pub(crate) fn new(l: usize, option: &str, value: &str) -> Result<Self, FirewallError> {
33 Ok(match option {
34 FirewallOption::DEST => Self::Dest(IpCollection::new(l, FirewallOption::DEST, value)?),
35 FirewallOption::DPORT => {
36 Self::Dport(PortCollection::new(l, FirewallOption::DPORT, value)?)
37 }
38 FirewallOption::ICMPTYPE => Self::IcmpType(
39 u8::from_str(value)
40 .map_err(|_| FirewallError::InvalidIcmpTypeValue(l, value.to_owned()))?,
41 ),
42 FirewallOption::PROTO => Self::Proto(
43 u8::from_str(value)
44 .map_err(|_| FirewallError::InvalidProtocolValue(l, value.to_owned()))?,
45 ),
46 FirewallOption::SOURCE => {
47 Self::Source(IpCollection::new(l, FirewallOption::SOURCE, value)?)
48 }
49 FirewallOption::SPORT => {
50 Self::Sport(PortCollection::new(l, FirewallOption::SPORT, value)?)
51 }
52 x => return Err(FirewallError::UnknownOption(l, x.to_owned())),
53 })
54 }
55
56 pub(crate) fn matches_packet(&self, fields: &Fields) -> bool {
57 match self {
58 FirewallOption::Dest(ip_collection) => ip_collection.contains(fields.dest),
59 FirewallOption::Dport(port_collection) => port_collection.contains(fields.dport),
60 FirewallOption::IcmpType(icmp_type) => {
61 if let Some(observed_icmp) = fields.icmp_type {
62 icmp_type.eq(&observed_icmp)
63 } else {
64 false
65 }
66 }
67 FirewallOption::Proto(proto) => {
68 if let Some(observed_proto) = fields.proto {
69 proto.eq(&observed_proto)
70 } else {
71 false
72 }
73 }
74 FirewallOption::Source(ip_collection) => ip_collection.contains(fields.source),
75 FirewallOption::Sport(port_collection) => port_collection.contains(fields.sport),
76 }
77 }
78
79 pub(crate) fn to_option_str(&self) -> &str {
80 match self {
81 FirewallOption::Dest(_) => FirewallOption::DEST,
82 FirewallOption::Dport(_) => FirewallOption::DPORT,
83 FirewallOption::Proto(_) => FirewallOption::PROTO,
84 FirewallOption::Source(_) => FirewallOption::SOURCE,
85 FirewallOption::Sport(_) => FirewallOption::SPORT,
86 FirewallOption::IcmpType(_) => FirewallOption::ICMPTYPE,
87 }
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use crate::firewall_option::FirewallOption;
94 use crate::utils::ip_collection::IpCollection;
95 use crate::utils::port_collection::PortCollection;
96 use crate::utils::raw_packets::test_packets::{
97 ARP_PACKET, ICMP_PACKET, TCP_PACKET, UDP_IPV6_PACKET,
98 };
99 use crate::{DataLink, Fields, FirewallError};
100
101 #[test]
102 fn test_new_dest_option() {
103 assert_eq!(
104 FirewallOption::new(
105 1,
106 "--dest",
107 "1.1.1.1,2.2.2.2,3.3.3.3-5.5.5.5,10.0.0.1-10.0.0.255,9.9.9.9"
108 )
109 .unwrap(),
110 FirewallOption::Dest(
111 IpCollection::new(
112 1,
113 FirewallOption::DEST,
114 "1.1.1.1,2.2.2.2,3.3.3.3-5.5.5.5,10.0.0.1-10.0.0.255,9.9.9.9"
115 )
116 .unwrap()
117 )
118 );
119
120 assert_eq!(
121 FirewallOption::new(
122 2,
123 "--dest",
124 "2001:db8:1234:0000:0000:0000:0000:0000-2001:db8:1234:ffff:ffff:ffff:ffff:ffff,daa::aad,caa::aac"
125 ).unwrap(),
126 FirewallOption::Dest(IpCollection::new(
127 2,
128 FirewallOption::DEST,
129 "2001:db8:1234:0000:0000:0000:0000:0000-2001:db8:1234:ffff:ffff:ffff:ffff:ffff,daa::aad,caa::aac"
130 ).unwrap())
131 );
132 }
133
134 #[test]
135 fn test_new_dport_option() {
136 assert_eq!(
137 FirewallOption::new(3, "--dport", "1,2,10:20,3,4,999:1200").unwrap(),
138 FirewallOption::Dport(
139 PortCollection::new(3, FirewallOption::DPORT, "1,2,10:20,3,4,999:1200").unwrap()
140 )
141 );
142 }
143
144 #[test]
145 fn test_new_icmp_type_option() {
146 assert_eq!(
147 FirewallOption::new(4, "--icmp-type", "8").unwrap(),
148 FirewallOption::IcmpType(8)
149 );
150 }
151
152 #[test]
153 fn test_new_proto_option() {
154 assert_eq!(
155 FirewallOption::new(5, "--proto", "1").unwrap(),
156 FirewallOption::Proto(1)
157 );
158 }
159
160 #[test]
161 fn test_new_sport_option() {
162 assert_eq!(
163 FirewallOption::new(6, "--sport", "1,2,10:20,3,4,999:1200").unwrap(),
164 FirewallOption::Sport(
165 PortCollection::new(6, FirewallOption::SPORT, "1,2,10:20,3,4,999:1200").unwrap()
166 )
167 );
168 }
169
170 #[test]
171 fn test_new_source_option() {
172 assert_eq!(
173 FirewallOption::new(
174 5,
175 "--source",
176 "1.1.1.1,2.2.2.2,3.3.3.3-5.5.5.5,10.0.0.1-10.0.0.255,9.9.9.9"
177 )
178 .unwrap(),
179 FirewallOption::Source(
180 IpCollection::new(
181 5,
182 FirewallOption::SOURCE,
183 "1.1.1.1,2.2.2.2,3.3.3.3-5.5.5.5,10.0.0.1-10.0.0.255,9.9.9.9"
184 )
185 .unwrap()
186 )
187 );
188
189 assert_eq!(
190 FirewallOption::new(
191 10,
192 "--source",
193 "2001:db8:1234:0000:0000:0000:0000:0000-2001:db8:1234:ffff:ffff:ffff:ffff:ffff,daa::aad,caa::aac"
194 ).unwrap(),
195 FirewallOption::Source(IpCollection::new(
196 10,
197 FirewallOption::SOURCE,
198 "2001:db8:1234:0000:0000:0000:0000:0000-2001:db8:1234:ffff:ffff:ffff:ffff:ffff,daa::aad,caa::aac"
199 ).unwrap())
200 );
201 }
202
203 #[test]
204 fn test_not_existing_option() {
205 let err = FirewallOption::new(11, "--not-exists", "8.8.8.8").unwrap_err();
206 assert_eq!(
207 err,
208 FirewallError::UnknownOption(11, "--not-exists".to_owned())
209 );
210 assert_eq!(
211 err.to_string(),
212 "Firewall error at line 11 - the specified option '--not-exists' doesn't exist"
213 );
214 }
215
216 #[test]
217 fn test_invalid_dest_option() {
218 let err = FirewallOption::new(9, "--dest", "8").unwrap_err();
219 assert_eq!(err, FirewallError::InvalidDestValue(9, "8".to_owned()));
220 assert_eq!(
221 err.to_string(),
222 "Firewall error at line 9 - incorrect value for option '--dest 8'"
223 );
224 }
225
226 #[test]
227 fn test_invalid_dport_option() {
228 let err = FirewallOption::new(7, "--dport", "8.8.8.8").unwrap_err();
229 assert_eq!(
230 err,
231 FirewallError::InvalidDportValue(7, "8.8.8.8".to_owned())
232 );
233 assert_eq!(
234 err.to_string(),
235 "Firewall error at line 7 - incorrect value for option '--dport 8.8.8.8'"
236 );
237 }
238
239 #[test]
240 fn test_invalid_source_option() {
241 let err = FirewallOption::new(55, "--source", "8").unwrap_err();
242 assert_eq!(err, FirewallError::InvalidSourceValue(55, "8".to_owned()));
243 assert_eq!(
244 err.to_string(),
245 "Firewall error at line 55 - incorrect value for option '--source 8'"
246 );
247 }
248
249 #[test]
250 fn test_invalid_sport_option() {
251 let err = FirewallOption::new(23, "--sport", "8.8.8.8").unwrap_err();
252 assert_eq!(
253 err,
254 FirewallError::InvalidSportValue(23, "8.8.8.8".to_owned())
255 );
256 assert_eq!(
257 err.to_string(),
258 "Firewall error at line 23 - incorrect value for option '--sport 8.8.8.8'"
259 );
260 }
261
262 #[test]
263 fn test_invalid_proto_option() {
264 let err = FirewallOption::new(1, "--proto", "256").unwrap_err();
265 assert_eq!(
266 err,
267 FirewallError::InvalidProtocolValue(1, "256".to_owned())
268 );
269 assert_eq!(
270 err.to_string(),
271 "Firewall error at line 1 - incorrect value for option '--proto 256'"
272 );
273 }
274
275 #[test]
276 fn test_invalid_icmp_type_option() {
277 let err = FirewallOption::new(2, "--icmp-type", "-1").unwrap_err();
278 assert_eq!(err, FirewallError::InvalidIcmpTypeValue(2, "-1".to_owned()));
279 assert_eq!(
280 err.to_string(),
281 "Firewall error at line 2 - incorrect value for option '--icmp-type -1'"
282 );
283 }
284
285 #[test]
286 fn test_dest_matches_packets() {
287 let dest_opt = FirewallOption::new(1, "--dest", "192.168.200.21,8.8.8.8,2.1.1.2").unwrap();
288 let range_dest_opt =
289 FirewallOption::new(1, "--dest", "192.168.200.0-192.168.200.255,8.8.8.8").unwrap();
290 let range_dest_opt_miss =
291 FirewallOption::new(5, "--dest", "192.168.200.0-192.168.200.20,8.8.8.8").unwrap();
292
293 let tcp_packet_fields = Fields::new(&TCP_PACKET, DataLink::Ethernet);
295 assert!(dest_opt.matches_packet(&tcp_packet_fields));
296 assert!(range_dest_opt.matches_packet(&tcp_packet_fields));
297 assert!(!range_dest_opt_miss.matches_packet(&tcp_packet_fields));
298
299 let icmp_packet_fields = Fields::new(&ICMP_PACKET, DataLink::Ethernet);
301 assert!(!dest_opt.matches_packet(&icmp_packet_fields));
302 assert!(!range_dest_opt.matches_packet(&icmp_packet_fields));
303 assert!(!range_dest_opt_miss.matches_packet(&icmp_packet_fields));
304
305 let arp_packet_fields = Fields::new(&ARP_PACKET, DataLink::Ethernet);
307 assert!(!dest_opt.matches_packet(&arp_packet_fields));
308 assert!(!range_dest_opt.matches_packet(&arp_packet_fields));
309 assert!(!range_dest_opt_miss.matches_packet(&arp_packet_fields));
310 }
311
312 #[test]
313 fn test_dport_matches_packets() {
314 let dport_opt = FirewallOption::new(2, "--dport", "2000").unwrap();
315 let range_dport_opt = FirewallOption::new(22, "--dport", "6700:6750").unwrap();
316
317 let tcp_packet_fields = Fields::new(&TCP_PACKET, DataLink::Ethernet);
319 assert!(dport_opt.matches_packet(&tcp_packet_fields));
320 assert!(!range_dport_opt.matches_packet(&tcp_packet_fields));
321
322 let icmp_packet_fields = Fields::new(&ICMP_PACKET, DataLink::Ethernet);
324 assert!(!dport_opt.matches_packet(&icmp_packet_fields));
325 assert!(!range_dport_opt.matches_packet(&icmp_packet_fields));
326
327 let arp_packet_fields = Fields::new(&ARP_PACKET, DataLink::Ethernet);
329 assert!(!dport_opt.matches_packet(&arp_packet_fields));
330 assert!(!range_dport_opt.matches_packet(&arp_packet_fields));
331 }
332
333 #[test]
334 fn test_icmp_type_matches_packets() {
335 let icmp_type_opt = FirewallOption::new(3, "--icmp-type", "8").unwrap();
336 let wrong_icmp_type_opt = FirewallOption::new(4, "--icmp-type", "7").unwrap();
337
338 let tcp_packet_fields = Fields::new(&TCP_PACKET, DataLink::Ethernet);
340 assert!(!icmp_type_opt.matches_packet(&tcp_packet_fields));
341 assert!(!wrong_icmp_type_opt.matches_packet(&tcp_packet_fields));
342
343 let icmp_packet_fields = Fields::new(&ICMP_PACKET, DataLink::Ethernet);
345 assert!(icmp_type_opt.matches_packet(&icmp_packet_fields));
346 assert!(!wrong_icmp_type_opt.matches_packet(&icmp_packet_fields));
347
348 let arp_packet_fields = Fields::new(&ARP_PACKET, DataLink::Ethernet);
350 assert!(!icmp_type_opt.matches_packet(&arp_packet_fields));
351 assert!(!wrong_icmp_type_opt.matches_packet(&arp_packet_fields));
352 }
353
354 #[test]
355 fn test_proto_matches_packets() {
356 let tcp_proto_opt = FirewallOption::new(5, "--proto", "6").unwrap();
357 let icmp_proto_opt = FirewallOption::new(7, "--proto", "1").unwrap();
358
359 let tcp_packet_fields = Fields::new(&TCP_PACKET, DataLink::Ethernet);
361 assert!(tcp_proto_opt.matches_packet(&tcp_packet_fields));
362 assert!(!icmp_proto_opt.matches_packet(&tcp_packet_fields));
363
364 let icmp_packet_fields = Fields::new(&ICMP_PACKET, DataLink::Ethernet);
366 assert!(!tcp_proto_opt.matches_packet(&icmp_packet_fields));
367 assert!(icmp_proto_opt.matches_packet(&icmp_packet_fields));
368
369 let arp_packet_fields = Fields::new(&ARP_PACKET, DataLink::Ethernet);
371 assert!(!tcp_proto_opt.matches_packet(&arp_packet_fields));
372 assert!(!icmp_proto_opt.matches_packet(&arp_packet_fields));
373 }
374
375 #[test]
376 fn test_source_matches_packets() {
377 let source_opt =
378 FirewallOption::new(88, "--source", "192.168.200.0-192.168.200.255,2.1.1.2").unwrap();
379
380 let tcp_packet_fields = Fields::new(&TCP_PACKET, DataLink::Ethernet);
382 assert!(source_opt.matches_packet(&tcp_packet_fields));
383
384 let icmp_packet_fields = Fields::new(&ICMP_PACKET, DataLink::Ethernet);
386 assert!(source_opt.matches_packet(&icmp_packet_fields));
387
388 let arp_packet_fields = Fields::new(&ARP_PACKET, DataLink::Ethernet);
390 assert!(!source_opt.matches_packet(&arp_packet_fields));
391 }
392
393 #[test]
394 fn test_sport_matches_packets() {
395 let sport_opt_wrong = FirewallOption::new(6, "--sport", "2000").unwrap();
396 let sport_opt_miss = FirewallOption::new(7, "--sport", "6712").unwrap();
397 let range_sport_opt = FirewallOption::new(8, "--sport", "6711:6750").unwrap();
398 let range_sport_opt_miss = FirewallOption::new(6, "--sport", "6712:6750").unwrap();
399
400 let tcp_packet_fields = Fields::new(&TCP_PACKET, DataLink::Ethernet);
402 assert!(!sport_opt_wrong.matches_packet(&tcp_packet_fields));
403 assert!(!sport_opt_miss.matches_packet(&tcp_packet_fields));
404 assert!(range_sport_opt.matches_packet(&tcp_packet_fields));
405 assert!(!range_sport_opt_miss.matches_packet(&tcp_packet_fields));
406
407 let icmp_packet_fields = Fields::new(&ICMP_PACKET, DataLink::Ethernet);
409 assert!(!sport_opt_wrong.matches_packet(&icmp_packet_fields));
410 assert!(!sport_opt_miss.matches_packet(&icmp_packet_fields));
411 assert!(!range_sport_opt.matches_packet(&icmp_packet_fields));
412 assert!(!range_sport_opt_miss.matches_packet(&icmp_packet_fields));
413
414 let arp_packet_fields = Fields::new(&ARP_PACKET, DataLink::Ethernet);
416 assert!(!sport_opt_wrong.matches_packet(&arp_packet_fields));
417 assert!(!sport_opt_miss.matches_packet(&arp_packet_fields));
418 assert!(!range_sport_opt.matches_packet(&arp_packet_fields));
419 assert!(!range_sport_opt_miss.matches_packet(&arp_packet_fields));
420 }
421
422 #[test]
423 fn test_dest_matches_ipv6() {
424 let dest_ok = FirewallOption::new(2, "--dest", "3ffe:507:0:1:200:86ff:fe05:8da").unwrap();
425 let dest_ko = FirewallOption::new(2, "--dest", "3ffe:501:4819::42").unwrap();
426 let range_dest_ok = FirewallOption::new(
427 1,
428 "--dest",
429 "3ffe:507:0:1:200:86ff:fe05:800-3ffe:507:0:1:200:86ff:fe05:900",
430 )
431 .unwrap();
432 let range_dest_ko = FirewallOption::new(
433 3,
434 "--dest",
435 "3ffe:507:0:1:200:86ff:fe05:800-3ffe:507:0:1:200:86ff:fe05:8bf",
436 )
437 .unwrap();
438
439 let udp_ipv6_packet_fields = Fields::new(&UDP_IPV6_PACKET, DataLink::Ethernet);
441 assert!(dest_ok.matches_packet(&udp_ipv6_packet_fields));
442 assert!(!dest_ko.matches_packet(&udp_ipv6_packet_fields));
443 assert!(range_dest_ok.matches_packet(&udp_ipv6_packet_fields));
444 assert!(!range_dest_ko.matches_packet(&udp_ipv6_packet_fields));
445 }
446
447 #[test]
448 fn test_dport_matches_ipv6() {
449 let dport_ok = FirewallOption::new(3, "--dport", "2396").unwrap();
450 let dport_ko = FirewallOption::new(4, "--dport", "3296").unwrap();
451 let range_dport_ok = FirewallOption::new(44, "--dport", "2000:2500").unwrap();
452 let range_dport_ko = FirewallOption::new(4, "--dport", "53:63").unwrap();
453
454 let udp_ipv6_packet_fields = Fields::new(&UDP_IPV6_PACKET, DataLink::Ethernet);
456 assert!(dport_ok.matches_packet(&udp_ipv6_packet_fields));
457 assert!(!dport_ko.matches_packet(&udp_ipv6_packet_fields));
458 assert!(range_dport_ok.matches_packet(&udp_ipv6_packet_fields));
459 assert!(!range_dport_ko.matches_packet(&udp_ipv6_packet_fields));
460 }
461
462 #[test]
463 fn test_icmp_type_matches_ipv6() {
464 let icmp_type = FirewallOption::new(1, "--icmp-type", "8").unwrap();
465
466 let udp_ipv6_packet_fields = Fields::new(&UDP_IPV6_PACKET, DataLink::Ethernet);
468 assert!(!icmp_type.matches_packet(&udp_ipv6_packet_fields));
469 }
470
471 #[test]
472 fn test_proto_matches_ipv6() {
473 let proto_ok = FirewallOption::new(1, "--proto", "17").unwrap();
474 let proto_ko = FirewallOption::new(2, "--proto", "18").unwrap();
475
476 let udp_ipv6_packet_fields = Fields::new(&UDP_IPV6_PACKET, DataLink::Ethernet);
478 assert!(proto_ok.matches_packet(&udp_ipv6_packet_fields));
479 assert!(!proto_ko.matches_packet(&udp_ipv6_packet_fields));
480 }
481
482 #[test]
483 fn test_source_matches_ipv6() {
484 let source_ko =
485 FirewallOption::new(65, "--source", "3ffe:507:0:1:200:86ff:fe05:8da").unwrap();
486 let source_ok = FirewallOption::new(6, "--source", "3ffe:501:4819::42").unwrap();
487 let range_source_ok =
488 FirewallOption::new(2, "--source", "3ffe:501:4819::35-3ffe:501:4819::45").unwrap();
489 let range_source_ok_2 = FirewallOption::new(
490 21,
491 "--source",
492 "3ffe:501:4819::31-3ffe:501:4819::41,3ffe:501:4819::42",
493 )
494 .unwrap();
495 let range_source_ko =
496 FirewallOption::new(1, "--source", "3ffe:501:4819::31-3ffe:501:4819::41").unwrap();
497
498 let udp_ipv6_packet_fields = Fields::new(&UDP_IPV6_PACKET, DataLink::Ethernet);
500 assert!(!source_ko.matches_packet(&udp_ipv6_packet_fields));
501 assert!(source_ok.matches_packet(&udp_ipv6_packet_fields));
502 assert!(range_source_ok.matches_packet(&udp_ipv6_packet_fields));
503 assert!(range_source_ok_2.matches_packet(&udp_ipv6_packet_fields));
504 assert!(!range_source_ko.matches_packet(&udp_ipv6_packet_fields));
505 }
506
507 #[test]
508 fn test_sport_matches_ipv6() {
509 let sport_ok = FirewallOption::new(1, "--sport", "53").unwrap();
510 let sport_ko = FirewallOption::new(1, "--sport", "55").unwrap();
511 let range_sport_ok = FirewallOption::new(3, "--sport", "53:63").unwrap();
512 let range_sport_ko = FirewallOption::new(3, "--sport", "2000:2500").unwrap();
513
514 let udp_ipv6_packet_fields = Fields::new(&UDP_IPV6_PACKET, DataLink::Ethernet);
516 assert!(sport_ok.matches_packet(&udp_ipv6_packet_fields));
517 assert!(!sport_ko.matches_packet(&udp_ipv6_packet_fields));
518 assert!(range_sport_ok.matches_packet(&udp_ipv6_packet_fields));
519 assert!(!range_sport_ko.matches_packet(&udp_ipv6_packet_fields));
520 }
521
522 #[test]
523 fn test_invalid_packets_do_not_match_options() {
524 let sport = FirewallOption::new(7, "--sport", "53").unwrap();
525 let source = FirewallOption::new(5, "--source", "55.55.55.55").unwrap();
526 let dest = FirewallOption::new(3, "--dest", "0.0.0.0-255.255.255.255").unwrap();
527 let dport = FirewallOption::new(9, "--dport", "0:65535").unwrap();
528 let proto = FirewallOption::new(11, "--proto", "1").unwrap();
529 let icmp_type = FirewallOption::new(13, "--icmp-type", "8").unwrap();
530
531 let packet_1 = [];
533 let fields_1 = Fields::new(&packet_1, DataLink::Ethernet);
534 assert!(!sport.matches_packet(&fields_1));
535 assert!(!source.matches_packet(&fields_1));
536 assert!(!dest.matches_packet(&fields_1));
537 assert!(!dport.matches_packet(&fields_1));
538 assert!(!proto.matches_packet(&fields_1));
539 assert!(!icmp_type.matches_packet(&fields_1));
540
541 let packet_2 = [b'n', b'o', b't', b'v', b'a', b'l', b'i', b'd'];
543 let fields_2 = Fields::new(&packet_2, DataLink::Ethernet);
544 assert!(!sport.matches_packet(&fields_2));
545 assert!(!source.matches_packet(&fields_2));
546 assert!(!dest.matches_packet(&fields_2));
547 assert!(!dport.matches_packet(&fields_2));
548 assert!(!proto.matches_packet(&fields_2));
549 assert!(!icmp_type.matches_packet(&fields_2));
550 }
551}