1use 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 pub fn init() -> Result<IpsetSys, IpsetSysError> {
64 let is = unsafe {
65 bindings::ipset_load_types();
66 bindings::ipset_init()
67 };
68
69 if is.is_null() {
72 return Err(IpsetSysError::CantInit);
73 }
74
75 Ok(IpsetSys { is })
76 }
77
78 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 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}