From 77de46690a91aec5b8c516059f6b4236a26b1ee8 Mon Sep 17 00:00:00 2001 From: terassyi Date: Sat, 14 Dec 2024 23:45:35 +0900 Subject: [PATCH 01/13] Support IPCTNL_MSG_CT_NEW and IPCTNL_MSG_CT_GET Signed-off-by: terassyi --- examples/ctnetlink.rs | 121 +++++++ src/buffer.rs | 5 + src/constants.rs | 177 ++++++++++ src/ctnetlink/message.rs | 99 ++++++ src/ctnetlink/mod.rs | 4 + src/ctnetlink/nlas/flow/ip_tuple.rs | 424 +++++++++++++++++++++++ src/ctnetlink/nlas/flow/mod.rs | 205 +++++++++++ src/ctnetlink/nlas/flow/nla.rs | 120 +++++++ src/ctnetlink/nlas/flow/protocol_info.rs | 243 +++++++++++++ src/ctnetlink/nlas/flow/status.rs | 72 ++++ src/ctnetlink/nlas/mod.rs | 3 + src/lib.rs | 1 + src/message.rs | 18 +- 13 files changed, 1491 insertions(+), 1 deletion(-) create mode 100644 examples/ctnetlink.rs create mode 100644 src/ctnetlink/message.rs create mode 100644 src/ctnetlink/mod.rs create mode 100644 src/ctnetlink/nlas/flow/ip_tuple.rs create mode 100644 src/ctnetlink/nlas/flow/mod.rs create mode 100644 src/ctnetlink/nlas/flow/nla.rs create mode 100644 src/ctnetlink/nlas/flow/protocol_info.rs create mode 100644 src/ctnetlink/nlas/flow/status.rs create mode 100644 src/ctnetlink/nlas/mod.rs diff --git a/examples/ctnetlink.rs b/examples/ctnetlink.rs new file mode 100644 index 0000000..854861a --- /dev/null +++ b/examples/ctnetlink.rs @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_core::{ + NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST, +}; +use netlink_packet_netfilter::{ + constants::{AF_INET, NFNETLINK_V0}, + ctnetlink::{ + message::CtNetlinkMessage, + nlas::flow::{ip_tuple::TupleNla, nla::FlowNla}, + }, + NetfilterHeader, NetfilterMessage, NetfilterMessageInner, +}; +use netlink_sys::{protocols::NETLINK_NETFILTER, Socket}; + +fn main() { + let mut receive_buffer = vec![0; 4096]; + + let mut socket = Socket::new(NETLINK_NETFILTER).unwrap(); + socket.bind_auto().unwrap(); + + // List all conntrack entries + let packet = list_request(AF_INET, 0); + let mut buf = vec![0; packet.header.length as usize]; + packet.serialize(&mut buf[..]); + println!(">>> {:?}", packet); + socket.send(&buf[..], 0).unwrap(); + + // pick one ip_tuple from the result of list + let mut orig: Option> = None; + + let mut done = false; + loop { + let size = socket.recv(&mut &mut receive_buffer[..], 0).unwrap(); + let bytes = &receive_buffer[..size]; + let mut read = 0; + let mut msg_count = 0; + while bytes.len() > read { + let rx_packet = + >::deserialize(&bytes[read..]) + .unwrap(); + if let NetlinkPayload::Done(_) = rx_packet.payload { + done = true; + break; + } + read += rx_packet.buffer_len(); + msg_count += 1; + println!( + "<<< counter={} packet_len={}\n{:?}", + msg_count, + rx_packet.buffer_len(), + rx_packet + ); + + if let NetlinkPayload::InnerMessage(ct) = rx_packet.payload { + if let NetfilterMessageInner::CtNetlink( + CtNetlinkMessage::New(nlas), + ) = ct.inner + { + for nla in nlas.iter() { + if let FlowNla::Orig(attrs) = nla { + orig = Some(attrs.clone()) + } + } + } + } else if let NetlinkPayload::Error(e) = rx_packet.payload { + println!("{}", e); + assert_eq!(e.code, None); + } + } + if done { + break; + } + } + + // Get a specific conntrack entry + let orig = orig.unwrap(); + let packet = get_request(AF_INET, 0, orig); + let mut buf = vec![0; packet.header.length as usize]; + packet.serialize(&mut buf[..]); + println!(">>> {:?}", packet); + socket.send(&buf[..], 0).unwrap(); + + let size = socket.recv(&mut &mut receive_buffer[..], 0).unwrap(); + let bytes = &receive_buffer[..size]; + let rx_packet = + >::deserialize(bytes).unwrap(); + println!("<<< packet_len={}\n{:?}", rx_packet.buffer_len(), rx_packet); +} + +fn list_request(family: u8, res_id: u16) -> NetlinkMessage { + let mut hdr = NetlinkHeader::default(); + hdr.flags = NLM_F_REQUEST | NLM_F_DUMP; + let mut message = NetlinkMessage::new( + hdr, + NetlinkPayload::from(NetfilterMessage::new( + NetfilterHeader::new(family, NFNETLINK_V0, res_id), + CtNetlinkMessage::Get(None), + )), + ); + message.finalize(); + message +} + +fn get_request( + family: u8, + res_id: u16, + tuple: Vec, +) -> NetlinkMessage { + let mut hdr = NetlinkHeader::default(); + hdr.flags = NLM_F_REQUEST; + let mut message = NetlinkMessage::new( + hdr, + NetlinkPayload::from(NetfilterMessage::new( + NetfilterHeader::new(family, NFNETLINK_V0, res_id), + CtNetlinkMessage::Get(Some(vec![FlowNla::Orig(tuple)])), + )), + ); + message.finalize(); + message +} diff --git a/src/buffer.rs b/src/buffer.rs index ada5cde..7f1a62e 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT use crate::{ + ctnetlink::message::CtNetlinkMessage, message::{ NetfilterHeader, NetfilterMessage, NetfilterMessageInner, NETFILTER_HEADER_LEN, @@ -60,6 +61,10 @@ impl<'a, T: AsRef<[u8]> + ?Sized> NfLogMessage::parse_with_param(buf, message_type) .context("failed to parse nflog payload")?, ), + CtNetlinkMessage::SUBSYS => NetfilterMessageInner::CtNetlink( + CtNetlinkMessage::parse_with_param(buf, message_type) + .context("failed to parse ctnetlink payload")?, + ), _ => NetfilterMessageInner::Other { subsys, message_type, diff --git a/src/constants.rs b/src/constants.rs index 2b73b6d..6fb57f9 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -88,3 +88,180 @@ 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; + +// netflter/nfnetlink_conntrack.h +// There is no definitions in rust-lang/libc +pub const IPCTNL_MSG_CT_NEW: u8 = 0; +pub const IPCTNL_MSG_CT_GET: u8 = 1; +pub const IPCTNL_MSG_CT_DELETE: u8 = 2; +pub const IPCTNL_MSG_CT_GET_CTRZERO: u8 = 3; +pub const IPCTNL_MSG_CT_GET_STATS_CPU: u8 = 4; +pub const IPCTNL_MSG_CT_GET_STATS: u8 = 5; +pub const IPCTNL_MSG_CT_DYING: u8 = 6; +pub const IPCTNL_MSG_CT_GET_UNCONFIRMED: u8 = 7; + +pub const CTA_UNSPEC: u16 = 0; +pub const CTA_TUPLE_ORIG: u16 = 1; +pub const CTA_TUPLE_REPLY: u16 = 2; +pub const CTA_STATUS: u16 = 3; +pub const CTA_PROTOINFO: u16 = 4; +pub const CTA_HELP: u16 = 5; +pub const CTA_NAT_SRC: u16 = 6; +pub const CTA_NAT: u16 = CTA_NAT_SRC; /* backwards compatibility */ +pub const CTA_TIMEOUT: u16 = 7; +pub const CTA_MARK: u16 = 8; +pub const CTA_COUNTERS_ORIG: u16 = 9; +pub const CTA_COUNTERS_REPLY: u16 = 10; +pub const CTA_USE: u16 = 11; +pub const CTA_ID: u16 = 12; +pub const CTA_NAT_DST: u16 = 13; +pub const CTA_TUPLE_MASTER: u16 = 14; +pub const CTA_SEQ_ADJ_ORIG: u16 = 15; +pub const CTA_NAT_SEQ_ADJ_ORIG: u16 = CTA_SEQ_ADJ_ORIG; +pub const CTA_SEQ_ADJ_REPLY: u16 = 16; +pub const CTA_NAT_SEQ_ADJ_REPLY: u16 = CTA_SEQ_ADJ_REPLY; +pub const CTA_SECMARK: u16 = 17; /* obsolete */ +pub const CTA_ZONE: u16 = 18; +pub const CTA_SECCTX: u16 = 19; +pub const CTA_TIMESTAMP: u16 = 20; +pub const CTA_MARK_MASK: u16 = 21; +pub const CTA_LABELS: u16 = 22; +pub const CTA_LABELS_MASK: u16 = 23; +pub const CTA_SYNPROXY: u16 = 24; +pub const CTA_FILTER: u16 = 25; +pub const CTA_STATUS_MASK: u16 = 26; + +pub const CTA_TUPLE_UNSPEC: u16 = 0; +pub const CTA_TUPLE_IP: u16 = 1; +pub const CTA_TUPLE_PROTO: u16 = 2; +pub const CTA_TUPLE_ZONE: u16 = 3; + +pub const CTA_IP_UNSPEC: u16 = 0; +pub const CTA_IP_V4_SRC: u16 = 1; +pub const CTA_IP_V4_DST: u16 = 2; +pub const CTA_IP_V6_SRC: u16 = 3; +pub const CTA_IP_V6_DST: u16 = 4; + +pub const CTA_PROTO_UNSPEC: u16 = 0; +pub const CTA_PROTO_NUM: u16 = 1; +pub const CTA_PROTO_SRC_PORT: u16 = 2; +pub const CTA_PROTO_DST_PORT: u16 = 3; +pub const CTA_PROTO_ICMP_ID: u16 = 4; +pub const CTA_PROTO_ICMP_TYPE: u16 = 5; +pub const CTA_PROTO_ICMP_CODE: u16 = 6; +pub const CTA_PROTO_ICMPV6_ID: u16 = 7; +pub const CTA_PROTO_ICMPV6_TYPE: u16 = 8; +pub const CTA_PROTO_ICMPV6_CODE: u16 = 9; + +pub const CTA_PROTOINFO_UNSPEC: u16 = 0; +pub const CTA_PROTOINFO_TCP: u16 = 1; +pub const CTA_PROTOINFO_DCCP: u16 = 2; +pub const CTA_PROTOINFO_SCTP: u16 = 3; + +pub const CTA_PROTOINFO_TCP_UNSPEC: u16 = 0; +pub const CTA_PROTOINFO_TCP_STATE: u16 = 1; +pub const CTA_PROTOINFO_TCP_WSCALE_ORIGINAL: u16 = 2; +pub const CTA_PROTOINFO_TCP_WSCALE_REPLY: u16 = 3; +pub const CTA_PROTOINFO_TCP_FLAGS_ORIGINAL: u16 = 4; +pub const CTA_PROTOINFO_TCP_FLAGS_REPLY: u16 = 5; + +pub const CTA_PROTOINFO_DCCP_UNSPEC: u16 = 0; +pub const CTA_PROTOINFO_DCCP_STATE: u16 = 1; +pub const CTA_PROTOINFO_DCCP_ROLE: u16 = 2; +pub const CTA_PROTOINFO_DCCP_HANDSHAKE_SEQ: u16 = 3; +pub const CTA_PROTOINFO_DCCP_PAD: u16 = 4; + +pub const CTA_PROTOINFO_SCTP_UNSPEC: u16 = 0; +pub const CTA_PROTOINFO_SCTP_STATE: u16 = 1; +pub const CTA_PROTOINFO_SCTP_VTAG_ORIGINAL: u16 = 2; +pub const CTA_PROTOINFO_SCTP_VTAG_REPLY: u16 = 3; + +pub const CTA_COUNTERS_UNSPEC: u8 = 0; +pub const CTA_COUNTERS_PACKETS: u8 = 1; /* 64bit counters */ +pub const CTA_COUNTERS_BYTES: u8 = 2; /* 64bit counters */ +pub const CTA_COUNTERS32_PACKETS: u8 = 3; /* old 32bit counters, unused */ +pub const CTA_COUNTERS32_BYTES: u8 = 4; /* old 32bit counters, unused */ +pub const CTA_COUNTERS_PAD: u8 = 5; + +pub const CTA_TIMESTAMP_UNSPEC: u8 = 0; +pub const CTA_TIMESTAMP_START: u8 = 1; +pub const CTA_TIMESTAMP_STOP: u8 = 2; +pub const CTA_TIMESTAMP_PAD: u8 = 3; + +pub const CTA_NAT_UNSPEC: u8 = 0; +pub const CTA_NAT_V4_MINIP: u8 = 1; +pub const CTA_NAT_MINIP: u8 = CTA_NAT_V4_MINIP; +pub const CTA_NAT_V4_MAXIP: u8 = 2; +pub const CTA_NAT_MAXIP: u8 = CTA_NAT_V4_MAXIP; +pub const CTA_NAT_PROTO: u8 = 3; +pub const CTA_NAT_V6_MINIP: u8 = 4; +pub const CTA_NAT_V6_MAXIP: u8 = 5; + +pub const CTA_PROTONAT_UNSPEC: u8 = 0; +pub const CTA_PROTONAT_PORT_MIN: u8 = 1; +pub const CTA_PROTONAT_PORT_MAX: u8 = 2; + +pub const CTA_SEQADJ_UNSPEC: u8 = 0; +pub const CTA_SEQADJ_CORRECTION_POS: u8 = 1; +pub const CTA_SEQADJ_OFFSET_BEFORE: u8 = 2; +pub const CTA_SEQADJ_OFFSET_AFTER: u8 = 3; + +pub const CTA_NAT_SEQ_UNSPEC: u8 = 0; +pub const CTA_NAT_SEQ_CORRECTION_POS: u8 = 1; +pub const CTA_NAT_SEQ_OFFSET_BEFORE: u8 = 2; +pub const CTA_NAT_SEQ_OFFSET_AFTER: u8 = 3; + +pub const CTA_SYNPROXY_UNSPEC: u8 = 0; +pub const CTA_SYNPROXY_ISN: u8 = 1; +pub const CTA_SYNPROXY_ITS: u8 = 2; +pub const CTA_SYNPROXY_TSOFF: u8 = 3; + +pub const CTA_EXPECT_UNSPEC: u8 = 0; +pub const CTA_EXPECT_MASTER: u8 = 1; +pub const CTA_EXPECT_TUPLE: u8 = 2; +pub const CTA_EXPECT_MASK: u8 = 3; +pub const CTA_EXPECT_TIMEOUT: u8 = 4; +pub const CTA_EXPECT_ID: u8 = 5; +pub const CTA_EXPECT_HELP_NAME: u8 = 6; +pub const CTA_EXPECT_ZONE: u8 = 7; +pub const CTA_EXPECT_FLAGS: u8 = 8; +pub const CTA_EXPECT_CLASS: u8 = 9; +pub const CTA_EXPECT_NAT: u8 = 10; +pub const CTA_EXPECT_FN: u8 = 11; + +pub const CTA_EXPECT_NAT_UNSPEC: u8 = 0; +pub const CTA_EXPECT_NAT_DIR: u8 = 1; +pub const CTA_EXPECT_NAT_TUPLE: u8 = 2; + +pub const CTA_SECCTX_UNSPEC: u8 = 0; +pub const CTA_SECCTX_NAME: u8 = 1; + +pub const CTA_STATS_UNSPEC: u8 = 0; +pub const CTA_STATS_SEARCHED: u8 = 1; /* no longer used */ +pub const CTA_STATS_FOUND: u8 = 2; +pub const CTA_STATS_NEW: u8 = 3; /* no longer used */ +pub const CTA_STATS_INVALID: u8 = 4; +pub const CTA_STATS_IGNORE: u8 = 5; /* no longer used */ +pub const CTA_STATS_DELETE: u8 = 6; /* no longer used */ +pub const CTA_STATS_DELETE_LIST: u8 = 7; /* no longer used */ +pub const CTA_STATS_INSERT: u8 = 8; +pub const CTA_STATS_INSERT_FAILED: u8 = 9; +pub const CTA_STATS_DROP: u8 = 10; +pub const CTA_STATS_EARLY_DROP: u8 = 10; +pub const CTA_STATS_ERROR: u8 = 11; +pub const CTA_STATS_SEARCH_RESTART: u8 = 12; +pub const CTA_STATS_CLASH_RESOLVE: u8 = 13; +pub const CTA_STATS_CHAIN_TOOLONG: u8 = 14; + +pub const CTA_STATS_GLOBAL_UNSPEC: u8 = 0; +pub const CTA_STATS_GLOBAL_ENTRIES: u8 = 1; +pub const CTA_STATS_GLOBAL_MAX_ENTRIES: u8 = 2; + +pub const CTA_STATS_EXP_UNSPEC: u8 = 0; +pub const CTA_STATS_EXP_NEW: u8 = 1; +pub const CTA_STATS_EXP_CREATE: u8 = 2; +pub const CTA_STATS_EXP_DELETE: u8 = 3; + +pub const CTA_FILTER_UNSPEC: u8 = 0; +pub const CTA_FILTER_ORIG_FLAGS: u8 = 1; +pub const CTA_FILTER_REPLY_FLAGS: u8 = 2; diff --git a/src/ctnetlink/message.rs b/src/ctnetlink/message.rs new file mode 100644 index 0000000..739e1ff --- /dev/null +++ b/src/ctnetlink/message.rs @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_utils::{ + nla::DefaultNla, DecodeError, Emitable, Parseable, ParseableParametrized, +}; + +use crate::{ + buffer::NetfilterBuffer, + constants::{IPCTNL_MSG_CT_GET, IPCTNL_MSG_CT_NEW, NFNL_SUBSYS_CTNETLINK}, +}; + +use super::nlas::flow::nla::FlowNla; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum CtNetlinkMessage { + New(Vec), + Get(Option>), + // Delete, + // GetCrtZero, + // GetStatsCPU, + // GetStats, + // GetDying, + // GetUnconfirmed, + Other { + message_type: u8, + nlas: Vec, + }, +} + +impl CtNetlinkMessage { + pub const SUBSYS: u8 = NFNL_SUBSYS_CTNETLINK; + + pub fn message_type(&self) -> u8 { + match self { + CtNetlinkMessage::New(_) => IPCTNL_MSG_CT_NEW, + CtNetlinkMessage::Get(_) => IPCTNL_MSG_CT_GET, + CtNetlinkMessage::Other { message_type, .. } => *message_type, + } + } +} + +impl Emitable for CtNetlinkMessage { + fn buffer_len(&self) -> usize { + match self { + CtNetlinkMessage::New(nlas) => nlas.as_slice().buffer_len(), + CtNetlinkMessage::Get(nlas) => match nlas { + Some(nlas) => nlas.as_slice().buffer_len(), + None => 0, + }, + CtNetlinkMessage::Other { nlas, .. } => { + nlas.as_slice().buffer_len() + } + } + } + + fn emit(&self, buffer: &mut [u8]) { + match self { + CtNetlinkMessage::New(nlas) => nlas.as_slice().emit(buffer), + CtNetlinkMessage::Get(nlas) => { + if let Some(nlas) = nlas { + nlas.as_slice().emit(buffer); + } + } + CtNetlinkMessage::Other { nlas, .. } => { + nlas.as_slice().emit(buffer) + } + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> + ParseableParametrized, u8> for CtNetlinkMessage +{ + fn parse_with_param( + buf: &NetfilterBuffer<&'a T>, + message_type: u8, + ) -> Result { + Ok(match message_type { + IPCTNL_MSG_CT_NEW => { + let nlas = + buf.parse_all_nlas(|nla_buf| FlowNla::parse(&nla_buf))?; + CtNetlinkMessage::New(nlas) + } + IPCTNL_MSG_CT_GET => { + if !buf.payload().is_empty() { + CtNetlinkMessage::Get(None) + } else { + let nlas = + buf.parse_all_nlas(|nla_buf| FlowNla::parse(&nla_buf))?; + CtNetlinkMessage::Get(Some(nlas)) + } + } + _ => CtNetlinkMessage::Other { + message_type, + nlas: buf.default_nlas()?, + }, + }) + } +} diff --git a/src/ctnetlink/mod.rs b/src/ctnetlink/mod.rs new file mode 100644 index 0000000..ebb009a --- /dev/null +++ b/src/ctnetlink/mod.rs @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: MIT + +pub mod message; +pub mod nlas; diff --git a/src/ctnetlink/nlas/flow/ip_tuple.rs b/src/ctnetlink/nlas/flow/ip_tuple.rs new file mode 100644 index 0000000..01f2965 --- /dev/null +++ b/src/ctnetlink/nlas/flow/ip_tuple.rs @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: MIT + +use std::{convert::TryFrom, net::IpAddr}; + +use byteorder::{BigEndian, ByteOrder}; +use netlink_packet_utils::{ + nla::{Nla, NlaBuffer, NLA_F_NESTED, NLA_HEADER_SIZE}, + parsers::parse_ip, + DecodeError, Parseable, +}; + +use crate::constants::{ + CTA_IP_V4_DST, CTA_IP_V4_SRC, CTA_IP_V6_DST, CTA_IP_V6_SRC, + CTA_PROTO_DST_PORT, CTA_PROTO_NUM, CTA_PROTO_SRC_PORT, CTA_TUPLE_IP, + CTA_TUPLE_PROTO, +}; + +use super::{CtAttr, CtAttrBuilder}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum TupleNla { + Ip(IpTuple), + Protocol(ProtocolTuple), +} + +impl Nla for TupleNla { + fn value_len(&self) -> usize { + match self { + TupleNla::Ip(attr) => attr.value_len(), + TupleNla::Protocol(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + TupleNla::Ip(attr) => attr.kind(), + TupleNla::Protocol(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + TupleNla::Ip(attr) => attr.emit_value(buffer), + TupleNla::Protocol(attr) => attr.emit_value(buffer), + } + } +} + +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for TupleNla +{ + fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { + let attr = CtAttr::parse(buf)?; + match attr.attr_type { + CTA_TUPLE_IP => Ok(TupleNla::Ip(IpTuple::try_from(attr)?)), + CTA_TUPLE_PROTO => { + Ok(TupleNla::Protocol(ProtocolTuple::try_from(attr)?)) + } + _ => Err(DecodeError::from("CTA_TUPLE_{IP|PROTO} is expected")), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct IpTuple { + pub src_addr: IpAddr, + pub dst_addr: IpAddr, +} + +impl Nla for IpTuple { + fn value_len(&self) -> usize { + let mut l = 0; + l += match self.src_addr { + IpAddr::V4(_) => 4 + NLA_HEADER_SIZE, + IpAddr::V6(_) => 16 + NLA_HEADER_SIZE, + }; + l += match self.dst_addr { + IpAddr::V4(_) => 4 + NLA_HEADER_SIZE, + IpAddr::V6(_) => 16 + NLA_HEADER_SIZE, + }; + l + } + + fn kind(&self) -> u16 { + CTA_TUPLE_IP + NLA_F_NESTED + } + + fn emit_value(&self, buffer: &mut [u8]) { + let mut builder = CtAttrBuilder::new(CTA_TUPLE_IP); + match self.src_addr { + IpAddr::V4(addr) => { + let src_ip_attr = CtAttrBuilder::new(CTA_IP_V4_SRC) + .value(&addr.octets()) + .build(); + builder = builder.nested_attr(src_ip_attr); + } + IpAddr::V6(addr) => { + let src_ip_attr = CtAttrBuilder::new(CTA_IP_V6_SRC) + .value(&addr.octets()) + .build(); + builder = builder.nested_attr(src_ip_attr); + } + } + match self.dst_addr { + IpAddr::V4(addr) => { + let dst_ip_attr = CtAttrBuilder::new(CTA_IP_V4_DST) + .value(&addr.octets()) + .build(); + builder = builder.nested_attr(dst_ip_attr); + } + IpAddr::V6(addr) => { + let dst_ip_attr = CtAttrBuilder::new(CTA_IP_V6_DST) + .value(&addr.octets()) + .build(); + builder = builder.nested_attr(dst_ip_attr); + } + } + + builder.build().emit_value(buffer); + } +} + +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for IpTuple +{ + fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { + let ip_tuple = CtAttr::parse(buf)?; + let mut builder = IpTupleBuilder::default(); + + if let Some(attrs) = ip_tuple.nested { + for attr in attrs.iter() { + match attr.attr_type { + CTA_IP_V4_SRC | CTA_IP_V6_SRC => { + if let Some(value) = &attr.value { + let addr = parse_ip(value)?; + builder = builder.src_addr(addr); + } + } + CTA_IP_V4_DST | CTA_IP_V6_DST => { + if let Some(value) = &attr.value { + let addr = parse_ip(value)?; + builder = builder.dst_addr(addr); + } + } + _ => {} + } + } + builder.build() + } else { + Err(DecodeError::from("CTA_TUPLE_IP must be nested")) + } + } +} + +impl TryFrom for IpTuple { + type Error = DecodeError; + + fn try_from(attr: CtAttr) -> Result { + if attr.attr_type != CTA_TUPLE_IP { + return Err(DecodeError::from("CTA_TUPLE_IP is expected")); + } + let mut builder = IpTupleBuilder::default(); + + if let Some(attrs) = attr.nested { + for attr in attrs.iter() { + match attr.attr_type { + CTA_IP_V4_SRC | CTA_IP_V6_SRC => { + if let Some(value) = &attr.value { + let addr = parse_ip(value)?; + builder = builder.src_addr(addr); + } + } + CTA_IP_V4_DST | CTA_IP_V6_DST => { + if let Some(value) = &attr.value { + let addr = parse_ip(value)?; + builder = builder.dst_addr(addr); + } + } + _ => {} + } + } + builder.build() + } else { + Err(DecodeError::from("CTA_TUPLE_IP must be nested")) + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct IpTupleBuilder { + src_addr: Option, + dst_addr: Option, +} + +impl IpTupleBuilder { + pub fn src_addr(mut self, addr: IpAddr) -> Self { + self.src_addr = Some(addr); + self + } + + pub fn dst_addr(mut self, addr: IpAddr) -> Self { + self.dst_addr = Some(addr); + self + } + + pub fn build(&self) -> Result { + Ok(IpTuple { + src_addr: self + .src_addr + .ok_or(DecodeError::from("ip_tuple.src_addr is none"))?, + dst_addr: self + .dst_addr + .ok_or(DecodeError::from("ip_tuple.dst_addr is none"))?, + }) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct ProtocolTuple { + pub src_port: u16, + pub dst_port: u16, + pub protocol: u8, +} + +impl TryFrom for ProtocolTuple { + type Error = DecodeError; + + fn try_from(attr: CtAttr) -> Result { + if attr.attr_type != CTA_TUPLE_PROTO { + return Err(DecodeError::from("CTA_TUPLE_PROTO is expected")); + } + let mut builder = ProtocolTupleBuilder::default(); + + if let Some(attrs) = attr.nested { + for attr in attrs.iter() { + match attr.attr_type { + CTA_PROTO_NUM => { + if let Some(value) = &attr.value { + builder = builder.protocol(value[0]); + } + } + CTA_PROTO_SRC_PORT => { + if let Some(value) = &attr.value { + builder = + builder.src_port(BigEndian::read_u16(value)); + } + } + CTA_PROTO_DST_PORT => { + if let Some(value) = &attr.value { + builder = + builder.dst_port(BigEndian::read_u16(value)); + } + } + _ => {} + } + } + builder.build() + } else { + Err(DecodeError::from("CTA_TUPLE_PROTO must be nested")) + } + } +} + +impl Nla for ProtocolTuple { + fn value_len(&self) -> usize { + 24 + } + + fn kind(&self) -> u16 { + CTA_TUPLE_PROTO + NLA_F_NESTED + } + + fn emit_value(&self, buffer: &mut [u8]) { + let mut builder = CtAttrBuilder::new(CTA_TUPLE_PROTO); + builder = builder.nested_attr( + CtAttrBuilder::new(CTA_PROTO_NUM) + .value(vec![self.protocol].as_ref()) + .build(), + ); + let mut src_port_buf = [0u8; 2]; + BigEndian::write_u16(&mut src_port_buf, self.src_port); + let mut dst_port_buf = [0u8; 2]; + BigEndian::write_u16(&mut dst_port_buf, self.dst_port); + builder = builder.nested_attr( + CtAttrBuilder::new(CTA_PROTO_SRC_PORT) + .value(&src_port_buf) + .build(), + ); + builder = builder.nested_attr( + CtAttrBuilder::new(CTA_PROTO_DST_PORT) + .value(&dst_port_buf) + .build(), + ); + + builder.build().emit_value(buffer); + } +} + +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for ProtocolTuple +{ + fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { + let proto_tuple = CtAttr::parse(buf)?; + let mut builder = ProtocolTupleBuilder::default(); + + if let Some(attrs) = proto_tuple.nested { + for attr in attrs.iter() { + match attr.attr_type { + CTA_PROTO_NUM => { + if let Some(value) = &attr.value { + builder = builder.protocol(value[0]); + } + } + CTA_PROTO_SRC_PORT => { + if let Some(value) = &attr.value { + builder = + builder.src_port(BigEndian::read_u16(value)); + } + } + CTA_PROTO_DST_PORT => { + if let Some(value) = &attr.value { + builder = + builder.dst_port(BigEndian::read_u16(value)); + } + } + _ => {} + } + } + builder.build() + } else { + Err(DecodeError::from("CTA_TUPLE_PROTO must be nested")) + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct ProtocolTupleBuilder { + src_port: Option, + dst_port: Option, + protocol: Option, +} + +impl ProtocolTupleBuilder { + pub fn src_port(mut self, port: u16) -> Self { + self.src_port = Some(port); + self + } + + pub fn dst_port(mut self, port: u16) -> Self { + self.dst_port = Some(port); + self + } + + pub fn protocol(mut self, proto: u8) -> Self { + self.protocol = Some(proto); + self + } + + pub fn build(&self) -> Result { + Ok(ProtocolTuple { + src_port: self + .src_port + .ok_or(DecodeError::from("ip_tuple.src_port is none"))?, + dst_port: self + .dst_port + .ok_or(DecodeError::from("ip_tuple.dst_port is none"))?, + protocol: self + .protocol + .ok_or(DecodeError::from("ip_tuple.protocol is none"))?, + }) + } +} + +#[cfg(test)] +mod tests { + + use std::{net::IpAddr, str::FromStr}; + + use netlink_packet_utils::{nla::NlaBuffer, Emitable, Parseable}; + + use crate::ctnetlink::nlas::flow::ip_tuple::{IpTuple, ProtocolTuple}; + + const DATA: [u8; 48] = [ + 20, 0, 1, 128, 8, 0, 1, 0, 1, 2, 3, 4, 8, 0, 2, 0, 1, 2, 3, 4, 28, 0, + 2, 128, 5, 0, 1, 0, 17, 0, 0, 0, 6, 0, 2, 0, 220, 210, 0, 0, 6, 0, 3, + 0, 7, 108, 0, 0, + ]; + + #[test] + fn test_ip_tuple_parse() { + let buf = NlaBuffer::new(&DATA); + let ip_tuple = IpTuple::parse(&buf).unwrap(); + assert_eq!(ip_tuple.src_addr, IpAddr::from_str("1.2.3.4").unwrap()); + assert_eq!(ip_tuple.dst_addr, IpAddr::from_str("1.2.3.4").unwrap()); + + let buf = NlaBuffer::new(&DATA[ip_tuple.buffer_len()..]); + let proto_tuple = ProtocolTuple::parse(&buf).unwrap(); + assert_eq!(proto_tuple.protocol, 17); + assert_eq!(proto_tuple.src_port, 56530); + assert_eq!(proto_tuple.dst_port, 1900); + } + + #[test] + fn test_ip_tuple_to_vec() { + let buf = NlaBuffer::new(&DATA); + let ip_tuple = IpTuple::parse(&buf).unwrap(); + assert_eq!(ip_tuple.src_addr, IpAddr::from_str("1.2.3.4").unwrap()); + assert_eq!(ip_tuple.dst_addr, IpAddr::from_str("1.2.3.4").unwrap()); + + let mut attr_data = [0u8; 20]; + ip_tuple.emit(&mut attr_data); + assert_eq!(attr_data, DATA[..20]); + + let buf = NlaBuffer::new(&DATA[ip_tuple.buffer_len()..]); + let proto_tuple = ProtocolTuple::parse(&buf).unwrap(); + assert_eq!(proto_tuple.protocol, 17); + assert_eq!(proto_tuple.src_port, 56530); + assert_eq!(proto_tuple.dst_port, 1900); + + let mut attr_data = [0u8; 28]; + proto_tuple.emit(&mut attr_data); + assert_eq!(attr_data, DATA[20..]); + } +} diff --git a/src/ctnetlink/nlas/flow/mod.rs b/src/ctnetlink/nlas/flow/mod.rs new file mode 100644 index 0000000..2617408 --- /dev/null +++ b/src/ctnetlink/nlas/flow/mod.rs @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_utils::{ + nla::{Nla, NlaBuffer, NLA_F_NESTED, NLA_HEADER_SIZE}, + DecodeError, Emitable, Parseable, +}; + +pub mod ip_tuple; +pub mod nla; +pub mod protocol_info; +pub mod status; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CtAttr { + pub nested: Option>, + pub attr_type: u16, + pub length: u16, + pub value: Option>, +} + +impl Nla for CtAttr { + fn value_len(&self) -> usize { + (self.length as usize) - NLA_HEADER_SIZE + } + + fn kind(&self) -> u16 { + if self.is_nested() { + self.attr_type | NLA_F_NESTED + } else { + self.attr_type + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + if let Some(attrs) = &self.nested { + let mut attrs_buf = vec![]; + for attr in attrs.iter() { + let l = if attr.length % 4 != 0 { + attr.length + 4 - (attr.length % 4) + } else { + attr.length + } as usize; + let mut buf = vec![0u8; l]; + attr.emit(&mut buf); + attrs_buf.append(&mut buf); + } + buffer[..attrs_buf.len()].copy_from_slice(&attrs_buf); + } else if let Some(value) = &self.value { + buffer[..value.len()].copy_from_slice(value); + } + } +} + +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for CtAttr +{ + fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { + let length = buf.length(); + let is_nested = buf.nested_flag(); + let attr_type = buf.kind(); + let value_l = (length as usize) - NLA_HEADER_SIZE; + let value = buf.value(); + if is_nested { + let mut nested_attrs = vec![]; + let mut read = 0; + while value_l > read { + let nla_buf = NlaBuffer::new(&value[read..]); + let attr = Self::parse(&nla_buf)?; + read += attr.length as usize; + if attr.length % 4 != 0 { + read += 4 - (attr.length as usize % 4); + } + nested_attrs.push(attr); + } + Ok(CtAttr { + nested: Some(nested_attrs), + length, + attr_type, + value: None, + }) + } else { + Ok(CtAttr { + nested: None, + attr_type, + // padding bytes are not included + length, + value: Some(value[..value_l].to_vec()), + }) + } + } +} + +impl CtAttr { + pub fn is_nested(&self) -> bool { + self.nested.is_some() + } +} + +#[derive(Debug, Clone)] +pub struct CtAttrBuilder { + nested: Option>, + attr_type: u16, + value: Option>, + length: u16, +} + +impl CtAttrBuilder { + pub fn new(attr_type: u16) -> CtAttrBuilder { + CtAttrBuilder { + nested: None, + attr_type, + value: None, + length: 0, + } + } + pub fn nested_attr(mut self, attr: CtAttr) -> Self { + self.length += attr.length; + if attr.length % 4 != 0 { + self.length += 4 - (attr.length % 4); + } + if let Some(ref mut nested) = self.nested { + nested.push(attr); + } else { + self.nested = Some(vec![attr]); + } + self.attr_type |= NLA_F_NESTED; + self + } + + pub fn value(mut self, v: &[u8]) -> Self { + self.length += v.len() as u16; + self.value = Some(v.to_vec()); + self + } + + pub fn build(&self) -> CtAttr { + CtAttr { + nested: self.nested.clone(), + attr_type: self.attr_type, + length: self.length + NLA_HEADER_SIZE as u16, + value: self.value.clone(), + } + } +} + +#[cfg(test)] +mod tests { + use netlink_packet_utils::{nla::NlaBuffer, Emitable, Parseable}; + + use crate::{ + constants::{ + CTA_IP_V4_DST, CTA_IP_V4_SRC, CTA_PROTO_DST_PORT, CTA_PROTO_NUM, + CTA_PROTO_SRC_PORT, CTA_TUPLE_IP, CTA_TUPLE_PROTO, + }, + ctnetlink::nlas::flow::CtAttr, + }; + const DATA: [u8; 48] = [ + 20, 0, 1, 128, 8, 0, 1, 0, 1, 2, 3, 4, 8, 0, 2, 0, 1, 2, 3, 4, 28, 0, + 2, 128, 5, 0, 1, 0, 17, 0, 0, 0, 6, 0, 2, 0, 220, 210, 0, 0, 6, 0, 3, + 0, 7, 108, 0, 0, + ]; + + #[test] + fn test_ct_attr_parse() { + let buf = NlaBuffer::new(&DATA); + // first + let ct_attr = CtAttr::parse(&buf).unwrap(); + assert_eq!(ct_attr.length, 20); + assert!(ct_attr.is_nested()); + assert_eq!(ct_attr.attr_type, CTA_TUPLE_IP); + + let nested_attrs = ct_attr.nested.unwrap(); + assert_eq!(nested_attrs.len(), 2); + assert_eq!(nested_attrs[0].attr_type, CTA_IP_V4_SRC); + assert_eq!(nested_attrs[0].length, 8); + + assert_eq!(nested_attrs[1].attr_type, CTA_IP_V4_DST); + assert_eq!(nested_attrs[1].length, 8); + + // second + let buf = NlaBuffer::new(&DATA[(ct_attr.length as usize)..]); + let ct_attr = CtAttr::parse(&buf).unwrap(); + assert_eq!(ct_attr.length, 28); + assert!(ct_attr.is_nested()); + assert_eq!(ct_attr.attr_type, CTA_TUPLE_PROTO); + let nested_attr = ct_attr.nested.unwrap(); + assert_eq!(nested_attr.len(), 3); + assert_eq!(nested_attr[0].attr_type, CTA_PROTO_NUM); + assert_eq!(nested_attr[1].attr_type, CTA_PROTO_SRC_PORT); + assert_eq!(nested_attr[2].attr_type, CTA_PROTO_DST_PORT); + } + + #[test] + fn test_ct_attr_emit() { + let buf = NlaBuffer::new(&DATA); + let ct_attr = CtAttr::parse(&buf).unwrap(); + assert_eq!(ct_attr.length, 20); + assert!(ct_attr.is_nested()); + assert_eq!(ct_attr.attr_type, CTA_TUPLE_IP); + + let mut attr_data = [0u8; 20]; + ct_attr.emit(&mut attr_data); + assert_eq!(attr_data, DATA[..20]) + } +} diff --git a/src/ctnetlink/nlas/flow/nla.rs b/src/ctnetlink/nlas/flow/nla.rs new file mode 100644 index 0000000..9d72fa9 --- /dev/null +++ b/src/ctnetlink/nlas/flow/nla.rs @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT + +use byteorder::{BigEndian, ByteOrder}; +use netlink_packet_utils::{ + nla::{DefaultNla, Nla, NlaBuffer, NLA_F_NESTED}, + Emitable, Parseable, +}; + +use crate::constants::{ + CTA_ID, CTA_MARK, CTA_PROTOINFO, CTA_STATUS, CTA_TIMEOUT, CTA_TUPLE_ORIG, + CTA_TUPLE_REPLY, CTA_USE, +}; + +use super::{ + ip_tuple::TupleNla, protocol_info::ProtocolInfo, status::ConnectionStatus, +}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum FlowNla { + Orig(Vec), + Reply(Vec), + Status(ConnectionStatus), + ProtocolInfo(ProtocolInfo), + Timeout(u32), + Mark(u32), + Use(u32), + Id(u32), + Other(DefaultNla), +} + +impl Nla for FlowNla { + fn value_len(&self) -> usize { + match self { + FlowNla::Orig(attrs) => { + attrs.iter().fold(0, |l, attr| l + attr.buffer_len()) + } + FlowNla::Reply(attrs) => { + attrs.iter().fold(0, |l, attr| l + attr.buffer_len()) + } + FlowNla::Status(attr) => attr.value_len(), + FlowNla::ProtocolInfo(attr) => attr.value_len(), + FlowNla::Timeout(_) => 4, + FlowNla::Mark(_) => 4, + FlowNla::Use(_) => 4, + FlowNla::Id(_) => 4, + FlowNla::Other(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + FlowNla::Orig(_) => CTA_TUPLE_ORIG | NLA_F_NESTED, + FlowNla::Reply(_) => CTA_TUPLE_REPLY | NLA_F_NESTED, + FlowNla::Status(_) => CTA_STATUS, + FlowNla::ProtocolInfo(_) => CTA_PROTOINFO | NLA_F_NESTED, + FlowNla::Timeout(_) => CTA_TIMEOUT, + FlowNla::Mark(_) => CTA_MARK, + FlowNla::Use(_) => CTA_USE, + FlowNla::Id(_) => CTA_ID, + FlowNla::Other(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + FlowNla::Orig(attrs) => { + attrs.as_slice().emit(buffer); + } + FlowNla::Reply(attrs) => { + attrs.as_slice().emit(buffer); + } + FlowNla::Status(status) => status.emit_value(buffer), + FlowNla::ProtocolInfo(info) => info.emit_value(buffer), + FlowNla::Timeout(val) => BigEndian::write_u32(buffer, *val), + FlowNla::Mark(val) => BigEndian::write_u32(buffer, *val), + FlowNla::Use(val) => BigEndian::write_u32(buffer, *val), + FlowNla::Id(val) => BigEndian::write_u32(buffer, *val), + FlowNla::Other(attr) => attr.emit_value(buffer), + } + } +} + +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for FlowNla +{ + fn parse( + buf: &NlaBuffer<&'buffer T>, + ) -> Result { + let kind = buf.kind(); + let payload = buf.value(); + let nla = match kind { + CTA_TUPLE_ORIG => FlowNla::Orig({ + let b = NlaBuffer::new(payload); + let ip = TupleNla::parse(&b)?; + let b = NlaBuffer::new(&payload[ip.buffer_len()..]); + let proto = TupleNla::parse(&b)?; + vec![ip, proto] + }), + CTA_TUPLE_REPLY => FlowNla::Reply({ + let b = NlaBuffer::new(payload); + let ip = TupleNla::parse(&b)?; + let b = NlaBuffer::new(&payload[ip.buffer_len()..]); + let proto = TupleNla::parse(&b)?; + vec![ip, proto] + }), + CTA_STATUS => FlowNla::Status({ + ConnectionStatus::from(BigEndian::read_u32(payload)) + }), + CTA_PROTOINFO => { + FlowNla::ProtocolInfo(ProtocolInfo::parse_from_bytes(payload)?) + } + CTA_TIMEOUT => FlowNla::Timeout(BigEndian::read_u32(payload)), + CTA_MARK => FlowNla::Mark(BigEndian::read_u32(payload)), + CTA_USE => FlowNla::Use(BigEndian::read_u32(payload)), + CTA_ID => FlowNla::Id(BigEndian::read_u32(payload)), + _ => FlowNla::Other(DefaultNla::parse(buf)?), + }; + Ok(nla) + } +} diff --git a/src/ctnetlink/nlas/flow/protocol_info.rs b/src/ctnetlink/nlas/flow/protocol_info.rs new file mode 100644 index 0000000..849088b --- /dev/null +++ b/src/ctnetlink/nlas/flow/protocol_info.rs @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: MIT + +use std::convert::TryFrom; + +use byteorder::{ByteOrder, NativeEndian}; +use netlink_packet_utils::{ + nla::{Nla, NlaBuffer, NLA_F_NESTED}, + DecodeError, Emitable, Parseable, +}; + +use crate::{ + constants::{ + CTA_PROTOINFO_DCCP, CTA_PROTOINFO_SCTP, CTA_PROTOINFO_TCP, + CTA_PROTOINFO_TCP_FLAGS_ORIGINAL, CTA_PROTOINFO_TCP_FLAGS_REPLY, + CTA_PROTOINFO_TCP_STATE, CTA_PROTOINFO_TCP_WSCALE_ORIGINAL, + CTA_PROTOINFO_TCP_WSCALE_REPLY, CTA_PROTOINFO_UNSPEC, + }, + ctnetlink::nlas::flow::CtAttrBuilder, +}; + +use super::CtAttr; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum ProtocolInfo { + Tcp(ProtocolInfoTcp), + Dccp(CtAttr), + Sctp(CtAttr), + Other(CtAttr), +} + +impl ProtocolInfo { + pub(super) fn parse_from_bytes( + buf: &[u8], + ) -> Result { + let b = NlaBuffer::new(buf); + ProtocolInfo::parse(&b) + } +} + +impl Nla for ProtocolInfo { + fn value_len(&self) -> usize { + match self { + ProtocolInfo::Tcp(info) => info.buffer_len(), + ProtocolInfo::Dccp(attr) => attr.buffer_len(), + ProtocolInfo::Sctp(attr) => attr.buffer_len(), + ProtocolInfo::Other(attr) => attr.buffer_len(), + } + } + + fn kind(&self) -> u16 { + match self { + ProtocolInfo::Tcp(_) => CTA_PROTOINFO_TCP | NLA_F_NESTED, + ProtocolInfo::Dccp(_) => CTA_PROTOINFO_DCCP | NLA_F_NESTED, + ProtocolInfo::Sctp(_) => CTA_PROTOINFO_SCTP | NLA_F_NESTED, + ProtocolInfo::Other(_) => CTA_PROTOINFO_UNSPEC, + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + ProtocolInfo::Tcp(info) => info.emit(buffer), + ProtocolInfo::Dccp(attr) => attr.emit(buffer), + ProtocolInfo::Sctp(attr) => attr.emit(buffer), + ProtocolInfo::Other(attr) => attr.emit(buffer), + } + } +} + +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for ProtocolInfo +{ + fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { + let attr = CtAttr::parse(buf)?; + + match attr.attr_type { + CTA_PROTOINFO_TCP => { + Ok(ProtocolInfo::Tcp(ProtocolInfoTcp::try_from(attr)?)) + } + CTA_PROTOINFO_DCCP => Ok(ProtocolInfo::Dccp(attr)), + CTA_PROTOINFO_SCTP => Ok(ProtocolInfo::Sctp(attr)), + _ => Ok(ProtocolInfo::Other(attr)), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] +pub struct ProtocolInfoTcp { + state: u8, + wscale_original: u8, + wscale_reply: u8, + flgas_original: u16, + flags_reply: u16, +} + +impl Nla for ProtocolInfoTcp { + fn value_len(&self) -> usize { + 40 + } + + fn kind(&self) -> u16 { + CTA_PROTOINFO_TCP | NLA_F_NESTED + } + + fn emit_value(&self, buffer: &mut [u8]) { + let mut flag_orig = [0u8; 2]; + let mut flag_reply = [0u8; 2]; + NativeEndian::write_u16(&mut flag_orig, self.flgas_original); + NativeEndian::write_u16(&mut flag_reply, self.flags_reply); + + let info = CtAttrBuilder::new(CTA_PROTOINFO_TCP) + .nested_attr( + CtAttrBuilder::new(CTA_PROTOINFO_TCP_STATE) + .value(vec![self.state].as_ref()) + .build(), + ) + .nested_attr( + CtAttrBuilder::new(CTA_PROTOINFO_TCP_WSCALE_ORIGINAL) + .value(vec![self.wscale_original].as_ref()) + .build(), + ) + .nested_attr( + CtAttrBuilder::new(CTA_PROTOINFO_TCP_WSCALE_REPLY) + .value(vec![self.wscale_reply].as_ref()) + .build(), + ) + .nested_attr( + CtAttrBuilder::new(CTA_PROTOINFO_TCP_FLAGS_ORIGINAL) + .value(&flag_orig) + .build(), + ) + .nested_attr( + CtAttrBuilder::new(CTA_PROTOINFO_TCP_FLAGS_REPLY) + .value(&flag_reply) + .build(), + ) + .build(); + info.emit_value(buffer); + } +} + +impl TryFrom for ProtocolInfoTcp { + type Error = DecodeError; + + fn try_from(attr: CtAttr) -> Result { + if let Some(attrs) = attr.nested { + let mut info = ProtocolInfoTcp::default(); + for attr in attrs.iter() { + match attr.attr_type { + CTA_PROTOINFO_TCP_STATE => { + if let Some(v) = &attr.value { + if v.len() != 1 { + return Err(DecodeError::from( + "invalid CTA_PROTOINFO_TCP_STATE value", + )); + } + info.state = v[0]; + } + } + CTA_PROTOINFO_TCP_WSCALE_ORIGINAL => { + if let Some(v) = &attr.value { + if v.len() != 1 { + return Err(DecodeError::from("invalid CTA_PROTOINFO_TCP_WSCALE_ORIGINAL value")); + } + info.wscale_original = v[0]; + } + } + CTA_PROTOINFO_TCP_WSCALE_REPLY => { + if let Some(v) = &attr.value { + if v.len() != 1 { + return Err(DecodeError::from("invalid CTA_PROTOINFO_TCP_WSCALE_REPLY value")); + } + info.wscale_reply = v[0]; + } + } + CTA_PROTOINFO_TCP_FLAGS_ORIGINAL => { + if let Some(v) = &attr.value { + info.flgas_original = NativeEndian::read_u16(v); + } + } + CTA_PROTOINFO_TCP_FLAGS_REPLY => { + if let Some(v) = &attr.value { + info.flags_reply = NativeEndian::read_u16(v); + } + } + _ => {} + } + } + Ok(info) + } else { + Err(DecodeError::from( + "CTA_PROTOINFO_TCP must have nested attributes", + )) + } + } +} + +#[cfg(test)] +mod tests { + use netlink_packet_utils::{ + nla::{NlaBuffer, NLA_HEADER_SIZE}, + Emitable, Parseable, + }; + + use super::ProtocolInfo; + const DATA: [u8; 44] = [ + 44, 0, 1, 128, 5, 0, 1, 0, 3, 0, 0, 0, 5, 0, 2, 0, 7, 0, 0, 0, 5, 0, 3, + 0, 7, 0, 0, 0, 6, 0, 4, 0, 35, 0, 0, 0, 6, 0, 5, 0, 35, 0, 0, 0, + ]; + + #[test] + fn test_protocol_info_parse() { + let buf = NlaBuffer::new(&DATA); + let info = ProtocolInfo::parse(&buf).unwrap(); + if let ProtocolInfo::Tcp(info) = info { + assert_eq!(info.state, 3); + assert_eq!(info.wscale_original, 7); + assert_eq!(info.wscale_reply, 7); + assert_eq!(info.flgas_original, 35); + assert_eq!(info.flags_reply, 35); + } else { + panic!("invalid protocol info") + } + } + + #[test] + fn test_protocol_info_emit() { + let buf = NlaBuffer::new(&DATA); + let info = ProtocolInfo::parse(&buf).unwrap(); + if let ProtocolInfo::Tcp(info) = info { + assert_eq!(info.state, 3); + assert_eq!(info.wscale_original, 7); + assert_eq!(info.wscale_reply, 7); + assert_eq!(info.flgas_original, 35); + assert_eq!(info.flags_reply, 35); + } else { + panic!("invalid protocol info") + } + + let mut attr_data = [0u8; 48]; + info.emit(&mut attr_data); + assert_eq!(attr_data[NLA_HEADER_SIZE..], DATA); + } +} diff --git a/src/ctnetlink/nlas/flow/status.rs b/src/ctnetlink/nlas/flow/status.rs new file mode 100644 index 0000000..f1a8604 --- /dev/null +++ b/src/ctnetlink/nlas/flow/status.rs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT + +use byteorder::{BigEndian, ByteOrder}; +use netlink_packet_utils::nla::Nla; + +use crate::constants::CTA_STATUS; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum ConnectionStatusFlag { + Offload = 1 << 15, + Helper = 1 << 14, + Untracked = 1 << 13, + Template = 1 << 12, + FixedTimeout = 1 << 11, + Dying = 1 << 10, + DestinationNATDone = 1 << 9, + SourceNATDone = 1 << 8, + SequenceAdjust = 1 << 7, + DestinationNAT = 1 << 6, + SourceNAT = 1 << 5, + Confirmed = 1 << 4, + Assured = 1 << 3, + SeenReply = 1 << 2, + Expected = 1, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] +pub struct ConnectionStatus { + inner: u32, +} + +impl ConnectionStatus { + pub fn get(&self) -> u32 { + self.inner + } + + pub fn set(&mut self, flag: ConnectionStatusFlag) { + self.inner += flag as u32 + } + + pub fn is_set(&self, flag: ConnectionStatusFlag) -> bool { + self.inner & flag as u32 == flag as u32 + } +} + +impl From for ConnectionStatus { + fn from(value: u32) -> Self { + Self { inner: value } + } +} + +impl From for ConnectionStatus { + fn from(value: ConnectionStatusFlag) -> Self { + Self { + inner: value as u32, + } + } +} + +impl Nla for ConnectionStatus { + fn value_len(&self) -> usize { + 4 + } + + fn kind(&self) -> u16 { + CTA_STATUS + } + + fn emit_value(&self, buffer: &mut [u8]) { + BigEndian::write_u32(buffer, self.inner); + } +} diff --git a/src/ctnetlink/nlas/mod.rs b/src/ctnetlink/nlas/mod.rs new file mode 100644 index 0000000..8a40ae2 --- /dev/null +++ b/src/ctnetlink/nlas/mod.rs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: MIT + +pub mod flow; diff --git a/src/lib.rs b/src/lib.rs index aca295b..a3341d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,4 +4,5 @@ pub(crate) mod buffer; pub mod constants; mod message; pub use message::{NetfilterHeader, NetfilterMessage, NetfilterMessageInner}; +pub mod ctnetlink; pub mod nflog; diff --git a/src/message.rs b/src/message.rs index 8d41eb2..4da50f5 100644 --- a/src/message.rs +++ b/src/message.rs @@ -8,7 +8,10 @@ use netlink_packet_utils::{ ParseableParametrized, }; -use crate::{buffer::NetfilterBuffer, nflog::NfLogMessage}; +use crate::{ + buffer::NetfilterBuffer, ctnetlink::message::CtNetlinkMessage, + nflog::NfLogMessage, +}; pub const NETFILTER_HEADER_LEN: usize = 4; @@ -62,6 +65,7 @@ impl> Parseable> for NetfilterHeader { #[derive(Debug, PartialEq, Eq, Clone)] pub enum NetfilterMessageInner { NfLog(NfLogMessage), + CtNetlink(CtNetlinkMessage), Other { subsys: u8, message_type: u8, @@ -75,10 +79,17 @@ impl From for NetfilterMessageInner { } } +impl From for NetfilterMessageInner { + fn from(message: CtNetlinkMessage) -> Self { + Self::CtNetlink(message) + } +} + impl Emitable for NetfilterMessageInner { fn buffer_len(&self) -> usize { match self { NetfilterMessageInner::NfLog(message) => message.buffer_len(), + NetfilterMessageInner::CtNetlink(message) => message.buffer_len(), NetfilterMessageInner::Other { nlas, .. } => { nlas.as_slice().buffer_len() } @@ -88,6 +99,7 @@ impl Emitable for NetfilterMessageInner { fn emit(&self, buffer: &mut [u8]) { match self { NetfilterMessageInner::NfLog(message) => message.emit(buffer), + NetfilterMessageInner::CtNetlink(message) => message.emit(buffer), NetfilterMessageInner::Other { nlas, .. } => { nlas.as_slice().emit(buffer) } @@ -115,6 +127,7 @@ impl NetfilterMessage { pub fn subsys(&self) -> u8 { match self.inner { NetfilterMessageInner::NfLog(_) => NfLogMessage::SUBSYS, + NetfilterMessageInner::CtNetlink(_) => CtNetlinkMessage::SUBSYS, NetfilterMessageInner::Other { subsys, .. } => subsys, } } @@ -122,6 +135,9 @@ impl NetfilterMessage { pub fn message_type(&self) -> u8 { match self.inner { NetfilterMessageInner::NfLog(ref message) => message.message_type(), + NetfilterMessageInner::CtNetlink(ref message) => { + message.message_type() + } NetfilterMessageInner::Other { message_type, .. } => message_type, } } From c46bd62652718c980c306c045f7a2f77898bc90e Mon Sep 17 00:00:00 2001 From: terassyi Date: Fri, 20 Dec 2024 23:46:21 +0900 Subject: [PATCH 02/13] Support IPCTNL_MSG_CT_DELETE Signed-off-by: terassyi --- examples/ctnetlink.rs | 53 +++++++++++++++++++++++++++++++++++++++- src/ctnetlink/message.rs | 15 ++++++++++-- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/examples/ctnetlink.rs b/examples/ctnetlink.rs index 854861a..599d14a 100644 --- a/examples/ctnetlink.rs +++ b/examples/ctnetlink.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT +use std::num::NonZero; + use netlink_packet_core::{ NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST, }; @@ -75,7 +77,7 @@ fn main() { // Get a specific conntrack entry let orig = orig.unwrap(); - let packet = get_request(AF_INET, 0, orig); + let packet = get_request(AF_INET, 0, orig.clone()); let mut buf = vec![0; packet.header.length as usize]; packet.serialize(&mut buf[..]); println!(">>> {:?}", packet); @@ -86,6 +88,37 @@ fn main() { let rx_packet = >::deserialize(bytes).unwrap(); println!("<<< packet_len={}\n{:?}", rx_packet.buffer_len(), rx_packet); + + // Delete one entry + let packet = delete_request(AF_INET, 0, orig.clone()); + let mut buf = vec![0; packet.header.length as usize]; + packet.serialize(&mut buf[..]); + println!(">>> {:?}", packet); + socket.send(&buf[..], 0).unwrap(); + + // Confirm the etntry is deleted + let packet = get_request(AF_INET, 0, orig.clone()); + let mut buf = vec![0; packet.header.length as usize]; + packet.serialize(&mut buf[..]); + println!(">>> {:?}", packet); + socket.send(&buf[..], 0).unwrap(); + + let size = socket.recv(&mut &mut receive_buffer[..], 0).unwrap(); + let bytes = &receive_buffer[..size]; + let rx_packet = + >::deserialize(bytes).unwrap(); + println!("<<< packet_len={}\n{:?}", rx_packet.buffer_len(), rx_packet); + if let NetlinkPayload::Error(e) = rx_packet.payload { + if let Some(code) = e.code { + if NonZero::new(-2).unwrap().ne(&code) { + panic!("found the other error"); + } + } + } else { + panic!("NetlinkPayload::Error is expected"); + } + + println!(">>> An entry is deleted correctly") } fn list_request(family: u8, res_id: u16) -> NetlinkMessage { @@ -119,3 +152,21 @@ fn get_request( message.finalize(); message } + +fn delete_request( + family: u8, + res_id: u16, + tuple: Vec, +) -> NetlinkMessage { + let mut hdr = NetlinkHeader::default(); + hdr.flags = NLM_F_REQUEST; + let mut message = NetlinkMessage::new( + hdr, + NetlinkPayload::from(NetfilterMessage::new( + NetfilterHeader::new(family, NFNETLINK_V0, res_id), + CtNetlinkMessage::Delete(vec![FlowNla::Orig(tuple)]), + )), + ); + message.finalize(); + message +} diff --git a/src/ctnetlink/message.rs b/src/ctnetlink/message.rs index 739e1ff..d242977 100644 --- a/src/ctnetlink/message.rs +++ b/src/ctnetlink/message.rs @@ -6,7 +6,10 @@ use netlink_packet_utils::{ use crate::{ buffer::NetfilterBuffer, - constants::{IPCTNL_MSG_CT_GET, IPCTNL_MSG_CT_NEW, NFNL_SUBSYS_CTNETLINK}, + constants::{ + IPCTNL_MSG_CT_DELETE, IPCTNL_MSG_CT_GET, IPCTNL_MSG_CT_NEW, + NFNL_SUBSYS_CTNETLINK, + }, }; use super::nlas::flow::nla::FlowNla; @@ -15,7 +18,7 @@ use super::nlas::flow::nla::FlowNla; pub enum CtNetlinkMessage { New(Vec), Get(Option>), - // Delete, + Delete(Vec), // GetCrtZero, // GetStatsCPU, // GetStats, @@ -34,6 +37,7 @@ impl CtNetlinkMessage { match self { CtNetlinkMessage::New(_) => IPCTNL_MSG_CT_NEW, CtNetlinkMessage::Get(_) => IPCTNL_MSG_CT_GET, + CtNetlinkMessage::Delete(_) => IPCTNL_MSG_CT_DELETE, CtNetlinkMessage::Other { message_type, .. } => *message_type, } } @@ -47,6 +51,7 @@ impl Emitable for CtNetlinkMessage { Some(nlas) => nlas.as_slice().buffer_len(), None => 0, }, + CtNetlinkMessage::Delete(nlas) => nlas.as_slice().buffer_len(), CtNetlinkMessage::Other { nlas, .. } => { nlas.as_slice().buffer_len() } @@ -61,6 +66,7 @@ impl Emitable for CtNetlinkMessage { nlas.as_slice().emit(buffer); } } + CtNetlinkMessage::Delete(nlas) => nlas.as_slice().emit(buffer), CtNetlinkMessage::Other { nlas, .. } => { nlas.as_slice().emit(buffer) } @@ -90,6 +96,11 @@ impl<'a, T: AsRef<[u8]> + ?Sized> CtNetlinkMessage::Get(Some(nlas)) } } + IPCTNL_MSG_CT_DELETE => { + let nlas = + buf.parse_all_nlas(|nla_buf| FlowNla::parse(&nla_buf))?; + CtNetlinkMessage::Delete(nlas) + } _ => CtNetlinkMessage::Other { message_type, nlas: buf.default_nlas()?, From 050bd989c7e08bfe3da81e9129866d3f98eafb85 Mon Sep 17 00:00:00 2001 From: terassyi Date: Sat, 21 Dec 2024 12:22:57 +0900 Subject: [PATCH 03/13] Fix to parse get message Signed-off-by: terassyi --- src/ctnetlink/message.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ctnetlink/message.rs b/src/ctnetlink/message.rs index d242977..cdb5280 100644 --- a/src/ctnetlink/message.rs +++ b/src/ctnetlink/message.rs @@ -88,7 +88,7 @@ impl<'a, T: AsRef<[u8]> + ?Sized> CtNetlinkMessage::New(nlas) } IPCTNL_MSG_CT_GET => { - if !buf.payload().is_empty() { + if buf.payload().is_empty() { CtNetlinkMessage::Get(None) } else { let nlas = From af15691c1e38234ecde28faa89a687062835478d Mon Sep 17 00:00:00 2001 From: terassyi Date: Sat, 21 Dec 2024 12:31:14 +0900 Subject: [PATCH 04/13] Support IPCTNL_MSG_CT_GET_STATS Signed-off-by: terassyi --- examples/ctnetlink.rs | 28 +++- src/ctnetlink/message.rs | 27 ++- src/ctnetlink/nlas/ct_attr.rs | 200 +++++++++++++++++++++++ src/ctnetlink/nlas/flow/ip_tuple.rs | 13 +- src/ctnetlink/nlas/flow/mod.rs | 199 ---------------------- src/ctnetlink/nlas/flow/protocol_info.rs | 4 +- src/ctnetlink/nlas/mod.rs | 2 + src/ctnetlink/nlas/stat/mod.rs | 3 + src/ctnetlink/nlas/stat/nla.rs | 59 +++++++ 9 files changed, 322 insertions(+), 213 deletions(-) create mode 100644 src/ctnetlink/nlas/ct_attr.rs create mode 100644 src/ctnetlink/nlas/stat/mod.rs create mode 100644 src/ctnetlink/nlas/stat/nla.rs diff --git a/examples/ctnetlink.rs b/examples/ctnetlink.rs index 599d14a..eba3bca 100644 --- a/examples/ctnetlink.rs +++ b/examples/ctnetlink.rs @@ -118,7 +118,19 @@ fn main() { panic!("NetlinkPayload::Error is expected"); } - println!(">>> An entry is deleted correctly") + println!(">>> An entry is deleted correctly"); + + // stat + let packet = stat_request(AF_INET, 0); + let mut buf = vec![0; packet.header.length as usize]; + packet.serialize(&mut buf); + println!(">>> {:?}", packet); + socket.send(&buf[..], 0).unwrap(); + let size = socket.recv(&mut &mut receive_buffer[..], 0).unwrap(); + let bytes = &receive_buffer[..size]; + let rx_packet = + >::deserialize(bytes).unwrap(); + println!("<<< packet_len={}\n{:?}", rx_packet.buffer_len(), rx_packet); } fn list_request(family: u8, res_id: u16) -> NetlinkMessage { @@ -170,3 +182,17 @@ fn delete_request( message.finalize(); message } + +fn stat_request(family: u8, res_id: u16) -> NetlinkMessage { + let mut hdr = NetlinkHeader::default(); + hdr.flags = NLM_F_REQUEST; + let mut message = NetlinkMessage::new( + hdr, + NetlinkPayload::from(NetfilterMessage::new( + NetfilterHeader::new(family, NFNETLINK_V0, res_id), + CtNetlinkMessage::GetStats(None), + )), + ); + message.finalize(); + message +} diff --git a/src/ctnetlink/message.rs b/src/ctnetlink/message.rs index cdb5280..bd840f6 100644 --- a/src/ctnetlink/message.rs +++ b/src/ctnetlink/message.rs @@ -7,12 +7,12 @@ use netlink_packet_utils::{ use crate::{ buffer::NetfilterBuffer, constants::{ - IPCTNL_MSG_CT_DELETE, IPCTNL_MSG_CT_GET, IPCTNL_MSG_CT_NEW, - NFNL_SUBSYS_CTNETLINK, + IPCTNL_MSG_CT_DELETE, IPCTNL_MSG_CT_GET, IPCTNL_MSG_CT_GET_STATS, + IPCTNL_MSG_CT_NEW, NFNL_SUBSYS_CTNETLINK, }, }; -use super::nlas::flow::nla::FlowNla; +use super::nlas::{flow::nla::FlowNla, stat::nla::StatNla}; #[derive(Debug, PartialEq, Eq, Clone)] pub enum CtNetlinkMessage { @@ -21,7 +21,7 @@ pub enum CtNetlinkMessage { Delete(Vec), // GetCrtZero, // GetStatsCPU, - // GetStats, + GetStats(Option>), // GetDying, // GetUnconfirmed, Other { @@ -38,6 +38,7 @@ impl CtNetlinkMessage { CtNetlinkMessage::New(_) => IPCTNL_MSG_CT_NEW, CtNetlinkMessage::Get(_) => IPCTNL_MSG_CT_GET, CtNetlinkMessage::Delete(_) => IPCTNL_MSG_CT_DELETE, + CtNetlinkMessage::GetStats(_) => IPCTNL_MSG_CT_GET_STATS, CtNetlinkMessage::Other { message_type, .. } => *message_type, } } @@ -52,6 +53,10 @@ impl Emitable for CtNetlinkMessage { None => 0, }, CtNetlinkMessage::Delete(nlas) => nlas.as_slice().buffer_len(), + CtNetlinkMessage::GetStats(nlas) => match nlas { + Some(nlas) => nlas.as_slice().buffer_len(), + None => 0, + }, CtNetlinkMessage::Other { nlas, .. } => { nlas.as_slice().buffer_len() } @@ -67,6 +72,11 @@ impl Emitable for CtNetlinkMessage { } } CtNetlinkMessage::Delete(nlas) => nlas.as_slice().emit(buffer), + CtNetlinkMessage::GetStats(nlas) => { + if let Some(nlas) = nlas { + nlas.as_slice().emit(buffer) + } + } CtNetlinkMessage::Other { nlas, .. } => { nlas.as_slice().emit(buffer) } @@ -101,6 +111,15 @@ impl<'a, T: AsRef<[u8]> + ?Sized> buf.parse_all_nlas(|nla_buf| FlowNla::parse(&nla_buf))?; CtNetlinkMessage::Delete(nlas) } + IPCTNL_MSG_CT_GET_STATS => { + if buf.payload().is_empty() { + CtNetlinkMessage::GetStats(None) + } else { + let nlas = + buf.parse_all_nlas(|nla_buf| StatNla::parse(&nla_buf))?; + CtNetlinkMessage::GetStats(Some(nlas)) + } + } _ => CtNetlinkMessage::Other { message_type, nlas: buf.default_nlas()?, diff --git a/src/ctnetlink/nlas/ct_attr.rs b/src/ctnetlink/nlas/ct_attr.rs new file mode 100644 index 0000000..c124462 --- /dev/null +++ b/src/ctnetlink/nlas/ct_attr.rs @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_utils::{ + nla::{Nla, NlaBuffer, NLA_F_NESTED, NLA_HEADER_SIZE}, + DecodeError, Emitable, Parseable, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CtAttr { + pub nested: Option>, + pub attr_type: u16, + pub length: u16, + pub value: Option>, +} + +impl Nla for CtAttr { + fn value_len(&self) -> usize { + (self.length as usize) - NLA_HEADER_SIZE + } + + fn kind(&self) -> u16 { + if self.is_nested() { + self.attr_type | NLA_F_NESTED + } else { + self.attr_type + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + if let Some(attrs) = &self.nested { + let mut attrs_buf = vec![]; + for attr in attrs.iter() { + let l = if attr.length % 4 != 0 { + attr.length + 4 - (attr.length % 4) + } else { + attr.length + } as usize; + let mut buf = vec![0u8; l]; + attr.emit(&mut buf); + attrs_buf.append(&mut buf); + } + buffer[..attrs_buf.len()].copy_from_slice(&attrs_buf); + } else if let Some(value) = &self.value { + buffer[..value.len()].copy_from_slice(value); + } + } +} + +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for CtAttr +{ + fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { + let length = buf.length(); + let is_nested = buf.nested_flag(); + let attr_type = buf.kind(); + let value_l = (length as usize) - NLA_HEADER_SIZE; + let value = buf.value(); + if is_nested { + let mut nested_attrs = vec![]; + let mut read = 0; + while value_l > read { + let nla_buf = NlaBuffer::new(&value[read..]); + let attr = Self::parse(&nla_buf)?; + read += attr.length as usize; + if attr.length % 4 != 0 { + read += 4 - (attr.length as usize % 4); + } + nested_attrs.push(attr); + } + Ok(CtAttr { + nested: Some(nested_attrs), + length, + attr_type, + value: None, + }) + } else { + Ok(CtAttr { + nested: None, + attr_type, + // padding bytes are not included + length, + value: Some(value[..value_l].to_vec()), + }) + } + } +} + +impl CtAttr { + pub fn is_nested(&self) -> bool { + self.nested.is_some() + } +} + +#[derive(Debug, Clone)] +pub struct CtAttrBuilder { + nested: Option>, + attr_type: u16, + value: Option>, + length: u16, +} + +impl CtAttrBuilder { + pub fn new(attr_type: u16) -> CtAttrBuilder { + CtAttrBuilder { + nested: None, + attr_type, + value: None, + length: 0, + } + } + pub fn nested_attr(mut self, attr: CtAttr) -> Self { + self.length += attr.length; + if attr.length % 4 != 0 { + self.length += 4 - (attr.length % 4); + } + if let Some(ref mut nested) = self.nested { + nested.push(attr); + } else { + self.nested = Some(vec![attr]); + } + self.attr_type |= NLA_F_NESTED; + self + } + + pub fn value(mut self, v: &[u8]) -> Self { + self.length += v.len() as u16; + self.value = Some(v.to_vec()); + self + } + + pub fn build(&self) -> CtAttr { + CtAttr { + nested: self.nested.clone(), + attr_type: self.attr_type, + length: self.length + NLA_HEADER_SIZE as u16, + value: self.value.clone(), + } + } +} + +#[cfg(test)] +mod tests { + use netlink_packet_utils::{nla::NlaBuffer, Emitable, Parseable}; + + use crate::{ + constants::{ + CTA_IP_V4_DST, CTA_IP_V4_SRC, CTA_PROTO_DST_PORT, CTA_PROTO_NUM, + CTA_PROTO_SRC_PORT, CTA_TUPLE_IP, CTA_TUPLE_PROTO, + }, + ctnetlink::nlas::ct_attr::CtAttr, + }; + const DATA: [u8; 48] = [ + 20, 0, 1, 128, 8, 0, 1, 0, 1, 2, 3, 4, 8, 0, 2, 0, 1, 2, 3, 4, 28, 0, + 2, 128, 5, 0, 1, 0, 17, 0, 0, 0, 6, 0, 2, 0, 220, 210, 0, 0, 6, 0, 3, + 0, 7, 108, 0, 0, + ]; + + #[test] + fn test_ct_attr_parse() { + let buf = NlaBuffer::new(&DATA); + // first + let ct_attr = CtAttr::parse(&buf).unwrap(); + assert_eq!(ct_attr.length, 20); + assert!(ct_attr.is_nested()); + assert_eq!(ct_attr.attr_type, CTA_TUPLE_IP); + + let nested_attrs = ct_attr.nested.unwrap(); + assert_eq!(nested_attrs.len(), 2); + assert_eq!(nested_attrs[0].attr_type, CTA_IP_V4_SRC); + assert_eq!(nested_attrs[0].length, 8); + + assert_eq!(nested_attrs[1].attr_type, CTA_IP_V4_DST); + assert_eq!(nested_attrs[1].length, 8); + + // second + let buf = NlaBuffer::new(&DATA[(ct_attr.length as usize)..]); + let ct_attr = CtAttr::parse(&buf).unwrap(); + assert_eq!(ct_attr.length, 28); + assert!(ct_attr.is_nested()); + assert_eq!(ct_attr.attr_type, CTA_TUPLE_PROTO); + let nested_attr = ct_attr.nested.unwrap(); + assert_eq!(nested_attr.len(), 3); + assert_eq!(nested_attr[0].attr_type, CTA_PROTO_NUM); + assert_eq!(nested_attr[1].attr_type, CTA_PROTO_SRC_PORT); + assert_eq!(nested_attr[2].attr_type, CTA_PROTO_DST_PORT); + } + + #[test] + fn test_ct_attr_emit() { + let buf = NlaBuffer::new(&DATA); + let ct_attr = CtAttr::parse(&buf).unwrap(); + assert_eq!(ct_attr.length, 20); + assert!(ct_attr.is_nested()); + assert_eq!(ct_attr.attr_type, CTA_TUPLE_IP); + + let mut attr_data = [0u8; 20]; + ct_attr.emit(&mut attr_data); + assert_eq!(attr_data, DATA[..20]) + } +} diff --git a/src/ctnetlink/nlas/flow/ip_tuple.rs b/src/ctnetlink/nlas/flow/ip_tuple.rs index 01f2965..89995a9 100644 --- a/src/ctnetlink/nlas/flow/ip_tuple.rs +++ b/src/ctnetlink/nlas/flow/ip_tuple.rs @@ -9,14 +9,15 @@ use netlink_packet_utils::{ DecodeError, Parseable, }; -use crate::constants::{ - CTA_IP_V4_DST, CTA_IP_V4_SRC, CTA_IP_V6_DST, CTA_IP_V6_SRC, - CTA_PROTO_DST_PORT, CTA_PROTO_NUM, CTA_PROTO_SRC_PORT, CTA_TUPLE_IP, - CTA_TUPLE_PROTO, +use crate::{ + constants::{ + CTA_IP_V4_DST, CTA_IP_V4_SRC, CTA_IP_V6_DST, CTA_IP_V6_SRC, + CTA_PROTO_DST_PORT, CTA_PROTO_NUM, CTA_PROTO_SRC_PORT, CTA_TUPLE_IP, + CTA_TUPLE_PROTO, + }, + ctnetlink::nlas::ct_attr::{CtAttr, CtAttrBuilder}, }; -use super::{CtAttr, CtAttrBuilder}; - #[derive(Debug, PartialEq, Eq, Clone)] pub enum TupleNla { Ip(IpTuple), diff --git a/src/ctnetlink/nlas/flow/mod.rs b/src/ctnetlink/nlas/flow/mod.rs index 2617408..63e4da8 100644 --- a/src/ctnetlink/nlas/flow/mod.rs +++ b/src/ctnetlink/nlas/flow/mod.rs @@ -1,205 +1,6 @@ // SPDX-License-Identifier: MIT -use netlink_packet_utils::{ - nla::{Nla, NlaBuffer, NLA_F_NESTED, NLA_HEADER_SIZE}, - DecodeError, Emitable, Parseable, -}; - pub mod ip_tuple; pub mod nla; pub mod protocol_info; pub mod status; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct CtAttr { - pub nested: Option>, - pub attr_type: u16, - pub length: u16, - pub value: Option>, -} - -impl Nla for CtAttr { - fn value_len(&self) -> usize { - (self.length as usize) - NLA_HEADER_SIZE - } - - fn kind(&self) -> u16 { - if self.is_nested() { - self.attr_type | NLA_F_NESTED - } else { - self.attr_type - } - } - - fn emit_value(&self, buffer: &mut [u8]) { - if let Some(attrs) = &self.nested { - let mut attrs_buf = vec![]; - for attr in attrs.iter() { - let l = if attr.length % 4 != 0 { - attr.length + 4 - (attr.length % 4) - } else { - attr.length - } as usize; - let mut buf = vec![0u8; l]; - attr.emit(&mut buf); - attrs_buf.append(&mut buf); - } - buffer[..attrs_buf.len()].copy_from_slice(&attrs_buf); - } else if let Some(value) = &self.value { - buffer[..value.len()].copy_from_slice(value); - } - } -} - -impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> - for CtAttr -{ - fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { - let length = buf.length(); - let is_nested = buf.nested_flag(); - let attr_type = buf.kind(); - let value_l = (length as usize) - NLA_HEADER_SIZE; - let value = buf.value(); - if is_nested { - let mut nested_attrs = vec![]; - let mut read = 0; - while value_l > read { - let nla_buf = NlaBuffer::new(&value[read..]); - let attr = Self::parse(&nla_buf)?; - read += attr.length as usize; - if attr.length % 4 != 0 { - read += 4 - (attr.length as usize % 4); - } - nested_attrs.push(attr); - } - Ok(CtAttr { - nested: Some(nested_attrs), - length, - attr_type, - value: None, - }) - } else { - Ok(CtAttr { - nested: None, - attr_type, - // padding bytes are not included - length, - value: Some(value[..value_l].to_vec()), - }) - } - } -} - -impl CtAttr { - pub fn is_nested(&self) -> bool { - self.nested.is_some() - } -} - -#[derive(Debug, Clone)] -pub struct CtAttrBuilder { - nested: Option>, - attr_type: u16, - value: Option>, - length: u16, -} - -impl CtAttrBuilder { - pub fn new(attr_type: u16) -> CtAttrBuilder { - CtAttrBuilder { - nested: None, - attr_type, - value: None, - length: 0, - } - } - pub fn nested_attr(mut self, attr: CtAttr) -> Self { - self.length += attr.length; - if attr.length % 4 != 0 { - self.length += 4 - (attr.length % 4); - } - if let Some(ref mut nested) = self.nested { - nested.push(attr); - } else { - self.nested = Some(vec![attr]); - } - self.attr_type |= NLA_F_NESTED; - self - } - - pub fn value(mut self, v: &[u8]) -> Self { - self.length += v.len() as u16; - self.value = Some(v.to_vec()); - self - } - - pub fn build(&self) -> CtAttr { - CtAttr { - nested: self.nested.clone(), - attr_type: self.attr_type, - length: self.length + NLA_HEADER_SIZE as u16, - value: self.value.clone(), - } - } -} - -#[cfg(test)] -mod tests { - use netlink_packet_utils::{nla::NlaBuffer, Emitable, Parseable}; - - use crate::{ - constants::{ - CTA_IP_V4_DST, CTA_IP_V4_SRC, CTA_PROTO_DST_PORT, CTA_PROTO_NUM, - CTA_PROTO_SRC_PORT, CTA_TUPLE_IP, CTA_TUPLE_PROTO, - }, - ctnetlink::nlas::flow::CtAttr, - }; - const DATA: [u8; 48] = [ - 20, 0, 1, 128, 8, 0, 1, 0, 1, 2, 3, 4, 8, 0, 2, 0, 1, 2, 3, 4, 28, 0, - 2, 128, 5, 0, 1, 0, 17, 0, 0, 0, 6, 0, 2, 0, 220, 210, 0, 0, 6, 0, 3, - 0, 7, 108, 0, 0, - ]; - - #[test] - fn test_ct_attr_parse() { - let buf = NlaBuffer::new(&DATA); - // first - let ct_attr = CtAttr::parse(&buf).unwrap(); - assert_eq!(ct_attr.length, 20); - assert!(ct_attr.is_nested()); - assert_eq!(ct_attr.attr_type, CTA_TUPLE_IP); - - let nested_attrs = ct_attr.nested.unwrap(); - assert_eq!(nested_attrs.len(), 2); - assert_eq!(nested_attrs[0].attr_type, CTA_IP_V4_SRC); - assert_eq!(nested_attrs[0].length, 8); - - assert_eq!(nested_attrs[1].attr_type, CTA_IP_V4_DST); - assert_eq!(nested_attrs[1].length, 8); - - // second - let buf = NlaBuffer::new(&DATA[(ct_attr.length as usize)..]); - let ct_attr = CtAttr::parse(&buf).unwrap(); - assert_eq!(ct_attr.length, 28); - assert!(ct_attr.is_nested()); - assert_eq!(ct_attr.attr_type, CTA_TUPLE_PROTO); - let nested_attr = ct_attr.nested.unwrap(); - assert_eq!(nested_attr.len(), 3); - assert_eq!(nested_attr[0].attr_type, CTA_PROTO_NUM); - assert_eq!(nested_attr[1].attr_type, CTA_PROTO_SRC_PORT); - assert_eq!(nested_attr[2].attr_type, CTA_PROTO_DST_PORT); - } - - #[test] - fn test_ct_attr_emit() { - let buf = NlaBuffer::new(&DATA); - let ct_attr = CtAttr::parse(&buf).unwrap(); - assert_eq!(ct_attr.length, 20); - assert!(ct_attr.is_nested()); - assert_eq!(ct_attr.attr_type, CTA_TUPLE_IP); - - let mut attr_data = [0u8; 20]; - ct_attr.emit(&mut attr_data); - assert_eq!(attr_data, DATA[..20]) - } -} diff --git a/src/ctnetlink/nlas/flow/protocol_info.rs b/src/ctnetlink/nlas/flow/protocol_info.rs index 849088b..31f9b32 100644 --- a/src/ctnetlink/nlas/flow/protocol_info.rs +++ b/src/ctnetlink/nlas/flow/protocol_info.rs @@ -15,11 +15,9 @@ use crate::{ CTA_PROTOINFO_TCP_STATE, CTA_PROTOINFO_TCP_WSCALE_ORIGINAL, CTA_PROTOINFO_TCP_WSCALE_REPLY, CTA_PROTOINFO_UNSPEC, }, - ctnetlink::nlas::flow::CtAttrBuilder, + ctnetlink::nlas::ct_attr::{CtAttr, CtAttrBuilder}, }; -use super::CtAttr; - #[derive(Debug, PartialEq, Eq, Clone)] pub enum ProtocolInfo { Tcp(ProtocolInfoTcp), diff --git a/src/ctnetlink/nlas/mod.rs b/src/ctnetlink/nlas/mod.rs index 8a40ae2..bc3f58f 100644 --- a/src/ctnetlink/nlas/mod.rs +++ b/src/ctnetlink/nlas/mod.rs @@ -1,3 +1,5 @@ // SPDX-License-Identifier: MIT +mod ct_attr; pub mod flow; +pub mod stat; diff --git a/src/ctnetlink/nlas/stat/mod.rs b/src/ctnetlink/nlas/stat/mod.rs new file mode 100644 index 0000000..d341c3e --- /dev/null +++ b/src/ctnetlink/nlas/stat/mod.rs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: MIT + +pub mod nla; diff --git a/src/ctnetlink/nlas/stat/nla.rs b/src/ctnetlink/nlas/stat/nla.rs new file mode 100644 index 0000000..fbc3257 --- /dev/null +++ b/src/ctnetlink/nlas/stat/nla.rs @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT + +use byteorder::{BigEndian, ByteOrder}; +use netlink_packet_utils::{ + nla::{DefaultNla, Nla, NlaBuffer}, + Parseable, +}; + +use crate::constants::{CTA_TUPLE_ORIG, CTA_TUPLE_REPLY}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum StatNla { + Orig(u32), + Reply(u32), + Other(DefaultNla), +} + +impl Nla for StatNla { + fn value_len(&self) -> usize { + match self { + StatNla::Orig(_) => 4, + StatNla::Reply(_) => 4, + StatNla::Other(nla) => nla.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + StatNla::Orig(_) => CTA_TUPLE_ORIG, + StatNla::Reply(_) => CTA_TUPLE_REPLY, + StatNla::Other(nla) => nla.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + StatNla::Orig(val) => BigEndian::write_u32(buffer, *val), + StatNla::Reply(val) => BigEndian::write_u32(buffer, *val), + StatNla::Other(attr) => attr.emit_value(buffer), + } + } +} + +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for StatNla +{ + fn parse( + buf: &NlaBuffer<&'buffer T>, + ) -> Result { + let kind = buf.kind(); + let payload = buf.value(); + let nla = match kind { + CTA_TUPLE_ORIG => StatNla::Orig(BigEndian::read_u32(payload)), + CTA_TUPLE_REPLY => StatNla::Reply(BigEndian::read_u32(payload)), + _ => StatNla::Other(DefaultNla::parse(buf)?), + }; + Ok(nla) + } +} From 3d01568ba10420a0a41ee5a6eac063182dbde32f Mon Sep 17 00:00:00 2001 From: terassyi Date: Sat, 21 Dec 2024 14:32:07 +0900 Subject: [PATCH 05/13] Support IPCTNL_MSG_CT_GET_STATS_CPU Signed-off-by: terassyi --- examples/ctnetlink.rs | 60 ++++++++++++++++++++++++++++++- src/ctnetlink/message.rs | 23 ++++++++++-- src/ctnetlink/nlas/stat/nla.rs | 66 +++++++++++++++++++++++++++++++++- 3 files changed, 145 insertions(+), 4 deletions(-) diff --git a/examples/ctnetlink.rs b/examples/ctnetlink.rs index eba3bca..cc2acde 100644 --- a/examples/ctnetlink.rs +++ b/examples/ctnetlink.rs @@ -3,7 +3,8 @@ use std::num::NonZero; use netlink_packet_core::{ - NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST, + NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_MATCH, + NLM_F_REQUEST, NLM_F_ROOT, }; use netlink_packet_netfilter::{ constants::{AF_INET, NFNETLINK_V0}, @@ -131,6 +132,46 @@ fn main() { let rx_packet = >::deserialize(bytes).unwrap(); println!("<<< packet_len={}\n{:?}", rx_packet.buffer_len(), rx_packet); + + // stat CPU + let packet = stat_cpu_request(AF_INET, 0); + let mut buf = vec![0; packet.header.length as usize]; + packet.serialize(&mut buf[..]); + println!(">>> {:?}", packet); + socket.send(&buf[..], 0).unwrap(); + + let mut done = false; + loop { + let size = socket.recv(&mut &mut receive_buffer[..], 0).unwrap(); + let bytes = &receive_buffer[..size]; + let mut read = 0; + let mut msg_count = 0; + while bytes.len() > read { + let rx_packet = + >::deserialize(&bytes[read..]) + .unwrap(); + if let NetlinkPayload::Done(_) = rx_packet.payload { + done = true; + break; + } + read += rx_packet.buffer_len(); + msg_count += 1; + println!( + "<<< counter={} packet_len={}\n{:?}", + msg_count, + rx_packet.buffer_len(), + rx_packet + ); + + if let NetlinkPayload::Error(e) = rx_packet.payload { + println!("{}", e); + assert_eq!(e.code, None); + } + } + if done { + break; + } + } } fn list_request(family: u8, res_id: u16) -> NetlinkMessage { @@ -196,3 +237,20 @@ fn stat_request(family: u8, res_id: u16) -> NetlinkMessage { message.finalize(); message } + +fn stat_cpu_request( + family: u8, + res_id: u16, +) -> NetlinkMessage { + let mut hdr = NetlinkHeader::default(); + hdr.flags = NLM_F_REQUEST | NLM_F_ROOT | NLM_F_MATCH; + let mut message = NetlinkMessage::new( + hdr, + NetlinkPayload::from(NetfilterMessage::new( + NetfilterHeader::new(family, NFNETLINK_V0, res_id), + CtNetlinkMessage::GetStatsCPU(None), + )), + ); + message.finalize(); + message +} diff --git a/src/ctnetlink/message.rs b/src/ctnetlink/message.rs index bd840f6..58ee6f7 100644 --- a/src/ctnetlink/message.rs +++ b/src/ctnetlink/message.rs @@ -8,7 +8,7 @@ use crate::{ buffer::NetfilterBuffer, constants::{ IPCTNL_MSG_CT_DELETE, IPCTNL_MSG_CT_GET, IPCTNL_MSG_CT_GET_STATS, - IPCTNL_MSG_CT_NEW, NFNL_SUBSYS_CTNETLINK, + IPCTNL_MSG_CT_GET_STATS_CPU, IPCTNL_MSG_CT_NEW, NFNL_SUBSYS_CTNETLINK, }, }; @@ -20,7 +20,7 @@ pub enum CtNetlinkMessage { Get(Option>), Delete(Vec), // GetCrtZero, - // GetStatsCPU, + GetStatsCPU(Option>), GetStats(Option>), // GetDying, // GetUnconfirmed, @@ -38,6 +38,7 @@ impl CtNetlinkMessage { CtNetlinkMessage::New(_) => IPCTNL_MSG_CT_NEW, CtNetlinkMessage::Get(_) => IPCTNL_MSG_CT_GET, CtNetlinkMessage::Delete(_) => IPCTNL_MSG_CT_DELETE, + CtNetlinkMessage::GetStatsCPU(_) => IPCTNL_MSG_CT_GET_STATS_CPU, CtNetlinkMessage::GetStats(_) => IPCTNL_MSG_CT_GET_STATS, CtNetlinkMessage::Other { message_type, .. } => *message_type, } @@ -53,6 +54,10 @@ impl Emitable for CtNetlinkMessage { None => 0, }, CtNetlinkMessage::Delete(nlas) => nlas.as_slice().buffer_len(), + CtNetlinkMessage::GetStatsCPU(nlas) => match nlas { + Some(nlas) => nlas.as_slice().buffer_len(), + None => 0, + }, CtNetlinkMessage::GetStats(nlas) => match nlas { Some(nlas) => nlas.as_slice().buffer_len(), None => 0, @@ -72,6 +77,11 @@ impl Emitable for CtNetlinkMessage { } } CtNetlinkMessage::Delete(nlas) => nlas.as_slice().emit(buffer), + CtNetlinkMessage::GetStatsCPU(nlas) => { + if let Some(nlas) = nlas { + nlas.as_slice().emit(buffer) + } + } CtNetlinkMessage::GetStats(nlas) => { if let Some(nlas) = nlas { nlas.as_slice().emit(buffer) @@ -111,6 +121,15 @@ impl<'a, T: AsRef<[u8]> + ?Sized> buf.parse_all_nlas(|nla_buf| FlowNla::parse(&nla_buf))?; CtNetlinkMessage::Delete(nlas) } + IPCTNL_MSG_CT_GET_STATS_CPU => { + if buf.payload().is_empty() { + CtNetlinkMessage::GetStatsCPU(None) + } else { + let nlas = + buf.parse_all_nlas(|nla_buf| StatNla::parse(&nla_buf))?; + CtNetlinkMessage::GetStatsCPU(Some(nlas)) + } + } IPCTNL_MSG_CT_GET_STATS => { if buf.payload().is_empty() { CtNetlinkMessage::GetStats(None) diff --git a/src/ctnetlink/nlas/stat/nla.rs b/src/ctnetlink/nlas/stat/nla.rs index fbc3257..1423d1e 100644 --- a/src/ctnetlink/nlas/stat/nla.rs +++ b/src/ctnetlink/nlas/stat/nla.rs @@ -6,12 +6,26 @@ use netlink_packet_utils::{ Parseable, }; -use crate::constants::{CTA_TUPLE_ORIG, CTA_TUPLE_REPLY}; +use crate::constants::{ + CTA_COUNTERS_ORIG, CTA_COUNTERS_REPLY, CTA_ID, CTA_MARK, CTA_NAT_DST, + CTA_PROTOINFO, CTA_SEQ_ADJ_ORIG, CTA_SEQ_ADJ_REPLY, CTA_TUPLE_MASTER, + CTA_TUPLE_ORIG, CTA_TUPLE_REPLY, CTA_USE, +}; #[derive(Debug, PartialEq, Eq, Clone)] pub enum StatNla { Orig(u32), Reply(u32), + ProtocolInfo(u32), + Mark(u32), + CountersOrig(u32), + CountersReply(u32), + Use(u32), + Id(u32), + NATDst(u32), + Master(u32), + SeqAdjOrig(u32), + SeqAdjReply(u32), Other(DefaultNla), } @@ -20,6 +34,16 @@ impl Nla for StatNla { match self { StatNla::Orig(_) => 4, StatNla::Reply(_) => 4, + StatNla::ProtocolInfo(_) => 4, + StatNla::Mark(_) => 4, + StatNla::CountersOrig(_) => 4, + StatNla::CountersReply(_) => 4, + StatNla::Use(_) => 4, + StatNla::Id(_) => 4, + StatNla::NATDst(_) => 4, + StatNla::Master(_) => 4, + StatNla::SeqAdjOrig(_) => 4, + StatNla::SeqAdjReply(_) => 4, StatNla::Other(nla) => nla.value_len(), } } @@ -28,6 +52,16 @@ impl Nla for StatNla { match self { StatNla::Orig(_) => CTA_TUPLE_ORIG, StatNla::Reply(_) => CTA_TUPLE_REPLY, + StatNla::ProtocolInfo(_) => CTA_PROTOINFO, + StatNla::Mark(_) => CTA_MARK, + StatNla::CountersOrig(_) => CTA_COUNTERS_ORIG, + StatNla::CountersReply(_) => CTA_COUNTERS_REPLY, + StatNla::Use(_) => CTA_USE, + StatNla::Id(_) => CTA_ID, + StatNla::NATDst(_) => CTA_NAT_DST, + StatNla::Master(_) => CTA_TUPLE_MASTER, + StatNla::SeqAdjOrig(_) => CTA_SEQ_ADJ_ORIG, + StatNla::SeqAdjReply(_) => CTA_SEQ_ADJ_REPLY, StatNla::Other(nla) => nla.kind(), } } @@ -36,6 +70,16 @@ impl Nla for StatNla { match self { StatNla::Orig(val) => BigEndian::write_u32(buffer, *val), StatNla::Reply(val) => BigEndian::write_u32(buffer, *val), + StatNla::ProtocolInfo(val) => BigEndian::write_u32(buffer, *val), + StatNla::Mark(val) => BigEndian::write_u32(buffer, *val), + StatNla::CountersOrig(val) => BigEndian::write_u32(buffer, *val), + StatNla::CountersReply(val) => BigEndian::write_u32(buffer, *val), + StatNla::Use(val) => BigEndian::write_u32(buffer, *val), + StatNla::Id(val) => BigEndian::write_u32(buffer, *val), + StatNla::NATDst(val) => BigEndian::write_u32(buffer, *val), + StatNla::Master(val) => BigEndian::write_u32(buffer, *val), + StatNla::SeqAdjOrig(val) => BigEndian::write_u32(buffer, *val), + StatNla::SeqAdjReply(val) => BigEndian::write_u32(buffer, *val), StatNla::Other(attr) => attr.emit_value(buffer), } } @@ -52,6 +96,26 @@ impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> let nla = match kind { CTA_TUPLE_ORIG => StatNla::Orig(BigEndian::read_u32(payload)), CTA_TUPLE_REPLY => StatNla::Reply(BigEndian::read_u32(payload)), + CTA_PROTOINFO => { + StatNla::ProtocolInfo(BigEndian::read_u32(payload)) + } + CTA_MARK => StatNla::Mark(BigEndian::read_u32(payload)), + CTA_COUNTERS_ORIG => { + StatNla::CountersOrig(BigEndian::read_u32(payload)) + } + CTA_COUNTERS_REPLY => { + StatNla::CountersReply(BigEndian::read_u32(payload)) + } + CTA_USE => StatNla::Use(BigEndian::read_u32(payload)), + CTA_ID => StatNla::Id(BigEndian::read_u32(payload)), + CTA_NAT_DST => StatNla::NATDst(BigEndian::read_u32(payload)), + CTA_TUPLE_MASTER => StatNla::Master(BigEndian::read_u32(payload)), + CTA_SEQ_ADJ_ORIG => { + StatNla::SeqAdjOrig(BigEndian::read_u32(payload)) + } + CTA_SEQ_ADJ_REPLY => { + StatNla::SeqAdjReply(BigEndian::read_u32(payload)) + } _ => StatNla::Other(DefaultNla::parse(buf)?), }; Ok(nla) From ff6258364003b094723e97699356dd98ca0c8194 Mon Sep 17 00:00:00 2001 From: terassyi Date: Sat, 21 Dec 2024 19:12:09 +0900 Subject: [PATCH 06/13] Support IPCTNL_MSG_CT_GET_CTRZERO Signed-off-by: terassyi --- examples/ctnetlink.rs | 71 +++++++++++++++++++++++++++++++++++----- src/ctnetlink/message.rs | 26 +++++++++++++-- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/examples/ctnetlink.rs b/examples/ctnetlink.rs index cc2acde..448ae4e 100644 --- a/examples/ctnetlink.rs +++ b/examples/ctnetlink.rs @@ -23,7 +23,7 @@ fn main() { socket.bind_auto().unwrap(); // List all conntrack entries - let packet = list_request(AF_INET, 0); + let packet = list_request(AF_INET, 0, false); let mut buf = vec![0; packet.header.length as usize]; packet.serialize(&mut buf[..]); println!(">>> {:?}", packet); @@ -172,18 +172,71 @@ fn main() { break; } } + + // List all conntrack entries + let packet = list_request(AF_INET, 0, true); + let mut buf = vec![0; packet.header.length as usize]; + packet.serialize(&mut buf[..]); + println!(">>> {:?}", packet); + socket.send(&buf[..], 0).unwrap(); + let mut done = false; + loop { + let size = socket.recv(&mut &mut receive_buffer[..], 0).unwrap(); + let bytes = &receive_buffer[..size]; + let mut read = 0; + let mut msg_count = 0; + while bytes.len() > read { + let rx_packet = + >::deserialize(&bytes[read..]) + .unwrap(); + if let NetlinkPayload::Done(_) = rx_packet.payload { + done = true; + break; + } + read += rx_packet.buffer_len(); + msg_count += 1; + println!( + "<<< counter={} packet_len={}\n{:?}", + msg_count, + rx_packet.buffer_len(), + rx_packet + ); + + if let NetlinkPayload::Error(e) = rx_packet.payload { + println!("{}", e); + assert_eq!(e.code, None); + } + } + if done { + break; + } + } } -fn list_request(family: u8, res_id: u16) -> NetlinkMessage { +fn list_request( + family: u8, + res_id: u16, + zero: bool, +) -> NetlinkMessage { let mut hdr = NetlinkHeader::default(); hdr.flags = NLM_F_REQUEST | NLM_F_DUMP; - let mut message = NetlinkMessage::new( - hdr, - NetlinkPayload::from(NetfilterMessage::new( - NetfilterHeader::new(family, NFNETLINK_V0, res_id), - CtNetlinkMessage::Get(None), - )), - ); + let mut message = if zero { + NetlinkMessage::new( + hdr, + NetlinkPayload::from(NetfilterMessage::new( + NetfilterHeader::new(family, NFNETLINK_V0, res_id), + CtNetlinkMessage::GetCrtZero(None), + )), + ) + } else { + NetlinkMessage::new( + hdr, + NetlinkPayload::from(NetfilterMessage::new( + NetfilterHeader::new(family, NFNETLINK_V0, res_id), + CtNetlinkMessage::Get(None), + )), + ) + }; message.finalize(); message } diff --git a/src/ctnetlink/message.rs b/src/ctnetlink/message.rs index 58ee6f7..3b5b94b 100644 --- a/src/ctnetlink/message.rs +++ b/src/ctnetlink/message.rs @@ -7,8 +7,9 @@ use netlink_packet_utils::{ use crate::{ buffer::NetfilterBuffer, constants::{ - IPCTNL_MSG_CT_DELETE, IPCTNL_MSG_CT_GET, IPCTNL_MSG_CT_GET_STATS, - IPCTNL_MSG_CT_GET_STATS_CPU, IPCTNL_MSG_CT_NEW, NFNL_SUBSYS_CTNETLINK, + IPCTNL_MSG_CT_DELETE, IPCTNL_MSG_CT_GET, IPCTNL_MSG_CT_GET_CTRZERO, + IPCTNL_MSG_CT_GET_STATS, IPCTNL_MSG_CT_GET_STATS_CPU, + IPCTNL_MSG_CT_NEW, NFNL_SUBSYS_CTNETLINK, }, }; @@ -19,7 +20,7 @@ pub enum CtNetlinkMessage { New(Vec), Get(Option>), Delete(Vec), - // GetCrtZero, + GetCrtZero(Option>), GetStatsCPU(Option>), GetStats(Option>), // GetDying, @@ -38,6 +39,7 @@ impl CtNetlinkMessage { CtNetlinkMessage::New(_) => IPCTNL_MSG_CT_NEW, CtNetlinkMessage::Get(_) => IPCTNL_MSG_CT_GET, CtNetlinkMessage::Delete(_) => IPCTNL_MSG_CT_DELETE, + CtNetlinkMessage::GetCrtZero(_) => IPCTNL_MSG_CT_GET_CTRZERO, CtNetlinkMessage::GetStatsCPU(_) => IPCTNL_MSG_CT_GET_STATS_CPU, CtNetlinkMessage::GetStats(_) => IPCTNL_MSG_CT_GET_STATS, CtNetlinkMessage::Other { message_type, .. } => *message_type, @@ -54,6 +56,10 @@ impl Emitable for CtNetlinkMessage { None => 0, }, CtNetlinkMessage::Delete(nlas) => nlas.as_slice().buffer_len(), + CtNetlinkMessage::GetCrtZero(nlas) => match nlas { + Some(nlas) => nlas.as_slice().buffer_len(), + None => 0, + }, CtNetlinkMessage::GetStatsCPU(nlas) => match nlas { Some(nlas) => nlas.as_slice().buffer_len(), None => 0, @@ -76,6 +82,11 @@ impl Emitable for CtNetlinkMessage { nlas.as_slice().emit(buffer); } } + CtNetlinkMessage::GetCrtZero(nlas) => { + if let Some(nlas) = nlas { + nlas.as_slice().emit(buffer); + } + } CtNetlinkMessage::Delete(nlas) => nlas.as_slice().emit(buffer), CtNetlinkMessage::GetStatsCPU(nlas) => { if let Some(nlas) = nlas { @@ -121,6 +132,15 @@ impl<'a, T: AsRef<[u8]> + ?Sized> buf.parse_all_nlas(|nla_buf| FlowNla::parse(&nla_buf))?; CtNetlinkMessage::Delete(nlas) } + IPCTNL_MSG_CT_GET_CTRZERO => { + if buf.payload().is_empty() { + CtNetlinkMessage::GetCrtZero(None) + } else { + let nlas = + buf.parse_all_nlas(|nla_buf| FlowNla::parse(&nla_buf))?; + CtNetlinkMessage::GetCrtZero(Some(nlas)) + } + } IPCTNL_MSG_CT_GET_STATS_CPU => { if buf.payload().is_empty() { CtNetlinkMessage::GetStatsCPU(None) From 8c3e16b933cb53d310d55e750ad86005124ee3a5 Mon Sep 17 00:00:00 2001 From: terassyi Date: Sun, 22 Dec 2024 10:54:16 +0900 Subject: [PATCH 07/13] Support IPCTNL_MSG_CT_GET_DYING and IPCTNL_MSG_CT_GET_UNCONFIRMED Signed-off-by: terassyi --- src/constants.rs | 2 +- src/ctnetlink/message.rs | 47 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 6fb57f9..8409baf 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -97,7 +97,7 @@ pub const IPCTNL_MSG_CT_DELETE: u8 = 2; pub const IPCTNL_MSG_CT_GET_CTRZERO: u8 = 3; pub const IPCTNL_MSG_CT_GET_STATS_CPU: u8 = 4; pub const IPCTNL_MSG_CT_GET_STATS: u8 = 5; -pub const IPCTNL_MSG_CT_DYING: u8 = 6; +pub const IPCTNL_MSG_CT_GET_DYING: u8 = 6; pub const IPCTNL_MSG_CT_GET_UNCONFIRMED: u8 = 7; pub const CTA_UNSPEC: u16 = 0; diff --git a/src/ctnetlink/message.rs b/src/ctnetlink/message.rs index 3b5b94b..d008a46 100644 --- a/src/ctnetlink/message.rs +++ b/src/ctnetlink/message.rs @@ -8,7 +8,8 @@ use crate::{ buffer::NetfilterBuffer, constants::{ IPCTNL_MSG_CT_DELETE, IPCTNL_MSG_CT_GET, IPCTNL_MSG_CT_GET_CTRZERO, - IPCTNL_MSG_CT_GET_STATS, IPCTNL_MSG_CT_GET_STATS_CPU, + IPCTNL_MSG_CT_GET_DYING, IPCTNL_MSG_CT_GET_STATS, + IPCTNL_MSG_CT_GET_STATS_CPU, IPCTNL_MSG_CT_GET_UNCONFIRMED, IPCTNL_MSG_CT_NEW, NFNL_SUBSYS_CTNETLINK, }, }; @@ -23,8 +24,8 @@ pub enum CtNetlinkMessage { GetCrtZero(Option>), GetStatsCPU(Option>), GetStats(Option>), - // GetDying, - // GetUnconfirmed, + GetDying(Option>), + GetUnconfirmed(Option>), Other { message_type: u8, nlas: Vec, @@ -42,6 +43,10 @@ impl CtNetlinkMessage { CtNetlinkMessage::GetCrtZero(_) => IPCTNL_MSG_CT_GET_CTRZERO, CtNetlinkMessage::GetStatsCPU(_) => IPCTNL_MSG_CT_GET_STATS_CPU, CtNetlinkMessage::GetStats(_) => IPCTNL_MSG_CT_GET_STATS, + CtNetlinkMessage::GetDying(_) => IPCTNL_MSG_CT_GET_DYING, + CtNetlinkMessage::GetUnconfirmed(_) => { + IPCTNL_MSG_CT_GET_UNCONFIRMED + } CtNetlinkMessage::Other { message_type, .. } => *message_type, } } @@ -68,6 +73,14 @@ impl Emitable for CtNetlinkMessage { Some(nlas) => nlas.as_slice().buffer_len(), None => 0, }, + CtNetlinkMessage::GetDying(nlas) => match nlas { + Some(nlas) => nlas.as_slice().buffer_len(), + None => 0, + }, + CtNetlinkMessage::GetUnconfirmed(nlas) => match nlas { + Some(nlas) => nlas.as_slice().buffer_len(), + None => 0, + }, CtNetlinkMessage::Other { nlas, .. } => { nlas.as_slice().buffer_len() } @@ -98,6 +111,16 @@ impl Emitable for CtNetlinkMessage { nlas.as_slice().emit(buffer) } } + CtNetlinkMessage::GetDying(nlas) => { + if let Some(nlas) = nlas { + nlas.as_slice().emit(buffer); + } + } + CtNetlinkMessage::GetUnconfirmed(nlas) => { + if let Some(nlas) = nlas { + nlas.as_slice().emit(buffer); + } + } CtNetlinkMessage::Other { nlas, .. } => { nlas.as_slice().emit(buffer) } @@ -159,6 +182,24 @@ impl<'a, T: AsRef<[u8]> + ?Sized> CtNetlinkMessage::GetStats(Some(nlas)) } } + IPCTNL_MSG_CT_GET_DYING => { + if buf.payload().is_empty() { + CtNetlinkMessage::GetDying(None) + } else { + let nlas = + buf.parse_all_nlas(|nla_buf| FlowNla::parse(&nla_buf))?; + CtNetlinkMessage::GetDying(Some(nlas)) + } + } + IPCTNL_MSG_CT_GET_UNCONFIRMED => { + if buf.payload().is_empty() { + CtNetlinkMessage::GetUnconfirmed(None) + } else { + let nlas = + buf.parse_all_nlas(|nla_buf| FlowNla::parse(&nla_buf))?; + CtNetlinkMessage::GetUnconfirmed(Some(nlas)) + } + } _ => CtNetlinkMessage::Other { message_type, nlas: buf.default_nlas()?, From a3a8b4b5d186139f2d845cb7fae97582dd4651cf Mon Sep 17 00:00:00 2001 From: terassyi Date: Thu, 26 Dec 2024 01:07:44 +0900 Subject: [PATCH 08/13] Export ProtocolInfoTcp's fields Signed-off-by: terassyi --- src/ctnetlink/nlas/flow/protocol_info.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ctnetlink/nlas/flow/protocol_info.rs b/src/ctnetlink/nlas/flow/protocol_info.rs index 31f9b32..65b51ff 100644 --- a/src/ctnetlink/nlas/flow/protocol_info.rs +++ b/src/ctnetlink/nlas/flow/protocol_info.rs @@ -83,11 +83,11 @@ impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct ProtocolInfoTcp { - state: u8, - wscale_original: u8, - wscale_reply: u8, - flgas_original: u16, - flags_reply: u16, + pub state: u8, + pub wscale_original: u8, + pub wscale_reply: u8, + pub flgas_original: u16, + pub flags_reply: u16, } impl Nla for ProtocolInfoTcp { From 0675579311e3e4b2315d7b5584e73dceb34282b8 Mon Sep 17 00:00:00 2001 From: terassyi Date: Tue, 7 Jan 2025 18:47:40 +0900 Subject: [PATCH 09/13] Add Hash trait for ConnectionStatusFlag Signed-off-by: terassyi --- src/ctnetlink/nlas/flow/status.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ctnetlink/nlas/flow/status.rs b/src/ctnetlink/nlas/flow/status.rs index f1a8604..021352c 100644 --- a/src/ctnetlink/nlas/flow/status.rs +++ b/src/ctnetlink/nlas/flow/status.rs @@ -5,7 +5,7 @@ use netlink_packet_utils::nla::Nla; use crate::constants::CTA_STATUS; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ConnectionStatusFlag { Offload = 1 << 15, Helper = 1 << 14, From 147656afff33fce0ae28a179bc23e40d60405f60 Mon Sep 17 00:00:00 2001 From: terassyi Date: Tue, 7 Jan 2025 19:22:24 +0900 Subject: [PATCH 10/13] Fix ConnectionStatusFlag's value Signed-off-by: terassyi --- src/ctnetlink/nlas/flow/status.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ctnetlink/nlas/flow/status.rs b/src/ctnetlink/nlas/flow/status.rs index 021352c..5e129bd 100644 --- a/src/ctnetlink/nlas/flow/status.rs +++ b/src/ctnetlink/nlas/flow/status.rs @@ -7,20 +7,20 @@ use crate::constants::CTA_STATUS; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ConnectionStatusFlag { - Offload = 1 << 15, - Helper = 1 << 14, - Untracked = 1 << 13, - Template = 1 << 12, - FixedTimeout = 1 << 11, - Dying = 1 << 10, - DestinationNATDone = 1 << 9, - SourceNATDone = 1 << 8, - SequenceAdjust = 1 << 7, - DestinationNAT = 1 << 6, - SourceNAT = 1 << 5, - Confirmed = 1 << 4, - Assured = 1 << 3, - SeenReply = 1 << 2, + Offload = 1 << 14, + Helper = 1 << 13, + Untracked = 1 << 12, + Template = 1 << 11, + FixedTimeout = 1 << 10, + Dying = 1 << 9, + DestinationNATDone = 1 << 8, + SourceNATDone = 1 << 7, + SequenceAdjust = 1 << 6, + DestinationNAT = 1 << 5, + SourceNAT = 1 << 4, + Confirmed = 1 << 3, + Assured = 1 << 2, + SeenReply = 1 << 1, Expected = 1, } From 386a809bda10466d6d1dafafd7fbd62c1662a03d Mon Sep 17 00:00:00 2001 From: terassyi Date: Wed, 8 Jan 2025 23:45:05 +0900 Subject: [PATCH 11/13] Export CtAttr Signed-off-by: terassyi --- src/ctnetlink/nlas/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ctnetlink/nlas/mod.rs b/src/ctnetlink/nlas/mod.rs index bc3f58f..3c1c108 100644 --- a/src/ctnetlink/nlas/mod.rs +++ b/src/ctnetlink/nlas/mod.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -mod ct_attr; +pub mod ct_attr; pub mod flow; pub mod stat; From 2cb429bb62fa0632bca4e6782e6af4407c90fa51 Mon Sep 17 00:00:00 2001 From: terassyi Date: Sun, 2 Feb 2025 22:25:44 +0900 Subject: [PATCH 12/13] fix StatNla variants Signed-off-by: terassyi --- src/constants.rs | 32 +++---- src/ctnetlink/nlas/stat/nla.rs | 157 +++++++++++++++++++-------------- 2 files changed, 105 insertions(+), 84 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 8409baf..47078c0 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -236,22 +236,22 @@ pub const CTA_EXPECT_NAT_TUPLE: u8 = 2; pub const CTA_SECCTX_UNSPEC: u8 = 0; pub const CTA_SECCTX_NAME: u8 = 1; -pub const CTA_STATS_UNSPEC: u8 = 0; -pub const CTA_STATS_SEARCHED: u8 = 1; /* no longer used */ -pub const CTA_STATS_FOUND: u8 = 2; -pub const CTA_STATS_NEW: u8 = 3; /* no longer used */ -pub const CTA_STATS_INVALID: u8 = 4; -pub const CTA_STATS_IGNORE: u8 = 5; /* no longer used */ -pub const CTA_STATS_DELETE: u8 = 6; /* no longer used */ -pub const CTA_STATS_DELETE_LIST: u8 = 7; /* no longer used */ -pub const CTA_STATS_INSERT: u8 = 8; -pub const CTA_STATS_INSERT_FAILED: u8 = 9; -pub const CTA_STATS_DROP: u8 = 10; -pub const CTA_STATS_EARLY_DROP: u8 = 10; -pub const CTA_STATS_ERROR: u8 = 11; -pub const CTA_STATS_SEARCH_RESTART: u8 = 12; -pub const CTA_STATS_CLASH_RESOLVE: u8 = 13; -pub const CTA_STATS_CHAIN_TOOLONG: u8 = 14; +pub const CTA_STATS_UNSPEC: u16 = 0; +pub const CTA_STATS_SEARCHED: u16 = 1; /* no longer used */ +pub const CTA_STATS_FOUND: u16 = 2; +pub const CTA_STATS_NEW: u16 = 3; /* no longer used */ +pub const CTA_STATS_INVALID: u16 = 4; +pub const CTA_STATS_IGNORE: u16 = 5; /* no longer used */ +pub const CTA_STATS_DELETE: u16 = 6; /* no longer used */ +pub const CTA_STATS_DELETE_LIST: u16 = 7; /* no longer used */ +pub const CTA_STATS_INSERT: u16 = 8; +pub const CTA_STATS_INSERT_FAILED: u16 = 9; +pub const CTA_STATS_DROP: u16 = 10; +pub const CTA_STATS_EARLY_DROP: u16 = 11; +pub const CTA_STATS_ERROR: u16 = 12; +pub const CTA_STATS_SEARCH_RESTART: u16 = 13; +pub const CTA_STATS_CLASH_RESOLVE: u16 = 14; +pub const CTA_STATS_CHAIN_TOOLONG: u16 = 15; pub const CTA_STATS_GLOBAL_UNSPEC: u8 = 0; pub const CTA_STATS_GLOBAL_ENTRIES: u8 = 1; diff --git a/src/ctnetlink/nlas/stat/nla.rs b/src/ctnetlink/nlas/stat/nla.rs index 1423d1e..a57a80e 100644 --- a/src/ctnetlink/nlas/stat/nla.rs +++ b/src/ctnetlink/nlas/stat/nla.rs @@ -7,79 +7,93 @@ use netlink_packet_utils::{ }; use crate::constants::{ - CTA_COUNTERS_ORIG, CTA_COUNTERS_REPLY, CTA_ID, CTA_MARK, CTA_NAT_DST, - CTA_PROTOINFO, CTA_SEQ_ADJ_ORIG, CTA_SEQ_ADJ_REPLY, CTA_TUPLE_MASTER, - CTA_TUPLE_ORIG, CTA_TUPLE_REPLY, CTA_USE, + CTA_STATS_CHAIN_TOOLONG, CTA_STATS_CLASH_RESOLVE, CTA_STATS_DELETE, + CTA_STATS_DELETE_LIST, CTA_STATS_DROP, CTA_STATS_EARLY_DROP, + CTA_STATS_ERROR, CTA_STATS_FOUND, CTA_STATS_IGNORE, CTA_STATS_INSERT, + CTA_STATS_INSERT_FAILED, CTA_STATS_INVALID, CTA_STATS_NEW, + CTA_STATS_SEARCHED, CTA_STATS_SEARCH_RESTART, }; #[derive(Debug, PartialEq, Eq, Clone)] pub enum StatNla { - Orig(u32), - Reply(u32), - ProtocolInfo(u32), - Mark(u32), - CountersOrig(u32), - CountersReply(u32), - Use(u32), - Id(u32), - NATDst(u32), - Master(u32), - SeqAdjOrig(u32), - SeqAdjReply(u32), + Searched(u32), // no longer used + Found(u32), + New(u32), // no longer used + Invalid(u32), + Ignore(u32), // no longer used + Delete(u32), // no longer used + DeleteList(u32), // no longer used + Insert(u32), + InsertFailed(u32), + Drop(u32), + EarlyDrop(u32), + Error(u32), + SearchRestart(u32), + ClashResolve(u32), + ChainTooLong(u32), Other(DefaultNla), } impl Nla for StatNla { fn value_len(&self) -> usize { match self { - StatNla::Orig(_) => 4, - StatNla::Reply(_) => 4, - StatNla::ProtocolInfo(_) => 4, - StatNla::Mark(_) => 4, - StatNla::CountersOrig(_) => 4, - StatNla::CountersReply(_) => 4, - StatNla::Use(_) => 4, - StatNla::Id(_) => 4, - StatNla::NATDst(_) => 4, - StatNla::Master(_) => 4, - StatNla::SeqAdjOrig(_) => 4, - StatNla::SeqAdjReply(_) => 4, + StatNla::Searched(_) => 4, + StatNla::Found(_) => 4, + StatNla::New(_) => 4, + StatNla::Invalid(_) => 4, + StatNla::Ignore(_) => 4, + StatNla::Delete(_) => 4, + StatNla::DeleteList(_) => 4, + StatNla::Insert(_) => 4, + StatNla::InsertFailed(_) => 4, + StatNla::Drop(_) => 4, + StatNla::EarlyDrop(_) => 4, + StatNla::Error(_) => 4, + StatNla::SearchRestart(_) => 4, + StatNla::ClashResolve(_) => 4, + StatNla::ChainTooLong(_) => 4, StatNla::Other(nla) => nla.value_len(), } } fn kind(&self) -> u16 { match self { - StatNla::Orig(_) => CTA_TUPLE_ORIG, - StatNla::Reply(_) => CTA_TUPLE_REPLY, - StatNla::ProtocolInfo(_) => CTA_PROTOINFO, - StatNla::Mark(_) => CTA_MARK, - StatNla::CountersOrig(_) => CTA_COUNTERS_ORIG, - StatNla::CountersReply(_) => CTA_COUNTERS_REPLY, - StatNla::Use(_) => CTA_USE, - StatNla::Id(_) => CTA_ID, - StatNla::NATDst(_) => CTA_NAT_DST, - StatNla::Master(_) => CTA_TUPLE_MASTER, - StatNla::SeqAdjOrig(_) => CTA_SEQ_ADJ_ORIG, - StatNla::SeqAdjReply(_) => CTA_SEQ_ADJ_REPLY, + StatNla::Searched(_) => CTA_STATS_SEARCHED, + StatNla::Found(_) => CTA_STATS_FOUND, + StatNla::New(_) => CTA_STATS_NEW, + StatNla::Invalid(_) => CTA_STATS_INVALID, + StatNla::Ignore(_) => CTA_STATS_IGNORE, + StatNla::Delete(_) => CTA_STATS_DELETE, + StatNla::DeleteList(_) => CTA_STATS_DELETE_LIST, + StatNla::Insert(_) => CTA_STATS_INSERT, + StatNla::InsertFailed(_) => CTA_STATS_INSERT_FAILED, + StatNla::Drop(_) => CTA_STATS_DROP, + StatNla::EarlyDrop(_) => CTA_STATS_EARLY_DROP, + StatNla::Error(_) => CTA_STATS_ERROR, + StatNla::SearchRestart(_) => CTA_STATS_SEARCH_RESTART, + StatNla::ClashResolve(_) => CTA_STATS_CLASH_RESOLVE, + StatNla::ChainTooLong(_) => CTA_STATS_CHAIN_TOOLONG, StatNla::Other(nla) => nla.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { - StatNla::Orig(val) => BigEndian::write_u32(buffer, *val), - StatNla::Reply(val) => BigEndian::write_u32(buffer, *val), - StatNla::ProtocolInfo(val) => BigEndian::write_u32(buffer, *val), - StatNla::Mark(val) => BigEndian::write_u32(buffer, *val), - StatNla::CountersOrig(val) => BigEndian::write_u32(buffer, *val), - StatNla::CountersReply(val) => BigEndian::write_u32(buffer, *val), - StatNla::Use(val) => BigEndian::write_u32(buffer, *val), - StatNla::Id(val) => BigEndian::write_u32(buffer, *val), - StatNla::NATDst(val) => BigEndian::write_u32(buffer, *val), - StatNla::Master(val) => BigEndian::write_u32(buffer, *val), - StatNla::SeqAdjOrig(val) => BigEndian::write_u32(buffer, *val), - StatNla::SeqAdjReply(val) => BigEndian::write_u32(buffer, *val), + StatNla::Searched(val) => BigEndian::write_u32(buffer, *val), + StatNla::Found(val) => BigEndian::write_u32(buffer, *val), + StatNla::New(val) => BigEndian::write_u32(buffer, *val), + StatNla::Invalid(val) => BigEndian::write_u32(buffer, *val), + StatNla::Ignore(val) => BigEndian::write_u32(buffer, *val), + StatNla::Delete(val) => BigEndian::write_u32(buffer, *val), + StatNla::DeleteList(val) => BigEndian::write_u32(buffer, *val), + StatNla::Insert(val) => BigEndian::write_u32(buffer, *val), + StatNla::InsertFailed(val) => BigEndian::write_u32(buffer, *val), + StatNla::Drop(val) => BigEndian::write_u32(buffer, *val), + StatNla::EarlyDrop(val) => BigEndian::write_u32(buffer, *val), + StatNla::Error(val) => BigEndian::write_u32(buffer, *val), + StatNla::SearchRestart(val) => BigEndian::write_u32(buffer, *val), + StatNla::ClashResolve(val) => BigEndian::write_u32(buffer, *val), + StatNla::ChainTooLong(val) => BigEndian::write_u32(buffer, *val), StatNla::Other(attr) => attr.emit_value(buffer), } } @@ -94,27 +108,34 @@ impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> let kind = buf.kind(); let payload = buf.value(); let nla = match kind { - CTA_TUPLE_ORIG => StatNla::Orig(BigEndian::read_u32(payload)), - CTA_TUPLE_REPLY => StatNla::Reply(BigEndian::read_u32(payload)), - CTA_PROTOINFO => { - StatNla::ProtocolInfo(BigEndian::read_u32(payload)) + CTA_STATS_SEARCHED => { + StatNla::Searched(BigEndian::read_u32(payload)) } - CTA_MARK => StatNla::Mark(BigEndian::read_u32(payload)), - CTA_COUNTERS_ORIG => { - StatNla::CountersOrig(BigEndian::read_u32(payload)) + CTA_STATS_FOUND => StatNla::Found(BigEndian::read_u32(payload)), + CTA_STATS_NEW => StatNla::New(BigEndian::read_u32(payload)), + CTA_STATS_INVALID => StatNla::Invalid(BigEndian::read_u32(payload)), + CTA_STATS_IGNORE => StatNla::Ignore(BigEndian::read_u32(payload)), + CTA_STATS_DELETE => StatNla::Delete(BigEndian::read_u32(payload)), + CTA_STATS_DELETE_LIST => { + StatNla::DeleteList(BigEndian::read_u32(payload)) } - CTA_COUNTERS_REPLY => { - StatNla::CountersReply(BigEndian::read_u32(payload)) + CTA_STATS_INSERT => StatNla::Insert(BigEndian::read_u32(payload)), + CTA_STATS_INSERT_FAILED => { + StatNla::InsertFailed(BigEndian::read_u32(payload)) } - CTA_USE => StatNla::Use(BigEndian::read_u32(payload)), - CTA_ID => StatNla::Id(BigEndian::read_u32(payload)), - CTA_NAT_DST => StatNla::NATDst(BigEndian::read_u32(payload)), - CTA_TUPLE_MASTER => StatNla::Master(BigEndian::read_u32(payload)), - CTA_SEQ_ADJ_ORIG => { - StatNla::SeqAdjOrig(BigEndian::read_u32(payload)) + CTA_STATS_DROP => StatNla::Drop(BigEndian::read_u32(payload)), + CTA_STATS_EARLY_DROP => { + StatNla::EarlyDrop(BigEndian::read_u32(payload)) } - CTA_SEQ_ADJ_REPLY => { - StatNla::SeqAdjReply(BigEndian::read_u32(payload)) + CTA_STATS_ERROR => StatNla::Error(BigEndian::read_u32(payload)), + CTA_STATS_SEARCH_RESTART => { + StatNla::SearchRestart(BigEndian::read_u32(payload)) + } + CTA_STATS_CLASH_RESOLVE => { + StatNla::ClashResolve(BigEndian::read_u32(payload)) + } + CTA_STATS_CHAIN_TOOLONG => { + StatNla::ChainTooLong(BigEndian::read_u32(payload)) } _ => StatNla::Other(DefaultNla::parse(buf)?), }; From d6819830524dc78416518641db8b8a4a488cbceb Mon Sep 17 00:00:00 2001 From: terassyi Date: Sun, 23 Feb 2025 23:13:28 +0900 Subject: [PATCH 13/13] WIP: reflect reviews - rename from CtAttr to ConntrackAttribute - rename from FlowNla and StatNla to FlowAttribute and StatCpuAttribute - use buffier!() for ProtocolInfoTcp - use bitflags!() for ConnectionStatusFlag - remove constant values and enum variants that is no longer used - move values from src/constatnts.rs to its users - stop exposing internal modules - introduce StatGlobalAttribute that represents ctattr_stats_global in the kernel - fix typo Signed-off-by: terassyi --- examples/ctnetlink.rs | 10 +- src/buffer.rs | 2 +- src/constants.rs | 177 ------------------ src/ctnetlink/message.rs | 78 ++++---- src/ctnetlink/mod.rs | 3 +- src/ctnetlink/nlas/ct_attr.rs | 46 ++--- src/ctnetlink/nlas/flow/ip_tuple.rs | 35 ++-- src/ctnetlink/nlas/flow/nla.rs | 101 +++++----- src/ctnetlink/nlas/flow/protocol_info.rs | 74 +++++--- src/ctnetlink/nlas/flow/status.rs | 67 ++++--- src/ctnetlink/nlas/stat/nla.rs | 223 ++++++++++++++--------- src/message.rs | 3 +- 12 files changed, 388 insertions(+), 431 deletions(-) diff --git a/examples/ctnetlink.rs b/examples/ctnetlink.rs index 448ae4e..f9a9d35 100644 --- a/examples/ctnetlink.rs +++ b/examples/ctnetlink.rs @@ -9,8 +9,8 @@ use netlink_packet_core::{ use netlink_packet_netfilter::{ constants::{AF_INET, NFNETLINK_V0}, ctnetlink::{ - message::CtNetlinkMessage, - nlas::flow::{ip_tuple::TupleNla, nla::FlowNla}, + nlas::flow::{ip_tuple::TupleNla, nla::FlowAttribute}, + CtNetlinkMessage, }, NetfilterHeader, NetfilterMessage, NetfilterMessageInner, }; @@ -61,7 +61,7 @@ fn main() { ) = ct.inner { for nla in nlas.iter() { - if let FlowNla::Orig(attrs) = nla { + if let FlowAttribute::Orig(attrs) = nla { orig = Some(attrs.clone()) } } @@ -252,7 +252,7 @@ fn get_request( hdr, NetlinkPayload::from(NetfilterMessage::new( NetfilterHeader::new(family, NFNETLINK_V0, res_id), - CtNetlinkMessage::Get(Some(vec![FlowNla::Orig(tuple)])), + CtNetlinkMessage::Get(Some(vec![FlowAttribute::Orig(tuple)])), )), ); message.finalize(); @@ -270,7 +270,7 @@ fn delete_request( hdr, NetlinkPayload::from(NetfilterMessage::new( NetfilterHeader::new(family, NFNETLINK_V0, res_id), - CtNetlinkMessage::Delete(vec![FlowNla::Orig(tuple)]), + CtNetlinkMessage::Delete(vec![FlowAttribute::Orig(tuple)]), )), ); message.finalize(); diff --git a/src/buffer.rs b/src/buffer.rs index 7f1a62e..e1ef446 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT use crate::{ - ctnetlink::message::CtNetlinkMessage, + ctnetlink::CtNetlinkMessage, message::{ NetfilterHeader, NetfilterMessage, NetfilterMessageInner, NETFILTER_HEADER_LEN, diff --git a/src/constants.rs b/src/constants.rs index 47078c0..2b73b6d 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -88,180 +88,3 @@ 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; - -// netflter/nfnetlink_conntrack.h -// There is no definitions in rust-lang/libc -pub const IPCTNL_MSG_CT_NEW: u8 = 0; -pub const IPCTNL_MSG_CT_GET: u8 = 1; -pub const IPCTNL_MSG_CT_DELETE: u8 = 2; -pub const IPCTNL_MSG_CT_GET_CTRZERO: u8 = 3; -pub const IPCTNL_MSG_CT_GET_STATS_CPU: u8 = 4; -pub const IPCTNL_MSG_CT_GET_STATS: u8 = 5; -pub const IPCTNL_MSG_CT_GET_DYING: u8 = 6; -pub const IPCTNL_MSG_CT_GET_UNCONFIRMED: u8 = 7; - -pub const CTA_UNSPEC: u16 = 0; -pub const CTA_TUPLE_ORIG: u16 = 1; -pub const CTA_TUPLE_REPLY: u16 = 2; -pub const CTA_STATUS: u16 = 3; -pub const CTA_PROTOINFO: u16 = 4; -pub const CTA_HELP: u16 = 5; -pub const CTA_NAT_SRC: u16 = 6; -pub const CTA_NAT: u16 = CTA_NAT_SRC; /* backwards compatibility */ -pub const CTA_TIMEOUT: u16 = 7; -pub const CTA_MARK: u16 = 8; -pub const CTA_COUNTERS_ORIG: u16 = 9; -pub const CTA_COUNTERS_REPLY: u16 = 10; -pub const CTA_USE: u16 = 11; -pub const CTA_ID: u16 = 12; -pub const CTA_NAT_DST: u16 = 13; -pub const CTA_TUPLE_MASTER: u16 = 14; -pub const CTA_SEQ_ADJ_ORIG: u16 = 15; -pub const CTA_NAT_SEQ_ADJ_ORIG: u16 = CTA_SEQ_ADJ_ORIG; -pub const CTA_SEQ_ADJ_REPLY: u16 = 16; -pub const CTA_NAT_SEQ_ADJ_REPLY: u16 = CTA_SEQ_ADJ_REPLY; -pub const CTA_SECMARK: u16 = 17; /* obsolete */ -pub const CTA_ZONE: u16 = 18; -pub const CTA_SECCTX: u16 = 19; -pub const CTA_TIMESTAMP: u16 = 20; -pub const CTA_MARK_MASK: u16 = 21; -pub const CTA_LABELS: u16 = 22; -pub const CTA_LABELS_MASK: u16 = 23; -pub const CTA_SYNPROXY: u16 = 24; -pub const CTA_FILTER: u16 = 25; -pub const CTA_STATUS_MASK: u16 = 26; - -pub const CTA_TUPLE_UNSPEC: u16 = 0; -pub const CTA_TUPLE_IP: u16 = 1; -pub const CTA_TUPLE_PROTO: u16 = 2; -pub const CTA_TUPLE_ZONE: u16 = 3; - -pub const CTA_IP_UNSPEC: u16 = 0; -pub const CTA_IP_V4_SRC: u16 = 1; -pub const CTA_IP_V4_DST: u16 = 2; -pub const CTA_IP_V6_SRC: u16 = 3; -pub const CTA_IP_V6_DST: u16 = 4; - -pub const CTA_PROTO_UNSPEC: u16 = 0; -pub const CTA_PROTO_NUM: u16 = 1; -pub const CTA_PROTO_SRC_PORT: u16 = 2; -pub const CTA_PROTO_DST_PORT: u16 = 3; -pub const CTA_PROTO_ICMP_ID: u16 = 4; -pub const CTA_PROTO_ICMP_TYPE: u16 = 5; -pub const CTA_PROTO_ICMP_CODE: u16 = 6; -pub const CTA_PROTO_ICMPV6_ID: u16 = 7; -pub const CTA_PROTO_ICMPV6_TYPE: u16 = 8; -pub const CTA_PROTO_ICMPV6_CODE: u16 = 9; - -pub const CTA_PROTOINFO_UNSPEC: u16 = 0; -pub const CTA_PROTOINFO_TCP: u16 = 1; -pub const CTA_PROTOINFO_DCCP: u16 = 2; -pub const CTA_PROTOINFO_SCTP: u16 = 3; - -pub const CTA_PROTOINFO_TCP_UNSPEC: u16 = 0; -pub const CTA_PROTOINFO_TCP_STATE: u16 = 1; -pub const CTA_PROTOINFO_TCP_WSCALE_ORIGINAL: u16 = 2; -pub const CTA_PROTOINFO_TCP_WSCALE_REPLY: u16 = 3; -pub const CTA_PROTOINFO_TCP_FLAGS_ORIGINAL: u16 = 4; -pub const CTA_PROTOINFO_TCP_FLAGS_REPLY: u16 = 5; - -pub const CTA_PROTOINFO_DCCP_UNSPEC: u16 = 0; -pub const CTA_PROTOINFO_DCCP_STATE: u16 = 1; -pub const CTA_PROTOINFO_DCCP_ROLE: u16 = 2; -pub const CTA_PROTOINFO_DCCP_HANDSHAKE_SEQ: u16 = 3; -pub const CTA_PROTOINFO_DCCP_PAD: u16 = 4; - -pub const CTA_PROTOINFO_SCTP_UNSPEC: u16 = 0; -pub const CTA_PROTOINFO_SCTP_STATE: u16 = 1; -pub const CTA_PROTOINFO_SCTP_VTAG_ORIGINAL: u16 = 2; -pub const CTA_PROTOINFO_SCTP_VTAG_REPLY: u16 = 3; - -pub const CTA_COUNTERS_UNSPEC: u8 = 0; -pub const CTA_COUNTERS_PACKETS: u8 = 1; /* 64bit counters */ -pub const CTA_COUNTERS_BYTES: u8 = 2; /* 64bit counters */ -pub const CTA_COUNTERS32_PACKETS: u8 = 3; /* old 32bit counters, unused */ -pub const CTA_COUNTERS32_BYTES: u8 = 4; /* old 32bit counters, unused */ -pub const CTA_COUNTERS_PAD: u8 = 5; - -pub const CTA_TIMESTAMP_UNSPEC: u8 = 0; -pub const CTA_TIMESTAMP_START: u8 = 1; -pub const CTA_TIMESTAMP_STOP: u8 = 2; -pub const CTA_TIMESTAMP_PAD: u8 = 3; - -pub const CTA_NAT_UNSPEC: u8 = 0; -pub const CTA_NAT_V4_MINIP: u8 = 1; -pub const CTA_NAT_MINIP: u8 = CTA_NAT_V4_MINIP; -pub const CTA_NAT_V4_MAXIP: u8 = 2; -pub const CTA_NAT_MAXIP: u8 = CTA_NAT_V4_MAXIP; -pub const CTA_NAT_PROTO: u8 = 3; -pub const CTA_NAT_V6_MINIP: u8 = 4; -pub const CTA_NAT_V6_MAXIP: u8 = 5; - -pub const CTA_PROTONAT_UNSPEC: u8 = 0; -pub const CTA_PROTONAT_PORT_MIN: u8 = 1; -pub const CTA_PROTONAT_PORT_MAX: u8 = 2; - -pub const CTA_SEQADJ_UNSPEC: u8 = 0; -pub const CTA_SEQADJ_CORRECTION_POS: u8 = 1; -pub const CTA_SEQADJ_OFFSET_BEFORE: u8 = 2; -pub const CTA_SEQADJ_OFFSET_AFTER: u8 = 3; - -pub const CTA_NAT_SEQ_UNSPEC: u8 = 0; -pub const CTA_NAT_SEQ_CORRECTION_POS: u8 = 1; -pub const CTA_NAT_SEQ_OFFSET_BEFORE: u8 = 2; -pub const CTA_NAT_SEQ_OFFSET_AFTER: u8 = 3; - -pub const CTA_SYNPROXY_UNSPEC: u8 = 0; -pub const CTA_SYNPROXY_ISN: u8 = 1; -pub const CTA_SYNPROXY_ITS: u8 = 2; -pub const CTA_SYNPROXY_TSOFF: u8 = 3; - -pub const CTA_EXPECT_UNSPEC: u8 = 0; -pub const CTA_EXPECT_MASTER: u8 = 1; -pub const CTA_EXPECT_TUPLE: u8 = 2; -pub const CTA_EXPECT_MASK: u8 = 3; -pub const CTA_EXPECT_TIMEOUT: u8 = 4; -pub const CTA_EXPECT_ID: u8 = 5; -pub const CTA_EXPECT_HELP_NAME: u8 = 6; -pub const CTA_EXPECT_ZONE: u8 = 7; -pub const CTA_EXPECT_FLAGS: u8 = 8; -pub const CTA_EXPECT_CLASS: u8 = 9; -pub const CTA_EXPECT_NAT: u8 = 10; -pub const CTA_EXPECT_FN: u8 = 11; - -pub const CTA_EXPECT_NAT_UNSPEC: u8 = 0; -pub const CTA_EXPECT_NAT_DIR: u8 = 1; -pub const CTA_EXPECT_NAT_TUPLE: u8 = 2; - -pub const CTA_SECCTX_UNSPEC: u8 = 0; -pub const CTA_SECCTX_NAME: u8 = 1; - -pub const CTA_STATS_UNSPEC: u16 = 0; -pub const CTA_STATS_SEARCHED: u16 = 1; /* no longer used */ -pub const CTA_STATS_FOUND: u16 = 2; -pub const CTA_STATS_NEW: u16 = 3; /* no longer used */ -pub const CTA_STATS_INVALID: u16 = 4; -pub const CTA_STATS_IGNORE: u16 = 5; /* no longer used */ -pub const CTA_STATS_DELETE: u16 = 6; /* no longer used */ -pub const CTA_STATS_DELETE_LIST: u16 = 7; /* no longer used */ -pub const CTA_STATS_INSERT: u16 = 8; -pub const CTA_STATS_INSERT_FAILED: u16 = 9; -pub const CTA_STATS_DROP: u16 = 10; -pub const CTA_STATS_EARLY_DROP: u16 = 11; -pub const CTA_STATS_ERROR: u16 = 12; -pub const CTA_STATS_SEARCH_RESTART: u16 = 13; -pub const CTA_STATS_CLASH_RESOLVE: u16 = 14; -pub const CTA_STATS_CHAIN_TOOLONG: u16 = 15; - -pub const CTA_STATS_GLOBAL_UNSPEC: u8 = 0; -pub const CTA_STATS_GLOBAL_ENTRIES: u8 = 1; -pub const CTA_STATS_GLOBAL_MAX_ENTRIES: u8 = 2; - -pub const CTA_STATS_EXP_UNSPEC: u8 = 0; -pub const CTA_STATS_EXP_NEW: u8 = 1; -pub const CTA_STATS_EXP_CREATE: u8 = 2; -pub const CTA_STATS_EXP_DELETE: u8 = 3; - -pub const CTA_FILTER_UNSPEC: u8 = 0; -pub const CTA_FILTER_ORIG_FLAGS: u8 = 1; -pub const CTA_FILTER_REPLY_FLAGS: u8 = 2; diff --git a/src/ctnetlink/message.rs b/src/ctnetlink/message.rs index d008a46..5b65024 100644 --- a/src/ctnetlink/message.rs +++ b/src/ctnetlink/message.rs @@ -4,28 +4,34 @@ use netlink_packet_utils::{ nla::DefaultNla, DecodeError, Emitable, Parseable, ParseableParametrized, }; -use crate::{ - buffer::NetfilterBuffer, - constants::{ - IPCTNL_MSG_CT_DELETE, IPCTNL_MSG_CT_GET, IPCTNL_MSG_CT_GET_CTRZERO, - IPCTNL_MSG_CT_GET_DYING, IPCTNL_MSG_CT_GET_STATS, - IPCTNL_MSG_CT_GET_STATS_CPU, IPCTNL_MSG_CT_GET_UNCONFIRMED, - IPCTNL_MSG_CT_NEW, NFNL_SUBSYS_CTNETLINK, - }, +use crate::{buffer::NetfilterBuffer, constants::NFNL_SUBSYS_CTNETLINK}; + +use super::nlas::{ + flow::nla::FlowAttribute, + stat::nla::{StatCpuAttribute, StatGlobalAttribute}, }; -use super::nlas::{flow::nla::FlowNla, stat::nla::StatNla}; +// netflter/nfnetlink_conntrack.h +// There is no definitions in rust-lang/libc +const IPCTNL_MSG_CT_NEW: u8 = 0; +const IPCTNL_MSG_CT_GET: u8 = 1; +const IPCTNL_MSG_CT_DELETE: u8 = 2; +const IPCTNL_MSG_CT_GET_CTRZERO: u8 = 3; +const IPCTNL_MSG_CT_GET_STATS_CPU: u8 = 4; +const IPCTNL_MSG_CT_GET_STATS: u8 = 5; +const IPCTNL_MSG_CT_GET_DYING: u8 = 6; +const IPCTNL_MSG_CT_GET_UNCONFIRMED: u8 = 7; #[derive(Debug, PartialEq, Eq, Clone)] pub enum CtNetlinkMessage { - New(Vec), - Get(Option>), - Delete(Vec), - GetCrtZero(Option>), - GetStatsCPU(Option>), - GetStats(Option>), - GetDying(Option>), - GetUnconfirmed(Option>), + New(Vec), + Get(Option>), + Delete(Vec), + GetCrtZero(Option>), + GetStatsCPU(Option>), + GetStats(Option>), + GetDying(Option>), + GetUnconfirmed(Option>), Other { message_type: u8, nlas: Vec, @@ -137,30 +143,32 @@ impl<'a, T: AsRef<[u8]> + ?Sized> ) -> Result { Ok(match message_type { IPCTNL_MSG_CT_NEW => { - let nlas = - buf.parse_all_nlas(|nla_buf| FlowNla::parse(&nla_buf))?; + let nlas = buf + .parse_all_nlas(|nla_buf| FlowAttribute::parse(&nla_buf))?; CtNetlinkMessage::New(nlas) } IPCTNL_MSG_CT_GET => { if buf.payload().is_empty() { CtNetlinkMessage::Get(None) } else { - let nlas = - buf.parse_all_nlas(|nla_buf| FlowNla::parse(&nla_buf))?; + let nlas = buf.parse_all_nlas(|nla_buf| { + FlowAttribute::parse(&nla_buf) + })?; CtNetlinkMessage::Get(Some(nlas)) } } IPCTNL_MSG_CT_DELETE => { - let nlas = - buf.parse_all_nlas(|nla_buf| FlowNla::parse(&nla_buf))?; + let nlas = buf + .parse_all_nlas(|nla_buf| FlowAttribute::parse(&nla_buf))?; CtNetlinkMessage::Delete(nlas) } IPCTNL_MSG_CT_GET_CTRZERO => { if buf.payload().is_empty() { CtNetlinkMessage::GetCrtZero(None) } else { - let nlas = - buf.parse_all_nlas(|nla_buf| FlowNla::parse(&nla_buf))?; + let nlas = buf.parse_all_nlas(|nla_buf| { + FlowAttribute::parse(&nla_buf) + })?; CtNetlinkMessage::GetCrtZero(Some(nlas)) } } @@ -168,8 +176,9 @@ impl<'a, T: AsRef<[u8]> + ?Sized> if buf.payload().is_empty() { CtNetlinkMessage::GetStatsCPU(None) } else { - let nlas = - buf.parse_all_nlas(|nla_buf| StatNla::parse(&nla_buf))?; + let nlas = buf.parse_all_nlas(|nla_buf| { + StatCpuAttribute::parse(&nla_buf) + })?; CtNetlinkMessage::GetStatsCPU(Some(nlas)) } } @@ -177,8 +186,9 @@ impl<'a, T: AsRef<[u8]> + ?Sized> if buf.payload().is_empty() { CtNetlinkMessage::GetStats(None) } else { - let nlas = - buf.parse_all_nlas(|nla_buf| StatNla::parse(&nla_buf))?; + let nlas = buf.parse_all_nlas(|nla_buf| { + StatGlobalAttribute::parse(&nla_buf) + })?; CtNetlinkMessage::GetStats(Some(nlas)) } } @@ -186,8 +196,9 @@ impl<'a, T: AsRef<[u8]> + ?Sized> if buf.payload().is_empty() { CtNetlinkMessage::GetDying(None) } else { - let nlas = - buf.parse_all_nlas(|nla_buf| FlowNla::parse(&nla_buf))?; + let nlas = buf.parse_all_nlas(|nla_buf| { + FlowAttribute::parse(&nla_buf) + })?; CtNetlinkMessage::GetDying(Some(nlas)) } } @@ -195,8 +206,9 @@ impl<'a, T: AsRef<[u8]> + ?Sized> if buf.payload().is_empty() { CtNetlinkMessage::GetUnconfirmed(None) } else { - let nlas = - buf.parse_all_nlas(|nla_buf| FlowNla::parse(&nla_buf))?; + let nlas = buf.parse_all_nlas(|nla_buf| { + FlowAttribute::parse(&nla_buf) + })?; CtNetlinkMessage::GetUnconfirmed(Some(nlas)) } } diff --git a/src/ctnetlink/mod.rs b/src/ctnetlink/mod.rs index ebb009a..82a95bc 100644 --- a/src/ctnetlink/mod.rs +++ b/src/ctnetlink/mod.rs @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT -pub mod message; +mod message; +pub use message::CtNetlinkMessage; pub mod nlas; diff --git a/src/ctnetlink/nlas/ct_attr.rs b/src/ctnetlink/nlas/ct_attr.rs index c124462..8feb708 100644 --- a/src/ctnetlink/nlas/ct_attr.rs +++ b/src/ctnetlink/nlas/ct_attr.rs @@ -6,14 +6,14 @@ use netlink_packet_utils::{ }; #[derive(Debug, Clone, PartialEq, Eq)] -pub struct CtAttr { - pub nested: Option>, +pub struct ConntrackAttribute { + pub nested: Option>, pub attr_type: u16, pub length: u16, pub value: Option>, } -impl Nla for CtAttr { +impl Nla for ConntrackAttribute { fn value_len(&self) -> usize { (self.length as usize) - NLA_HEADER_SIZE } @@ -47,7 +47,7 @@ impl Nla for CtAttr { } impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> - for CtAttr + for ConntrackAttribute { fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { let length = buf.length(); @@ -67,14 +67,14 @@ impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> } nested_attrs.push(attr); } - Ok(CtAttr { + Ok(ConntrackAttribute { nested: Some(nested_attrs), length, attr_type, value: None, }) } else { - Ok(CtAttr { + Ok(ConntrackAttribute { nested: None, attr_type, // padding bytes are not included @@ -85,7 +85,7 @@ impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> } } -impl CtAttr { +impl ConntrackAttribute { pub fn is_nested(&self) -> bool { self.nested.is_some() } @@ -93,7 +93,7 @@ impl CtAttr { #[derive(Debug, Clone)] pub struct CtAttrBuilder { - nested: Option>, + nested: Option>, attr_type: u16, value: Option>, length: u16, @@ -108,7 +108,7 @@ impl CtAttrBuilder { length: 0, } } - pub fn nested_attr(mut self, attr: CtAttr) -> Self { + pub fn nested_attr(mut self, attr: ConntrackAttribute) -> Self { self.length += attr.length; if attr.length % 4 != 0 { self.length += 4 - (attr.length % 4); @@ -128,8 +128,8 @@ impl CtAttrBuilder { self } - pub fn build(&self) -> CtAttr { - CtAttr { + pub fn build(&self) -> ConntrackAttribute { + ConntrackAttribute { nested: self.nested.clone(), attr_type: self.attr_type, length: self.length + NLA_HEADER_SIZE as u16, @@ -142,24 +142,28 @@ impl CtAttrBuilder { mod tests { use netlink_packet_utils::{nla::NlaBuffer, Emitable, Parseable}; - use crate::{ - constants::{ - CTA_IP_V4_DST, CTA_IP_V4_SRC, CTA_PROTO_DST_PORT, CTA_PROTO_NUM, - CTA_PROTO_SRC_PORT, CTA_TUPLE_IP, CTA_TUPLE_PROTO, - }, - ctnetlink::nlas::ct_attr::CtAttr, - }; + use crate::ctnetlink::nlas::ct_attr::ConntrackAttribute; const DATA: [u8; 48] = [ 20, 0, 1, 128, 8, 0, 1, 0, 1, 2, 3, 4, 8, 0, 2, 0, 1, 2, 3, 4, 28, 0, 2, 128, 5, 0, 1, 0, 17, 0, 0, 0, 6, 0, 2, 0, 220, 210, 0, 0, 6, 0, 3, 0, 7, 108, 0, 0, ]; + const CTA_IP_V4_SRC: u16 = 1; + const CTA_IP_V4_DST: u16 = 2; + + const CTA_TUPLE_IP: u16 = 1; + const CTA_TUPLE_PROTO: u16 = 2; + + const CTA_PROTO_NUM: u16 = 1; + const CTA_PROTO_SRC_PORT: u16 = 2; + const CTA_PROTO_DST_PORT: u16 = 3; + #[test] fn test_ct_attr_parse() { let buf = NlaBuffer::new(&DATA); // first - let ct_attr = CtAttr::parse(&buf).unwrap(); + let ct_attr = ConntrackAttribute::parse(&buf).unwrap(); assert_eq!(ct_attr.length, 20); assert!(ct_attr.is_nested()); assert_eq!(ct_attr.attr_type, CTA_TUPLE_IP); @@ -174,7 +178,7 @@ mod tests { // second let buf = NlaBuffer::new(&DATA[(ct_attr.length as usize)..]); - let ct_attr = CtAttr::parse(&buf).unwrap(); + let ct_attr = ConntrackAttribute::parse(&buf).unwrap(); assert_eq!(ct_attr.length, 28); assert!(ct_attr.is_nested()); assert_eq!(ct_attr.attr_type, CTA_TUPLE_PROTO); @@ -188,7 +192,7 @@ mod tests { #[test] fn test_ct_attr_emit() { let buf = NlaBuffer::new(&DATA); - let ct_attr = CtAttr::parse(&buf).unwrap(); + let ct_attr = ConntrackAttribute::parse(&buf).unwrap(); assert_eq!(ct_attr.length, 20); assert!(ct_attr.is_nested()); assert_eq!(ct_attr.attr_type, CTA_TUPLE_IP); diff --git a/src/ctnetlink/nlas/flow/ip_tuple.rs b/src/ctnetlink/nlas/flow/ip_tuple.rs index 89995a9..5e6582d 100644 --- a/src/ctnetlink/nlas/flow/ip_tuple.rs +++ b/src/ctnetlink/nlas/flow/ip_tuple.rs @@ -9,14 +9,19 @@ use netlink_packet_utils::{ DecodeError, Parseable, }; -use crate::{ - constants::{ - CTA_IP_V4_DST, CTA_IP_V4_SRC, CTA_IP_V6_DST, CTA_IP_V6_SRC, - CTA_PROTO_DST_PORT, CTA_PROTO_NUM, CTA_PROTO_SRC_PORT, CTA_TUPLE_IP, - CTA_TUPLE_PROTO, - }, - ctnetlink::nlas::ct_attr::{CtAttr, CtAttrBuilder}, -}; +use crate::ctnetlink::nlas::ct_attr::{ConntrackAttribute, CtAttrBuilder}; + +const CTA_IP_V4_SRC: u16 = 1; +const CTA_IP_V4_DST: u16 = 2; +const CTA_IP_V6_SRC: u16 = 3; +const CTA_IP_V6_DST: u16 = 4; + +const CTA_TUPLE_IP: u16 = 1; +const CTA_TUPLE_PROTO: u16 = 2; + +const CTA_PROTO_NUM: u16 = 1; +const CTA_PROTO_SRC_PORT: u16 = 2; +const CTA_PROTO_DST_PORT: u16 = 3; #[derive(Debug, PartialEq, Eq, Clone)] pub enum TupleNla { @@ -51,7 +56,7 @@ impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> for TupleNla { fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { - let attr = CtAttr::parse(buf)?; + let attr = ConntrackAttribute::parse(buf)?; match attr.attr_type { CTA_TUPLE_IP => Ok(TupleNla::Ip(IpTuple::try_from(attr)?)), CTA_TUPLE_PROTO => { @@ -125,7 +130,7 @@ impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> for IpTuple { fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { - let ip_tuple = CtAttr::parse(buf)?; + let ip_tuple = ConntrackAttribute::parse(buf)?; let mut builder = IpTupleBuilder::default(); if let Some(attrs) = ip_tuple.nested { @@ -153,10 +158,10 @@ impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> } } -impl TryFrom for IpTuple { +impl TryFrom for IpTuple { type Error = DecodeError; - fn try_from(attr: CtAttr) -> Result { + fn try_from(attr: ConntrackAttribute) -> Result { if attr.attr_type != CTA_TUPLE_IP { return Err(DecodeError::from("CTA_TUPLE_IP is expected")); } @@ -223,10 +228,10 @@ pub struct ProtocolTuple { pub protocol: u8, } -impl TryFrom for ProtocolTuple { +impl TryFrom for ProtocolTuple { type Error = DecodeError; - fn try_from(attr: CtAttr) -> Result { + fn try_from(attr: ConntrackAttribute) -> Result { if attr.attr_type != CTA_TUPLE_PROTO { return Err(DecodeError::from("CTA_TUPLE_PROTO is expected")); } @@ -301,7 +306,7 @@ impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> for ProtocolTuple { fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { - let proto_tuple = CtAttr::parse(buf)?; + let proto_tuple = ConntrackAttribute::parse(buf)?; let mut builder = ProtocolTupleBuilder::default(); if let Some(attrs) = proto_tuple.nested { diff --git a/src/ctnetlink/nlas/flow/nla.rs b/src/ctnetlink/nlas/flow/nla.rs index 9d72fa9..601624e 100644 --- a/src/ctnetlink/nlas/flow/nla.rs +++ b/src/ctnetlink/nlas/flow/nla.rs @@ -6,17 +6,22 @@ use netlink_packet_utils::{ Emitable, Parseable, }; -use crate::constants::{ - CTA_ID, CTA_MARK, CTA_PROTOINFO, CTA_STATUS, CTA_TIMEOUT, CTA_TUPLE_ORIG, - CTA_TUPLE_REPLY, CTA_USE, -}; - use super::{ ip_tuple::TupleNla, protocol_info::ProtocolInfo, status::ConnectionStatus, }; +pub(super) const CTA_STATUS: u16 = 3; + +const CTA_TUPLE_ORIG: u16 = 1; +const CTA_TUPLE_REPLY: u16 = 2; +const CTA_PROTOINFO: u16 = 4; +const CTA_TIMEOUT: u16 = 7; +const CTA_MARK: u16 = 8; +const CTA_USE: u16 = 11; +const CTA_ID: u16 = 12; + #[derive(Debug, PartialEq, Eq, Clone)] -pub enum FlowNla { +pub enum FlowAttribute { Orig(Vec), Reply(Vec), Status(ConnectionStatus), @@ -28,60 +33,60 @@ pub enum FlowNla { Other(DefaultNla), } -impl Nla for FlowNla { +impl Nla for FlowAttribute { fn value_len(&self) -> usize { match self { - FlowNla::Orig(attrs) => { + FlowAttribute::Orig(attrs) => { attrs.iter().fold(0, |l, attr| l + attr.buffer_len()) } - FlowNla::Reply(attrs) => { + FlowAttribute::Reply(attrs) => { attrs.iter().fold(0, |l, attr| l + attr.buffer_len()) } - FlowNla::Status(attr) => attr.value_len(), - FlowNla::ProtocolInfo(attr) => attr.value_len(), - FlowNla::Timeout(_) => 4, - FlowNla::Mark(_) => 4, - FlowNla::Use(_) => 4, - FlowNla::Id(_) => 4, - FlowNla::Other(attr) => attr.value_len(), + FlowAttribute::Status(attr) => attr.value_len(), + FlowAttribute::ProtocolInfo(attr) => attr.value_len(), + FlowAttribute::Timeout(_) => 4, + FlowAttribute::Mark(_) => 4, + FlowAttribute::Use(_) => 4, + FlowAttribute::Id(_) => 4, + FlowAttribute::Other(attr) => attr.value_len(), } } fn kind(&self) -> u16 { match self { - FlowNla::Orig(_) => CTA_TUPLE_ORIG | NLA_F_NESTED, - FlowNla::Reply(_) => CTA_TUPLE_REPLY | NLA_F_NESTED, - FlowNla::Status(_) => CTA_STATUS, - FlowNla::ProtocolInfo(_) => CTA_PROTOINFO | NLA_F_NESTED, - FlowNla::Timeout(_) => CTA_TIMEOUT, - FlowNla::Mark(_) => CTA_MARK, - FlowNla::Use(_) => CTA_USE, - FlowNla::Id(_) => CTA_ID, - FlowNla::Other(attr) => attr.kind(), + FlowAttribute::Orig(_) => CTA_TUPLE_ORIG | NLA_F_NESTED, + FlowAttribute::Reply(_) => CTA_TUPLE_REPLY | NLA_F_NESTED, + FlowAttribute::Status(_) => CTA_STATUS, + FlowAttribute::ProtocolInfo(_) => CTA_PROTOINFO | NLA_F_NESTED, + FlowAttribute::Timeout(_) => CTA_TIMEOUT, + FlowAttribute::Mark(_) => CTA_MARK, + FlowAttribute::Use(_) => CTA_USE, + FlowAttribute::Id(_) => CTA_ID, + FlowAttribute::Other(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { - FlowNla::Orig(attrs) => { + FlowAttribute::Orig(attrs) => { attrs.as_slice().emit(buffer); } - FlowNla::Reply(attrs) => { + FlowAttribute::Reply(attrs) => { attrs.as_slice().emit(buffer); } - FlowNla::Status(status) => status.emit_value(buffer), - FlowNla::ProtocolInfo(info) => info.emit_value(buffer), - FlowNla::Timeout(val) => BigEndian::write_u32(buffer, *val), - FlowNla::Mark(val) => BigEndian::write_u32(buffer, *val), - FlowNla::Use(val) => BigEndian::write_u32(buffer, *val), - FlowNla::Id(val) => BigEndian::write_u32(buffer, *val), - FlowNla::Other(attr) => attr.emit_value(buffer), + FlowAttribute::Status(status) => status.emit_value(buffer), + FlowAttribute::ProtocolInfo(info) => info.emit_value(buffer), + FlowAttribute::Timeout(val) => BigEndian::write_u32(buffer, *val), + FlowAttribute::Mark(val) => BigEndian::write_u32(buffer, *val), + FlowAttribute::Use(val) => BigEndian::write_u32(buffer, *val), + FlowAttribute::Id(val) => BigEndian::write_u32(buffer, *val), + FlowAttribute::Other(attr) => attr.emit_value(buffer), } } } impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> - for FlowNla + for FlowAttribute { fn parse( buf: &NlaBuffer<&'buffer T>, @@ -89,31 +94,31 @@ impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> let kind = buf.kind(); let payload = buf.value(); let nla = match kind { - CTA_TUPLE_ORIG => FlowNla::Orig({ + CTA_TUPLE_ORIG => FlowAttribute::Orig({ let b = NlaBuffer::new(payload); let ip = TupleNla::parse(&b)?; let b = NlaBuffer::new(&payload[ip.buffer_len()..]); let proto = TupleNla::parse(&b)?; vec![ip, proto] }), - CTA_TUPLE_REPLY => FlowNla::Reply({ + CTA_TUPLE_REPLY => FlowAttribute::Reply({ let b = NlaBuffer::new(payload); let ip = TupleNla::parse(&b)?; let b = NlaBuffer::new(&payload[ip.buffer_len()..]); let proto = TupleNla::parse(&b)?; vec![ip, proto] }), - CTA_STATUS => FlowNla::Status({ - ConnectionStatus::from(BigEndian::read_u32(payload)) - }), - CTA_PROTOINFO => { - FlowNla::ProtocolInfo(ProtocolInfo::parse_from_bytes(payload)?) - } - CTA_TIMEOUT => FlowNla::Timeout(BigEndian::read_u32(payload)), - CTA_MARK => FlowNla::Mark(BigEndian::read_u32(payload)), - CTA_USE => FlowNla::Use(BigEndian::read_u32(payload)), - CTA_ID => FlowNla::Id(BigEndian::read_u32(payload)), - _ => FlowNla::Other(DefaultNla::parse(buf)?), + CTA_STATUS => FlowAttribute::Status(ConnectionStatus::from( + BigEndian::read_u32(payload), + )), + CTA_PROTOINFO => FlowAttribute::ProtocolInfo( + ProtocolInfo::parse_from_bytes(payload)?, + ), + CTA_TIMEOUT => FlowAttribute::Timeout(BigEndian::read_u32(payload)), + CTA_MARK => FlowAttribute::Mark(BigEndian::read_u32(payload)), + CTA_USE => FlowAttribute::Use(BigEndian::read_u32(payload)), + CTA_ID => FlowAttribute::Id(BigEndian::read_u32(payload)), + _ => FlowAttribute::Other(DefaultNla::parse(buf)?), }; Ok(nla) } diff --git a/src/ctnetlink/nlas/flow/protocol_info.rs b/src/ctnetlink/nlas/flow/protocol_info.rs index 65b51ff..2e23efa 100644 --- a/src/ctnetlink/nlas/flow/protocol_info.rs +++ b/src/ctnetlink/nlas/flow/protocol_info.rs @@ -4,26 +4,30 @@ use std::convert::TryFrom; use byteorder::{ByteOrder, NativeEndian}; use netlink_packet_utils::{ + buffer, nla::{Nla, NlaBuffer, NLA_F_NESTED}, DecodeError, Emitable, Parseable, }; -use crate::{ - constants::{ - CTA_PROTOINFO_DCCP, CTA_PROTOINFO_SCTP, CTA_PROTOINFO_TCP, - CTA_PROTOINFO_TCP_FLAGS_ORIGINAL, CTA_PROTOINFO_TCP_FLAGS_REPLY, - CTA_PROTOINFO_TCP_STATE, CTA_PROTOINFO_TCP_WSCALE_ORIGINAL, - CTA_PROTOINFO_TCP_WSCALE_REPLY, CTA_PROTOINFO_UNSPEC, - }, - ctnetlink::nlas::ct_attr::{CtAttr, CtAttrBuilder}, -}; +use crate::ctnetlink::nlas::ct_attr::{ConntrackAttribute, CtAttrBuilder}; + +const CTA_PROTOINFO_UNSPEC: u16 = 0; +const CTA_PROTOINFO_TCP: u16 = 1; +const CTA_PROTOINFO_DCCP: u16 = 2; +const CTA_PROTOINFO_SCTP: u16 = 3; + +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(Debug, PartialEq, Eq, Clone)] pub enum ProtocolInfo { Tcp(ProtocolInfoTcp), - Dccp(CtAttr), - Sctp(CtAttr), - Other(CtAttr), + Dccp(ConntrackAttribute), + Sctp(ConntrackAttribute), + Other(ConntrackAttribute), } impl ProtocolInfo { @@ -68,7 +72,7 @@ impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> for ProtocolInfo { fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { - let attr = CtAttr::parse(buf)?; + let attr = ConntrackAttribute::parse(buf)?; match attr.attr_type { CTA_PROTOINFO_TCP => { @@ -81,12 +85,34 @@ impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> } } +buffer!(ProtocolInfoTcpBuffer { + state: (u8, 0), + wscale_original: (u8, 1), + wscale_reply: (u8, 2), + flags_original: (u16, 3..5), + flags_reply: (u16, 5..7), +}); + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> + for ProtocolInfoTcp +{ + fn parse(buf: &ProtocolInfoTcpBuffer<&'a T>) -> Result { + Ok(ProtocolInfoTcp { + state: buf.state(), + wscale_original: buf.wscale_original(), + wscale_reply: buf.wscale_reply(), + flags_original: buf.flags_original(), + flags_reply: buf.flags_reply(), + }) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct ProtocolInfoTcp { pub state: u8, pub wscale_original: u8, pub wscale_reply: u8, - pub flgas_original: u16, + pub flags_original: u16, pub flags_reply: u16, } @@ -102,7 +128,7 @@ impl Nla for ProtocolInfoTcp { fn emit_value(&self, buffer: &mut [u8]) { let mut flag_orig = [0u8; 2]; let mut flag_reply = [0u8; 2]; - NativeEndian::write_u16(&mut flag_orig, self.flgas_original); + NativeEndian::write_u16(&mut flag_orig, self.flags_original); NativeEndian::write_u16(&mut flag_reply, self.flags_reply); let info = CtAttrBuilder::new(CTA_PROTOINFO_TCP) @@ -136,10 +162,10 @@ impl Nla for ProtocolInfoTcp { } } -impl TryFrom for ProtocolInfoTcp { +impl TryFrom for ProtocolInfoTcp { type Error = DecodeError; - fn try_from(attr: CtAttr) -> Result { + fn try_from(attr: ConntrackAttribute) -> Result { if let Some(attrs) = attr.nested { let mut info = ProtocolInfoTcp::default(); for attr in attrs.iter() { @@ -157,7 +183,9 @@ impl TryFrom for ProtocolInfoTcp { CTA_PROTOINFO_TCP_WSCALE_ORIGINAL => { if let Some(v) = &attr.value { if v.len() != 1 { - return Err(DecodeError::from("invalid CTA_PROTOINFO_TCP_WSCALE_ORIGINAL value")); + return Err(DecodeError::from( + "invalid CTA_PROTOINFO_TCP_WSCALE_ORIGINAL value", + )); } info.wscale_original = v[0]; } @@ -165,14 +193,16 @@ impl TryFrom for ProtocolInfoTcp { CTA_PROTOINFO_TCP_WSCALE_REPLY => { if let Some(v) = &attr.value { if v.len() != 1 { - return Err(DecodeError::from("invalid CTA_PROTOINFO_TCP_WSCALE_REPLY value")); + return Err(DecodeError::from( + "invalid CTA_PROTOINFO_TCP_WSCALE_REPLY value", + )); } info.wscale_reply = v[0]; } } CTA_PROTOINFO_TCP_FLAGS_ORIGINAL => { if let Some(v) = &attr.value { - info.flgas_original = NativeEndian::read_u16(v); + info.flags_original = NativeEndian::read_u16(v); } } CTA_PROTOINFO_TCP_FLAGS_REPLY => { @@ -213,7 +243,7 @@ mod tests { assert_eq!(info.state, 3); assert_eq!(info.wscale_original, 7); assert_eq!(info.wscale_reply, 7); - assert_eq!(info.flgas_original, 35); + assert_eq!(info.flags_original, 35); assert_eq!(info.flags_reply, 35); } else { panic!("invalid protocol info") @@ -228,7 +258,7 @@ mod tests { assert_eq!(info.state, 3); assert_eq!(info.wscale_original, 7); assert_eq!(info.wscale_reply, 7); - assert_eq!(info.flgas_original, 35); + assert_eq!(info.flags_original, 35); assert_eq!(info.flags_reply, 35); } else { panic!("invalid protocol info") diff --git a/src/ctnetlink/nlas/flow/status.rs b/src/ctnetlink/nlas/flow/status.rs index 5e129bd..a5d21af 100644 --- a/src/ctnetlink/nlas/flow/status.rs +++ b/src/ctnetlink/nlas/flow/status.rs @@ -1,27 +1,30 @@ // SPDX-License-Identifier: MIT +use bitflags::bitflags; use byteorder::{BigEndian, ByteOrder}; use netlink_packet_utils::nla::Nla; -use crate::constants::CTA_STATUS; +use super::nla::CTA_STATUS; +bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum ConnectionStatusFlag { - Offload = 1 << 14, - Helper = 1 << 13, - Untracked = 1 << 12, - Template = 1 << 11, - FixedTimeout = 1 << 10, - Dying = 1 << 9, - DestinationNATDone = 1 << 8, - SourceNATDone = 1 << 7, - SequenceAdjust = 1 << 6, - DestinationNAT = 1 << 5, - SourceNAT = 1 << 4, - Confirmed = 1 << 3, - Assured = 1 << 2, - SeenReply = 1 << 1, - Expected = 1, + pub struct ConnectionStatusFlag: u32 { + const Expected = 1; + const SeenReply = 1 << 1; + const Assured = 1 << 2; + const Confirmed = 1 << 3; + const SourceNAT = 1 << 4; + const DestinationNAT = 1 << 5; + const SequenceAdjust = 1 << 6; + const SourceNATDone = 1 << 7; + const DestinationNATDone = 1 << 8; + const Dying = 1 << 9; + const FixedTimeout = 1 << 10; + const Template = 1 << 11; + const Untracked = 1 << 12; + const Helper = 1 << 13; + const Offload = 1 << 14; + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] @@ -35,11 +38,11 @@ impl ConnectionStatus { } pub fn set(&mut self, flag: ConnectionStatusFlag) { - self.inner += flag as u32 + self.inner += flag.bits(); } pub fn is_set(&self, flag: ConnectionStatusFlag) -> bool { - self.inner & flag as u32 == flag as u32 + self.inner & flag.bits() == flag.bits() } } @@ -50,10 +53,8 @@ impl From for ConnectionStatus { } impl From for ConnectionStatus { - fn from(value: ConnectionStatusFlag) -> Self { - Self { - inner: value as u32, - } + fn from(flag: ConnectionStatusFlag) -> Self { + Self { inner: flag.bits() } } } @@ -70,3 +71,23 @@ impl Nla for ConnectionStatus { BigEndian::write_u32(buffer, self.inner); } } + +#[cfg(test)] +mod tests { + use super::{ConnectionStatus, ConnectionStatusFlag}; + + #[test] + fn test_connection_status_flag_set() { + let mut status = ConnectionStatus::from(ConnectionStatusFlag::Expected); + assert!(status.is_set(ConnectionStatusFlag::Expected)); + + status.set(ConnectionStatusFlag::Assured); + assert!(status.is_set(ConnectionStatusFlag::Assured)); + + assert_eq!( + status.get(), + ConnectionStatusFlag::Assured.bits() + + ConnectionStatusFlag::Expected.bits() + ); + } +} diff --git a/src/ctnetlink/nlas/stat/nla.rs b/src/ctnetlink/nlas/stat/nla.rs index a57a80e..f68b617 100644 --- a/src/ctnetlink/nlas/stat/nla.rs +++ b/src/ctnetlink/nlas/stat/nla.rs @@ -6,23 +6,24 @@ use netlink_packet_utils::{ Parseable, }; -use crate::constants::{ - CTA_STATS_CHAIN_TOOLONG, CTA_STATS_CLASH_RESOLVE, CTA_STATS_DELETE, - CTA_STATS_DELETE_LIST, CTA_STATS_DROP, CTA_STATS_EARLY_DROP, - CTA_STATS_ERROR, CTA_STATS_FOUND, CTA_STATS_IGNORE, CTA_STATS_INSERT, - CTA_STATS_INSERT_FAILED, CTA_STATS_INVALID, CTA_STATS_NEW, - CTA_STATS_SEARCHED, CTA_STATS_SEARCH_RESTART, -}; +const CTA_STATS_FOUND: u16 = 2; +const CTA_STATS_INVALID: u16 = 4; +const CTA_STATS_INSERT: u16 = 8; +const CTA_STATS_INSERT_FAILED: u16 = 9; +const CTA_STATS_DROP: u16 = 10; +const CTA_STATS_EARLY_DROP: u16 = 11; +const CTA_STATS_ERROR: u16 = 12; +const CTA_STATS_SEARCH_RESTART: u16 = 13; +const CTA_STATS_CLASH_RESOLVE: u16 = 14; +const CTA_STATS_CHAIN_TOOLONG: u16 = 15; + +const CTA_STATS_GLOBAL_ENTRIES: u16 = 1; +const CTA_STATS_GLOBAL_MAX_ENTRIES: u16 = 2; #[derive(Debug, PartialEq, Eq, Clone)] -pub enum StatNla { - Searched(u32), // no longer used +pub enum StatCpuAttribute { Found(u32), - New(u32), // no longer used Invalid(u32), - Ignore(u32), // no longer used - Delete(u32), // no longer used - DeleteList(u32), // no longer used Insert(u32), InsertFailed(u32), Drop(u32), @@ -34,73 +35,70 @@ pub enum StatNla { Other(DefaultNla), } -impl Nla for StatNla { +impl Nla for StatCpuAttribute { fn value_len(&self) -> usize { match self { - StatNla::Searched(_) => 4, - StatNla::Found(_) => 4, - StatNla::New(_) => 4, - StatNla::Invalid(_) => 4, - StatNla::Ignore(_) => 4, - StatNla::Delete(_) => 4, - StatNla::DeleteList(_) => 4, - StatNla::Insert(_) => 4, - StatNla::InsertFailed(_) => 4, - StatNla::Drop(_) => 4, - StatNla::EarlyDrop(_) => 4, - StatNla::Error(_) => 4, - StatNla::SearchRestart(_) => 4, - StatNla::ClashResolve(_) => 4, - StatNla::ChainTooLong(_) => 4, - StatNla::Other(nla) => nla.value_len(), + StatCpuAttribute::Found(_) => 4, + StatCpuAttribute::Invalid(_) => 4, + StatCpuAttribute::Insert(_) => 4, + StatCpuAttribute::InsertFailed(_) => 4, + StatCpuAttribute::Drop(_) => 4, + StatCpuAttribute::EarlyDrop(_) => 4, + StatCpuAttribute::Error(_) => 4, + StatCpuAttribute::SearchRestart(_) => 4, + StatCpuAttribute::ClashResolve(_) => 4, + StatCpuAttribute::ChainTooLong(_) => 4, + StatCpuAttribute::Other(nla) => nla.value_len(), } } fn kind(&self) -> u16 { match self { - StatNla::Searched(_) => CTA_STATS_SEARCHED, - StatNla::Found(_) => CTA_STATS_FOUND, - StatNla::New(_) => CTA_STATS_NEW, - StatNla::Invalid(_) => CTA_STATS_INVALID, - StatNla::Ignore(_) => CTA_STATS_IGNORE, - StatNla::Delete(_) => CTA_STATS_DELETE, - StatNla::DeleteList(_) => CTA_STATS_DELETE_LIST, - StatNla::Insert(_) => CTA_STATS_INSERT, - StatNla::InsertFailed(_) => CTA_STATS_INSERT_FAILED, - StatNla::Drop(_) => CTA_STATS_DROP, - StatNla::EarlyDrop(_) => CTA_STATS_EARLY_DROP, - StatNla::Error(_) => CTA_STATS_ERROR, - StatNla::SearchRestart(_) => CTA_STATS_SEARCH_RESTART, - StatNla::ClashResolve(_) => CTA_STATS_CLASH_RESOLVE, - StatNla::ChainTooLong(_) => CTA_STATS_CHAIN_TOOLONG, - StatNla::Other(nla) => nla.kind(), + StatCpuAttribute::Found(_) => CTA_STATS_FOUND, + StatCpuAttribute::Invalid(_) => CTA_STATS_INVALID, + StatCpuAttribute::Insert(_) => CTA_STATS_INSERT, + StatCpuAttribute::InsertFailed(_) => CTA_STATS_INSERT_FAILED, + StatCpuAttribute::Drop(_) => CTA_STATS_DROP, + StatCpuAttribute::EarlyDrop(_) => CTA_STATS_EARLY_DROP, + StatCpuAttribute::Error(_) => CTA_STATS_ERROR, + StatCpuAttribute::SearchRestart(_) => CTA_STATS_SEARCH_RESTART, + StatCpuAttribute::ClashResolve(_) => CTA_STATS_CLASH_RESOLVE, + StatCpuAttribute::ChainTooLong(_) => CTA_STATS_CHAIN_TOOLONG, + StatCpuAttribute::Other(nla) => nla.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { - StatNla::Searched(val) => BigEndian::write_u32(buffer, *val), - StatNla::Found(val) => BigEndian::write_u32(buffer, *val), - StatNla::New(val) => BigEndian::write_u32(buffer, *val), - StatNla::Invalid(val) => BigEndian::write_u32(buffer, *val), - StatNla::Ignore(val) => BigEndian::write_u32(buffer, *val), - StatNla::Delete(val) => BigEndian::write_u32(buffer, *val), - StatNla::DeleteList(val) => BigEndian::write_u32(buffer, *val), - StatNla::Insert(val) => BigEndian::write_u32(buffer, *val), - StatNla::InsertFailed(val) => BigEndian::write_u32(buffer, *val), - StatNla::Drop(val) => BigEndian::write_u32(buffer, *val), - StatNla::EarlyDrop(val) => BigEndian::write_u32(buffer, *val), - StatNla::Error(val) => BigEndian::write_u32(buffer, *val), - StatNla::SearchRestart(val) => BigEndian::write_u32(buffer, *val), - StatNla::ClashResolve(val) => BigEndian::write_u32(buffer, *val), - StatNla::ChainTooLong(val) => BigEndian::write_u32(buffer, *val), - StatNla::Other(attr) => attr.emit_value(buffer), + StatCpuAttribute::Found(val) => BigEndian::write_u32(buffer, *val), + StatCpuAttribute::Invalid(val) => { + BigEndian::write_u32(buffer, *val) + } + StatCpuAttribute::Insert(val) => BigEndian::write_u32(buffer, *val), + StatCpuAttribute::InsertFailed(val) => { + BigEndian::write_u32(buffer, *val) + } + StatCpuAttribute::Drop(val) => BigEndian::write_u32(buffer, *val), + StatCpuAttribute::EarlyDrop(val) => { + BigEndian::write_u32(buffer, *val) + } + StatCpuAttribute::Error(val) => BigEndian::write_u32(buffer, *val), + StatCpuAttribute::SearchRestart(val) => { + BigEndian::write_u32(buffer, *val) + } + StatCpuAttribute::ClashResolve(val) => { + BigEndian::write_u32(buffer, *val) + } + StatCpuAttribute::ChainTooLong(val) => { + BigEndian::write_u32(buffer, *val) + } + StatCpuAttribute::Other(attr) => attr.emit_value(buffer), } } } impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> - for StatNla + for StatCpuAttribute { fn parse( buf: &NlaBuffer<&'buffer T>, @@ -108,36 +106,95 @@ impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> let kind = buf.kind(); let payload = buf.value(); let nla = match kind { - CTA_STATS_SEARCHED => { - StatNla::Searched(BigEndian::read_u32(payload)) - } - CTA_STATS_FOUND => StatNla::Found(BigEndian::read_u32(payload)), - CTA_STATS_NEW => StatNla::New(BigEndian::read_u32(payload)), - CTA_STATS_INVALID => StatNla::Invalid(BigEndian::read_u32(payload)), - CTA_STATS_IGNORE => StatNla::Ignore(BigEndian::read_u32(payload)), - CTA_STATS_DELETE => StatNla::Delete(BigEndian::read_u32(payload)), - CTA_STATS_DELETE_LIST => { - StatNla::DeleteList(BigEndian::read_u32(payload)) - } - CTA_STATS_INSERT => StatNla::Insert(BigEndian::read_u32(payload)), + CTA_STATS_FOUND => { + StatCpuAttribute::Found(BigEndian::read_u32(payload)) + } + CTA_STATS_INVALID => { + StatCpuAttribute::Invalid(BigEndian::read_u32(payload)) + } + CTA_STATS_INSERT => { + StatCpuAttribute::Insert(BigEndian::read_u32(payload)) + } CTA_STATS_INSERT_FAILED => { - StatNla::InsertFailed(BigEndian::read_u32(payload)) + StatCpuAttribute::InsertFailed(BigEndian::read_u32(payload)) + } + CTA_STATS_DROP => { + StatCpuAttribute::Drop(BigEndian::read_u32(payload)) } - CTA_STATS_DROP => StatNla::Drop(BigEndian::read_u32(payload)), CTA_STATS_EARLY_DROP => { - StatNla::EarlyDrop(BigEndian::read_u32(payload)) + StatCpuAttribute::EarlyDrop(BigEndian::read_u32(payload)) + } + CTA_STATS_ERROR => { + StatCpuAttribute::Error(BigEndian::read_u32(payload)) } - CTA_STATS_ERROR => StatNla::Error(BigEndian::read_u32(payload)), CTA_STATS_SEARCH_RESTART => { - StatNla::SearchRestart(BigEndian::read_u32(payload)) + StatCpuAttribute::SearchRestart(BigEndian::read_u32(payload)) } CTA_STATS_CLASH_RESOLVE => { - StatNla::ClashResolve(BigEndian::read_u32(payload)) + StatCpuAttribute::ClashResolve(BigEndian::read_u32(payload)) } CTA_STATS_CHAIN_TOOLONG => { - StatNla::ChainTooLong(BigEndian::read_u32(payload)) + StatCpuAttribute::ChainTooLong(BigEndian::read_u32(payload)) + } + _ => StatCpuAttribute::Other(DefaultNla::parse(buf)?), + }; + Ok(nla) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum StatGlobalAttribute { + Entries(u32), + MaxEntries(u32), + Other(DefaultNla), +} + +impl Nla for StatGlobalAttribute { + fn value_len(&self) -> usize { + match self { + StatGlobalAttribute::Entries(_) => 4, + StatGlobalAttribute::MaxEntries(_) => 4, + StatGlobalAttribute::Other(nla) => nla.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + StatGlobalAttribute::Entries(_) => CTA_STATS_GLOBAL_ENTRIES, + StatGlobalAttribute::MaxEntries(_) => CTA_STATS_GLOBAL_MAX_ENTRIES, + StatGlobalAttribute::Other(nla) => nla.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + StatGlobalAttribute::Entries(val) => { + BigEndian::write_u32(buffer, *val) + } + StatGlobalAttribute::MaxEntries(val) => { + BigEndian::write_u32(buffer, *val) + } + StatGlobalAttribute::Other(attr) => attr.emit_value(buffer), + } + } +} + +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for StatGlobalAttribute +{ + fn parse( + buf: &NlaBuffer<&'buffer T>, + ) -> Result { + let kind = buf.kind(); + let payload = buf.value(); + let nla = match kind { + CTA_STATS_GLOBAL_ENTRIES => { + StatGlobalAttribute::Entries(BigEndian::read_u32(payload)) + } + CTA_STATS_GLOBAL_MAX_ENTRIES => { + StatGlobalAttribute::MaxEntries(BigEndian::read_u32(payload)) } - _ => StatNla::Other(DefaultNla::parse(buf)?), + _ => StatGlobalAttribute::Other(DefaultNla::parse(buf)?), }; Ok(nla) } diff --git a/src/message.rs b/src/message.rs index 4da50f5..54df920 100644 --- a/src/message.rs +++ b/src/message.rs @@ -9,8 +9,7 @@ use netlink_packet_utils::{ }; use crate::{ - buffer::NetfilterBuffer, ctnetlink::message::CtNetlinkMessage, - nflog::NfLogMessage, + buffer::NetfilterBuffer, ctnetlink::CtNetlinkMessage, nflog::NfLogMessage, }; pub const NETFILTER_HEADER_LEN: usize = 4;