ipset_sys/
lib.rs

1//! A small crate to talk to ipset from rust.
2//!
3//! supports both regular `libipset` commands, and custom/faster handler for adding ipv4 to sets
4//!
5//! # Example
6//!
7//! ```rust
8//! use ipset_sys::IpsetSys;
9//!
10//! fn main() {
11//!     let mut is = IpsetSys::init().unwrap();
12//!
13//!     // regular libipset command
14//!     is.run("create bob hash:ip timeout 3600").unwrap();
15//!
16//!     // custom ipv4 handler to add to a set
17//!     let addr = std::net::Ipv4Addr::new(1, 4, 4, 4);
18//!     is.add_v4("bob", addr).unwrap();
19//!
20//!     is.run("destroy bob").unwrap();
21//! }
22//! ```
23//!
24use std::ffi::{c_void, CString};
25use std::net::Ipv4Addr;
26use thiserror::Error;
27
28#[derive(Error, Debug)]
29pub enum IpsetSysError {
30    #[error("can not init ipset")]
31    CantInit,
32    #[error("can not execute ipset command")]
33    CantExecuteCommand,
34    #[error("command invalid")]
35    CommandInvalid,
36    #[error("argument too long")]
37    ArgTooLong,
38    #[error("invalid timeout, should be 0 < t < 2147483")]
39    InvalidTimeout,
40    #[error("invalid command - can not parse to CString")]
41    InvalidCommand {
42        #[from]
43        source: std::ffi::NulError,
44    },
45}
46
47mod bindings;
48
49macro_rules! ensure_ptr {
50    ( $x:expr ) => {
51        if $x.is_null() {
52            return Err(IpsetSysError::CantExecuteCommand);
53        }
54    };
55}
56
57pub struct IpsetSys {
58    is: *mut bindings::ipset,
59}
60
61impl IpsetSys {
62    /// init ipset handler. will fail upon unsufficient rights, or lacking library.
63    pub fn init() -> Result<IpsetSys, IpsetSysError> {
64        let is = unsafe {
65            bindings::ipset_load_types();
66            bindings::ipset_init()
67        };
68
69        // todo: printf
70
71        if is.is_null() {
72            return Err(IpsetSysError::CantInit);
73        }
74
75        Ok(IpsetSys { is })
76    }
77
78    /// normal libipset command handler. parses string commands as directed by CLI
79    /// see `man ipset` for more details
80    ///
81    /// # Example
82    ///
83    /// ```rust
84    ///   let mut is = ipset_sys::IpsetSys::init().unwrap();
85    ///   is.run("create bob hash:ip timeout 3600").unwrap();
86    ///   is.run("add bob 1.4.4.4").unwrap();
87    /// ```
88    pub fn run(&mut self, cmd: &str) -> Result<(), IpsetSysError> {
89        let ccmd = CString::new(cmd.to_string())?;
90        let ret = unsafe { bindings::ipset_parse_line(self.is, ccmd.into_raw()) };
91
92        if ret < 0 {
93            return Err(IpsetSysError::CantExecuteCommand);
94        }
95
96        Ok(())
97    }
98
99    /// custom ipset command to add an ipv4 to a given set.
100    /// this command directly issues ipset commands without going through the CLI parser, etc...
101    ///
102    /// # Example
103    ///
104    /// ```rust
105    ///   let mut is = ipset_sys::IpsetSys::init().unwrap();
106    ///   let addr = Ipv4Addr::new(1, 4, 4, 4);
107    ///   is.add_v4("bob", addr).unwrap();
108    /// ```
109    pub fn add_v4(&mut self, set: &str, target: Ipv4Addr) -> Result<(), IpsetSysError> {
110        let set_ptr = parse_user_string(set)?;
111        let target = parse_ipv4(target);
112        let target_ptr = &target as *const _ as *const c_void;
113
114        let nf_ipv4_ptr = &bindings::NFPROTO_IPV4 as *const _ as *const c_void;
115
116        unsafe {
117            let (sess, data) = self.get_sess_and_data()?;
118            set_data(data, bindings::ipset_opt_IPSET_SETNAME, set_ptr)?;
119            set_data(data, bindings::ipset_opt_IPSET_OPT_TYPE, nf_ipv4_ptr)?;
120            set_data(data, bindings::ipset_opt_IPSET_OPT_FAMILY, nf_ipv4_ptr)?;
121            set_data(data, bindings::ipset_opt_IPSET_OPT_IP, target_ptr)?;
122
123            run_cmd(sess, bindings::ipset_cmd_IPSET_CMD_ADD)?;
124        }
125        Ok(())
126    }
127
128    unsafe fn get_sess_and_data(
129        &mut self,
130    ) -> Result<(*mut bindings::ipset_session, *mut bindings::ipset_data), IpsetSysError> {
131        let sess = bindings::ipset_session(self.is);
132        ensure_ptr!(sess);
133        let data = bindings::ipset_session_data(sess);
134        ensure_ptr!(data);
135        Ok((sess, data))
136    }
137}
138
139unsafe fn set_data(
140    data: *mut bindings::ipset_data,
141    cmd: bindings::ipset_opt,
142    val: *const c_void,
143) -> Result<(), IpsetSysError> {
144    let ret = bindings::ipset_data_set(data, cmd, val);
145    if ret < 0 {
146        return Err(IpsetSysError::CantExecuteCommand);
147    }
148    Ok(())
149}
150
151unsafe fn run_cmd(
152    sess: *mut bindings::ipset_session,
153    cmd: bindings::ipset_cmd,
154) -> Result<(), IpsetSysError> {
155    let ret = bindings::ipset_cmd(sess, cmd, 0);
156    if ret < 0 {
157        return Err(IpsetSysError::CantExecuteCommand);
158    }
159    Ok(())
160}
161
162fn parse_user_string(set: &str) -> Result<*const c_void, IpsetSysError> {
163    if set.len() as u32 > bindings::IPSET_MAXNAMELEN {
164        return Err(IpsetSysError::ArgTooLong);
165    }
166    let cset = CString::new(set.to_string())?;
167    let ptr = cset.into_raw() as *const c_void;
168    Ok(ptr)
169}
170
171fn parse_ipv4(target: Ipv4Addr) -> libc::in_addr {
172    let addr_u32: u32 = target.into();
173    libc::in_addr {
174        s_addr: addr_u32.to_be(),
175    }
176}
177
178impl Drop for IpsetSys {
179    fn drop(&mut self) {
180        unsafe { bindings::ipset_fini(self.is) };
181    }
182}