feat: update ipset

This commit is contained in:
2025-08-02 09:47:20 +08:00
parent 116297b7e5
commit 3c30a313dd

View File

@@ -1,40 +1,74 @@
use rust_util::{opt_result, simple_error, XResult}; use rust_util::{opt_result, simple_error, XResult};
use std::error::Error;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::process::Command; use std::process::Command;
const CMD_IPSET: &str = "ipset"; const CMD_IPSET: &str = "ipset";
// TODO pub struct IpSet {
// list names
// list ipsetname
// add
// del
struct IpSet {
name: String, name: String,
} }
impl IpSet { impl IpSet {
pub fn list_names() -> Vec<String> { pub fn new(name: &str) -> XResult<IpSet> {
vec![] if name.is_empty() {
return simple_error!("ipset name cannot be empty");
}
Ok(IpSet {
name: String::from(name),
})
} }
pub fn list(&self) -> Vec<String> { pub fn list_names() -> XResult<Vec<String>> {
vec![] list_names()
} }
pub fn add(&self, ip: &str) -> () {}
pub fn del(&self, ip: &str) -> () {}
} }
fn list_names() {} impl IpSet {
pub fn list(&self) -> XResult<Vec<String>> {
let (stdout, _stderr) = execute_ipset(&["list", &self.name])?;
let ipset_list_ips = opt_result!(
String::from_utf8(stdout.clone()),
"Parse output: {} failed: {}",
String::from_utf8_lossy(&stdout)
);
Ok(parse_ipset_list_ips(&ipset_list_ips))
}
pub fn add(&self, ip: &str) -> XResult<()> {
add_ipset(&self.name, ip)
}
pub fn del(&self, ip: &str) -> XResult<()> {
del_ipset(&self.name, ip)
}
}
fn list_names() -> XResult<Vec<String>> {
let (stdout, _stderr) = execute_ipset(&["list", "-n"])?;
let ipset_list_names = opt_result!(
String::from_utf8(stdout.clone()),
"Parse output: {} failed: {}",
String::from_utf8_lossy(&stdout)
);
Ok(parse_ipset_list(&ipset_list_names))
}
fn list_ipset(ipset_name: &str) {} fn list_ipset(ipset_name: &str) {}
fn add_ipset(ipset_name: &str, ip: &str) {} fn add_ipset(ipset_name: &str, ip: &str) -> XResult<()> {
if let Err(err) = execute_ipset(&["add", ipset_name, ip]) {
return check_result(err, &["cannot be added to the set: it's already added"]);
}
Ok(())
}
fn del_ipset(ipset_name: &str, ip: &str) {} fn del_ipset(ipset_name: &str, ip: &str) -> XResult<()> {
if let Err(err) = execute_ipset(&["del", ipset_name, ip]) {
return check_result(err, &["cannot be deleted from the set: it's not added"]);
}
Ok(())
}
fn execute_ipset<I, S>(args: I) -> XResult<(Vec<u8>, Vec<u8>)> fn execute_ipset<I, S>(args: I) -> XResult<(Vec<u8>, Vec<u8>)>
where where
@@ -46,9 +80,100 @@ where
let cmd_output = opt_result!(cmd_ipset.output(), "Execute ipset with failed: {}"); let cmd_output = opt_result!(cmd_ipset.output(), "Execute ipset with failed: {}");
if !cmd_output.status.success() { if !cmd_output.status.success() {
return simple_error!( return simple_error!(
"Execute ipset not failed, exit code: {:?}", "Execute ipset not failed, exit code: {:?}, stdout: {}, stderr: {}",
cmd_output.status.code() cmd_output.status.code(),
String::from_utf8_lossy(&cmd_output.stdout),
String::from_utf8_lossy(&cmd_output.stderr)
); );
} }
Ok((cmd_output.stdout, cmd_output.stderr)) Ok((cmd_output.stdout, cmd_output.stderr))
} }
fn check_result(e: Box<dyn Error>, allowed_messages: &[&str]) -> XResult<()> {
let e_msg = e.to_string();
for allowed_message in allowed_messages {
if e_msg.contains(allowed_message) {
return Ok(());
}
}
Err(e)
}
fn parse_ipset_list(ipset_list: &str) -> Vec<String> {
ipset_list
.split('\n')
.map(|name| name.trim())
.filter(|name| !name.is_empty())
.map(ToString::to_string)
.collect::<Vec<_>>()
}
fn parse_ipset_list_ips(ipset_list: &str) -> Vec<String> {
let mut result = vec![];
let mut is_after_members = false;
ipset_list.split('\n').for_each(|ln| {
let ln = ln.trim();
if !ln.is_empty() {
if is_after_members {
result.push(ln.to_string());
} else {
if ln.to_lowercase() == "members:" {
is_after_members = true;
}
}
}
});
return result;
}
#[test]
fn test_parse_ipset_list() {
let empty_vec: Vec<String> = vec![];
assert_eq!(empty_vec, parse_ipset_list(""));
assert_eq!(vec!["test".to_string()], parse_ipset_list("test\n"));
assert_eq!(
vec!["allowipset".to_string()],
parse_ipset_list("allowipset")
);
assert_eq!(
vec!["allowipset".to_string()],
parse_ipset_list("allowipset\n")
);
}
#[test]
fn test_parse_ipset_list_ips_1() {
let ipset_list_ips = r"Name: allowipset
Type: hash:ip
Revision: 1
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 21264
References: 6
Members:
115.195.128.60
36.18.233.45
36.27.0.166
60.186.198.141
223.104.246.130";
let ips = parse_ipset_list_ips(ipset_list_ips);
assert_eq!(5, ips.len());
assert_eq!("115.195.128.60", &ips[0]);
assert_eq!("36.18.233.45", &ips[1]);
assert_eq!("36.27.0.166", &ips[2]);
assert_eq!("60.186.198.141", &ips[3]);
assert_eq!("223.104.246.130", &ips[4]);
}
#[test]
fn test_parse_ipset_list_ips_2() {
let ipset_list_ips = r"Size in memory: 30688
References: 6
Members:
36.28.155.64
36.20.56.144
";
let ips = parse_ipset_list_ips(ipset_list_ips);
assert_eq!(2, ips.len());
assert_eq!("36.28.155.64", &ips[0]);
assert_eq!("36.20.56.144", &ips[1]);
}