ipfw_rs/
lib.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//! ipfw-rs
34//! 
35//! <img src="https://cdn.4neko.org/ipfw.webp" width="280"/> <img src="https://cdn.4neko.org/source_avail.webp" width="280"/> <img src="https://cdn.4neko.org/bds2_2.webp" width="280"/>
36//! 
37//! Provides a userspace (partial) interface to the raw socket of the FreeBSD's firewall IPFW.
38//!
39//! Supports:
40//! - Add, Test, Delete multiple IP/IPv6/DNS
41//! 
42//! This crate supports only FreeBSD 14, but it that protocol is compatiable with the FreeBSD 13.
43//!
44//! This is an experimantal crate!
45extern crate libc;
46extern crate nix;
47extern crate mac_address;
48extern crate cdns_rs;
49
50mod xbuffer;
51mod ipfw;
52mod ipfw2;
53mod misc;
54
55mod portable;
56
57#[macro_use] mod common;
58#[macro_use] pub mod runtime_exception;
59
60pub use ipfw_api::*;
61
62pub mod ipfw_api
63{
64    use std::os::fd::{AsFd, FromRawFd, OwnedFd};
65
66    use nix::errno::Errno;
67
68    use crate::{ipfw::{ipfw_check_name, ipfw_obj_header}, ipfw2::IP_FW3Opcodes, runtime_exception::IpfwResult};
69
70
71    pub enum IpfwCmd<'host>
72    {
73        /// Adds a host/hostname to table
74        Add{ hosts: Vec<&'host str>, req_atomic_op: bool }, 
75
76        /// Removes a host/hostname from table
77        Delete{ hosts: Vec<&'host str> }, 
78
79        /// Checks if host/hostname presents in table
80        Test{ hosts: Vec<&'host str> },
81
82        /// Flushes the 
83        Flush,
84    }
85
86    impl<'host> IpfwCmd<'host>
87    {
88        pub 
89        fn require_atomic(&self) -> bool
90        {
91            match *self
92            {
93                Self::Add{ req_atomic_op, .. } => 
94                    return req_atomic_op,
95                _ => 
96                    return false,
97            }
98        }
99
100        pub 
101        fn count_hosts(&self) -> usize
102        {
103            match *self
104            {
105                Self::Add{ ref hosts, .. } => return hosts.len(),
106                Self::Delete{ ref hosts } => return hosts.len(),
107                Self::Test{ ref hosts } => return hosts.len(), // lookup
108                Self::Flush => return 0,
109            }
110        }
111    }
112
113
114    /// The crates main, public instance which should be used by the program.
115    pub struct Ipfw
116    {
117        /// A raw socket to the IPFW instance.
118        socket_fd: OwnedFd,
119    }
120
121    impl Ipfw
122    {
123        /// Creates new instance and opens the connection to the IPFW instance in kernel.
124        pub 
125        fn new() -> IpfwResult<Self>
126        {
127            let fd = 
128                unsafe 
129                { 
130                    libc::socket(libc::AF_INET, libc::SOCK_RAW, libc::IPPROTO_RAW) 
131                };
132
133            if fd < 0
134            {
135                runtime_error!("socket() failed: {}", nix::Error::last());
136            }
137
138            return Ok(
139                Self{ socket_fd: unsafe{ OwnedFd::from_raw_fd(fd) } }
140            );
141        }
142
143        // https://www.unix.com/man-page/FreeBSD/8/ipfw/
144        /// A commands over tables only.
145        /// 
146        /// # Arguments
147        /// 
148        /// * `tablename` - a title of the table.
149        /// 
150        /// * `cmd` - a [IpfwCmd] command which is required to run.
151        /// 
152        /// * `quite` - do not output to stdout (unused).
153        /// 
154        /// # Returns 
155        /// 
156        /// A [Result] is returned as type [IpfwResult]
157        /// 
158        /// * [Result::Ok] - with the amount of the successfull operations.
159        /// 
160        /// * [Result::Err] - an error description witht eh error code.
161        pub 
162        fn ipfw_table_handler<T>(&self, tablename: T, cmd: IpfwCmd, quite: bool) -> IpfwResult<i32>
163        where T: AsRef<str>
164        {
165            //let do_add: bool = false;
166            let mut is_all: libc::c_int = 0;
167            let mut oh = ipfw_obj_header::new_zeroed();
168            let tbl_name = tablename.as_ref();
169
170            let set = 0;
171                /*if g_co_set != 0
172                {
173                    g_co_set - 1
174                }
175                else
176                {
177                    0
178                };*/
179
180            // performing operations on table name
181            match ipfw_check_name(tbl_name)
182            {
183                Ok(_) =>
184                {
185                    oh.ntlv.table_fill_ntlv(tbl_name, set, 1)?;
186                    oh.idx = 1;
187                },
188                Err(e) =>
189                {
190                    if tbl_name == "all"
191                    {
192                        is_all = 1;
193                    }
194                    else
195                    {
196                        return Err(e);
197                    }
198                }
199            }
200
201            // table command
202            // tcmd = get_token(tablecmds, *av, "table command");
203            //let tcmd = _s_x::get_token(tablecmds, IpfwCmd::to_cmd(&self), "table command")?;
204            
205            /*if req_atomic_op == true
206            {
207                if tcmd != tokens::TOK_ADD
208                {
209                    runtime_error!("atomic is not compatible with: '{}'", IpfwCmd::to_cmd(&self));
210                }
211            }*/
212
213            /*match tcmd
214            { 
215                tokens::TOK_LIST | tokens::TOK_INFO | 
216                tokens::TOK_DETAIL | 
217                tokens::TOK_FLUSH | tokens::TOK_DESTROY => {},
218                _ =>
219                {
220                    if is_all != 0
221                    {
222                        runtime_error!("table name is required");
223                    }
224                }
225            }*/
226            match cmd
227            {
228                IpfwCmd::Flush => {},
229                _ => 
230                {
231                    if is_all != 0
232                    {
233                        runtime_error!("table name is required");
234                    }
235                }
236            }
237
238            // https://github.com/freebsd/freebsd-src/blob/373ffc62c158e52cde86a5b934ab4a51307f9f2e/sbin/ipfw/tables.c#L225
239            match cmd
240            {
241                IpfwCmd::Add{ .. } | IpfwCmd::Delete{ .. } =>
242                {
243                    //C table_modify_record(&oh, ac, av, do_add, g_co.do_quiet, g_co.do_quiet, atomic);
244                    // I don't know what is update argument but it is supplied with quite flag, so when quite flag is
245                    //  raised then update is 1
246                    //todo
247                    return oh.table_modify_record(self.socket_fd.as_fd(), cmd, quite);
248                },
249                /*tokens::TOK_CREATE =>
250                {
251
252                },*/
253                /*TOK_MODIFY =>
254                {
255
256                },*/
257                /*TOK_DESTROY => 
258                {
259
260                },*/
261                IpfwCmd::Flush =>
262                {
263                    // Flush ALL only!
264                    match IP_FW3Opcodes::table_flush(self.socket_fd.as_fd(), &oh)
265                    {
266                        Ok(_) => 
267                            return Ok(0),
268                        Err(e) if e.get_errno() == Errno::ESRCH => 
269                            return Ok(0),
270                        Err(e) => 
271                            runtime_error!("Flush of table: '{}' failed with error: {}", tbl_name, e),
272                    }
273                },
274                //TOK_SWAP
275                //TOK_LOCK | TOK_UNLOCK
276                //TOK_DETAIL | TOK_INFO
277                // TOK_LIST
278                IpfwCmd::Test{ hosts } =>  // TOK_LOOKUP
279                {
280                    let mut ret: i32 = 0;
281                    for host in hosts
282                    {
283                        match oh.table_lookup(self.socket_fd.as_fd(), host)
284                        {
285                            Ok(res) => ret += res,
286                            Err(e) =>
287                            {
288                                println!("{}", e)
289                            }
290                        }
291
292                    }
293                    
294                    return Ok(ret);
295                }
296            }
297
298        }
299
300    }
301
302}