From 61dbec7403a351f75c4709f2a3ba848301c3411b Mon Sep 17 00:00:00 2001 From: Shivang K Raghuvanshi Date: Fri, 3 Oct 2025 16:15:47 +0530 Subject: [PATCH 1/2] feat: conntrack get netlink netfilter message types Implemented the following attributes required to successfully construct a conntrack get request: * iptuple * protoinfo * protoinfotcp * prototuple * tcp_flags * tuple Signed-off-by: Shivang K Raghuvanshi --- src/buffer.rs | 7 +- src/conntrack/attributes/attribute.rs | 96 +++++++++++++ src/conntrack/attributes/iptuple.rs | 91 ++++++++++++ src/conntrack/attributes/mod.rs | 17 +++ src/conntrack/attributes/protoinfo.rs | 69 +++++++++ src/conntrack/attributes/protoinfotcp.rs | 95 +++++++++++++ src/conntrack/attributes/prototuple.rs | 130 +++++++++++++++++ src/conntrack/attributes/tcp_flags.rs | 40 ++++++ src/conntrack/attributes/tuple.rs | 88 ++++++++++++ src/conntrack/message.rs | 76 ++++++++++ src/conntrack/mod.rs | 9 ++ src/constants.rs | 2 + src/lib.rs | 3 + src/message.rs | 31 ++++- src/tests.rs | 170 +++++++++++++++++++++++ 15 files changed, 916 insertions(+), 8 deletions(-) create mode 100644 src/conntrack/attributes/attribute.rs create mode 100644 src/conntrack/attributes/iptuple.rs create mode 100644 src/conntrack/attributes/mod.rs create mode 100644 src/conntrack/attributes/protoinfo.rs create mode 100644 src/conntrack/attributes/protoinfotcp.rs create mode 100644 src/conntrack/attributes/prototuple.rs create mode 100644 src/conntrack/attributes/tcp_flags.rs create mode 100644 src/conntrack/attributes/tuple.rs create mode 100644 src/conntrack/message.rs create mode 100644 src/conntrack/mod.rs create mode 100644 src/tests.rs diff --git a/src/buffer.rs b/src/buffer.rs index a9d4917..f2b5073 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT use crate::{ + conntrack::ConntrackMessage, message::{ NetfilterHeader, NetfilterMessage, NetfilterMessageInner, NETFILTER_HEADER_LEN, @@ -57,10 +58,14 @@ impl<'a, T: AsRef<[u8]> + ?Sized> NfLogMessage::parse_with_param(buf, message_type) .context("failed to parse nflog payload")?, ), + ConntrackMessage::SUBSYS => NetfilterMessageInner::Conntrack( + ConntrackMessage::parse_with_param(buf, message_type) + .context("failed to parse conntrack payload")?, + ), _ => NetfilterMessageInner::Other { subsys, message_type, - nlas: buf.default_nlas()?, + attributes: buf.default_nlas()?, }, }; Ok(NetfilterMessage::new(header, inner)) diff --git a/src/conntrack/attributes/attribute.rs b/src/conntrack/attributes/attribute.rs new file mode 100644 index 0000000..272a903 --- /dev/null +++ b/src/conntrack/attributes/attribute.rs @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_core::{ + DecodeError, DefaultNla, Emitable, ErrorContext, Nla, NlaBuffer, + NlasIterator, Parseable, +}; + +use crate::conntrack::attributes::{protoinfo::ProtoInfo, tuple::Tuple}; + +const CTA_TUPLE_ORIG: u16 = 1; +const CTA_PROTOINFO: u16 = 4; + +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum ConntrackNla { + CtaTupleOrig(Vec), + CtaProtoInfo(Vec), + Other(DefaultNla), +} + +impl Nla for ConntrackNla { + fn value_len(&self) -> usize { + match self { + ConntrackNla::CtaTupleOrig(attr) => { + attr.iter().map(|op| op.buffer_len()).sum() + } + ConntrackNla::CtaProtoInfo(attr) => { + attr.iter().map(|op| op.buffer_len()).sum() + } + ConntrackNla::Other(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + ConntrackNla::CtaTupleOrig(_) => CTA_TUPLE_ORIG, + ConntrackNla::CtaProtoInfo(_) => CTA_PROTOINFO, + ConntrackNla::Other(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + ConntrackNla::CtaTupleOrig(attr) => { + let mut len = 0; + for op in attr { + op.emit(&mut buffer[len..]); + len += op.buffer_len(); + } + } + ConntrackNla::CtaProtoInfo(attr) => { + let mut len = 0; + for op in attr { + op.emit(&mut buffer[len..]); + len += op.buffer_len(); + } + } + ConntrackNla::Other(attr) => attr.emit_value(buffer), + } + } + fn is_nested(&self) -> bool { + matches!( + self, + ConntrackNla::CtaTupleOrig(_) | ConntrackNla::CtaProtoInfo(_) + ) + } +} + +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for ConntrackNla +{ + fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { + let kind = buf.kind(); + let payload = buf.value(); + let nla = match kind { + CTA_TUPLE_ORIG => { + let mut tuples = Vec::new(); + for nlas in NlasIterator::new(payload) { + let nlas = &nlas.context("invalid CTA_TUPLE_ORIG value")?; + tuples.push(Tuple::parse(nlas)?); + } + ConntrackNla::CtaTupleOrig(tuples) + } + CTA_PROTOINFO => { + let mut proto_infos = Vec::new(); + for nlas in NlasIterator::new(payload) { + let nlas = &nlas.context("invalid CTA_PROTOINFO value")?; + proto_infos.push(ProtoInfo::parse(nlas)?); + } + ConntrackNla::CtaProtoInfo(proto_infos) + } + _ => ConntrackNla::Other(DefaultNla::parse(buf)?), + }; + Ok(nla) + } +} diff --git a/src/conntrack/attributes/iptuple.rs b/src/conntrack/attributes/iptuple.rs new file mode 100644 index 0000000..7dcd770 --- /dev/null +++ b/src/conntrack/attributes/iptuple.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_core::{ + parse_ip, DecodeError, DefaultNla, ErrorContext, Nla, NlaBuffer, Parseable, +}; +use std::net::IpAddr; + +const CTA_IP_V4_SRC: u16 = 1; +const CTA_IP_V6_SRC: u16 = 3; +const CTA_IP_V4_DST: u16 = 2; +const CTA_IP_V6_DST: u16 = 4; + +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum IPTuple { + SourceAddress(IpAddr), + DestinationAddress(IpAddr), + Other(DefaultNla), +} + +const IPV4_LEN: usize = 4; +const IPV6_LEN: usize = 16; + +// Helper function needed for implementing the Nla trait +pub fn emit_ip(addr: &IpAddr, buf: &mut [u8]) { + match addr { + IpAddr::V4(ip) => { + buf[..IPV4_LEN].copy_from_slice(ip.octets().as_slice()); + } + IpAddr::V6(ip) => { + buf[..IPV6_LEN].copy_from_slice(ip.octets().as_slice()); + } + } +} + +impl Nla for IPTuple { + fn value_len(&self) -> usize { + match self { + IPTuple::SourceAddress(attr) => match *attr { + IpAddr::V4(_) => IPV4_LEN, + IpAddr::V6(_) => IPV6_LEN, + }, + IPTuple::DestinationAddress(attr) => match *attr { + IpAddr::V4(_) => IPV4_LEN, + IpAddr::V6(_) => IPV6_LEN, + }, + IPTuple::Other(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + IPTuple::SourceAddress(attr) => match *attr { + IpAddr::V4(_) => CTA_IP_V4_SRC, + IpAddr::V6(_) => CTA_IP_V6_SRC, + }, + IPTuple::DestinationAddress(attr) => match *attr { + IpAddr::V4(_) => CTA_IP_V4_DST, + IpAddr::V6(_) => CTA_IP_V6_DST, + }, + IPTuple::Other(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + IPTuple::SourceAddress(attr) => emit_ip(attr, buffer), + IPTuple::DestinationAddress(attr) => emit_ip(attr, buffer), + IPTuple::Other(attr) => attr.emit_value(buffer), + } + } +} +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for IPTuple +{ + fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { + let kind = buf.kind(); + let payload = buf.value(); + let nla = match kind { + CTA_IP_V4_SRC | CTA_IP_V6_SRC => Self::SourceAddress( + parse_ip(payload).context("invalid SourceAddress value")?, + ), + CTA_IP_V4_DST | CTA_IP_V6_DST => Self::DestinationAddress( + parse_ip(payload) + .context("invalid DestinationAddress value")?, + ), + _ => IPTuple::Other(DefaultNla::parse(buf)?), + }; + Ok(nla) + } +} diff --git a/src/conntrack/attributes/mod.rs b/src/conntrack/attributes/mod.rs new file mode 100644 index 0000000..07dfe35 --- /dev/null +++ b/src/conntrack/attributes/mod.rs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +mod attribute; +mod iptuple; +mod protoinfo; +mod protoinfotcp; +mod prototuple; +mod tcp_flags; +mod tuple; + +pub use attribute::ConntrackNla; +pub use iptuple::IPTuple; +pub use protoinfo::ProtoInfo; +pub use protoinfotcp::ProtoInfoTCP; +pub use prototuple::{ProtoTuple, Protocol}; +pub use tcp_flags::TCPFlags; +pub use tuple::Tuple; diff --git a/src/conntrack/attributes/protoinfo.rs b/src/conntrack/attributes/protoinfo.rs new file mode 100644 index 0000000..8719cb2 --- /dev/null +++ b/src/conntrack/attributes/protoinfo.rs @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_core::{ + DecodeError, DefaultNla, Emitable, ErrorContext, Nla, NlaBuffer, + NlasIterator, Parseable, +}; + +use crate::conntrack::attributes::protoinfotcp::ProtoInfoTCP; + +const CTA_PROTOINFO_TCP: u16 = 1; + +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum ProtoInfo { + TCP(Vec), + Other(DefaultNla), +} +impl Nla for ProtoInfo { + fn value_len(&self) -> usize { + match self { + ProtoInfo::TCP(nlas) => nlas.iter().map(|op| op.buffer_len()).sum(), + ProtoInfo::Other(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + ProtoInfo::TCP(_) => CTA_PROTOINFO_TCP, + ProtoInfo::Other(attr) => attr.kind(), + } + } + fn emit_value(&self, buffer: &mut [u8]) { + match self { + ProtoInfo::TCP(nlas) => { + let mut len = 0; + for op in nlas { + op.emit(&mut buffer[len..]); + len += op.buffer_len(); + } + } + ProtoInfo::Other(attr) => attr.emit_value(buffer), + } + } + fn is_nested(&self) -> bool { + matches!(self, ProtoInfo::TCP(_)) + } +} + +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for ProtoInfo +{ + fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { + let kind = buf.kind(); + let payload = buf.value(); + let nla = match kind { + CTA_PROTOINFO_TCP => { + let mut proto_info_tcps = Vec::new(); + for nlas in NlasIterator::new(payload) { + let nlas = + &nlas.context("invailid CTA_PROTOINFO_TCP value")?; + proto_info_tcps.push(ProtoInfoTCP::parse(nlas)?); + } + ProtoInfo::TCP(proto_info_tcps) + } + _ => ProtoInfo::Other(DefaultNla::parse(buf)?), + }; + Ok(nla) + } +} diff --git a/src/conntrack/attributes/protoinfotcp.rs b/src/conntrack/attributes/protoinfotcp.rs new file mode 100644 index 0000000..cf3dc6c --- /dev/null +++ b/src/conntrack/attributes/protoinfotcp.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_core::{ + parse_u8, DecodeError, DefaultNla, Emitable, ErrorContext, Nla, NlaBuffer, + Parseable, +}; + +use crate::conntrack::attributes::tcp_flags::{TCPFlags, TCPFlagsBuffer}; + +const CTA_PROTOINFO_TCP_STATE: u16 = 1; +const CTA_PROTOINFO_TCP_WSCALE_ORIGINAL: u16 = 2; +const CTA_PROTOINFO_TCP_WSCALE_REPLY: u16 = 3; +const CTA_PROTOINFO_TCP_FLAGS_ORIGINAL: u16 = 4; +const CTA_PROTOINFO_TCP_FLAGS_REPLY: u16 = 5; + +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum ProtoInfoTCP { + State(u8), + OriginalWindowScale(u8), + ReplyWindowScale(u8), + OriginalFlags(TCPFlags), + ReplyFlags(TCPFlags), + Other(DefaultNla), +} +impl Nla for ProtoInfoTCP { + fn value_len(&self) -> usize { + match self { + ProtoInfoTCP::State(attr) => size_of_val(attr), + ProtoInfoTCP::OriginalWindowScale(attr) => size_of_val(attr), + ProtoInfoTCP::ReplyWindowScale(attr) => size_of_val(attr), + ProtoInfoTCP::OriginalFlags(attr) => attr.buffer_len(), + ProtoInfoTCP::ReplyFlags(attr) => attr.buffer_len(), + ProtoInfoTCP::Other(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + ProtoInfoTCP::State(_) => CTA_PROTOINFO_TCP_STATE, + ProtoInfoTCP::OriginalWindowScale(_) => { + CTA_PROTOINFO_TCP_WSCALE_ORIGINAL + } + ProtoInfoTCP::ReplyWindowScale(_) => CTA_PROTOINFO_TCP_WSCALE_REPLY, + ProtoInfoTCP::OriginalFlags(_) => CTA_PROTOINFO_TCP_FLAGS_ORIGINAL, + ProtoInfoTCP::ReplyFlags(_) => CTA_PROTOINFO_TCP_FLAGS_REPLY, + ProtoInfoTCP::Other(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + ProtoInfoTCP::State(attr) => buffer[0] = *attr, + ProtoInfoTCP::OriginalWindowScale(attr) => buffer[0] = *attr, + ProtoInfoTCP::ReplyWindowScale(attr) => buffer[0] = *attr, + ProtoInfoTCP::OriginalFlags(attr) => attr.emit(buffer), + ProtoInfoTCP::ReplyFlags(attr) => attr.emit(buffer), + ProtoInfoTCP::Other(attr) => attr.emit_value(buffer), + } + } +} +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for ProtoInfoTCP +{ + fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { + let kind = buf.kind(); + let payload = buf.value(); + let nla = match kind { + CTA_PROTOINFO_TCP_STATE => ProtoInfoTCP::State( + parse_u8(payload) + .context("invalid CTA_PROTOINFO_TCP_STATE value")?, + ), + CTA_PROTOINFO_TCP_WSCALE_ORIGINAL => { + ProtoInfoTCP::OriginalWindowScale(parse_u8(payload).context( + "invalid CTA_PROTOINFO_TCP_WSCALE_ORIGINAL value", + )?) + } + CTA_PROTOINFO_TCP_WSCALE_REPLY => ProtoInfoTCP::ReplyWindowScale( + parse_u8(payload) + .context("invalid CTA_PROTOINFO_TCP_WSCALE_REPLY value")?, + ), + CTA_PROTOINFO_TCP_FLAGS_ORIGINAL => ProtoInfoTCP::OriginalFlags( + TCPFlags::parse(&TCPFlagsBuffer::new(payload)).context( + "invalid CTA_PROTOINFO_TCP_FLAGS_ORIGINAL value", + )?, + ), + CTA_PROTOINFO_TCP_FLAGS_REPLY => ProtoInfoTCP::ReplyFlags( + TCPFlags::parse(&TCPFlagsBuffer::new(payload)) + .context("invalid CTA_PROTOINFO_TCP_FLAGS_REPLY value")?, + ), + _ => ProtoInfoTCP::Other(DefaultNla::parse(buf)?), + }; + Ok(nla) + } +} diff --git a/src/conntrack/attributes/prototuple.rs b/src/conntrack/attributes/prototuple.rs new file mode 100644 index 0000000..4486019 --- /dev/null +++ b/src/conntrack/attributes/prototuple.rs @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_core::{ + emit_u16_be, parse_u16_be, parse_u8, DecodeError, DefaultNla, ErrorContext, + Nla, NlaBuffer, Parseable, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum Protocol { + Icmp, + Igmp, + IpIp, + Tcp, + Udp, + Ipv6Icmp, + Gre, + UdpLite, + Sctp, + Dccp, + Other(u8), +} + +impl From for u8 { + fn from(protocol: Protocol) -> Self { + use libc::*; + match protocol { + Protocol::Icmp => IPPROTO_ICMP as u8, + Protocol::Igmp => IPPROTO_IGMP as u8, + Protocol::IpIp => IPPROTO_IPIP as u8, + Protocol::Tcp => IPPROTO_TCP as u8, + Protocol::Udp => IPPROTO_UDP as u8, + Protocol::Ipv6Icmp => IPPROTO_ICMPV6 as u8, + Protocol::Gre => IPPROTO_GRE as u8, + Protocol::UdpLite => IPPROTO_UDPLITE as u8, + Protocol::Sctp => IPPROTO_SCTP as u8, + Protocol::Dccp => IPPROTO_DCCP as u8, + Protocol::Other(p) => p, + } + } +} + +impl From for Protocol { + fn from(protocol_num: u8) -> Self { + use libc::*; + match protocol_num as i32 { + IPPROTO_ICMP => Protocol::Icmp, + IPPROTO_IGMP => Protocol::Igmp, + IPPROTO_IPIP => Protocol::IpIp, + IPPROTO_TCP => Protocol::Tcp, + IPPROTO_UDP => Protocol::Udp, + IPPROTO_ICMPV6 => Protocol::Ipv6Icmp, + IPPROTO_GRE => Protocol::Gre, + IPPROTO_UDPLITE => Protocol::UdpLite, + IPPROTO_SCTP => Protocol::Sctp, + IPPROTO_DCCP => Protocol::Dccp, + _ => Protocol::Other(protocol_num), + } + } +} + +const CTA_PROTO_NUM: u16 = 1; +const CTA_PROTO_SRC_PORT: u16 = 2; +const CTA_PROTO_DST_PORT: u16 = 3; + +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum ProtoTuple { + Protocol(Protocol), + SourcePort(u16), + DestinationPort(u16), + Other(DefaultNla), +} + +impl Nla for ProtoTuple { + fn value_len(&self) -> usize { + match self { + ProtoTuple::Protocol(_) => size_of::(), + ProtoTuple::SourcePort(attr) => size_of_val(attr), + ProtoTuple::DestinationPort(attr) => size_of_val(attr), + ProtoTuple::Other(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + ProtoTuple::Protocol(_) => CTA_PROTO_NUM, + ProtoTuple::SourcePort(_) => CTA_PROTO_SRC_PORT, + ProtoTuple::DestinationPort(_) => CTA_PROTO_DST_PORT, + ProtoTuple::Other(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + ProtoTuple::Protocol(attr) => buffer[0] = (*attr).into(), + ProtoTuple::SourcePort(attr) => emit_u16_be(buffer, *attr).unwrap(), + ProtoTuple::DestinationPort(attr) => { + emit_u16_be(buffer, *attr).unwrap() + } + ProtoTuple::Other(attr) => attr.emit_value(buffer), + } + } +} + +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for ProtoTuple +{ + fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { + let kind = buf.kind(); + let payload = buf.value(); + let nla = match kind { + CTA_PROTO_NUM => ProtoTuple::Protocol( + parse_u8(payload) + .context("invalid CTA_PROTO_NUM value")? + .into(), + ), + CTA_PROTO_SRC_PORT => ProtoTuple::SourcePort( + parse_u16_be(payload) + .context("invalid CTA_PROTO_SRC_PORT value")?, + ), + CTA_PROTO_DST_PORT => ProtoTuple::DestinationPort( + parse_u16_be(payload) + .context("invalid CTA_PROTO_DST_PORT value")?, + ), + _ => ProtoTuple::Other(DefaultNla::parse(buf)?), + }; + Ok(nla) + } +} diff --git a/src/conntrack/attributes/tcp_flags.rs b/src/conntrack/attributes/tcp_flags.rs new file mode 100644 index 0000000..6b10831 --- /dev/null +++ b/src/conntrack/attributes/tcp_flags.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_core::{ + buffer, fields, getter, setter, DecodeError, Emitable, Parseable, +}; + +const TCP_FLAGS_LEN: usize = 2; + +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub struct TCPFlags { + pub flags: u8, + pub mask: u8, +} + +buffer!(TCPFlagsBuffer(TCP_FLAGS_LEN) { + flags: (u8, 0), + mask: (u8, 1), +}); + +impl> Parseable> for TCPFlags { + fn parse(buf: &TCPFlagsBuffer) -> Result { + Ok(TCPFlags { + flags: buf.flags(), + mask: buf.mask(), + }) + } +} + +impl Emitable for TCPFlags { + fn buffer_len(&self) -> usize { + TCP_FLAGS_LEN + } + + fn emit(&self, buffer: &mut [u8]) { + let mut buffer = TCPFlagsBuffer::new(buffer); + buffer.set_flags(self.flags); + buffer.set_mask(self.mask); + } +} diff --git a/src/conntrack/attributes/tuple.rs b/src/conntrack/attributes/tuple.rs new file mode 100644 index 0000000..169e29c --- /dev/null +++ b/src/conntrack/attributes/tuple.rs @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_core::{ + DecodeError, DefaultNla, Emitable, ErrorContext, Nla, NlaBuffer, + NlasIterator, Parseable, +}; + +use crate::conntrack::attributes::{iptuple::IPTuple, prototuple::ProtoTuple}; + +const CTA_TUPLE_IP: u16 = 1; +const CTA_TUPLE_PROTO: u16 = 2; + +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum Tuple { + Ip(Vec), + Proto(Vec), + Other(DefaultNla), +} + +impl Nla for Tuple { + fn value_len(&self) -> usize { + match self { + Tuple::Ip(nlas) => nlas.iter().map(|op| op.buffer_len()).sum(), + Tuple::Proto(nlas) => nlas.iter().map(|op| op.buffer_len()).sum(), + Tuple::Other(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + Tuple::Ip(_) => CTA_TUPLE_IP, + Tuple::Proto(_) => CTA_TUPLE_PROTO, + Tuple::Other(attr) => attr.kind(), + } + } + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Tuple::Ip(nlas) => { + let mut len = 0; + for op in nlas { + op.emit(&mut buffer[len..]); + len += op.buffer_len(); + } + } + Tuple::Proto(nlas) => { + let mut len = 0; + for op in nlas { + op.emit(&mut buffer[len..]); + len += op.buffer_len(); + } + } + Tuple::Other(attr) => attr.emit_value(buffer), + } + } + fn is_nested(&self) -> bool { + matches!(self, Tuple::Ip(_) | Tuple::Proto(_)) + } +} +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for Tuple +{ + fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { + let kind = buf.kind(); + let payload = buf.value(); + let nla = match kind { + CTA_TUPLE_IP => { + let mut ip_tuples = Vec::new(); + for nlas in NlasIterator::new(payload) { + let nlas = &nlas.context("invalid CTA_TUPLE_IP value")?; + ip_tuples.push(IPTuple::parse(nlas)?); + } + Tuple::Ip(ip_tuples) + } + CTA_TUPLE_PROTO => { + let mut proto_tuples = Vec::new(); + for nlas in NlasIterator::new(payload) { + let nlas = + &nlas.context("invalid CTA_TUPLE_PROTO value")?; + proto_tuples.push(ProtoTuple::parse(nlas)?); + } + Tuple::Proto(proto_tuples) + } + _ => Tuple::Other(DefaultNla::parse(buf)?), + }; + Ok(nla) + } +} diff --git a/src/conntrack/message.rs b/src/conntrack/message.rs new file mode 100644 index 0000000..221ad17 --- /dev/null +++ b/src/conntrack/message.rs @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT + +use crate::{ + buffer::NetfilterBuffer, + conntrack::attributes::ConntrackNla, + constants::{IPCTNL_MSG_CT_GET, NFNL_SUBSYS_CTNETLINK}, +}; +use netlink_packet_core::{ + DecodeError, DefaultNla, Emitable, Parseable, ParseableParametrized, +}; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum ConntrackMessage { + Get(Vec), + Other { + message_type: u8, + attributes: Vec, + }, +} + +impl ConntrackMessage { + pub(crate) const SUBSYS: u8 = NFNL_SUBSYS_CTNETLINK; + + pub fn message_type(&self) -> u8 { + match self { + ConntrackMessage::Get(_) => IPCTNL_MSG_CT_GET, + ConntrackMessage::Other { message_type, .. } => *message_type, + } + } +} + +impl Emitable for ConntrackMessage { + fn buffer_len(&self) -> usize { + match self { + ConntrackMessage::Get(attributes) => { + attributes.as_slice().buffer_len() + } + ConntrackMessage::Other { attributes, .. } => { + attributes.as_slice().buffer_len() + } + } + } + + fn emit(&self, buffer: &mut [u8]) { + match self { + ConntrackMessage::Get(attributes) => { + attributes.as_slice().emit(buffer) + } + ConntrackMessage::Other { attributes, .. } => { + attributes.as_slice().emit(buffer) + } + }; + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> + ParseableParametrized, u8> for ConntrackMessage +{ + fn parse_with_param( + buf: &NetfilterBuffer<&'a T>, + message_type: u8, + ) -> Result { + Ok(match message_type { + IPCTNL_MSG_CT_GET => { + let attributes = buf + .parse_all_nlas(|nla_buf| ConntrackNla::parse(&nla_buf))?; + ConntrackMessage::Get(attributes) + } + _ => ConntrackMessage::Other { + message_type, + attributes: buf.default_nlas()?, + }, + }) + } +} diff --git a/src/conntrack/mod.rs b/src/conntrack/mod.rs new file mode 100644 index 0000000..cef6f26 --- /dev/null +++ b/src/conntrack/mod.rs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +mod message; +pub use message::ConntrackMessage; +mod attributes; +pub use attributes::{ + ConntrackNla, IPTuple, ProtoInfo, ProtoInfoTCP, ProtoTuple, Protocol, + TCPFlags, Tuple, +}; diff --git a/src/constants.rs b/src/constants.rs index 7eabf9c..4d2d229 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -86,3 +86,5 @@ pub const NFULA_CT_INFO: u16 = libc::NFULA_CT_INFO as u16; pub const NFULNL_MSG_CONFIG: u8 = libc::NFULNL_MSG_CONFIG as u8; pub const NFULNL_MSG_PACKET: u8 = libc::NFULNL_MSG_PACKET as u8; + +pub(crate) const IPCTNL_MSG_CT_GET: u8 = 1; diff --git a/src/lib.rs b/src/lib.rs index aca295b..e9cf671 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,4 +4,7 @@ pub(crate) mod buffer; pub mod constants; mod message; pub use message::{NetfilterHeader, NetfilterMessage, NetfilterMessageInner}; +pub mod conntrack; pub mod nflog; +#[cfg(test)] +mod tests; diff --git a/src/message.rs b/src/message.rs index 585b534..a609ae0 100644 --- a/src/message.rs +++ b/src/message.rs @@ -6,9 +6,11 @@ use netlink_packet_core::{ Parseable, ParseableParametrized, }; -use crate::{buffer::NetfilterBuffer, nflog::NfLogMessage}; +use crate::{ + buffer::NetfilterBuffer, conntrack::ConntrackMessage, nflog::NfLogMessage, +}; -pub const NETFILTER_HEADER_LEN: usize = 4; +pub(crate) const NETFILTER_HEADER_LEN: usize = 4; buffer!(NetfilterHeaderBuffer(NETFILTER_HEADER_LEN) { family: (u8, 0), @@ -17,6 +19,7 @@ buffer!(NetfilterHeaderBuffer(NETFILTER_HEADER_LEN) { }); #[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] pub struct NetfilterHeader { pub family: u8, pub version: u8, @@ -58,12 +61,14 @@ impl> Parseable> for NetfilterHeader { } #[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] pub enum NetfilterMessageInner { NfLog(NfLogMessage), + Conntrack(ConntrackMessage), Other { subsys: u8, message_type: u8, - nlas: Vec, + attributes: Vec, }, } @@ -72,13 +77,19 @@ impl From for NetfilterMessageInner { Self::NfLog(message) } } +impl From for NetfilterMessageInner { + fn from(message: ConntrackMessage) -> Self { + Self::Conntrack(message) + } +} impl Emitable for NetfilterMessageInner { fn buffer_len(&self) -> usize { match self { NetfilterMessageInner::NfLog(message) => message.buffer_len(), - NetfilterMessageInner::Other { nlas, .. } => { - nlas.as_slice().buffer_len() + NetfilterMessageInner::Conntrack(message) => message.buffer_len(), + NetfilterMessageInner::Other { attributes, .. } => { + attributes.as_slice().buffer_len() } } } @@ -86,14 +97,16 @@ impl Emitable for NetfilterMessageInner { fn emit(&self, buffer: &mut [u8]) { match self { NetfilterMessageInner::NfLog(message) => message.emit(buffer), - NetfilterMessageInner::Other { nlas, .. } => { - nlas.as_slice().emit(buffer) + NetfilterMessageInner::Conntrack(message) => message.emit(buffer), + NetfilterMessageInner::Other { attributes, .. } => { + attributes.as_slice().emit(buffer) } } } } #[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] pub struct NetfilterMessage { pub header: NetfilterHeader, pub inner: NetfilterMessageInner, @@ -113,6 +126,7 @@ impl NetfilterMessage { pub fn subsys(&self) -> u8 { match self.inner { NetfilterMessageInner::NfLog(_) => NfLogMessage::SUBSYS, + NetfilterMessageInner::Conntrack(_) => ConntrackMessage::SUBSYS, NetfilterMessageInner::Other { subsys, .. } => subsys, } } @@ -120,6 +134,9 @@ impl NetfilterMessage { pub fn message_type(&self) -> u8 { match self.inner { NetfilterMessageInner::NfLog(ref message) => message.message_type(), + NetfilterMessageInner::Conntrack(ref message) => { + message.message_type() + } NetfilterMessageInner::Other { message_type, .. } => message_type, } } diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..6eaaa01 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: MIT + +use std::net::IpAddr; + +use libc::NFNL_SUBSYS_CTNETLINK; +use netlink_packet_core::{Emitable, ParseableParametrized}; + +use crate::{ + buffer::NetfilterBuffer, + conntrack::{ + ConntrackMessage, ConntrackNla, IPTuple, ProtoInfo, ProtoInfoTCP, + ProtoTuple, Protocol, TCPFlags, Tuple, + }, + constants::{AF_INET, AF_INET6, AF_UNSPEC, IPCTNL_MSG_CT_GET}, + NetfilterHeader, NetfilterMessage, +}; + +// wireshark capture of nlmon against command (netlink message header removed): +// conntrack -L +#[test] +fn test_dump_conntrack() { + let raw: Vec = vec![0x00, 0x00, 0x00, 0x00]; + + let expected: NetfilterMessage = NetfilterMessage::new( + NetfilterHeader::new(AF_UNSPEC, 0, 0), + ConntrackMessage::Get(vec![]), + ); + + let mut buffer = vec![0; expected.buffer_len()]; + expected.emit(&mut buffer); + + // Check if the serialization was correct + assert_eq!(buffer, raw); + + let message_type = + ((NFNL_SUBSYS_CTNETLINK as u16) << 8) | (IPCTNL_MSG_CT_GET as u16); + // Check if the deserialization was correct + assert_eq!( + NetfilterMessage::parse_with_param( + &NetfilterBuffer::new(&raw), + message_type + ) + .unwrap(), + expected + ); +} + +// wireshark capture of nlmon against command (netlink message header removed): +// conntrack -G -p tcp -s 10.57.97.124 -d 148.113.20.105 --sport 39600 --dport +// 443 +#[test] +fn test_get_conntrack_tcp_ipv4() { + let raw: Vec = vec![ + 0x02, 0x00, 0x00, 0x00, 0x34, 0x00, 0x01, 0x80, 0x14, 0x00, 0x01, 0x80, + 0x08, 0x00, 0x01, 0x00, 0x0a, 0x39, 0x61, 0x7c, 0x08, 0x00, 0x02, 0x00, + 0x94, 0x71, 0x14, 0x69, 0x1c, 0x00, 0x02, 0x80, 0x05, 0x00, 0x01, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0x9a, 0xb0, 0x00, 0x00, + 0x06, 0x00, 0x03, 0x00, 0x01, 0xbb, 0x00, 0x00, 0x18, 0x00, 0x04, 0x80, + 0x14, 0x00, 0x01, 0x80, 0x06, 0x00, 0x04, 0x00, 0x0a, 0x0a, 0x00, 0x00, + 0x06, 0x00, 0x05, 0x00, 0x0a, 0x0a, 0x00, 0x00, + ]; + + let src_addr = + IPTuple::SourceAddress(IpAddr::V4("10.57.97.124".parse().unwrap())); + let dst_addr = IPTuple::DestinationAddress(IpAddr::V4( + "148.113.20.105".parse().unwrap(), + )); + + let proto_num = ProtoTuple::Protocol(Protocol::Tcp); + let src_port = ProtoTuple::SourcePort(39600); + let dst_port = ProtoTuple::DestinationPort(443); + + let ip_tuple = Tuple::Ip(vec![src_addr, dst_addr]); + let proto_tuple = Tuple::Proto(vec![proto_num, src_port, dst_port]); + + let proto_info = ProtoInfo::TCP(vec![ + ProtoInfoTCP::OriginalFlags(TCPFlags { + flags: 10, + mask: 10, + }), + ProtoInfoTCP::ReplyFlags(TCPFlags { + flags: 10, + mask: 10, + }), + ]); + + let attributes = vec![ + ConntrackNla::CtaTupleOrig(vec![ip_tuple, proto_tuple]), + ConntrackNla::CtaProtoInfo(vec![proto_info]), + ]; + + let expected: NetfilterMessage = NetfilterMessage::new( + NetfilterHeader::new(AF_INET, 0, 0), + ConntrackMessage::Get(attributes), + ); + + let mut buffer = vec![0; expected.buffer_len()]; + expected.emit(&mut buffer); + + // Check if the serialization was correct + assert_eq!(buffer, raw); + + let message_type = + ((NFNL_SUBSYS_CTNETLINK as u16) << 8) | (IPCTNL_MSG_CT_GET as u16); + // Check if the deserialization was correct + assert_eq!( + NetfilterMessage::parse_with_param( + &NetfilterBuffer::new(&raw), + message_type + ) + .unwrap(), + expected + ); +} + +// wireshark capture of nlmon against command (netlink message header removed): +// conntrack -G -p udp -s 2409:40c4:e8:6bc3:d1d8:1087:4fa2:68a3 --sport 58456 -d +// 2404:6800:4009:81d::200e --dport 443 +#[test] +fn test_get_conntrack_udp_ipv6() { + let raw: Vec = vec![ + 0x0a, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x01, 0x80, 0x2c, 0x00, 0x01, 0x80, + 0x14, 0x00, 0x03, 0x00, 0x24, 0x09, 0x40, 0xc4, 0x00, 0xe8, 0x6b, 0xc3, + 0xd1, 0xd8, 0x10, 0x87, 0x4f, 0xa2, 0x68, 0xa3, 0x14, 0x00, 0x04, 0x00, + 0x24, 0x04, 0x68, 0x00, 0x40, 0x09, 0x08, 0x1d, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x0e, 0x1c, 0x00, 0x02, 0x80, 0x05, 0x00, 0x01, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0xe4, 0x58, 0x00, 0x00, + 0x06, 0x00, 0x03, 0x00, 0x01, 0xbb, 0x00, 0x00, + ]; + + let src_addr = IPTuple::SourceAddress(IpAddr::V6( + "2409:40c4:e8:6bc3:d1d8:1087:4fa2:68a3".parse().unwrap(), + )); + let dst_addr = IPTuple::DestinationAddress(IpAddr::V6( + "2404:6800:4009:81d::200e".parse().unwrap(), + )); + + let proto_num = ProtoTuple::Protocol(Protocol::Udp); + let src_port = ProtoTuple::SourcePort(58456); + let dst_port = ProtoTuple::DestinationPort(443); + + let ip_tuple = Tuple::Ip(vec![src_addr, dst_addr]); + let proto_tuple = Tuple::Proto(vec![proto_num, src_port, dst_port]); + + let attributes = + vec![ConntrackNla::CtaTupleOrig(vec![ip_tuple, proto_tuple])]; + + let expected: NetfilterMessage = NetfilterMessage::new( + NetfilterHeader::new(AF_INET6, 0, 0), + ConntrackMessage::Get(attributes), + ); + + let mut buffer = vec![0; expected.buffer_len()]; + expected.emit(&mut buffer); + + // Check if the serialization was correct + assert_eq!(buffer, raw); + + let message_type = + ((NFNL_SUBSYS_CTNETLINK as u16) << 8) | (IPCTNL_MSG_CT_GET as u16); + // Check if the deserialization was correct + assert_eq!( + NetfilterMessage::parse_with_param( + &NetfilterBuffer::new(&raw), + message_type + ) + .unwrap(), + expected + ); +} From c9d7dc371467796ef7a6dc95c58b9d489cc80378 Mon Sep 17 00:00:00 2001 From: Shivang K Raghuvanshi Date: Tue, 28 Oct 2025 13:20:37 +0530 Subject: [PATCH 2/2] refactor: use enums for subsystem and message types This refactors the crate to use type-safe enums for netfilter subsystems and message types, for a safer and more idiomatic API. - Introduces a `Subsystem` enum to replace raw `u8` identifiers for `NfLog` and `Conntrack` subsystems. - Introduces `NfLogMessageType` and `ConntrackMessageType` enums to provide type safety for messages within each subsystem. - Makes the top-level `NetfilterMessage::message_type()` function private to guide users towards the safer pattern of matching on `NetfilterMessageInner`. - Updates the internal parsing logic in `buffer.rs` to use the new `Subsystem` enum. Signed-off-by: Shivang K Raghuvanshi --- src/buffer.rs | 12 ++++----- src/conntrack/message.rs | 57 +++++++++++++++++++++++++++++----------- src/conntrack/mod.rs | 2 +- src/constants.rs | 7 ----- src/lib.rs | 4 ++- src/message.rs | 49 ++++++++++++++++++++++++++++------ src/nflog/message.rs | 50 +++++++++++++++++++++++++++-------- src/nflog/mod.rs | 2 +- src/tests.rs | 20 +++++++------- 9 files changed, 142 insertions(+), 61 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index f2b5073..b31084d 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -3,7 +3,7 @@ use crate::{ conntrack::ConntrackMessage, message::{ - NetfilterHeader, NetfilterMessage, NetfilterMessageInner, + NetfilterHeader, NetfilterMessage, NetfilterMessageInner, Subsystem, NETFILTER_HEADER_LEN, }, nflog::NfLogMessage, @@ -53,17 +53,17 @@ impl<'a, T: AsRef<[u8]> + ?Sized> .context("failed to parse netfilter header")?; let subsys = (message_type >> 8) as u8; let message_type = message_type as u8; - let inner = match subsys { - NfLogMessage::SUBSYS => NetfilterMessageInner::NfLog( + let inner = match Subsystem::from(subsys) { + Subsystem::NfLog => NetfilterMessageInner::NfLog( NfLogMessage::parse_with_param(buf, message_type) .context("failed to parse nflog payload")?, ), - ConntrackMessage::SUBSYS => NetfilterMessageInner::Conntrack( + Subsystem::Conntrack => NetfilterMessageInner::Conntrack( ConntrackMessage::parse_with_param(buf, message_type) .context("failed to parse conntrack payload")?, ), - _ => NetfilterMessageInner::Other { - subsys, + subsys_enum @ Subsystem::Other(_) => NetfilterMessageInner::Other { + subsys: subsys_enum, message_type, attributes: buf.default_nlas()?, }, diff --git a/src/conntrack/message.rs b/src/conntrack/message.rs index 221ad17..de6bee5 100644 --- a/src/conntrack/message.rs +++ b/src/conntrack/message.rs @@ -1,10 +1,6 @@ // SPDX-License-Identifier: MIT -use crate::{ - buffer::NetfilterBuffer, - conntrack::attributes::ConntrackNla, - constants::{IPCTNL_MSG_CT_GET, NFNL_SUBSYS_CTNETLINK}, -}; +use crate::{buffer::NetfilterBuffer, conntrack::attributes::ConntrackNla}; use netlink_packet_core::{ DecodeError, DefaultNla, Emitable, Parseable, ParseableParametrized, }; @@ -19,13 +15,40 @@ pub enum ConntrackMessage { }, } -impl ConntrackMessage { - pub(crate) const SUBSYS: u8 = NFNL_SUBSYS_CTNETLINK; +const IPCTNL_MSG_CT_GET: u8 = 1; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum ConntrackMessageType { + Get, + Other(u8), +} + +impl From for ConntrackMessageType { + fn from(value: u8) -> Self { + match value { + IPCTNL_MSG_CT_GET => Self::Get, + v => Self::Other(v), + } + } +} + +impl From for u8 { + fn from(value: ConntrackMessageType) -> Self { + match value { + ConntrackMessageType::Get => IPCTNL_MSG_CT_GET, + ConntrackMessageType::Other(v) => v, + } + } +} - pub fn message_type(&self) -> u8 { +impl ConntrackMessage { + pub fn message_type(&self) -> ConntrackMessageType { match self { - ConntrackMessage::Get(_) => IPCTNL_MSG_CT_GET, - ConntrackMessage::Other { message_type, .. } => *message_type, + ConntrackMessage::Get(_) => ConntrackMessageType::Get, + ConntrackMessage::Other { message_type, .. } => { + (*message_type).into() + } } } } @@ -61,16 +84,18 @@ impl<'a, T: AsRef<[u8]> + ?Sized> buf: &NetfilterBuffer<&'a T>, message_type: u8, ) -> Result { - Ok(match message_type { - IPCTNL_MSG_CT_GET => { + Ok(match ConntrackMessageType::from(message_type) { + ConntrackMessageType::Get => { let attributes = buf .parse_all_nlas(|nla_buf| ConntrackNla::parse(&nla_buf))?; ConntrackMessage::Get(attributes) } - _ => ConntrackMessage::Other { - message_type, - attributes: buf.default_nlas()?, - }, + ConntrackMessageType::Other(message_type) => { + ConntrackMessage::Other { + message_type, + attributes: buf.default_nlas()?, + } + } }) } } diff --git a/src/conntrack/mod.rs b/src/conntrack/mod.rs index cef6f26..5d19102 100644 --- a/src/conntrack/mod.rs +++ b/src/conntrack/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT mod message; -pub use message::ConntrackMessage; +pub use message::{ConntrackMessage, ConntrackMessageType}; mod attributes; pub use attributes::{ ConntrackNla, IPTuple, ProtoInfo, ProtoInfoTCP, ProtoTuple, Protocol, diff --git a/src/constants.rs b/src/constants.rs index 4d2d229..15b3a8e 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -43,10 +43,8 @@ pub const AF_ALG: u8 = libc::AF_ALG as u8; pub const NFNETLINK_V0: u8 = libc::NFNETLINK_V0 as u8; pub const NFNL_SUBSYS_NONE: u8 = libc::NFNL_SUBSYS_NONE as u8; -pub const NFNL_SUBSYS_CTNETLINK: u8 = libc::NFNL_SUBSYS_CTNETLINK as u8; pub const NFNL_SUBSYS_CTNETLINK_EXP: u8 = libc::NFNL_SUBSYS_CTNETLINK_EXP as u8; pub const NFNL_SUBSYS_QUEUE: u8 = libc::NFNL_SUBSYS_QUEUE as u8; -pub const NFNL_SUBSYS_ULOG: u8 = libc::NFNL_SUBSYS_ULOG as u8; pub const NFNL_SUBSYS_OSF: u8 = libc::NFNL_SUBSYS_OSF as u8; pub const NFNL_SUBSYS_IPSET: u8 = libc::NFNL_SUBSYS_IPSET as u8; pub const NFNL_SUBSYS_ACCT: u8 = libc::NFNL_SUBSYS_ACCT as u8; @@ -83,8 +81,3 @@ pub const NFULA_HWHEADER: u16 = libc::NFULA_HWHEADER as u16; pub const NFULA_HWLEN: u16 = libc::NFULA_HWLEN as u16; pub const NFULA_CT: u16 = libc::NFULA_CT as u16; pub const NFULA_CT_INFO: u16 = libc::NFULA_CT_INFO as u16; - -pub const NFULNL_MSG_CONFIG: u8 = libc::NFULNL_MSG_CONFIG as u8; -pub const NFULNL_MSG_PACKET: u8 = libc::NFULNL_MSG_PACKET as u8; - -pub(crate) const IPCTNL_MSG_CT_GET: u8 = 1; diff --git a/src/lib.rs b/src/lib.rs index e9cf671..11f508b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,9 @@ pub(crate) mod buffer; pub mod constants; mod message; -pub use message::{NetfilterHeader, NetfilterMessage, NetfilterMessageInner}; +pub use message::{ + NetfilterHeader, NetfilterMessage, NetfilterMessageInner, Subsystem, +}; pub mod conntrack; pub mod nflog; #[cfg(test)] diff --git a/src/message.rs b/src/message.rs index a609ae0..d4e5b0e 100644 --- a/src/message.rs +++ b/src/message.rs @@ -60,13 +60,44 @@ impl> Parseable> for NetfilterHeader { } } +const NFNL_SUBSYS_CTNETLINK: u8 = libc::NFNL_SUBSYS_CTNETLINK as u8; +const NFNL_SUBSYS_ULOG: u8 = libc::NFNL_SUBSYS_ULOG as u8; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[non_exhaustive] +pub enum Subsystem { + NfLog, + Conntrack, + Other(u8), +} + +impl From for Subsystem { + fn from(value: u8) -> Self { + match value { + NFNL_SUBSYS_ULOG => Self::NfLog, + NFNL_SUBSYS_CTNETLINK => Self::Conntrack, + v => Self::Other(v), + } + } +} + +impl From for u8 { + fn from(value: Subsystem) -> Self { + match value { + Subsystem::NfLog => NFNL_SUBSYS_ULOG, + Subsystem::Conntrack => NFNL_SUBSYS_CTNETLINK, + Subsystem::Other(v) => v, + } + } +} + #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub enum NetfilterMessageInner { NfLog(NfLogMessage), Conntrack(ConntrackMessage), Other { - subsys: u8, + subsys: Subsystem, message_type: u8, attributes: Vec, }, @@ -123,19 +154,21 @@ impl NetfilterMessage { } } - pub fn subsys(&self) -> u8 { + pub fn subsys(&self) -> Subsystem { match self.inner { - NetfilterMessageInner::NfLog(_) => NfLogMessage::SUBSYS, - NetfilterMessageInner::Conntrack(_) => ConntrackMessage::SUBSYS, + NetfilterMessageInner::NfLog(_) => Subsystem::NfLog, + NetfilterMessageInner::Conntrack(_) => Subsystem::Conntrack, NetfilterMessageInner::Other { subsys, .. } => subsys, } } - pub fn message_type(&self) -> u8 { + fn message_type(&self) -> u8 { match self.inner { - NetfilterMessageInner::NfLog(ref message) => message.message_type(), + NetfilterMessageInner::NfLog(ref message) => { + message.message_type().into() + } NetfilterMessageInner::Conntrack(ref message) => { - message.message_type() + message.message_type().into() } NetfilterMessageInner::Other { message_type, .. } => message_type, } @@ -155,7 +188,7 @@ impl Emitable for NetfilterMessage { impl NetlinkSerializable for NetfilterMessage { fn message_type(&self) -> u16 { - ((self.subsys() as u16) << 8) | self.message_type() as u16 + ((u8::from(self.subsys()) as u16) << 8) | self.message_type() as u16 } fn buffer_len(&self) -> usize { diff --git a/src/nflog/message.rs b/src/nflog/message.rs index 9b8f464..696f562 100644 --- a/src/nflog/message.rs +++ b/src/nflog/message.rs @@ -6,7 +6,6 @@ use netlink_packet_core::{ use crate::{ buffer::NetfilterBuffer, - constants::{NFNL_SUBSYS_ULOG, NFULNL_MSG_CONFIG, NFULNL_MSG_PACKET}, nflog::nlas::{config::ConfigNla, packet::PacketNla}, }; @@ -20,14 +19,43 @@ pub enum NfLogMessage { }, } -impl NfLogMessage { - pub const SUBSYS: u8 = NFNL_SUBSYS_ULOG; +const NFULNL_MSG_CONFIG: u8 = libc::NFULNL_MSG_CONFIG as u8; +const NFULNL_MSG_PACKET: u8 = libc::NFULNL_MSG_PACKET as u8; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[non_exhaustive] +pub enum NfLogMessageType { + Config, + Packet, + Other(u8), +} + +impl From for NfLogMessageType { + fn from(value: u8) -> Self { + match value { + NFULNL_MSG_CONFIG => Self::Config, + NFULNL_MSG_PACKET => Self::Packet, + v => Self::Other(v), + } + } +} + +impl From for u8 { + fn from(value: NfLogMessageType) -> Self { + match value { + NfLogMessageType::Config => NFULNL_MSG_CONFIG, + NfLogMessageType::Packet => NFULNL_MSG_PACKET, + NfLogMessageType::Other(v) => v, + } + } +} - pub fn message_type(&self) -> u8 { +impl NfLogMessage { + pub fn message_type(&self) -> NfLogMessageType { match self { - NfLogMessage::Config(_) => NFULNL_MSG_CONFIG, - NfLogMessage::Packet(_) => NFULNL_MSG_PACKET, - NfLogMessage::Other { message_type, .. } => *message_type, + NfLogMessage::Config(_) => NfLogMessageType::Config, + NfLogMessage::Packet(_) => NfLogMessageType::Packet, + NfLogMessage::Other { message_type, .. } => (*message_type).into(), } } } @@ -57,18 +85,18 @@ impl<'a, T: AsRef<[u8]> + ?Sized> buf: &NetfilterBuffer<&'a T>, message_type: u8, ) -> Result { - Ok(match message_type { - NFULNL_MSG_CONFIG => { + Ok(match NfLogMessageType::from(message_type) { + NfLogMessageType::Config => { let nlas = buf.parse_all_nlas(|nla_buf| ConfigNla::parse(&nla_buf))?; NfLogMessage::Config(nlas) } - NFULNL_MSG_PACKET => { + NfLogMessageType::Packet => { let nlas = buf.parse_all_nlas(|nla_buf| PacketNla::parse(&nla_buf))?; NfLogMessage::Packet(nlas) } - _ => NfLogMessage::Other { + NfLogMessageType::Other(message_type) => NfLogMessage::Other { message_type, nlas: buf.default_nlas()?, }, diff --git a/src/nflog/mod.rs b/src/nflog/mod.rs index d3d39fc..5965029 100644 --- a/src/nflog/mod.rs +++ b/src/nflog/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT mod message; -pub use message::NfLogMessage; +pub use message::{NfLogMessage, NfLogMessageType}; pub mod nlas; use netlink_packet_core::{ diff --git a/src/tests.rs b/src/tests.rs index 6eaaa01..9f93cb4 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -2,16 +2,16 @@ use std::net::IpAddr; -use libc::NFNL_SUBSYS_CTNETLINK; use netlink_packet_core::{Emitable, ParseableParametrized}; use crate::{ buffer::NetfilterBuffer, conntrack::{ - ConntrackMessage, ConntrackNla, IPTuple, ProtoInfo, ProtoInfoTCP, - ProtoTuple, Protocol, TCPFlags, Tuple, + ConntrackMessage, ConntrackMessageType, ConntrackNla, IPTuple, + ProtoInfo, ProtoInfoTCP, ProtoTuple, Protocol, TCPFlags, Tuple, }, - constants::{AF_INET, AF_INET6, AF_UNSPEC, IPCTNL_MSG_CT_GET}, + constants::{AF_INET, AF_INET6, AF_UNSPEC}, + message::Subsystem, NetfilterHeader, NetfilterMessage, }; @@ -32,8 +32,8 @@ fn test_dump_conntrack() { // Check if the serialization was correct assert_eq!(buffer, raw); - let message_type = - ((NFNL_SUBSYS_CTNETLINK as u16) << 8) | (IPCTNL_MSG_CT_GET as u16); + let message_type = ((u8::from(Subsystem::Conntrack) as u16) << 8) + | (u8::from(ConntrackMessageType::Get) as u16); // Check if the deserialization was correct assert_eq!( NetfilterMessage::parse_with_param( @@ -100,8 +100,8 @@ fn test_get_conntrack_tcp_ipv4() { // Check if the serialization was correct assert_eq!(buffer, raw); - let message_type = - ((NFNL_SUBSYS_CTNETLINK as u16) << 8) | (IPCTNL_MSG_CT_GET as u16); + let message_type = ((u8::from(Subsystem::Conntrack) as u16) << 8) + | (u8::from(ConntrackMessageType::Get) as u16); // Check if the deserialization was correct assert_eq!( NetfilterMessage::parse_with_param( @@ -156,8 +156,8 @@ fn test_get_conntrack_udp_ipv6() { // Check if the serialization was correct assert_eq!(buffer, raw); - let message_type = - ((NFNL_SUBSYS_CTNETLINK as u16) << 8) | (IPCTNL_MSG_CT_GET as u16); + let message_type = ((u8::from(Subsystem::Conntrack) as u16) << 8) + | (u8::from(ConntrackMessageType::Get) as u16); // Check if the deserialization was correct assert_eq!( NetfilterMessage::parse_with_param(