nullnet_firewall/
lib.rs

1//! **Rust-based firewall for network drivers.**
2//!
3//! # Purpose
4//!
5//! This library is used to match network packets against a set of constraints (here called *firewall rules*)
6//! with the aim of deciding whether to permit or deny incoming/outgoing traffic.
7//!
8//! Given a set of firewall rules and a network packet, the library will *inform* the user
9//! about *how* to handle the packet.
10//!
11//! The library assumes that users are able to manipulate the stream of network packets in a way such
12//! it's possible to take proper actions to allow or deny the forwarding of single packets
13//! between the network card and the operating system; consequently, this framework is mainly intended
14//! to be used at the level of *network drivers*.
15//!
16//! Each of the packets passed to the firewall will be logged both in standard output
17//! and in a `SQLite` database with path `./log.sqlite`.
18//!
19//! # Firewall definition
20//!
21//! A new [`Firewall`] object is defined via the [`Firewall::new`] method, which accepts as parameter
22//! the path of a file defining a collection of firewall rules.
23//!
24//! Each of the **rules** defined in the file is placed on a new line and has the following structure:
25//! ``` txt
26//! [+] DIRECTION ACTION [OPTIONS]
27//! ```
28//!
29//! * Each rule can optionally be introduced by a `+` character; this will make the rule
30//! have higher priority (quick rule).
31//!
32//! * `DIRECTION` can be either `IN` or `OUT` and represents the traffic directionality
33//! (see [`FirewallDirection`]).
34//!
35//! * `ACTION` can be either `ACCEPT`, `DENY`, or `REJECT` and represents the action
36//! associated with the rule (see [`FirewallAction`]).
37//!
38//! * For each rule, a list of **options** can be specified to match the desired traffic:
39//!   * `--dest`: destination IP addresses; the value is expressed in the form of a comma-separated
40//!     list of IP addresses, in which each entry can also represent an address range (using the `-` character).
41//!   * `--dport`: destination transport ports; the value is expressed in the form of a comma-separated
42//!     list of port numbers, in which each entry can also represent a port range (using the `:` character).
43//!   * `--icmp-type`: ICMP message type; the value is expressed as a number representing
44//!     a specific message type (see [here](https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml#icmp-parameters-types) for more info).
45//!   * `--proto`: Internet Protocol number; the value is expressed as a number representing
46//!     a specific protocol number (see [here](https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml#protocol-numbers-1) for more info).
47//!   * `--source`: source IP addresses; the value is expressed in the form of a comma-separated
48//!     list of IP addresses, in which each entry can also represent an address range (using the `-` character).
49//!   * `--sport`: source transport ports; the value is expressed in the form of a comma-separated
50//!     list of port numbers, in which each entry can also represent a port range (using the `:` character).
51//!
52//! A **sample** firewall configuration file is reported in the following:
53//!
54//! ``` text
55//! # Firewall rules (this is a comment line)
56//!
57//! IN REJECT --source 8.8.8.8
58//! # Rules marked with '+' have higher priority
59//! + IN ACCEPT --source 8.8.8.0-8.8.8.10 --sport 8
60//! OUT ACCEPT --source 8.8.8.8,7.7.7.7 --dport 900:1000,1,2,3
61//! OUT DENY
62//! ```
63//!
64//! In case of invalid firewall configurations, a [`FirewallError`] will be returned.
65//!
66//! # Usage
67//!
68//! Once a [`Firewall`] has been defined, it can be used to determine which action to take for each
69//! of the netwrok packets in transit.
70//!
71//! This is done by invoking [`Firewall::resolve_packet`], which will answer with the
72//! action to take for the supplied packet.
73//!
74//! ```
75//! use nullnet_firewall::{Firewall, FirewallDirection, FirewallAction};
76//!
77//! // build the firewall from the rules in a file
78//! let firewall = Firewall::new("./samples/firewall.txt").unwrap();
79//!
80//! // here we suppose to have a packet to match against the firewall
81//! let packet = [/* ... */];
82//!
83//! // determine action for packet, supposing incoming direction for packet
84//! let action = firewall.resolve_packet(&packet, FirewallDirection::IN);
85//!
86//! // act accordingly
87//! match action {
88//!     FirewallAction::ACCEPT => {/* ... */}
89//!     FirewallAction::DENY => {/* ... */}
90//!     FirewallAction::REJECT => {/* ... */}
91//! }
92//! ```
93//!
94//! An existing firewall can be temporarily [disabled](Firewall::disable),
95//! its rules can be [updated](Firewall::update_rules),
96//! and the default [input policy](Firewall::policy_in) and
97//! [output policy](Firewall::policy_out) can
98//! be overridden for packets that don't match any of the firewall rules.
99
100use std::fs::File;
101use std::io::{BufRead, BufReader};
102use std::sync::mpsc;
103use std::sync::mpsc::{Receiver, Sender};
104use std::thread;
105
106pub use crate::data_link::DataLink;
107use crate::fields::fields::Fields;
108use crate::fields::ip_header::{get_dest, get_proto, get_source};
109use crate::fields::transport_header::{get_dport, get_icmp_type, get_sport};
110pub use crate::firewall_action::FirewallAction;
111pub use crate::firewall_direction::FirewallDirection;
112pub use crate::firewall_error::FirewallError;
113use crate::firewall_rule::FirewallRule;
114use crate::logs::log_entry::LogEntry;
115use crate::logs::logger::log;
116
117mod data_link;
118mod fields;
119mod firewall_action;
120mod firewall_direction;
121mod firewall_error;
122mod firewall_option;
123mod firewall_rule;
124mod logs;
125mod utils;
126
127/// Object embedding a collection of firewall rules and policies to determine
128/// the action to be taken for a given network packet.
129///
130/// A new `Firewall` can be created from a textual file listing a set of rules.
131#[derive(Debug)]
132pub struct Firewall {
133    rules: Vec<FirewallRule>,
134    enabled: bool,
135    policy_in: FirewallAction,
136    policy_out: FirewallAction,
137    tx: Sender<LogEntry>,
138    data_link: DataLink,
139    log: bool,
140}
141
142impl Firewall {
143    const COMMENT: char = '#';
144
145    /// Instantiates a new [`Firewall`] from a file.
146    ///
147    /// # Arguments
148    ///
149    /// * `file_path` - The path of a file defining the firewall rules.
150    ///
151    /// # Errors
152    ///
153    /// Will return a [`FirewallError`] if the rules defined in the file are not properly formatted.
154    ///
155    /// # Panics
156    ///
157    /// Will panic if the supplied `file_path` does not exist or the user does not have
158    /// permission to read it.
159    ///
160    /// # Examples
161    ///
162    /// ```
163    /// use nullnet_firewall::Firewall;
164    ///
165    /// let firewall = Firewall::new("./samples/firewall.txt").unwrap();
166    /// ```
167    ///
168    /// Sample file content:
169    ///
170    /// ``` txt
171    /// OUT REJECT --source 8.8.8.8 --sport 6700:6800,8080
172    /// OUT DENY --source 192.168.200.0-192.168.200.255 --sport 6700:6800,8080 --dport 1,2,2000
173    /// IN ACCEPT --source 2.1.1.2,2.1.1.3 --dest 2.1.1.1 --proto 1
174    /// IN REJECT --source 2.1.1.2 --dest 2.1.1.1 --proto 1 --icmp-type 8
175    /// OUT REJECT
176    /// IN ACCEPT
177    /// ```
178    pub fn new(file_path: &str) -> Result<Self, FirewallError> {
179        let (tx, rx): (Sender<LogEntry>, Receiver<LogEntry>) = mpsc::channel();
180        thread::Builder::new()
181            .name("logger".to_string())
182            .spawn(move || {
183                log(&rx);
184            })
185            .unwrap();
186
187        let mut firewall = Firewall {
188            rules: Vec::new(),
189            enabled: true,
190            policy_in: FirewallAction::default(),
191            policy_out: FirewallAction::default(),
192            tx,
193            data_link: DataLink::default(),
194            log: true,
195        };
196
197        firewall.update_rules(file_path)?;
198
199        Ok(firewall)
200    }
201
202    /// Returns the action to be taken for a supplied network packet,
203    /// according to rules defined for the [`Firewall`].
204    ///
205    /// # Arguments
206    ///
207    /// * `packet` - Raw network packet bytes, including headers and payload.
208    ///
209    /// * `direction` - The network packet direction (incoming or outgoing).
210    ///
211    /// # Panics
212    ///
213    /// Will panic if the logger routine of the firewall aborts for some reason.
214    ///
215    /// # Examples
216    ///
217    /// ```
218    /// use nullnet_firewall::{Firewall, FirewallDirection, FirewallAction};
219    ///
220    /// let firewall = Firewall::new("./samples/firewall.txt").unwrap();
221    ///
222    /// // here we suppose to have a packet to match against the firewall
223    /// let packet = [/* ... */];
224    ///
225    /// // determine action for packet, supposing incoming direction for packet
226    /// let action = firewall.resolve_packet(&packet, FirewallDirection::IN);
227    ///
228    /// // act accordingly
229    /// match action {
230    ///     FirewallAction::ACCEPT => {/* ... */}
231    ///     FirewallAction::DENY => {/* ... */}
232    ///     FirewallAction::REJECT => {/* ... */}
233    /// }
234    /// ```
235    #[must_use]
236    pub fn resolve_packet(&self, packet: &[u8], direction: FirewallDirection) -> FirewallAction {
237        if !self.enabled {
238            return FirewallAction::ACCEPT;
239        }
240
241        let mut action_opt = None;
242
243        // structure the packet as a set of relevant fields
244        let fields = Fields::new(packet, self.data_link);
245
246        // determine action for packet
247        for rule in &self.rules {
248            if rule.matches_packet(&fields, &direction) {
249                if rule.quick {
250                    action_opt = Some(rule.action);
251                    break;
252                } else if action_opt.is_none() {
253                    action_opt = Some(rule.action);
254                }
255            }
256        }
257
258        let action = action_opt.unwrap_or(match direction {
259            FirewallDirection::IN => self.policy_in,
260            FirewallDirection::OUT => self.policy_out,
261        });
262
263        if self.log {
264            // send the log entry to the logger thread
265            let log_entry = LogEntry::new(&fields, direction, action);
266            self.tx
267                .send(log_entry)
268                .expect("the firewall logger routine aborted");
269        }
270
271        action
272    }
273
274    /// Updates the rules of a previously instantiated [`Firewall`].
275    ///
276    /// # Arguments
277    ///
278    /// * `file_path` - The path of a file defining the firewall rules.
279    ///
280    /// # Errors
281    ///
282    /// Will return a [`FirewallError`] if the rules defined in the file are not properly formatted.
283    ///
284    /// # Panics
285    ///
286    /// Will panic if the supplied `file_path` does not exist or the user does not have
287    /// permission to read it.
288    ///
289    /// # Examples
290    ///
291    /// ```
292    /// use nullnet_firewall::Firewall;
293    ///
294    /// let mut firewall = Firewall::new("./samples/firewall.txt").unwrap();
295    ///
296    /// /* ... */
297    ///
298    /// firewall.update_rules("./samples/firewall_for_tests_1.txt");
299    /// ```
300    pub fn update_rules(&mut self, file_path: &str) -> Result<(), FirewallError> {
301        let mut rules = Vec::new();
302        let file = File::open(file_path).unwrap();
303
304        for (l, firewall_rule_str_result) in BufReader::new(file).lines().enumerate() {
305            let Ok(firewall_rule_str_raw) = firewall_rule_str_result else {
306                continue;
307            };
308            let firewall_rule_str = firewall_rule_str_raw.trim();
309            if !firewall_rule_str.starts_with(Self::COMMENT) && !firewall_rule_str.is_empty() {
310                rules.push(FirewallRule::new(l + 1, firewall_rule_str)?);
311            }
312        }
313
314        self.rules = rules;
315        Ok(())
316    }
317
318    /// Disables an existing [`Firewall`].
319    ///
320    /// This will make all the network packets be accepted
321    /// regardless of the rules defined for the firewall.
322    ///
323    /// # Examples
324    ///
325    /// ```
326    /// use nullnet_firewall::{Firewall, FirewallAction, FirewallDirection};
327    ///
328    /// let mut firewall = Firewall::new("./samples/firewall.txt").unwrap();
329    ///
330    /// // here we suppose to have a packet to match against the firewall
331    /// let packet = [/* ... */];
332    ///
333    /// // disable the firewall
334    /// firewall.disable();
335    ///
336    /// // a disabled firewall will accept everything
337    /// assert_eq!(
338    ///     firewall.resolve_packet(&packet, FirewallDirection::IN),
339    ///     FirewallAction::ACCEPT
340    /// );
341    /// ```
342    pub fn disable(&mut self) {
343        self.enabled = false;
344    }
345
346    /// Enables an existing [`Firewall`].
347    ///
348    /// When a new firewall is created, it's enabled by default.
349    ///
350    /// When the firewall is enabled, the actions to take for network packets are determined
351    /// according to the specified rules.
352    ///
353    /// # Examples
354    ///
355    /// ```
356    /// use nullnet_firewall::Firewall;
357    ///
358    /// // a new firewall is enabled by default
359    /// let mut firewall = Firewall::new("./samples/firewall.txt").unwrap();
360    ///
361    /// // disable the firewall
362    /// firewall.disable();
363    ///
364    /// /* ... */
365    ///
366    /// // enable the firewall again
367    /// firewall.enable();
368    /// ```
369    pub fn enable(&mut self) {
370        self.enabled = true;
371    }
372
373    /// Sets the input policy for an existing [`Firewall`].
374    ///
375    /// # Arguments
376    ///
377    /// * `policy` - The policy to use for incoming packets that don't match any of the specified rules.
378    ///
379    /// # Examples
380    ///
381    /// ```
382    /// use nullnet_firewall::{Firewall, FirewallAction};
383    ///
384    /// let mut firewall = Firewall::new("./samples/firewall.txt").unwrap();
385    ///
386    /// // set the firewall input policy
387    /// firewall.policy_in(FirewallAction::DENY);
388    /// ```
389    pub fn policy_in(&mut self, policy: FirewallAction) {
390        self.policy_in = policy;
391    }
392
393    /// Sets the output policy for an existing [`Firewall`].
394    ///
395    /// # Arguments
396    ///
397    /// * `policy` - The policy to use for outgoing packets that don't match any of the specified rules.
398    ///
399    /// # Examples
400    ///
401    /// ```
402    /// use nullnet_firewall::{Firewall, FirewallAction};
403    ///
404    /// let mut firewall = Firewall::new("./samples/firewall.txt").unwrap();
405    ///
406    /// // set the firewall output policy
407    /// firewall.policy_out(FirewallAction::ACCEPT);
408    /// ```
409    pub fn policy_out(&mut self, policy: FirewallAction) {
410        self.policy_out = policy;
411    }
412
413    /// Sets the [`DataLink`] type for an existing [`Firewall`].
414    ///
415    /// As default, a firewall will try to parse packets considering them Ethernet frames; if different kinds of packets
416    /// want to be inspected, it's necessary to set the corresponding data link type via this method.
417    ///
418    /// # Arguments
419    ///
420    /// * `data_link` - The data link type that'll be used to parse packets.
421    ///
422    /// # Examples
423    ///
424    /// ```
425    /// use nullnet_firewall::{DataLink, Firewall};
426    ///
427    /// let mut firewall = Firewall::new("./samples/firewall.txt").unwrap();
428    ///
429    /// // let the firewall know that submitted packets start with an IP header
430    /// firewall.data_link(DataLink::RawIP);
431    /// ```
432    pub fn data_link(&mut self, data_link: DataLink) {
433        self.data_link = data_link;
434    }
435
436    /// Enables or disables logging.
437    ///
438    /// If enabled (default) packets will be printed in stdout and will be logged into a DB.
439    ///
440    /// # Arguments
441    ///
442    /// * `log` - Whether the log activity should be enabled or not.
443    ///
444    /// # Examples
445    ///
446    /// ```
447    /// use nullnet_firewall::{Firewall};
448    ///
449    /// let mut firewall = Firewall::new("./samples/firewall.txt").unwrap();
450    ///
451    /// // disable logging
452    /// firewall.log(false);
453    /// ```
454    pub fn log(&mut self, log: bool) {
455        self.log = log;
456    }
457}
458
459#[cfg(test)]
460mod tests {
461    use std::sync::mpsc;
462    use std::sync::mpsc::{Receiver, Sender};
463
464    use crate::utils::raw_packets::test_packets::{
465        ARP_PACKET, ICMP_PACKET, TCP_PACKET, UDP_IPV6_PACKET,
466    };
467    use crate::{DataLink, Firewall, LogEntry};
468    use crate::{FirewallAction, FirewallDirection, FirewallRule};
469
470    const TEST_FILE_1: &str = "./samples/firewall_for_tests_1.txt";
471    const TEST_FILE_2: &str = "./samples/firewall_for_tests_2.txt";
472    const TEST_FILE_3: &str = "./samples/firewall_for_tests_3.txt";
473
474    fn get_error_file_path(name: &str) -> String {
475        format!("./samples/firewall_for_tests_error_{name}.txt")
476    }
477
478    #[test]
479    fn test_new_firewall_from_file_1() {
480        let rules = vec![
481            FirewallRule::new(1,"OUT REJECT --source 192.168.200.135 --sport 6700:6800,8080").unwrap(),
482            FirewallRule::new(2,"OUT REJECT --source 192.168.200.135 --sport 6700:6800,8080 --dport 1,2,2000").unwrap(),
483            FirewallRule::new(3, "+ OUT DENY --source 192.168.200.135-192.168.200.140 --sport 6700:6800,8080 --dport 1,2,2000").unwrap(),
484            FirewallRule::new(4,"OUT REJECT --source 192.168.200.135 --sport 6750:6800,8080 --dest 192.168.200.21 --dport 1,2,2000").unwrap(),
485            FirewallRule::new(5,"IN ACCEPT --source 2.1.1.2 --dest 2.1.1.1 --proto 1 --icmp-type 8").unwrap(),
486            FirewallRule::new(6,"+ IN REJECT --source 2.1.1.2 --dest 2.1.1.1 --proto 1 --icmp-type 8").unwrap(),
487            FirewallRule::new(7,"IN ACCEPT --source 2.1.1.2 --dest 2.1.1.1 --proto 1 --icmp-type 9").unwrap(),
488            FirewallRule::new(8,"IN ACCEPT --source 2.1.1.2 --dest 2.1.1.1 --proto 58 --icmp-type 8").unwrap(),
489            FirewallRule::new(9,"OUT REJECT").unwrap(),
490            FirewallRule::new(10,"IN ACCEPT").unwrap(),
491        ];
492
493        let mut firewall_from_file = Firewall::new(TEST_FILE_1).unwrap();
494
495        assert_eq!(firewall_from_file.rules, rules);
496        assert!(firewall_from_file.enabled);
497        assert_eq!(firewall_from_file.policy_out, FirewallAction::default());
498        assert_eq!(firewall_from_file.policy_in, FirewallAction::default());
499
500        firewall_from_file.disable();
501        firewall_from_file.policy_in(FirewallAction::DENY);
502        firewall_from_file.policy_out(FirewallAction::REJECT);
503        assert!(!firewall_from_file.enabled);
504        assert_eq!(firewall_from_file.policy_in, FirewallAction::DENY);
505        assert_eq!(firewall_from_file.policy_out, FirewallAction::REJECT);
506
507        firewall_from_file.enable();
508        assert!(firewall_from_file.enabled);
509    }
510
511    #[test]
512    fn test_firewall_determine_action_for_packets_file_1() {
513        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
514        firewall.policy_in(FirewallAction::DENY);
515        firewall.policy_out(FirewallAction::ACCEPT);
516
517        assert_eq!(firewall.data_link, DataLink::Ethernet);
518
519        // tcp packet
520        assert_eq!(
521            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::IN),
522            FirewallAction::ACCEPT
523        );
524        assert_eq!(
525            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::OUT),
526            FirewallAction::DENY
527        );
528
529        // icmp packet
530        assert_eq!(
531            firewall.resolve_packet(&ICMP_PACKET, FirewallDirection::IN),
532            FirewallAction::REJECT
533        );
534        assert_eq!(
535            firewall.resolve_packet(&ICMP_PACKET, FirewallDirection::OUT),
536            FirewallAction::REJECT
537        );
538
539        // arp packet
540        assert_eq!(
541            firewall.resolve_packet(&ARP_PACKET, FirewallDirection::IN),
542            FirewallAction::ACCEPT
543        );
544        assert_eq!(
545            firewall.resolve_packet(&ARP_PACKET, FirewallDirection::OUT),
546            FirewallAction::REJECT
547        );
548    }
549
550    #[test]
551    fn test_firewall_determine_action_for_packets_file_1_with_data_link_raw_ip() {
552        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
553        firewall.data_link(DataLink::RawIP);
554        firewall.policy_in(FirewallAction::DENY);
555        firewall.policy_out(FirewallAction::ACCEPT);
556
557        assert_eq!(firewall.data_link, DataLink::RawIP);
558
559        // tcp packet
560        assert_eq!(
561            firewall.resolve_packet(&TCP_PACKET[14..], FirewallDirection::IN),
562            FirewallAction::ACCEPT
563        );
564        assert_eq!(
565            firewall.resolve_packet(&TCP_PACKET[14..], FirewallDirection::OUT),
566            FirewallAction::DENY
567        );
568
569        // icmp packet
570        assert_eq!(
571            firewall.resolve_packet(&ICMP_PACKET[14..], FirewallDirection::IN),
572            FirewallAction::REJECT
573        );
574        assert_eq!(
575            firewall.resolve_packet(&ICMP_PACKET[14..], FirewallDirection::OUT),
576            FirewallAction::REJECT
577        );
578    }
579
580    #[test]
581    fn test_firewall_determine_action_for_packets_file_2() {
582        let mut firewall = Firewall::new(TEST_FILE_2).unwrap();
583        firewall.policy_in(FirewallAction::DENY);
584        firewall.policy_out(FirewallAction::ACCEPT);
585
586        // tcp packet
587        assert_eq!(
588            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::IN),
589            FirewallAction::DENY
590        );
591        assert_eq!(
592            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::OUT),
593            FirewallAction::DENY
594        );
595
596        // icmp packet
597        assert_eq!(
598            firewall.resolve_packet(&ICMP_PACKET, FirewallDirection::IN),
599            FirewallAction::ACCEPT
600        );
601        assert_eq!(
602            firewall.resolve_packet(&ICMP_PACKET, FirewallDirection::OUT),
603            FirewallAction::ACCEPT
604        );
605
606        // arp packet
607        assert_eq!(
608            firewall.resolve_packet(&ARP_PACKET, FirewallDirection::IN),
609            FirewallAction::DENY
610        );
611        assert_eq!(
612            firewall.resolve_packet(&ARP_PACKET, FirewallDirection::OUT),
613            FirewallAction::ACCEPT
614        );
615    }
616
617    #[test]
618    fn test_firewall_determine_action_for_packets_file_3() {
619        let mut firewall = Firewall::new(TEST_FILE_3).unwrap();
620
621        // ipv6 packet
622        assert_eq!(
623            firewall.resolve_packet(&UDP_IPV6_PACKET, FirewallDirection::IN),
624            FirewallAction::DENY
625        );
626        assert_eq!(
627            firewall.resolve_packet(&UDP_IPV6_PACKET, FirewallDirection::OUT),
628            FirewallAction::ACCEPT
629        );
630
631        // tcp packet
632        assert_eq!(
633            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::IN),
634            FirewallAction::default()
635        );
636        assert_eq!(
637            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::OUT),
638            FirewallAction::default()
639        );
640
641        // change default policies
642        firewall.policy_in(FirewallAction::DENY);
643        firewall.policy_out(FirewallAction::REJECT);
644
645        // ipv6 packet
646        assert_eq!(
647            firewall.resolve_packet(&UDP_IPV6_PACKET, FirewallDirection::IN),
648            FirewallAction::DENY
649        );
650        assert_eq!(
651            firewall.resolve_packet(&UDP_IPV6_PACKET, FirewallDirection::OUT),
652            FirewallAction::ACCEPT
653        );
654
655        // tcp packet
656        assert_eq!(
657            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::IN),
658            FirewallAction::DENY
659        );
660        assert_eq!(
661            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::OUT),
662            FirewallAction::REJECT
663        );
664    }
665
666    #[test]
667    fn test_firewall_determine_action_for_packets_file_3_with_data_link_raw_ip() {
668        let mut firewall = Firewall::new(TEST_FILE_3).unwrap();
669        firewall.data_link(DataLink::RawIP);
670
671        // ipv6 packet
672        assert_eq!(
673            firewall.resolve_packet(&UDP_IPV6_PACKET[14..], FirewallDirection::IN),
674            FirewallAction::DENY
675        );
676        assert_eq!(
677            firewall.resolve_packet(&UDP_IPV6_PACKET[14..], FirewallDirection::OUT),
678            FirewallAction::ACCEPT
679        );
680
681        // tcp packet
682        assert_eq!(
683            firewall.resolve_packet(&TCP_PACKET[14..], FirewallDirection::IN),
684            FirewallAction::default()
685        );
686        assert_eq!(
687            firewall.resolve_packet(&TCP_PACKET[14..], FirewallDirection::OUT),
688            FirewallAction::default()
689        );
690
691        // change default policies
692        firewall.policy_in(FirewallAction::DENY);
693        firewall.policy_out(FirewallAction::REJECT);
694
695        // ipv6 packet
696        assert_eq!(
697            firewall.resolve_packet(&UDP_IPV6_PACKET[14..], FirewallDirection::IN),
698            FirewallAction::DENY
699        );
700        assert_eq!(
701            firewall.resolve_packet(&UDP_IPV6_PACKET[14..], FirewallDirection::OUT),
702            FirewallAction::ACCEPT
703        );
704
705        // tcp packet
706        assert_eq!(
707            firewall.resolve_packet(&TCP_PACKET[14..], FirewallDirection::IN),
708            FirewallAction::DENY
709        );
710        assert_eq!(
711            firewall.resolve_packet(&TCP_PACKET[14..], FirewallDirection::OUT),
712            FirewallAction::REJECT
713        );
714    }
715
716    #[test]
717    fn test_firewall_determine_action_for_packets_while_disabled() {
718        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
719        firewall.policy_in(FirewallAction::REJECT); // doesn't matter
720        firewall.policy_out(FirewallAction::REJECT); // doesn't matter
721        firewall.disable(); // always accept
722
723        // tcp packet
724        assert_eq!(
725            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::IN),
726            FirewallAction::ACCEPT
727        );
728        assert_eq!(
729            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::OUT),
730            FirewallAction::ACCEPT
731        );
732
733        // icmp packet
734        assert_eq!(
735            firewall.resolve_packet(&ICMP_PACKET, FirewallDirection::IN),
736            FirewallAction::ACCEPT
737        );
738        assert_eq!(
739            firewall.resolve_packet(&ICMP_PACKET, FirewallDirection::OUT),
740            FirewallAction::ACCEPT
741        );
742
743        // arp packet
744        assert_eq!(
745            firewall.resolve_packet(&ARP_PACKET, FirewallDirection::IN),
746            FirewallAction::ACCEPT
747        );
748        assert_eq!(
749            firewall.resolve_packet(&ARP_PACKET, FirewallDirection::OUT),
750            FirewallAction::ACCEPT
751        );
752    }
753
754    #[test]
755    fn test_firewall_rules_precedence() {
756        let (tx, _rx): (Sender<LogEntry>, Receiver<LogEntry>) = mpsc::channel();
757        let mut firewall = Firewall {
758            rules: vec![],
759            enabled: true,
760            policy_in: Default::default(),
761            policy_out: Default::default(),
762            tx,
763            data_link: Default::default(),
764            log: true,
765        };
766
767        let rules_1 = vec![
768            // no quick, first match wins
769            FirewallRule::new(
770                1,
771                "OUT DENY --source 192.168.200.135 --sport 6700:6800,8080",
772            )
773            .unwrap(),
774            FirewallRule::new(
775                2,
776                "OUT ACCEPT --source 192.168.200.135 --sport 6700:6800,8080",
777            )
778            .unwrap(),
779            FirewallRule::new(
780                3,
781                "OUT REJECT --source 192.168.200.135 --sport 6700:6800,8080",
782            )
783            .unwrap(),
784        ];
785        firewall = Firewall {
786            rules: rules_1,
787            ..firewall
788        };
789        assert_eq!(
790            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::OUT),
791            FirewallAction::DENY
792        );
793        assert_eq!(
794            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::IN),
795            FirewallAction::default()
796        );
797
798        let rules_2 = vec![
799            // quick match wins
800            FirewallRule::new(
801                1,
802                "+ OUT DENY --source 192.168.200.135 --sport 6700:6800,8080",
803            )
804            .unwrap(),
805            FirewallRule::new(
806                2,
807                "OUT ACCEPT --source 192.168.200.135 --sport 6700:6800,8080",
808            )
809            .unwrap(),
810            FirewallRule::new(
811                3,
812                "OUT REJECT --source 192.168.200.135 --sport 6700:6800,8080",
813            )
814            .unwrap(),
815        ];
816        firewall = Firewall {
817            rules: rules_2,
818            ..firewall
819        };
820        assert_eq!(
821            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::OUT),
822            FirewallAction::DENY
823        );
824
825        let rules_3 = vec![
826            // quick match wins even if after other matches
827            FirewallRule::new(
828                1,
829                "OUT DENY --source 192.168.200.135 --sport 6700:6800,8080",
830            )
831            .unwrap(),
832            FirewallRule::new(
833                2,
834                "OUT ACCEPT --source 192.168.200.135 --sport 6700:6800,8080",
835            )
836            .unwrap(),
837            FirewallRule::new(
838                3,
839                "+ OUT REJECT --source 192.168.200.135 --sport 6700:6800,8080",
840            )
841            .unwrap(),
842        ];
843        firewall = Firewall {
844            rules: rules_3,
845            ..firewall
846        };
847        assert_eq!(
848            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::OUT),
849            FirewallAction::REJECT
850        );
851
852        let rules_4 = vec![
853            // first quick match wins
854            FirewallRule::new(
855                1,
856                "OUT DENY --source 192.168.200.135 --sport 6700:6800,8080",
857            )
858            .unwrap(),
859            FirewallRule::new(
860                2,
861                "+ OUT ACCEPT --source 192.168.200.135 --sport 6700:6800,8080",
862            )
863            .unwrap(),
864            FirewallRule::new(
865                3,
866                "+ OUT REJECT --source 192.168.200.135 --sport 6700:6800,8080",
867            )
868            .unwrap(),
869        ];
870        firewall = Firewall {
871            rules: rules_4,
872            ..firewall
873        };
874        assert_eq!(
875            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::OUT),
876            FirewallAction::ACCEPT
877        );
878
879        let rules_5 = vec![
880            // only quick rules, first wins
881            FirewallRule::new(
882                1,
883                "+ OUT DENY --source 192.168.200.135 --sport 6700:6800,8080",
884            )
885            .unwrap(),
886            FirewallRule::new(
887                2,
888                "+ OUT ACCEPT --source 192.168.200.135 --sport 6700:6800,8080",
889            )
890            .unwrap(),
891            FirewallRule::new(
892                3,
893                "+ OUT REJECT --source 192.168.200.135 --sport 6700:6800,8080",
894            )
895            .unwrap(),
896        ];
897        firewall = Firewall {
898            rules: rules_5,
899            ..firewall
900        };
901        assert_eq!(
902            firewall.resolve_packet(&TCP_PACKET, FirewallDirection::OUT),
903            FirewallAction::DENY
904        );
905    }
906
907    #[test]
908    fn test_update_firewall_rules() {
909        let rules_before_update = vec![
910            FirewallRule::new(1,"OUT REJECT --source 192.168.200.135 --sport 6700:6800,8080").unwrap(),
911            FirewallRule::new(2,"OUT REJECT --source 192.168.200.135 --sport 6700:6800,8080 --dport 1,2,2000").unwrap(),
912            FirewallRule::new(3,"+ OUT DENY --source 192.168.200.135-192.168.200.140 --sport 6700:6800,8080 --dport 1,2,2000").unwrap(),
913            FirewallRule::new(4,"OUT REJECT --source 192.168.200.135 --sport 6750:6800,8080 --dest 192.168.200.21 --dport 1,2,2000").unwrap(),
914            FirewallRule::new(5,"IN ACCEPT --source 2.1.1.2 --dest 2.1.1.1 --proto 1 --icmp-type 8").unwrap(),
915            FirewallRule::new(6,"+ IN REJECT --source 2.1.1.2 --dest 2.1.1.1 --proto 1 --icmp-type 8").unwrap(),
916            FirewallRule::new(7,"IN ACCEPT --source 2.1.1.2 --dest 2.1.1.1 --proto 1 --icmp-type 9").unwrap(),
917            FirewallRule::new(8,"IN ACCEPT --source 2.1.1.2 --dest 2.1.1.1 --proto 58 --icmp-type 8").unwrap(),
918            FirewallRule::new(9,"OUT REJECT").unwrap(),
919            FirewallRule::new(10,"IN ACCEPT").unwrap(),
920        ];
921
922        let rules_after_update = vec![
923            FirewallRule::new(1, "OUT REJECT --dest 3ffe:507:0:1:200:86ff:fe05:800-3ffe:507:0:1:200:86ff:fe05:08dd --sport 545:560,43,53").unwrap(),
924            FirewallRule::new(2,"+ OUT ACCEPT --dest 3ffe:507:0:1:200:86ff:fe05:800-3ffe:507:0:1:200:86ff:fe05:08dd --sport 545:560,43,53").unwrap(),
925            FirewallRule::new(3,"OUT DENY --dest 3ffe:507:0:1:200:86ff:fe05:800-3ffe:507:0:1:200:86ff:fe05:08dd --proto 17 --sport 545:560,43,53 --dport 2396").unwrap(),
926            FirewallRule::new(4,"OUT REJECT --dest 3ffe:507:0:1:200:86ff:fe05:800-3ffe:507:0:1:200:86ff:fe05:08dd --proto 17 --sport 545:560,43,53 --dport 2395").unwrap(),
927            FirewallRule::new(5,"IN DENY --sport 40:49,53").unwrap(),
928            FirewallRule::new(6,"IN REJECT --sport 40:49,53 --source 3ffe:501:4819::41,3ffe:501:4819::42").unwrap(),
929        ];
930
931        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
932        assert_eq!(firewall.rules, rules_before_update);
933        assert!(firewall.enabled);
934        assert_eq!(firewall.policy_in, FirewallAction::ACCEPT);
935        assert_eq!(firewall.policy_out, FirewallAction::ACCEPT);
936
937        // update the rules
938        firewall.update_rules(TEST_FILE_3).unwrap();
939
940        assert_eq!(firewall.rules, rules_after_update);
941        assert!(firewall.enabled);
942        assert_eq!(firewall.policy_in, FirewallAction::ACCEPT);
943        assert_eq!(firewall.policy_out, FirewallAction::ACCEPT);
944    }
945
946    #[test]
947    fn test_log_disable() {
948        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
949        assert!(firewall.log);
950        firewall.log(false);
951        assert!(!firewall.log);
952        firewall.log(true);
953        assert!(firewall.log);
954    }
955
956    #[test]
957    fn test_file_error_invalid_dport_value() {
958        let path = &get_error_file_path("invalid_dport_value");
959        let expected = String::from(
960            "Firewall error at line 12 - incorrect value for option '--dport 8.8.8.8'",
961        );
962
963        assert_eq!(Firewall::new(path).unwrap_err().to_string(), expected);
964
965        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
966        assert_eq!(
967            firewall.update_rules(path).unwrap_err().to_string(),
968            expected
969        );
970    }
971
972    #[test]
973    fn test_file_error_invalid_sport_value() {
974        let path = &get_error_file_path("invalid_sport_value");
975        let expected =
976            String::from("Firewall error at line 1 - incorrect value for option '--sport 70000'");
977
978        assert_eq!(Firewall::new(path).unwrap_err().to_string(), expected);
979
980        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
981        assert_eq!(
982            firewall.update_rules(path).unwrap_err().to_string(),
983            expected
984        );
985    }
986
987    #[test]
988    fn test_file_error_invalid_dest_value() {
989        let path = &get_error_file_path("invalid_dest_value");
990        let expected =
991            String::from("Firewall error at line 18 - incorrect value for option '--dest 8080'");
992
993        assert_eq!(Firewall::new(path).unwrap_err().to_string(), expected);
994
995        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
996        assert_eq!(
997            firewall.update_rules(path).unwrap_err().to_string(),
998            expected
999        );
1000    }
1001
1002    #[test]
1003    fn test_file_error_invalid_source_value() {
1004        let path = &get_error_file_path("invalid_source_value");
1005        let expected = String::from(
1006            "Firewall error at line 9 - incorrect value for option '--source 8.8.8.8.7'",
1007        );
1008
1009        assert_eq!(Firewall::new(path).unwrap_err().to_string(), expected);
1010
1011        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
1012        assert_eq!(
1013            firewall.update_rules(path).unwrap_err().to_string(),
1014            expected
1015        );
1016    }
1017
1018    #[test]
1019    fn test_file_error_invalid_icmp_type_value() {
1020        let path = &get_error_file_path("invalid_icmp_type_value");
1021        let expected = String::from(
1022            "Firewall error at line 7 - incorrect value for option '--icmp-type ciao'",
1023        );
1024
1025        assert_eq!(Firewall::new(path).unwrap_err().to_string(), expected);
1026
1027        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
1028        assert_eq!(
1029            firewall.update_rules(path).unwrap_err().to_string(),
1030            expected
1031        );
1032    }
1033
1034    #[test]
1035    fn test_file_error_invalid_protocol_value() {
1036        let path = &get_error_file_path("invalid_protocol_value");
1037        let expected =
1038            String::from("Firewall error at line 101 - incorrect value for option '--proto -58'");
1039
1040        assert_eq!(Firewall::new(path).unwrap_err().to_string(), expected);
1041
1042        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
1043        assert_eq!(
1044            firewall.update_rules(path).unwrap_err().to_string(),
1045            expected
1046        );
1047    }
1048
1049    #[test]
1050    fn test_file_error_invalid_direction() {
1051        let path = &get_error_file_path("invalid_direction");
1052        let expected = String::from("Firewall error at line 4 - incorrect direction 'this'");
1053
1054        assert_eq!(Firewall::new(path).unwrap_err().to_string(), expected);
1055
1056        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
1057        assert_eq!(
1058            firewall.update_rules(path).unwrap_err().to_string(),
1059            expected
1060        );
1061    }
1062
1063    #[test]
1064    fn test_file_error_invalid_action() {
1065        let path = &get_error_file_path("invalid_action");
1066        let expected =
1067            String::from("Firewall error at line 1 - incorrect action 'DROPTHISPACKETOMG'");
1068
1069        assert_eq!(Firewall::new(path).unwrap_err().to_string(), expected);
1070
1071        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
1072        assert_eq!(
1073            firewall.update_rules(path).unwrap_err().to_string(),
1074            expected
1075        );
1076    }
1077
1078    #[test]
1079    fn test_file_error_unknown_option() {
1080        let path = &get_error_file_path("unknown_option");
1081        let expected =
1082            String::from("Firewall error at line 3 - the specified option '-dest' doesn't exist");
1083
1084        assert_eq!(Firewall::new(path).unwrap_err().to_string(), expected);
1085
1086        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
1087        assert_eq!(
1088            firewall.update_rules(path).unwrap_err().to_string(),
1089            expected
1090        );
1091    }
1092
1093    #[test]
1094    fn test_file_error_not_enough_arguments() {
1095        let path = &get_error_file_path("not_enough_arguments");
1096        let expected =
1097            String::from("Firewall error at line 8 - not enough arguments supplied for rule");
1098
1099        assert_eq!(Firewall::new(path).unwrap_err().to_string(), expected);
1100
1101        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
1102        assert_eq!(
1103            firewall.update_rules(path).unwrap_err().to_string(),
1104            expected
1105        );
1106    }
1107
1108    #[test]
1109    fn test_file_error_empty_option() {
1110        let path = &get_error_file_path("empty_option");
1111        let expected =
1112            String::from("Firewall error at line 20 - the supplied option '--sport' is empty");
1113
1114        assert_eq!(Firewall::new(path).unwrap_err().to_string(), expected);
1115
1116        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
1117        assert_eq!(
1118            firewall.update_rules(path).unwrap_err().to_string(),
1119            expected
1120        );
1121    }
1122
1123    #[test]
1124    fn test_file_error_duplicated_option() {
1125        let path = &get_error_file_path("duplicated_option");
1126        let expected = String::from(
1127            "Firewall error at line 9 - duplicated option '--dport' for the same rule",
1128        );
1129
1130        assert_eq!(Firewall::new(path).unwrap_err().to_string(), expected);
1131
1132        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
1133        assert_eq!(
1134            firewall.update_rules(path).unwrap_err().to_string(),
1135            expected
1136        );
1137    }
1138
1139    #[test]
1140    fn test_file_error_not_applicable_icmp_type() {
1141        let path = &get_error_file_path("not_applicable_icmp_type");
1142        let expected = String::from("Firewall error at line 6 - option '--icmp-type' is valid only if '--proto 1' or '--proto 58' is also specified");
1143
1144        assert_eq!(Firewall::new(path).unwrap_err().to_string(), expected);
1145
1146        let mut firewall = Firewall::new(TEST_FILE_1).unwrap();
1147        assert_eq!(
1148            firewall.update_rules(path).unwrap_err().to_string(),
1149            expected
1150        );
1151    }
1152}