From c1352eb7ff3a380239142f47dd610ddf43234c71 Mon Sep 17 00:00:00 2001 From: Denis Krasnoper Date: Fri, 14 Feb 2025 16:42:18 +0100 Subject: [PATCH 1/3] Add missing fields --- src/types.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/types.rs b/src/types.rs index 5d34a20..3f56e69 100644 --- a/src/types.rs +++ b/src/types.rs @@ -877,7 +877,7 @@ impl NormalListResult { options.push(AddOption::Packets(fields[i + 1].parse()?)); } "bytes" => { - options.push(AddOption::Bytes(fields[i + 1].parse()?)); + options.push(AddOption::Bytes(fields[i + 1].trim().replace("\0", "").parse()?)); } "comment" => { options.push(AddOption::Comment(fields[i + 1].to_string())); @@ -925,10 +925,12 @@ impl NormalListResult { pub struct ListHeader { ipv6: bool, hash_size: u32, + bucket_size: Option, max_elem: u32, counters: bool, comment: bool, skbinfo: bool, + initval: Option } impl ListHeader { @@ -946,6 +948,10 @@ impl ListHeader { header.hash_size = s[i + 1].parse().unwrap(); i += 2; } + "bucketsize" => { + header.bucket_size = Some(s[i + 1].parse().unwrap()); + i += 2; + }, "maxelem" => { header.max_elem = s[i + 1].parse().unwrap(); i += 2; @@ -962,6 +968,13 @@ impl ListHeader { header.skbinfo = true; i += 1; } + "initval" => { + if let Some(initval) = s[i + 1].strip_prefix("0x") { + header.initval = Some(u32::from_str_radix(initval, 16).unwrap()); + } + i += 2; + } + _ => { unreachable!("{} not supported", s[i]); } From 21e6a8d181873615824dfd3e3f9e86a1e544084d Mon Sep 17 00:00:00 2001 From: Denis Krasnoper Date: Fri, 11 Apr 2025 20:23:01 +0200 Subject: [PATCH 2/3] Delete null bytes at earlyer stage --- src/session.rs | 1 + src/types.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/session.rs b/src/session.rs index d5de3d6..10b345c 100644 --- a/src/session.rs +++ b/src/session.rs @@ -288,6 +288,7 @@ impl Session { let mut result = NormalListResult::default(); for line in &self.output { for s in line.split("\n") { + let s = s.trim_end_matches('\0'); if !s.is_empty() { result.update_from_str(s)?; } diff --git a/src/types.rs b/src/types.rs index 3f56e69..3cec9bd 100644 --- a/src/types.rs +++ b/src/types.rs @@ -877,7 +877,7 @@ impl NormalListResult { options.push(AddOption::Packets(fields[i + 1].parse()?)); } "bytes" => { - options.push(AddOption::Bytes(fields[i + 1].trim().replace("\0", "").parse()?)); + options.push(AddOption::Bytes(fields[i + 1].parse()?)); } "comment" => { options.push(AddOption::Comment(fields[i + 1].to_string())); From 4dbe1d17ee22ef77e17fddd5fa060f9e8f03342a Mon Sep 17 00:00:00 2001 From: Denis Krasnoper Date: Sat, 4 Oct 2025 12:15:12 +0200 Subject: [PATCH 3/3] - Add timeout field to ListHeader - Fix multi-word comment parsing (quotes handling) - Add single-element tuple support for TypeName/SetData/Parse - Fix exists() corrupting session state - Improve exists() error handling for non-existent sets --- src/session.rs | 14 +++++++++++--- src/types.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/session.rs b/src/session.rs index 10b345c..9f991b1 100644 --- a/src/session.rs +++ b/src/session.rs @@ -251,14 +251,22 @@ impl Session { if unset { self.unset_option(EnvOption::ListSetName); } - match ret? { - ListResult::Normal(_) => { + // Reset session data after list to avoid interfering with subsequent operations + unsafe { + binding::ipset_data_reset(self.data); + } + match ret { + Ok(ListResult::Normal(_)) => { unreachable!("normal should not return") } - ListResult::Terse(names) => { + Ok(ListResult::Terse(names)) => { let name = self.name.to_string_lossy().to_string(); Ok(names.contains(&name)) } + Err(Error::Cmd(msg, true)) if msg.contains("does not exist") => { + Ok(false) + } + Err(e) => Err(e), } } diff --git a/src/types.rs b/src/types.rs index 3cec9bd..b30a1ea 100644 --- a/src/types.rs +++ b/src/types.rs @@ -392,6 +392,14 @@ impl_name!(PortDataType, "port"); impl_name!(IfaceDataType, "iface"); impl_name!(MarkDataType, "mark"); impl_name!(SetDataType, "set"); + +// Special case for single-element tuple +impl TypeName for (A,) { + fn name() -> String { + A::name() + } +} + impl_name!(A, B); impl_name!(A, B, C); @@ -410,6 +418,13 @@ macro_rules! impl_set_data { }; } +// Special case for single-element tuple +impl> SetData for (A,) { + fn set_data(&self, session: &Session, from: Option) -> Result<(), Error> { + self.0.set_data(session, from) + } +} + impl_set_data!(A, B); impl_set_data!(A, B, C); @@ -434,6 +449,13 @@ macro_rules! impl_parse { }; } +// Special case for single-element tuple +impl Parse for (A,) { + fn parse(&mut self, s: &str) -> Result<(), Error> { + self.0.parse(s) + } +} + impl_parse!(A, B); impl_parse!(A, B, C); @@ -880,7 +902,25 @@ impl NormalListResult { options.push(AddOption::Bytes(fields[i + 1].parse()?)); } "comment" => { - options.push(AddOption::Comment(fields[i + 1].to_string())); + // Collect all tokens between quotes + let mut comment = String::new(); + let mut j = i + 1; + while j < fields.len() { + if !comment.is_empty() { + comment.push(' '); + } + comment.push_str(fields[j]); + // Check if this token ends with a quote + if fields[j].ends_with('"') { + break; + } + j += 1; + } + // Remove quotes + comment = comment.trim_matches('"').to_string(); + options.push(AddOption::Comment(comment)); + i = j + 1; // Move past the last token of comment + continue; } "skbmark" => { let values: Vec<_> = fields[i + 1].split('/').collect(); @@ -927,6 +967,7 @@ pub struct ListHeader { hash_size: u32, bucket_size: Option, max_elem: u32, + timeout: Option, counters: bool, comment: bool, skbinfo: bool, @@ -956,6 +997,10 @@ impl ListHeader { header.max_elem = s[i + 1].parse().unwrap(); i += 2; } + "timeout" => { + header.timeout = Some(s[i + 1].parse().unwrap()); + i += 2; + } "counters" => { header.counters = true; i += 1;