pf_rs/
pf.rs

1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2021 Aleksandr Morozov, RELKOM s.r.o
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 *    - Redistributions of source code must retain the above copyright
12 *      notice, this list of conditions and the following disclaimer.
13 *    - Redistributions in binary form must reproduce the above
14 *      copyright notice, this list of conditions and the following
15 *      disclaimer in the documentation and/or other materials provided
16 *      with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 *
31 */
32 
33
34use std::fs::{File, OpenOptions};
35use std::os::fd::AsFd;
36use std::os::unix::fs::OpenOptionsExt;
37use std::path::Path;
38
39use nix::fcntl::{open, OFlag};
40use nix::sys::stat::Mode;
41use nix::libc;
42
43use crate::ioctl::pfioc_state_kill;
44use crate::pfr_buffer::{PfrBuffer, Pfrb};
45use crate::pfr_table::pfr_table;
46use crate::common;
47use crate::{error_coded, map_error_coded, runtime_exception::*};
48
49/*
50unsafe fn any_as_u8_slice<T: Sized>(p: &T) -> &[u8] {
51    ::core::slice::from_raw_parts(
52        (p as *const T) as *const u8,
53        ::core::mem::size_of::<T>(),
54    )
55}
56*/
57
58
59
60pub enum PfCmd
61{
62    /// Adds a host/hostname to table
63    Add{ hosts: Vec<String> }, 
64
65    /// Removes a host/hostname from table
66    Delete{ hosts: Vec<String> }, 
67
68    /// Checks if host/hostname presents in table
69    Test{ hosts: Vec<String> },
70
71    /// Flushes the 
72    Flush,
73}
74
75pub enum PfCmdFile<'p>
76{
77    /// Adds a host/hostname to table
78    Add{ file_path: &'p Path }, 
79
80    /// Removes a host/hostname from table
81    Delete{ file_path: &'p Path }, 
82
83    /// Checks if host/hostname presents in table
84    Test{ file_path: &'p Path },
85
86    /// Flushes the 
87    Flush,
88}
89
90
91pub struct Pf
92{
93    fd: File,
94    test: bool
95}
96
97impl Pf
98{
99    const DEFAULT_PF_DEVICE: &'static str = "/dev/pf";
100
101    /// Creates new instance. Opens the pf device in dev.
102    /// 
103    /// The file descriptor for the opened PF device will be closed on 
104    /// drop of this instance, so don't keep it.
105    pub 
106    fn new(pf_dev_path: Option<&str>) -> PfResult<Self>
107    {
108        let path = Path::new(pf_dev_path.unwrap_or(Self::DEFAULT_PF_DEVICE));
109
110        if path.exists() == false
111        {
112            error_coded!(PfErrCode::IOErr, "file: '{}' does not exist!", path.display());
113        }
114        else if path.is_dir() == true
115        {
116            error_coded!(PfErrCode::IOErr, "file: '{}' is directory!", path.display());
117        }
118
119        let file = 
120            OpenOptions::new()
121                .custom_flags(libc::O_NONBLOCK | libc::O_RDWR)
122                .open(path)
123                .map_err(|e|
124                    map_error_coded!(PfErrCode::IOErr, "open() failed path {} err: {}", Self::DEFAULT_PF_DEVICE, e)
125                )?;
126
127        /*let fd = 
128            open(path, OFlag::O_NONBLOCK | OFlag::O_RDWR, Mode::empty())
129                .map_err(|e| 
130                    map_error_coded!(PfErrCode::IOErr, "open() failed path {} err: {}", Self::DEFAULT_PF_DEVICE, e)
131                )?;*/
132
133        return Ok(Self{ fd: file, test: false });
134    }
135
136    #[cfg(test)]
137    pub 
138    fn new_test() -> PfResult<Self>
139    {
140        use std::os::fd::FromRawFd;
141
142        let file = unsafe { File::from_raw_fd(1) };
143
144        return Ok(Self{ fd: file, test: true });
145    }
146
147    // internal functions to be called from public interface
148    // https://github.com/freebsd/freebsd-src/blob/373ffc62c158e52cde86a5b934ab4a51307f9f2e/sbin/pfctl/pfctl.c#L638
149    
150    /// Kills the state from the state table of the firewall. When host address
151    /// is added to the table, it is not purged from the state table automatically
152    /// and the connection will stay alive until the state is not killed.
153    /// 
154    /// # Arguments
155    /// 
156    /// * `src` - a host IP address to be killed.
157    /// 
158    /// * `dest` - a destination IP address to match if any.
159    pub 
160    fn pfctl_kill_state<H: AsRef<str>>(&self, src: H, dest: Option<H>) -> PfResult<()>
161    {
162        return unsafe { pfioc_state_kill::new(self.fd.as_fd(), self.test, src, dest) };
163    }
164
165    /// Performs the operation on a specific table from the file.
166    /// 
167    /// # Arguments
168    /// 
169    /// * `table_name` - a reference to a name of the table on which the operation
170    ///     should be performed.
171    /// 
172    /// * `cmd` - a [PfCmd] operation to be performed with the payload data.
173    /// 
174    /// # Returns
175    /// 
176    /// A [PfResult] is returned.
177    /// 
178    /// * [Result::Ok] is returned with the amound of the successfully performed
179    ///     operations (pedends on the amount of the data in payload).
180    /// 
181    /// * [Result::Err] is returned with error description.
182    pub 
183    fn pfctl_table_file<'p, T: AsRef<str>>(&self, table_name: T, cmd: PfCmdFile<'p>) -> PfResult<i32>
184    {
185        let tname = table_name.as_ref();
186
187        let mut table: pfr_table = unsafe { pfr_table::new(tname)? };
188        //let mut b: PfrBuffer = PfrBuffer::new();
189        //let mut b2: PfrBuffer = PfrBuffer::new();
190
191        if tname.len() >= common::PF_TABLE_NAME_SIZE
192        {
193            error_coded!(PfErrCode::Einval, "table name: '{}' is too long!", tname);
194        }
195
196        // verify that onlu ASCII chars are in tname
197        if tname.chars().all(|c| c.is_ascii()) == false
198        {
199            error_coded!(PfErrCode::Einval, "non ascii character in table_name: {}", tname);
200        }
201
202        //table.pfrt_name.copy_from_slice(tname.as_bytes());
203
204        // https://github.com/freebsd/freebsd-src/blob/373ffc62c158e52cde86a5b934ab4a51307f9f2e/sbin/pfctl/pfctl_table.c :201
205        let ret_val = 
206            match cmd
207            {
208                PfCmdFile::Add{ file_path } =>
209                {
210                    let mut b: PfrBuffer = PfrBuffer::new(Pfrb::PFRB_ADDRS);
211                    
212                    unsafe
213                    {
214                        b.load_addr_from_file(file_path, 0)?;
215
216                        table.create_table(self.fd.as_fd(), self.test)?;
217
218                        table.add_addrs(self.fd.as_fd(), b, self.test)?
219                    }
220                },
221                PfCmdFile::Delete{ file_path } => 
222                {
223                    let mut b: PfrBuffer = PfrBuffer::new(Pfrb::PFRB_ADDRS);
224
225                    unsafe 
226                    {
227                        b.load_addr_from_file(file_path, 0)?;
228
229                        table.del_addrs(self.fd.as_fd(), b)? 
230                    }
231                },
232                PfCmdFile::Test{ file_path } => 
233                {
234                    let mut b: PfrBuffer = PfrBuffer::new(Pfrb::PFRB_ADDRS);
235
236                    unsafe 
237                    {
238                        b.load_addr_from_file(file_path, 0)?;
239
240                        table.tst_addrs(self.fd.as_fd(), b)?
241                    }
242                },
243                PfCmdFile::Flush => 
244                {
245                    unsafe { table.fls_addrs(self.fd.as_fd())? }
246                }
247            }; // match
248
249        return Ok(ret_val);
250    }
251
252    /// Performs the operation on a specific table.
253    /// 
254    /// # Arguments
255    /// 
256    /// * `table_name` - a reference to a name of the table on which the operation
257    ///     should be performed.
258    /// 
259    /// * `cmd` - a [PfCmd] operation to be performed with the payload data.
260    /// 
261    /// # Returns
262    /// 
263    /// A [PfResult] is returned.
264    /// 
265    /// * [Result::Ok] is returned with the amound of the successfully performed
266    ///     operations (pedends on the amount of the data in payload).
267    /// 
268    /// * [Result::Err] is returned with error description.
269    pub  
270    fn pfctl_table<T: AsRef<str>>(&self, table_name: T, cmd: PfCmd) -> PfResult<i32>
271    {
272        let tname = table_name.as_ref();
273
274        let mut table: pfr_table = unsafe { pfr_table::new(tname)? };
275        //let mut b: PfrBuffer = PfrBuffer::new();
276        //let mut b2: PfrBuffer = PfrBuffer::new();
277
278        if tname.len() >= common::PF_TABLE_NAME_SIZE
279        {
280            error_coded!(PfErrCode::Einval, "table name: '{}' is too long!", tname);
281        }
282
283        // verify that onlu ASCII chars are in tname
284        if tname.chars().all(|c| c.is_ascii()) == false
285        {
286            error_coded!(PfErrCode::Einval, "non ascii character in table_name: {}", tname);
287        }
288
289        //table.pfrt_name.copy_from_slice(tname.as_bytes());
290
291        // https://github.com/freebsd/freebsd-src/blob/373ffc62c158e52cde86a5b934ab4a51307f9f2e/sbin/pfctl/pfctl_table.c :201
292        let ret_val = 
293            match cmd
294            {
295                PfCmd::Add{ hosts } =>
296                {
297                    let mut b: PfrBuffer = PfrBuffer::new(Pfrb::PFRB_ADDRS);
298                    //b.pfrb_type = Pfrb::PFRB_ADDRS as libc::c_int; //C b.pfrb_type = PFRB_ADDRS;
299
300                    for host in hosts
301                    {
302                        unsafe { b.load_addr_from_str(host, 0)? }; //C if (load_addr(&b, argc, argv, file, 0))
303                    }
304
305                    unsafe
306                    {
307                        table.create_table(self.fd.as_fd(), self.test)?;
308
309                        table.add_addrs(self.fd.as_fd(), b, self.test)?
310                    }
311                },
312                PfCmd::Delete{ hosts } => 
313                {
314                    let mut b: PfrBuffer = PfrBuffer::new(Pfrb::PFRB_ADDRS);
315                    //b.pfrb_type = Pfrb::PFRB_ADDRS as libc::c_int; //C b.pfrb_type = PFRB_ADDRS;
316
317                    for host in hosts
318                    {
319                        unsafe { b.load_addr_from_str(host, 0)? }; //C if (load_addr(&b, argc, argv, file, 0))
320                    }
321
322                    unsafe { table.del_addrs(self.fd.as_fd(), b)? }
323                },
324                PfCmd::Test{ hosts } => 
325                {
326                    let mut b: PfrBuffer = PfrBuffer::new(Pfrb::PFRB_ADDRS);
327                    //b.pfrb_type = Pfrb::PFRB_ADDRS as libc::c_int; //C b.pfrb_type = PFRB_ADDRS;
328                    //b2.pfrb_type = Pfrb::PFRB_ADDRS as libc::c_int; //C b.pfrb_type = PFRB_ADDRS;
329
330                    for host in hosts
331                    {
332                        unsafe { b.load_addr_from_str(host, 1)? }; //C if (load_addr(&b, argc, argv, file, 1))
333                    }
334
335                    unsafe 
336                    {
337                        table.tst_addrs(self.fd.as_fd(), b)?
338                    }
339                },
340                PfCmd::Flush => 
341                {
342                    unsafe { table.fls_addrs(self.fd.as_fd())? }
343                }
344            }; // match
345
346        return Ok(ret_val);
347    }
348
349    
350}
351
352#[cfg(test)]
353mod tests
354{
355    use crate::{ioctl::pfioc_table, pfr_addr::pf_addr};
356
357    use super::*;
358    
359    #[test]
360    fn test_real_add()
361    {
362        let pf = Pf::new_test().unwrap();
363
364        pf.pfctl_table("test", PfCmd::Add{ hosts: vec!["192.168.2.1".to_string()] }).unwrap();
365    }
366
367    #[test]
368    fn test_real_check()
369    {
370        let pf = Pf::new_test().unwrap();
371
372        pf.pfctl_table("test", PfCmd::Test{ hosts: vec!["192.168.2.1".to_string()] }).unwrap();
373    }
374
375    #[test]
376    fn test_real_del() 
377    {
378        let pf = Pf::new_test().unwrap();
379
380        pf.pfctl_table("test", PfCmd::Delete{ hosts: vec!["192.168.2.1".to_string()] }).unwrap();
381    }
382
383    #[test]
384    fn test_kill_state()
385    {
386        let pf = Pf::new_test().unwrap();
387
388         pf.pfctl_kill_state("192.168.2.104", None).unwrap();
389    }
390
391    #[test]
392    fn fake_test_real_kill_state()
393    {
394        let pf = Pf::new_test().unwrap();
395
396        pf.pfctl_kill_state("192.168.2.1", None).unwrap();
397    }
398
399    #[test]
400    fn fake_test_add()
401    {
402        let pf = Pf::new_test().unwrap();
403
404        pf.pfctl_table("test", PfCmd::Add{ hosts: vec!["192.168.2.1".to_string()] }).unwrap();
405    }
406
407    #[test]
408    fn fake_test2_add()
409    {
410        let pf = Pf::new_test().unwrap();
411
412        pf.pfctl_table("test", PfCmd::Add{ hosts: vec!["192.168.2.0/24".to_string()] }).unwrap();
413    }
414
415    #[test]
416    fn test_struct_sizes()
417    {
418        let s = std::mem::size_of::<pfioc_table>();
419        println!("sizeof(struct pfioc_table = 1104) ?= {}", s);
420        assert_eq!(1104, s);
421
422        let s = std::mem::size_of::<pfr_table>();
423        println!("sizeof(struct pfr_table = 1064) ?= {}", s);
424        assert_eq!(1064, s);
425
426        let s = std::mem::size_of::<pfioc_state_kill>();
427        println!("sizeof(struct pfioc_state_kill = 224) ?= {}", s);
428        assert_eq!(224, s);
429        
430        let psk: pfioc_state_kill = unsafe {std::mem::zeroed()};
431        let s = std::mem::size_of_val(&psk.psk_af);
432        println!("sizeof(psk.psk_af) = {}", s);
433        assert_eq!(1, s);
434
435        let s = std::mem::size_of_val(&psk.psk_src);
436        println!("sizeof(psk.psk_src) = {}", s);
437        assert_eq!(56, s);
438
439        let s = std::mem::size_of_val(&psk.psk_dst);
440        println!("sizeof(psk.psk_dst) = {}", std::mem::size_of_val(&psk.psk_dst));
441        assert_eq!(56, s);
442
443        let s = std::mem::size_of_val(&psk.psk_ifname);
444        println!("sizeof(psk.psk_ifname) = {}", std::mem::size_of_val(&psk.psk_ifname));
445        assert_eq!(16, s);
446
447        let s = std::mem::size_of_val(&psk.psk_label);
448        println!("sizeof(psk.psk_label) = {}", std::mem::size_of_val(&psk.psk_label));
449        assert_eq!(64, s);
450
451        let s = std::mem::size_of_val(&psk.psk_killed);
452        println!("sizeof(psk.psk_killed) = {}", std::mem::size_of_val(&psk.psk_killed));
453        assert_eq!(4, s);
454
455        let s = std::mem::size_of_val(&psk.psk_proto);
456        println!("sizeof(psk.psk_proto) = {}", std::mem::size_of_val(&psk.psk_proto));
457        assert_eq!(4, s);
458
459        let s = std::mem::size_of_val(&psk.psk_pfcmp);
460        println!("sizeof(psk.psk_pfcmp) = {}", std::mem::size_of_val(&psk.psk_pfcmp));
461        assert_eq!(16, s);
462
463        let s = std::mem::size_of_val(&psk.psk_src.addr);
464        println!("sizeof(psk.psk_src.addr) = {}", std::mem::size_of_val(&psk.psk_src.addr));
465        assert_eq!(48, s);
466
467        let s = std::mem::size_of_val(&psk.psk_src.addr.v);
468        println!("sizeof(psk.psk_src.addr.v) = {}", std::mem::size_of_val(&psk.psk_src.addr.v));
469        assert_eq!(32, s);
470
471        let s = std::mem::size_of_val(&psk.psk_src.addr.p);
472        println!("sizeof(psk.psk_src.addr.p) = {}", std::mem::size_of_val(&psk.psk_src.addr.p));
473        assert_eq!(8, s);
474
475        let s = std::mem::size_of_val(&psk.psk_src.addr.tp);
476        println!("sizeof(psk.psk_src.addr.tp) = {}", std::mem::size_of_val(&psk.psk_src.addr.tp));
477        assert_eq!(1, s);
478
479        let s = std::mem::size_of_val(&psk.psk_src.addr.iflags);
480        println!("sizeof(psk.psk_src.addr.iflags) = {}", std::mem::size_of_val(&psk.psk_src.addr.iflags));
481        assert_eq!(1, s);
482    }
483
484    /*
485    DIOCKILLSTATES: 3235922985 sizeof(224)
486    psk.psk_af size: 1
487    psk.psk_src size: 56
488    psk.psk_dst size: 56
489    psk.psk_ifname size: 16
490    psk.psk_label size: 64
491    psk.psk_killed size: 4
492    psk.psk_proto size: 4
493    psk.psk_pfcmp size: 16
494    psk.psk_src.addr size: 48
495    psk.psk_src.addr size: 48
496
497    psk.psk_src.addr.v size: 32
498    psk.psk_src.addr.p size: 8
499    psk.psk_src.addr.type size: 1
500    psk.psk_src.addr.iflags size: 1
501
502    */
503    #[test]
504    fn test_unmask()
505    {
506        use std::net::Ipv4Addr;
507
508        fn create(ip: &'static str) -> libc::c_int
509        {
510            let ipv4: Ipv4Addr = ip.parse().unwrap();
511
512            let ip = pf_addr::from(ipv4.octets());
513
514            return ip.unmask(0);
515        }
516
517
518        assert_eq!(create("255.255.255.255"), 32);
519        assert_eq!(create("192.168.1.1"), 2);
520        assert_eq!(create("10.0.0.0"), 0);
521        assert_eq!(create("10.5.6.3"), 0);
522        assert_eq!(create("134.156.43.230"), 1);
523        assert_eq!(create("8.8.8.8"), 0);
524        assert_eq!(create("244.255.255.255"), 4);
525        assert_eq!(create("0.0.0.0"), 0);
526        assert_eq!(create("100.200.255.255"), 0);
527    }
528 
529
530   
531}