diff --git a/.gitignore b/.gitignore index bb55a70..32304d1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ target vendor/ *.swp +.vscode diff --git a/examples/nfqueue.rs b/examples/nfqueue.rs new file mode 100644 index 0000000..fa873e9 --- /dev/null +++ b/examples/nfqueue.rs @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: MIT + +// To run this example: +// 1) create a iptables/nft rules that send packet with nfqueue 0, for example: +// sudo iptables -A OUTPUT -p udp --dport 53 -j NFQUEUE --queue-num 0 +// 2) build the example: +// cargo build --example nfqueue +// 3) run it as root: +// sudo ./target/debug/examples/nfqueue + +use netlink_packet_core::{NetlinkMessage, NetlinkPayload}; +use netlink_packet_netfilter::{ + constants::*, + nfqueue::{ + config_request, + nlas::{ + config::{ + ConfigCmd, ConfigCmdType, ConfigFlags, ConfigNla, ConfigParams, + CopyMode, + }, + packet::PacketNla, + verdict::{VerdictHdr, VerdictNla, VerdictType}, + }, + verdict_message, NfQueueMessage, + }, + NetfilterMessage, NetfilterMessageInner, +}; + +use netlink_sys::{constants::NETLINK_NETFILTER, Socket}; + +fn get_packet_nlas(message: &NetlinkMessage) -> &[PacketNla] { + if let NetlinkPayload::InnerMessage(NetfilterMessage { + inner: NetfilterMessageInner::NfQueue(NfQueueMessage::Packet(nlas)), + .. + }) = &message.payload + { + nlas + } else { + &[] + } +} + +fn main() { + const QUEUE_NUM: u16 = 0; + + // First, we bind the socket + let mut socket = Socket::new(NETLINK_NETFILTER).unwrap(); + socket.bind_auto().unwrap(); + + // Then we issue the PfUnbind command + let packet = config_request( + AF_INET, + 0, + vec![ConfigNla::Cmd(ConfigCmd::new( + ConfigCmdType::PfUnbind, + AF_INET as u16, + ))], + ); + let mut tx_buffer = vec![0; packet.header.length as usize]; + packet.serialize(&mut tx_buffer[..]); + println!(">>> {:?}", packet); + socket.send(&tx_buffer[..], 0).unwrap(); + + let mut rx_buffer = vec![0; 8196]; + + // And check there is no error + let rx_size = socket.recv(&mut &mut rx_buffer[..], 0).unwrap(); + let rx_bytes = &rx_buffer[..rx_size]; + let rx_packet = + >::deserialize(rx_bytes).unwrap(); + println!("<<< {:?}", rx_packet); + assert!(matches!(rx_packet.payload, NetlinkPayload::Error(_))); + if let NetlinkPayload::Error(e) = rx_packet.payload { + assert_eq!(e.code, None); + } + + // Then we issue the PfBind command + let packet = config_request( + AF_INET, + 0, + vec![ConfigNla::Cmd(ConfigCmd::new( + ConfigCmdType::PfBind, + AF_INET as u16, + ))], + ); + let mut buf = vec![0; packet.header.length as usize]; + packet.serialize(&mut buf[..]); + println!(">>> {:?}", packet); + socket.send(&buf[..], 0).unwrap(); + + // And check there is no error + let rx_size = socket.recv(&mut &mut rx_buffer[..], 0).unwrap(); + let rx_bytes = &rx_buffer[..rx_size]; + let rx_packet = + >::deserialize(rx_bytes).unwrap(); + println!("<<< {:?}", rx_packet); + assert!(matches!(rx_packet.payload, NetlinkPayload::Error(_))); + if let NetlinkPayload::Error(e) = rx_packet.payload { + assert_eq!(e.code, None); + } + + // After that we issue a Bind command, to start receiving packets. We can + // also set various parameters at the same time + let packet = config_request( + AF_INET, + QUEUE_NUM, + vec![ + ConfigNla::Cmd(ConfigCmd::new(ConfigCmdType::Bind, AF_INET as u16)), + ConfigNla::Params(ConfigParams::new(0xFFFF, CopyMode::Packet)), + ConfigNla::Mask( + ConfigFlags::FAIL_OPEN + | ConfigFlags::CONNTRACK + | ConfigFlags::GSO + | ConfigFlags::UID_GID + | ConfigFlags::SECCTX, + ), + ConfigNla::Flags( + ConfigFlags::FAIL_OPEN + | ConfigFlags::CONNTRACK + | ConfigFlags::GSO + | ConfigFlags::UID_GID + | ConfigFlags::SECCTX, + ), + ], + ); + + let mut buffer = vec![0; packet.header.length as usize]; + packet.serialize(&mut buffer[..]); + println!(">>> {:?}", packet); + socket.send(&buffer[..], 0).unwrap(); + + let rx_size = socket.recv(&mut &mut rx_buffer[..], 0).unwrap(); + let rx_bytes = &rx_buffer[..rx_size]; + let rx_packet = + >::deserialize(rx_bytes).unwrap(); + println!("<<< {:?}", rx_packet); + assert!(matches!(rx_packet.payload, NetlinkPayload::Error(_))); + if let NetlinkPayload::Error(e) = rx_packet.payload { + assert_eq!(e.code, None); + } + + // And now we can receive the packets + loop { + println!("Waiting for messages"); + match socket.recv(&mut &mut rx_buffer[..], 0) { + Ok(rx_size) => { + let rx_bytes = &rx_buffer[..rx_size]; + let rx_packet = + >::deserialize(&rx_bytes) + .unwrap(); + assert_eq!(rx_packet.header.length as usize, rx_size); + println!("<<< {:?}", rx_packet); + + if let NetlinkPayload::Error(e) = rx_packet.payload { + assert_eq!(e.code, None); + continue; + } + + for nla in get_packet_nlas(&rx_packet) { + if let PacketNla::PacketHdr(hdr) = nla { + println!("packet_id: {}", hdr.packet_id); + let verdict_hdr = + VerdictHdr::new(VerdictType::Accept, hdr.packet_id); + let verdict_nla = VerdictNla::Verdict(verdict_hdr); + let verdict_msg = + verdict_message(AF_INET, QUEUE_NUM, verdict_nla); + let mut tx_buffer = + vec![0; verdict_msg.header.length as usize]; + verdict_msg.serialize(&mut tx_buffer[..]); + println!(">>> {:?}", verdict_msg); + socket.send(&tx_buffer[..], 0).unwrap(); + } + } + } + Err(e) => { + println!("error while receiving packets: {:?}", e); + break; + } + } + } +} diff --git a/src/buffer.rs b/src/buffer.rs index ada5cde..3079d47 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -6,6 +6,7 @@ use crate::{ NETFILTER_HEADER_LEN, }, nflog::NfLogMessage, + nfqueue::NfQueueMessage, }; use anyhow::Context; use netlink_packet_utils::{ @@ -60,6 +61,10 @@ impl<'a, T: AsRef<[u8]> + ?Sized> NfLogMessage::parse_with_param(buf, message_type) .context("failed to parse nflog payload")?, ), + NfQueueMessage::SUBSYS => NetfilterMessageInner::NfQueue( + NfQueueMessage::parse_with_param(buf, message_type) + .context("failed to parse nflog payload")?, + ), _ => NetfilterMessageInner::Other { subsys, message_type, diff --git a/src/constants.rs b/src/constants.rs index 2b73b6d..737983b 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -88,3 +88,72 @@ pub const NFULA_CT_INFO: u16 = libc::NFULA_CT_INFO as u16; pub const NFULNL_MSG_CONFIG: u8 = libc::NFULNL_MSG_CONFIG as u8; pub const NFULNL_MSG_PACKET: u8 = libc::NFULNL_MSG_PACKET as u8; + +pub const NFQA_UNSPEC: u16 = libc::NFQA_UNSPEC as u16; +pub const NFQA_PACKET_HDR: u16 = libc::NFQA_PACKET_HDR as u16; +pub const NFQA_VERDICT_HDR: u16 = libc::NFQA_VERDICT_HDR as u16; +pub const NFQA_MARK: u16 = libc::NFQA_MARK as u16; +pub const NFQA_TIMESTAMP: u16 = libc::NFQA_TIMESTAMP as u16; +pub const NFQA_IFINDEX_INDEV: u16 = libc::NFQA_IFINDEX_INDEV as u16; +pub const NFQA_IFINDEX_OUTDEV: u16 = libc::NFQA_IFINDEX_OUTDEV as u16; +pub const NFQA_IFINDEX_PHYSINDEV: u16 = libc::NFQA_IFINDEX_PHYSINDEV as u16; +pub const NFQA_IFINDEX_PHYSOUTDEV: u16 = libc::NFQA_IFINDEX_PHYSOUTDEV as u16; +pub const NFQA_HWADDR: u16 = libc::NFQA_HWADDR as u16; +pub const NFQA_PAYLOAD: u16 = libc::NFQA_PAYLOAD as u16; +pub const NFQA_CT: u16 = libc::NFQA_CT as u16; +pub const NFQA_CT_INFO: u16 = libc::NFQA_CT_INFO as u16; +pub const NFQA_CAP_LEN: u16 = libc::NFQA_CAP_LEN as u16; +pub const NFQA_SKB_INFO: u16 = libc::NFQA_SKB_INFO as u16; +pub const NFQA_EXP: u16 = libc::NFQA_EXP as u16; +pub const NFQA_UID: u16 = libc::NFQA_UID as u16; +pub const NFQA_GID: u16 = libc::NFQA_GID as u16; +pub const NFQA_SECCTX: u16 = libc::NFQA_SECCTX as u16; +pub const NFQA_VLAN: u16 = libc::NFQA_VLAN as u16; +pub const NFQA_L2HDR: u16 = libc::NFQA_L2HDR as u16; +pub const NFQA_PRIORITY: u16 = libc::NFQA_PRIORITY as u16; + +pub const NFQA_VLAN_UNSPEC: u16 = libc::NFQA_VLAN_UNSPEC as u16; +pub const NFQA_VLAN_PROTO: u16 = libc::NFQA_VLAN_PROTO as u16; +pub const NFQA_VLAN_TCI: u16 = libc::NFQA_VLAN_TCI as u16; + +pub const NFQNL_CFG_CMD_NONE: u8 = libc::NFQNL_CFG_CMD_NONE as u8; +pub const NFQNL_CFG_CMD_BIND: u8 = libc::NFQNL_CFG_CMD_BIND as u8; +pub const NFQNL_CFG_CMD_UNBIND: u8 = libc::NFQNL_CFG_CMD_UNBIND as u8; +pub const NFQNL_CFG_CMD_PF_BIND: u8 = libc::NFQNL_CFG_CMD_PF_BIND as u8; +pub const NFQNL_CFG_CMD_PF_UNBIND: u8 = libc::NFQNL_CFG_CMD_PF_UNBIND as u8; + +pub const NFQNL_COPY_NONE: u8 = libc::NFQNL_COPY_NONE as u8; +pub const NFQNL_COPY_META: u8 = libc::NFQNL_COPY_META as u8; +pub const NFQNL_COPY_PACKET: u8 = libc::NFQNL_COPY_PACKET as u8; + +pub const NFQA_CFG_UNSPEC: u16 = libc::NFQA_CFG_UNSPEC as u16; +pub const NFQA_CFG_CMD: u16 = libc::NFQA_CFG_CMD as u16; +pub const NFQA_CFG_PARAMS: u16 = libc::NFQA_CFG_PARAMS as u16; +pub const NFQA_CFG_QUEUE_MAXLEN: u16 = libc::NFQA_CFG_QUEUE_MAXLEN as u16; +pub const NFQA_CFG_MASK: u16 = libc::NFQA_CFG_MASK as u16; +pub const NFQA_CFG_FLAGS: u16 = libc::NFQA_CFG_FLAGS as u16; + +pub const NFQA_CFG_F_FAIL_OPEN: u32 = libc::NFQA_CFG_F_FAIL_OPEN as u32; +pub const NFQA_CFG_F_CONNTRACK: u32 = libc::NFQA_CFG_F_CONNTRACK as u32; +pub const NFQA_CFG_F_GSO: u32 = libc::NFQA_CFG_F_GSO as u32; +pub const NFQA_CFG_F_UID_GID: u32 = libc::NFQA_CFG_F_UID_GID as u32; +pub const NFQA_CFG_F_SECCTX: u32 = libc::NFQA_CFG_F_SECCTX as u32; +pub const NFQA_CFG_F_MAX: u32 = libc::NFQA_CFG_F_MAX as u32; + +pub const NFQA_SKB_CSUMNOTREADY: u32 = libc::NFQA_SKB_CSUMNOTREADY as u32; +pub const NFQA_SKB_GSO: u32 = libc::NFQA_SKB_GSO as u32; +pub const NFQA_SKB_CSUM_NOTVERIFIED: u32 = + libc::NFQA_SKB_CSUM_NOTVERIFIED as u32; + +pub const NFQNL_MSG_PACKET: u8 = libc::NFQNL_MSG_PACKET as u8; +pub const NFQNL_MSG_VERDICT: u8 = libc::NFQNL_MSG_VERDICT as u8; +pub const NFQNL_MSG_CONFIG: u8 = libc::NFQNL_MSG_CONFIG as u8; +pub const NFQNL_MSG_VERDICT_BATCH: u8 = libc::NFQNL_MSG_VERDICT_BATCH as u8; + +pub const NF_DROP: u32 = libc::NF_DROP as u32; +pub const NF_ACCEPT: u32 = libc::NF_ACCEPT as u32; +pub const NF_STOLEN: u32 = libc::NF_STOLEN as u32; +pub const NF_QUEUE: u32 = libc::NF_QUEUE as u32; +pub const NF_REPEAT: u32 = libc::NF_REPEAT as u32; +pub const NF_STOP: u32 = libc::NF_STOP as u32; +pub const NF_MAX_VERDICT: u32 = libc::NF_MAX_VERDICT as u32; diff --git a/src/lib.rs b/src/lib.rs index aca295b..37e4adf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,4 @@ pub mod constants; mod message; pub use message::{NetfilterHeader, NetfilterMessage, NetfilterMessageInner}; pub mod nflog; +pub mod nfqueue; diff --git a/src/message.rs b/src/message.rs index 8d41eb2..2516dd9 100644 --- a/src/message.rs +++ b/src/message.rs @@ -8,7 +8,9 @@ use netlink_packet_utils::{ ParseableParametrized, }; -use crate::{buffer::NetfilterBuffer, nflog::NfLogMessage}; +use crate::{ + buffer::NetfilterBuffer, nflog::NfLogMessage, nfqueue::NfQueueMessage, +}; pub const NETFILTER_HEADER_LEN: usize = 4; @@ -62,6 +64,7 @@ impl> Parseable> for NetfilterHeader { #[derive(Debug, PartialEq, Eq, Clone)] pub enum NetfilterMessageInner { NfLog(NfLogMessage), + NfQueue(NfQueueMessage), Other { subsys: u8, message_type: u8, @@ -75,10 +78,17 @@ impl From for NetfilterMessageInner { } } +impl From for NetfilterMessageInner { + fn from(message: NfQueueMessage) -> Self { + Self::NfQueue(message) + } +} + impl Emitable for NetfilterMessageInner { fn buffer_len(&self) -> usize { match self { NetfilterMessageInner::NfLog(message) => message.buffer_len(), + NetfilterMessageInner::NfQueue(message) => message.buffer_len(), NetfilterMessageInner::Other { nlas, .. } => { nlas.as_slice().buffer_len() } @@ -88,6 +98,7 @@ impl Emitable for NetfilterMessageInner { fn emit(&self, buffer: &mut [u8]) { match self { NetfilterMessageInner::NfLog(message) => message.emit(buffer), + NetfilterMessageInner::NfQueue(message) => message.emit(buffer), NetfilterMessageInner::Other { nlas, .. } => { nlas.as_slice().emit(buffer) } @@ -115,6 +126,7 @@ impl NetfilterMessage { pub fn subsys(&self) -> u8 { match self.inner { NetfilterMessageInner::NfLog(_) => NfLogMessage::SUBSYS, + NetfilterMessageInner::NfQueue(_) => NfQueueMessage::SUBSYS, NetfilterMessageInner::Other { subsys, .. } => subsys, } } @@ -122,6 +134,9 @@ impl NetfilterMessage { pub fn message_type(&self) -> u8 { match self.inner { NetfilterMessageInner::NfLog(ref message) => message.message_type(), + NetfilterMessageInner::NfQueue(ref message) => { + message.message_type() + } NetfilterMessageInner::Other { message_type, .. } => message_type, } } diff --git a/src/nfqueue/message.rs b/src/nfqueue/message.rs new file mode 100644 index 0000000..f51cffd --- /dev/null +++ b/src/nfqueue/message.rs @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_utils::{ + nla::DefaultNla, DecodeError, Emitable, Parseable, ParseableParametrized, +}; + +use crate::{ + buffer::NetfilterBuffer, + constants::{ + NFNL_SUBSYS_QUEUE, NFQNL_MSG_CONFIG, NFQNL_MSG_PACKET, + NFQNL_MSG_VERDICT, + }, + nfqueue::nlas::{ + config::ConfigNla, packet::PacketNla, verdict::VerdictNla, + }, +}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum NfQueueMessage { + Config(Vec), + Packet(Vec), + Verdict(Vec), + Other { + message_type: u8, + nlas: Vec, + }, +} + +impl NfQueueMessage { + pub const SUBSYS: u8 = NFNL_SUBSYS_QUEUE; + + pub fn message_type(&self) -> u8 { + match self { + NfQueueMessage::Config(_) => NFQNL_MSG_CONFIG, + NfQueueMessage::Packet(_) => NFQNL_MSG_PACKET, + NfQueueMessage::Verdict(_) => NFQNL_MSG_VERDICT, + NfQueueMessage::Other { message_type, .. } => *message_type, + } + } +} + +impl Emitable for NfQueueMessage { + fn buffer_len(&self) -> usize { + match self { + NfQueueMessage::Config(nlas) => nlas.as_slice().buffer_len(), + NfQueueMessage::Packet(nlas) => nlas.as_slice().buffer_len(), + NfQueueMessage::Verdict(nlas) => nlas.as_slice().buffer_len(), + NfQueueMessage::Other { nlas, .. } => nlas.as_slice().buffer_len(), + } + } + + fn emit(&self, buffer: &mut [u8]) { + match self { + NfQueueMessage::Config(nlas) => nlas.as_slice().emit(buffer), + NfQueueMessage::Packet(nlas) => nlas.as_slice().emit(buffer), + NfQueueMessage::Verdict(nlas) => nlas.as_slice().emit(buffer), + NfQueueMessage::Other { nlas, .. } => nlas.as_slice().emit(buffer), + }; + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> + ParseableParametrized, u8> for NfQueueMessage +{ + fn parse_with_param( + buffer: &NetfilterBuffer<&'a T>, + message_type: u8, + ) -> Result { + match message_type { + NFQNL_MSG_CONFIG => { + match buffer.parse_all_nlas(|nla| ConfigNla::parse(&nla)) { + Ok(nlas) => Ok(NfQueueMessage::Config(nlas)), + Err(error) => Err(error), + } + } + NFQNL_MSG_PACKET => { + match buffer.parse_all_nlas(|nla| PacketNla::parse(&nla)) { + Ok(nlas) => Ok(NfQueueMessage::Packet(nlas)), + Err(error) => Err(error), + } + } + NFQNL_MSG_VERDICT => { + match buffer.parse_all_nlas(|nla| VerdictNla::parse(&nla)) { + Ok(nlas) => Ok(NfQueueMessage::Verdict(nlas)), + Err(error) => Err(error), + } + } + _ => match buffer.default_nlas() { + Ok(nlas) => Ok(NfQueueMessage::Other { message_type, nlas }), + Err(error) => Err(error), + }, + } + } +} diff --git a/src/nfqueue/mod.rs b/src/nfqueue/mod.rs new file mode 100644 index 0000000..3c7fdf8 --- /dev/null +++ b/src/nfqueue/mod.rs @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +mod message; +pub use message::NfQueueMessage; +pub mod nlas; + +use netlink_packet_core::{ + NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_REQUEST, +}; + +use crate::{ + constants::NFNETLINK_V0, + nfqueue::nlas::{config::ConfigNla, verdict::VerdictNla}, + NetfilterHeader, NetfilterMessage, +}; + +pub fn config_request( + family: u8, + group_num: u16, + nlas: Vec, +) -> NetlinkMessage { + let mut hdr = NetlinkHeader::default(); + hdr.flags = NLM_F_REQUEST | NLM_F_ACK; + let mut message = NetlinkMessage::new( + hdr, + NetlinkPayload::from(NetfilterMessage::new( + NetfilterHeader::new(family, NFNETLINK_V0, group_num), + NfQueueMessage::Config(nlas), + )), + ); + message.finalize(); + message +} + +pub fn verdict_message( + family: u8, + queue_num: u16, + nla: VerdictNla, +) -> 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, queue_num), + NfQueueMessage::Verdict(vec![nla]), + )), + ); + message.finalize(); + message +} diff --git a/src/nfqueue/nlas/config/config_cmd.rs b/src/nfqueue/nlas/config/config_cmd.rs new file mode 100644 index 0000000..de621d4 --- /dev/null +++ b/src/nfqueue/nlas/config/config_cmd.rs @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_utils::{buffer, DecodeError, Emitable, Parseable}; + +use crate::constants::{ + NFQNL_CFG_CMD_BIND, NFQNL_CFG_CMD_NONE, NFQNL_CFG_CMD_PF_BIND, + NFQNL_CFG_CMD_PF_UNBIND, NFQNL_CFG_CMD_UNBIND, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ConfigCmdType { + None, + Bind, + Unbind, + PfBind, + PfUnbind, + Other(u8), +} + +impl From for u8 { + fn from(cmd: ConfigCmdType) -> Self { + match cmd { + ConfigCmdType::None => NFQNL_CFG_CMD_NONE, + ConfigCmdType::Bind => NFQNL_CFG_CMD_BIND, + ConfigCmdType::Unbind => NFQNL_CFG_CMD_UNBIND, + ConfigCmdType::PfBind => NFQNL_CFG_CMD_PF_BIND, + ConfigCmdType::PfUnbind => NFQNL_CFG_CMD_PF_UNBIND, + ConfigCmdType::Other(cmd) => cmd, + } + } +} + +impl From for ConfigCmdType { + fn from(cmd: u8) -> Self { + match cmd { + NFQNL_CFG_CMD_NONE => ConfigCmdType::None, + NFQNL_CFG_CMD_BIND => ConfigCmdType::Bind, + NFQNL_CFG_CMD_UNBIND => ConfigCmdType::Unbind, + NFQNL_CFG_CMD_PF_BIND => ConfigCmdType::PfBind, + NFQNL_CFG_CMD_PF_UNBIND => ConfigCmdType::PfUnbind, + cmd => ConfigCmdType::Other(cmd), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ConfigCmd { + cmd: ConfigCmdType, + pf: u16, +} + +impl ConfigCmd { + pub fn new(cmd: ConfigCmdType, pf: u16) -> Self { + Self { cmd, pf } + } +} + +const CONFIG_CMD_BUFFER_SIZE: usize = 4; + +buffer!(ConfigCmdBuffer(CONFIG_CMD_BUFFER_SIZE) { + cmd: (u8, 0), + pad: (u8, 1), + pf: (u16, 2..4) +}); + +impl From<&ConfigCmdBuffer<&[u8]>> for ConfigCmd { + fn from(buffer: &ConfigCmdBuffer<&[u8]>) -> Self { + ConfigCmd::new( + ConfigCmdType::from(buffer.cmd()), + u16::from_be(buffer.pf()), + ) + } +} + +impl Parseable<[u8]> for ConfigCmd { + fn parse(buffer: &[u8]) -> Result { + match ConfigCmdBuffer::new_checked(buffer) { + Ok(buffer) => Ok(ConfigCmd::from(&buffer)), + Err(error) => Err(error), + } + } +} + +impl Emitable for ConfigCmd { + fn buffer_len(&self) -> usize { + CONFIG_CMD_BUFFER_SIZE + } + + fn emit(&self, buffer: &mut [u8]) { + let mut buffer = ConfigCmdBuffer::new(buffer); + buffer.set_cmd(u8::from(self.cmd)); + buffer.set_pad(0); + buffer.set_pf(u16::to_be(self.pf)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse() { + let buffer: [u8; CONFIG_CMD_BUFFER_SIZE] = [ + 0x01, // NFQNL_CFG_CMD_BIND + 0x00, // pad + 0x00, 0x02, // AF_INET 0x0002 + ]; + match ConfigCmd::parse(&buffer) { + Ok(command) => { + assert_eq!(command.cmd, ConfigCmdType::Bind); + assert_eq!(command.pf, 0x0002); + } + Err(_) => assert!(false), + } + } + + #[test] + fn test_emit() { + let mut buffer = vec![0; 4]; + ConfigCmd::new(ConfigCmdType::Bind, 0x0002).emit(&mut buffer); + assert_eq!( + buffer, + [ + 0x01, // NFQNL_CFG_CMD_BIND + 0x00, // pad + 0x00, 0x02, // AF_INET 0x0002 + ] + ) + } +} diff --git a/src/nfqueue/nlas/config/config_flags.rs b/src/nfqueue/nlas/config/config_flags.rs new file mode 100644 index 0000000..ceb9964 --- /dev/null +++ b/src/nfqueue/nlas/config/config_flags.rs @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT + +use std::mem::size_of; + +use bitflags::bitflags; +use byteorder::{BigEndian, ByteOrder}; +use netlink_packet_utils::parsers::parse_u32_be; +use netlink_packet_utils::{DecodeError, Emitable, Parseable}; + +use crate::constants::{ + NFQA_CFG_F_CONNTRACK, NFQA_CFG_F_FAIL_OPEN, NFQA_CFG_F_GSO, + NFQA_CFG_F_SECCTX, NFQA_CFG_F_UID_GID, +}; + +bitflags! { + #[derive(Clone, Debug, Copy, PartialEq, Eq)] + pub struct ConfigFlags: u32 { + const FAIL_OPEN = NFQA_CFG_F_FAIL_OPEN; + const CONNTRACK = NFQA_CFG_F_CONNTRACK; + const GSO = NFQA_CFG_F_GSO; + const UID_GID = NFQA_CFG_F_UID_GID; + const SECCTX = NFQA_CFG_F_SECCTX; + } +} + +// see https://github.com/bitflags/bitflags/issues/263 +impl ConfigFlags { + pub fn from_bits_preserve(bits: u32) -> Self { + ConfigFlags::from_bits_truncate(bits) + } +} + +impl Parseable<[u8]> for ConfigFlags { + fn parse(buf: &[u8]) -> Result { + match parse_u32_be(buf) { + Ok(value) => Ok(ConfigFlags::from_bits_preserve(value)), + Err(error) => Err(error), + } + } +} + +impl Emitable for ConfigFlags { + fn buffer_len(&self) -> usize { + size_of::() + } + + fn emit(&self, buffer: &mut [u8]) { + BigEndian::write_u32(buffer, self.bits()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_emit() { + let flags = ConfigFlags::FAIL_OPEN | ConfigFlags::CONNTRACK; + + let mut buffer = vec![0; size_of::()]; + flags.emit(&mut buffer); + + assert_eq!( + buffer, + [ + 0x00, 0x00, 0x00, + 0x03 // ConfigFlags::FAIL_OPEN | ConfigFlags::CONNTRACK + ] + ); + } + + #[test] + fn test_parse() { + let buffer: [u8; size_of::()] = [ + 0x00, 0x00, 0x00, + 0x03, // ConfigFlags::FAIL_OPEN | ConfigFlags::CONNTRACK + ]; + match ConfigFlags::parse(&buffer) { + Ok(flags) => assert_eq!( + flags, + ConfigFlags::FAIL_OPEN | ConfigFlags::CONNTRACK + ), + Err(_) => assert!(false), + } + } +} diff --git a/src/nfqueue/nlas/config/config_params.rs b/src/nfqueue/nlas/config/config_params.rs new file mode 100644 index 0000000..68c1a69 --- /dev/null +++ b/src/nfqueue/nlas/config/config_params.rs @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_utils::{buffer, DecodeError, Emitable, Parseable}; + +use crate::constants::{NFQNL_COPY_META, NFQNL_COPY_NONE, NFQNL_COPY_PACKET}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CopyMode { + None, + Meta, + Packet, + Other(u8), +} + +impl From for u8 { + fn from(cmd: CopyMode) -> Self { + match cmd { + CopyMode::None => NFQNL_COPY_NONE, + CopyMode::Meta => NFQNL_COPY_META, + CopyMode::Packet => NFQNL_COPY_PACKET, + CopyMode::Other(cmd) => cmd, + } + } +} + +impl From for CopyMode { + fn from(cmd: u8) -> Self { + match cmd { + NFQNL_COPY_NONE => CopyMode::None, + NFQNL_COPY_META => CopyMode::Meta, + NFQNL_COPY_PACKET => CopyMode::Packet, + cmd => CopyMode::Other(cmd), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ConfigParams { + copy_range: u32, + copy_mode: CopyMode, +} + +impl ConfigParams { + pub fn new(copy_range: u32, copy_mode: CopyMode) -> Self { + Self { + copy_range, + copy_mode, + } + } +} + +const CONFIG_PARAMS_BUFFER_SIZE: usize = 8; + +buffer!(ConfigParamsBuffer(CONFIG_PARAMS_BUFFER_SIZE) { + copy_range: (u32, 0..4), + copy_mode: (u8, 4), + pad0: (u8, 5), + pad1: (u8, 6), + pad2: (u8, 7), +}); + +impl From<&ConfigParamsBuffer<&[u8]>> for ConfigParams { + fn from(buffer: &ConfigParamsBuffer<&[u8]>) -> Self { + Self::new( + u32::from_be(buffer.copy_range()), + CopyMode::from(buffer.copy_mode()), + ) + } +} + +impl Parseable<[u8]> for ConfigParams { + fn parse(buffer: &[u8]) -> Result { + match ConfigParamsBuffer::new_checked(buffer) { + Ok(buffer) => Ok(ConfigParams::from(&buffer)), + Err(error) => Err(error), + } + } +} + +impl Emitable for ConfigParams { + fn buffer_len(&self) -> usize { + CONFIG_PARAMS_BUFFER_SIZE + } + + fn emit(&self, buffer: &mut [u8]) { + let mut buffer = ConfigParamsBuffer::new(buffer); + buffer.set_copy_range(u32::to_be(self.copy_range)); + buffer.set_copy_mode(u8::from(self.copy_mode)); + buffer.set_pad0(0); + buffer.set_pad1(0); + buffer.set_pad2(0); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse() { + let buffer: [u8; CONFIG_PARAMS_BUFFER_SIZE] = [ + 0x00, 0x01, 0x02, 0x03, // copy_range 0x00010203 + 0x01, // copy_mode NFQNL_COPY_META + 0x00, 0x00, 0x00, // + ]; + + match ConfigParams::parse(&buffer) { + Ok(params) => { + assert_eq!(params.copy_range, 0x00010203); + assert_eq!(params.copy_mode, CopyMode::Meta); + } + Err(_) => assert!(false), + } + } + + #[test] + fn test_emit() { + let mut buffer = vec![0; CONFIG_PARAMS_BUFFER_SIZE]; + ConfigParams::new(0x00010203, CopyMode::Meta).emit(&mut buffer); + assert_eq!( + buffer, + [ + 0x00, 0x01, 0x02, 0x03, // copy_range 0x00010203 + 0x01, // copy_mode NFQNL_COPY_META + 0x00, 0x00, 0x00, // + ] + ) + } +} diff --git a/src/nfqueue/nlas/config/mod.rs b/src/nfqueue/nlas/config/mod.rs new file mode 100644 index 0000000..9cfe52d --- /dev/null +++ b/src/nfqueue/nlas/config/mod.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +mod config_cmd; +mod config_flags; +mod config_params; +mod nla; + +pub use config_cmd::{ConfigCmd, ConfigCmdType}; +pub use config_flags::ConfigFlags; +pub use config_params::{ConfigParams, CopyMode}; +pub use nla::ConfigNla; diff --git a/src/nfqueue/nlas/config/nla.rs b/src/nfqueue/nlas/config/nla.rs new file mode 100644 index 0000000..b569d07 --- /dev/null +++ b/src/nfqueue/nlas/config/nla.rs @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT + +use byteorder::{BigEndian, ByteOrder}; +use derive_more::IsVariant; + +use netlink_packet_utils::{ + errors::DecodeError, + nla::{DefaultNla, Nla, NlaBuffer}, + parsers::parse_u32_be, + Emitable, Parseable, +}; + +use crate::{ + constants::{ + NFQA_CFG_CMD, // + NFQA_CFG_FLAGS, + NFQA_CFG_MASK, + NFQA_CFG_PARAMS, + NFQA_CFG_QUEUE_MAXLEN, + }, + nfqueue::nlas::config::{ + config_cmd::ConfigCmd, // + config_flags::ConfigFlags, + config_params::ConfigParams, + }, +}; + +const U32_BYTES_SIZE: usize = 4; + +#[derive(Clone, Debug, PartialEq, Eq, IsVariant)] +pub enum ConfigNla { + Cmd(ConfigCmd), + Params(ConfigParams), + QueueMaxLen(u32), + Mask(ConfigFlags), + Flags(ConfigFlags), + Other(DefaultNla), +} + +impl Nla for ConfigNla { + fn value_len(&self) -> usize { + match self { + ConfigNla::Cmd(attr) => attr.buffer_len(), + ConfigNla::Params(attr) => attr.buffer_len(), + ConfigNla::QueueMaxLen(_) => U32_BYTES_SIZE, + ConfigNla::Mask(attr) => attr.buffer_len(), + ConfigNla::Flags(attr) => attr.buffer_len(), + ConfigNla::Other(attr) => attr.buffer_len(), + } + } + + fn kind(&self) -> u16 { + match self { + ConfigNla::Cmd(_) => NFQA_CFG_CMD, + ConfigNla::Params(_) => NFQA_CFG_PARAMS, + ConfigNla::QueueMaxLen(_) => NFQA_CFG_QUEUE_MAXLEN, + ConfigNla::Mask(_) => NFQA_CFG_MASK, + ConfigNla::Flags(_) => NFQA_CFG_FLAGS, + ConfigNla::Other(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + ConfigNla::Cmd(attr) => attr.emit(buffer), + ConfigNla::Params(attr) => attr.emit(buffer), + ConfigNla::QueueMaxLen(attr) => BigEndian::write_u32(buffer, *attr), + ConfigNla::Mask(attr) => attr.emit(buffer), + ConfigNla::Flags(attr) => attr.emit(buffer), + ConfigNla::Other(attr) => attr.emit_value(buffer), + } + } +} + +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for ConfigNla +{ + fn parse(buffer: &NlaBuffer<&'buffer T>) -> Result { + let kind = buffer.kind(); + let payload = buffer.value(); + match kind { + NFQA_CFG_CMD => match ConfigCmd::parse(payload) { + Ok(payload) => Ok(ConfigNla::Cmd(payload)), + Err(error) => Err(error), + }, + NFQA_CFG_PARAMS => match ConfigParams::parse(payload) { + Ok(payload) => Ok(ConfigNla::Params(payload)), + Err(error) => Err(error), + }, + NFQA_CFG_QUEUE_MAXLEN => match parse_u32_be(payload) { + Ok(payload) => Ok(ConfigNla::QueueMaxLen(payload)), + Err(error) => Err(error), + }, + NFQA_CFG_MASK => match ConfigFlags::parse(payload) { + Ok(payload) => Ok(ConfigNla::Mask(payload)), + Err(error) => Err(error), + }, + NFQA_CFG_FLAGS => match ConfigFlags::parse(payload) { + Ok(payload) => Ok(ConfigNla::Flags(payload)), + Err(error) => Err(error), + }, + _ => match DefaultNla::parse(buffer) { + Ok(attr) => Ok(ConfigNla::Other(attr)), + Err(error) => Err(error), + }, + } + } +} diff --git a/src/nfqueue/nlas/mod.rs b/src/nfqueue/nlas/mod.rs new file mode 100644 index 0000000..b60faa8 --- /dev/null +++ b/src/nfqueue/nlas/mod.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MIT + +pub mod config; +pub mod packet; +pub mod verdict; diff --git a/src/nfqueue/nlas/packet/hw_addr.rs b/src/nfqueue/nlas/packet/hw_addr.rs new file mode 100644 index 0000000..7c4e4c7 --- /dev/null +++ b/src/nfqueue/nlas/packet/hw_addr.rs @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_utils::{buffer, errors::DecodeError, Emitable, Parseable}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HwAddr { + len: u16, + address: [u8; 8], +} + +impl HwAddr { + pub fn new(len: u16, address: [u8; 8]) -> Self { + Self { len, address } + } +} + +const HW_ADDR_LEN: usize = 12; + +buffer!(HwAddrBuffer(HW_ADDR_LEN) { + hw_addr_len: (u16, 0..2), + hw_addr_0: (u8, 4), + hw_addr_1: (u8, 5), + hw_addr_2: (u8, 6), + hw_addr_3: (u8, 7), + hw_addr_4: (u8, 8), + hw_addr_5: (u8, 9), + hw_addr_6: (u8, 10), + hw_addr_7: (u8, 11), +}); + +impl From<&HwAddrBuffer<&[u8]>> for HwAddr { + fn from(buffer: &HwAddrBuffer<&[u8]>) -> Self { + Self { + len: u16::from_be(buffer.hw_addr_len()), + address: [ + buffer.hw_addr_0(), + buffer.hw_addr_1(), + buffer.hw_addr_2(), + buffer.hw_addr_3(), + buffer.hw_addr_4(), + buffer.hw_addr_5(), + buffer.hw_addr_6(), + buffer.hw_addr_7(), + ], + } + } +} + +impl Parseable<[u8]> for HwAddr { + fn parse(buffer: &[u8]) -> Result { + match HwAddrBuffer::new_checked(buffer) { + Ok(buffer) => Ok(HwAddr::from(&buffer)), + Err(error) => Err(error), + } + } +} + +impl Emitable for HwAddr { + fn buffer_len(&self) -> usize { + HW_ADDR_LEN + } + + fn emit(&self, buffer: &mut [u8]) { + let mut buffer = HwAddrBuffer::new(buffer); + buffer.set_hw_addr_len(self.len.to_be()); + buffer.set_hw_addr_0(self.address[0]); + buffer.set_hw_addr_1(self.address[1]); + buffer.set_hw_addr_2(self.address[2]); + buffer.set_hw_addr_3(self.address[3]); + buffer.set_hw_addr_4(self.address[4]); + buffer.set_hw_addr_5(self.address[5]); + buffer.set_hw_addr_6(self.address[6]); + buffer.set_hw_addr_7(self.address[7]); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse() { + let buffer: [u8; HW_ADDR_LEN] = [ + 0x00, 0x06, // len 0x0006 + 0x00, 0x00, // + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, // 0xAABBCCDDEEFF + 0x00, 0x00, // + ]; + match HwAddr::parse(&buffer) { + Ok(addr) => { + assert_eq!(addr.len, 0x0006); + assert_eq!( + addr.address, + [ + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, // + 0x00, 0x00, + ] + ); + } + Err(_) => assert!(false), + } + } + + #[test] + fn test_emit() { + let mut buffer = vec![0; HW_ADDR_LEN]; + let address = HwAddr::new( + 0x0006, + [ + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, // + 0x00, 0x00, + ], + ); + address.emit(&mut buffer); + assert_eq!( + buffer, + [ + 0x00, 0x06, // len 0x0006 + 0x00, 0x00, // + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, // 0xAABBCCDDEEFF + 0x00, 0x00, // + ] + ); + } +} diff --git a/src/nfqueue/nlas/packet/mod.rs b/src/nfqueue/nlas/packet/mod.rs new file mode 100644 index 0000000..143b412 --- /dev/null +++ b/src/nfqueue/nlas/packet/mod.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +mod hw_addr; +mod nla; +mod packet_hdr; +mod skb_flags; +mod timestamp; + +pub use hw_addr::HwAddr; +pub use nla::PacketNla; +pub use packet_hdr::PacketHdr; +pub use skb_flags::SkbFlags; +pub use timestamp::TimeStamp; diff --git a/src/nfqueue/nlas/packet/nla.rs b/src/nfqueue/nlas/packet/nla.rs new file mode 100644 index 0000000..685ad61 --- /dev/null +++ b/src/nfqueue/nlas/packet/nla.rs @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MIT + +use byteorder::{BigEndian, ByteOrder}; + +use netlink_packet_utils::{ + errors::DecodeError, + nla::{DefaultNla, Nla, NlaBuffer}, + parsers::parse_u32_be, + Emitable, Parseable, +}; + +use crate::{ + constants::{ + NFQA_CAP_LEN, NFQA_CT, NFQA_CT_INFO, NFQA_EXP, NFQA_GID, NFQA_HWADDR, + NFQA_IFINDEX_INDEV, NFQA_IFINDEX_OUTDEV, NFQA_IFINDEX_PHYSINDEV, + NFQA_IFINDEX_PHYSOUTDEV, NFQA_L2HDR, NFQA_MARK, NFQA_PACKET_HDR, + NFQA_PAYLOAD, NFQA_PRIORITY, NFQA_SECCTX, NFQA_SKB_INFO, + NFQA_TIMESTAMP, NFQA_UID, NFQA_VLAN, + }, + nfqueue::nlas::packet::{HwAddr, PacketHdr, SkbFlags, TimeStamp}, +}; + +const U32_BYTES_SIZE: usize = 4; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum PacketNla { + PacketHdr(PacketHdr), + Mark(u32), + TimeStamp(TimeStamp), + IfIndexInDev(u32), + IfIndexOutDev(u32), + IfIndexPhysInDev(u32), + IfIndexPhysOutDev(u32), + HwAddr(HwAddr), + Payload(Vec), + Conntrack(Vec), + ConntrackInfo(u32), + CapLen(u32), + SkbInfo(SkbFlags), + Exp(Vec), + Uid(u32), + Gid(u32), + SecCtx(Vec), + Vlan(Vec), + L2Hdr(Vec), + Priotity(u32), + Other(DefaultNla), +} + +impl Nla for PacketNla { + fn value_len(&self) -> usize { + match self { + PacketNla::PacketHdr(payload) => payload.buffer_len(), + PacketNla::Mark(_) => U32_BYTES_SIZE, + PacketNla::TimeStamp(payload) => payload.buffer_len(), + PacketNla::IfIndexInDev(_) => U32_BYTES_SIZE, + PacketNla::IfIndexOutDev(_) => U32_BYTES_SIZE, + PacketNla::IfIndexPhysInDev(_) => U32_BYTES_SIZE, + PacketNla::IfIndexPhysOutDev(_) => U32_BYTES_SIZE, + PacketNla::HwAddr(payload) => payload.buffer_len(), + PacketNla::Payload(payload) => payload.len(), + PacketNla::Conntrack(payload) => payload.len(), + PacketNla::ConntrackInfo(_) => U32_BYTES_SIZE, + PacketNla::CapLen(_) => U32_BYTES_SIZE, + PacketNla::SkbInfo(payload) => payload.buffer_len(), + PacketNla::Exp(payload) => payload.len(), + PacketNla::Uid(_) => U32_BYTES_SIZE, + PacketNla::Gid(_) => U32_BYTES_SIZE, + PacketNla::SecCtx(payload) => payload.len(), + PacketNla::Vlan(payload) => payload.len(), + PacketNla::L2Hdr(payload) => payload.len(), + PacketNla::Priotity(_) => U32_BYTES_SIZE, + PacketNla::Other(attr) => attr.buffer_len(), + } + } + + fn kind(&self) -> u16 { + match self { + PacketNla::PacketHdr(_) => NFQA_PACKET_HDR, + PacketNla::Mark(_) => NFQA_MARK, + PacketNla::TimeStamp(_) => NFQA_TIMESTAMP, + PacketNla::IfIndexInDev(_) => NFQA_IFINDEX_INDEV, + PacketNla::IfIndexOutDev(_) => NFQA_IFINDEX_OUTDEV, + PacketNla::IfIndexPhysInDev(_) => NFQA_IFINDEX_PHYSINDEV, + PacketNla::IfIndexPhysOutDev(_) => NFQA_IFINDEX_PHYSOUTDEV, + PacketNla::HwAddr(_) => NFQA_HWADDR, + PacketNla::Payload(_) => NFQA_PAYLOAD, + PacketNla::Conntrack(_) => NFQA_CT, + PacketNla::ConntrackInfo(_) => NFQA_CT_INFO, + PacketNla::CapLen(_) => NFQA_CAP_LEN, + PacketNla::SkbInfo(_) => NFQA_SKB_INFO, + PacketNla::Exp(_) => NFQA_EXP, + PacketNla::Uid(_) => NFQA_UID, + PacketNla::Gid(_) => NFQA_GID, + PacketNla::SecCtx(_) => NFQA_SECCTX, + PacketNla::Vlan(_) => NFQA_VLAN, + PacketNla::L2Hdr(_) => NFQA_L2HDR, + PacketNla::Priotity(_) => NFQA_PRIORITY, + PacketNla::Other(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + PacketNla::PacketHdr(payload) => payload.emit(buffer), + PacketNla::Mark(payload) => BigEndian::write_u32(buffer, *payload), + PacketNla::TimeStamp(payload) => payload.emit(buffer), + PacketNla::IfIndexInDev(payload) => { + BigEndian::write_u32(buffer, *payload) + } + PacketNla::IfIndexOutDev(payload) => { + BigEndian::write_u32(buffer, *payload) + } + PacketNla::IfIndexPhysInDev(payload) => { + BigEndian::write_u32(buffer, *payload) + } + PacketNla::IfIndexPhysOutDev(payload) => { + BigEndian::write_u32(buffer, *payload) + } + PacketNla::HwAddr(payload) => payload.emit(buffer), + PacketNla::Payload(payload) => buffer.copy_from_slice(payload), + PacketNla::Conntrack(payload) => buffer.copy_from_slice(payload), + PacketNla::ConntrackInfo(payload) => { + BigEndian::write_u32(buffer, *payload) + } + PacketNla::CapLen(payload) => { + BigEndian::write_u32(buffer, *payload) + } + PacketNla::SkbInfo(payload) => payload.emit(buffer), + PacketNla::Exp(payload) => buffer.copy_from_slice(payload), + PacketNla::Uid(payload) => BigEndian::write_u32(buffer, *payload), + PacketNla::Gid(payload) => BigEndian::write_u32(buffer, *payload), + PacketNla::SecCtx(payload) => buffer.copy_from_slice(payload), + PacketNla::Vlan(payload) => buffer.copy_from_slice(payload), + PacketNla::L2Hdr(payload) => buffer.copy_from_slice(payload), + PacketNla::Priotity(payload) => { + BigEndian::write_u32(buffer, *payload) + } + PacketNla::Other(attr) => attr.emit_value(buffer), + } + } +} + +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for PacketNla +{ + fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { + let kind = buf.kind(); + let payload = buf.value(); + match kind { + NFQA_PACKET_HDR => match PacketHdr::parse(payload) { + Ok(payload) => Ok(PacketNla::PacketHdr(payload)), + Err(error) => Err(error), + }, + NFQA_MARK => match parse_u32_be(payload) { + Ok(payload) => Ok(PacketNla::Mark(payload)), + Err(error) => Err(error), + }, + NFQA_TIMESTAMP => match TimeStamp::parse(payload) { + Ok(payload) => Ok(PacketNla::TimeStamp(payload)), + Err(error) => Err(error), + }, + NFQA_IFINDEX_INDEV => match parse_u32_be(payload) { + Ok(payload) => Ok(PacketNla::IfIndexInDev(payload)), + Err(error) => Err(error), + }, + NFQA_IFINDEX_OUTDEV => match parse_u32_be(payload) { + Ok(payload) => Ok(PacketNla::IfIndexOutDev(payload)), + Err(error) => Err(error), + }, + NFQA_IFINDEX_PHYSINDEV => match parse_u32_be(payload) { + Ok(payload) => Ok(PacketNla::IfIndexPhysInDev(payload)), + Err(error) => Err(error), + }, + NFQA_IFINDEX_PHYSOUTDEV => match parse_u32_be(payload) { + Ok(payload) => Ok(PacketNla::IfIndexPhysOutDev(payload)), + Err(error) => Err(error), + }, + NFQA_HWADDR => match HwAddr::parse(payload) { + Ok(payload) => Ok(PacketNla::HwAddr(payload)), + Err(error) => Err(error), + }, + NFQA_PAYLOAD => Ok(PacketNla::Payload(payload.to_vec())), + NFQA_CT => Ok(PacketNla::Conntrack(payload.to_vec())), + NFQA_CT_INFO => match parse_u32_be(payload) { + Ok(payload) => Ok(PacketNla::ConntrackInfo(payload)), + Err(error) => Err(error), + }, + NFQA_SKB_INFO => match SkbFlags::parse(payload) { + Ok(payload) => Ok(PacketNla::SkbInfo(payload)), + Err(error) => Err(error), + }, + NFQA_EXP => Ok(PacketNla::Exp(payload.to_vec())), + NFQA_UID => match parse_u32_be(payload) { + Ok(payload) => Ok(PacketNla::Uid(payload)), + Err(error) => Err(error), + }, + NFQA_GID => match parse_u32_be(payload) { + Ok(payload) => Ok(PacketNla::Gid(payload)), + Err(error) => Err(error), + }, + NFQA_SECCTX => Ok(PacketNla::SecCtx(payload.to_vec())), + NFQA_VLAN => Ok(PacketNla::Vlan(payload.to_vec())), + NFQA_L2HDR => Ok(PacketNla::L2Hdr(payload.to_vec())), + NFQA_PRIORITY => match parse_u32_be(payload) { + Ok(payload) => Ok(PacketNla::Priotity(payload)), + Err(error) => Err(error), + }, + _ => match DefaultNla::parse(buf) { + Ok(attr) => Ok(PacketNla::Other(attr)), + Err(error) => Err(error), + }, + } + } +} diff --git a/src/nfqueue/nlas/packet/packet_hdr.rs b/src/nfqueue/nlas/packet/packet_hdr.rs new file mode 100644 index 0000000..97b6ca6 --- /dev/null +++ b/src/nfqueue/nlas/packet/packet_hdr.rs @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_utils::{buffer, errors::DecodeError, Emitable, Parseable}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PacketHdr { + pub packet_id: u32, + pub hw_protocol: u16, + pub hook: u8, +} + +impl PacketHdr { + pub fn new(packet_id: u32, hw_protocol: u16, hook: u8) -> Self { + Self { + packet_id, + hw_protocol, + hook, + } + } +} + +const PACKET_HDR_LEN: usize = 7; + +buffer!(PacketHdrBuffer(PACKET_HDR_LEN) { + packet_id: (u32, 0..4), + hw_protocol: (u16, 4..6), + hook: (u8, 6), +}); + +impl From<&PacketHdrBuffer<&[u8]>> for PacketHdr { + fn from(buffer: &PacketHdrBuffer<&[u8]>) -> Self { + PacketHdr::new( + u32::from_be(buffer.packet_id()), + u16::from_be(buffer.hw_protocol()), + buffer.hook(), + ) + } +} + +impl Parseable<[u8]> for PacketHdr { + fn parse(buffer: &[u8]) -> Result { + match PacketHdrBuffer::new_checked(buffer) { + Ok(buffer) => Ok(PacketHdr::from(&buffer)), + Err(error) => Err(error), + } + } +} + +impl Emitable for PacketHdr { + fn buffer_len(&self) -> usize { + PACKET_HDR_LEN + } + + fn emit(&self, buffer: &mut [u8]) { + let mut buffer = PacketHdrBuffer::new(buffer); + buffer.set_packet_id(u32::to_be(self.packet_id)); + buffer.set_hw_protocol(u16::to_be(self.hw_protocol)); + buffer.set_hook(self.hook); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse() { + let buffer: [u8; PACKET_HDR_LEN] = [ + 0x00, 0x01, 0x02, 0x03, // packet_id 0x010203 + 0x04, 0x05, // hw_protocol 0x0405 + 0x06, // hook 0x06 + ]; + match PacketHdr::parse(&buffer) { + Ok(packet) => { + assert_eq!(packet.packet_id, 0x00010203); + assert_eq!(packet.hw_protocol, 0x0405); + assert_eq!(packet.hook, 0x06); + } + Err(_) => assert!(false), + } + } + + #[test] + fn test_emit() { + let mut buffer = vec![0; PACKET_HDR_LEN]; + PacketHdr::new(0x00010203, 0x0405, 0x06).emit(&mut buffer); + assert_eq!( + buffer, + [ + 0x00, 0x01, 0x02, 0x03, // packet_id 0x010203 + 0x04, 0x05, // hw_protocol 0x0405 + 0x06, // hook 0x06 + ] + ); + } +} diff --git a/src/nfqueue/nlas/packet/skb_flags.rs b/src/nfqueue/nlas/packet/skb_flags.rs new file mode 100644 index 0000000..a76d807 --- /dev/null +++ b/src/nfqueue/nlas/packet/skb_flags.rs @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT + +use std::mem::size_of; + +use bitflags::bitflags; +use byteorder::{BigEndian, ByteOrder}; +use netlink_packet_utils::parsers::parse_u32_be; +use netlink_packet_utils::{DecodeError, Emitable, Parseable}; + +use crate::constants::{ + NFQA_SKB_CSUMNOTREADY, NFQA_SKB_CSUM_NOTVERIFIED, NFQA_SKB_GSO, +}; + +bitflags! { + #[derive(Clone, Debug, Copy, PartialEq, Eq)] + pub struct SkbFlags: u32 { + const CSUMNOTREADY = NFQA_SKB_CSUMNOTREADY; + const GSO = NFQA_SKB_GSO; + const CSUM_NOTVERIFIED = NFQA_SKB_CSUM_NOTVERIFIED; + } +} + +// see https://github.com/bitflags/bitflags/issues/263 +impl SkbFlags { + pub fn from_bits_preserve(bits: u32) -> Self { + SkbFlags::from_bits_truncate(bits) + } +} + +impl Parseable<[u8]> for SkbFlags { + fn parse(buf: &[u8]) -> Result { + match parse_u32_be(buf) { + Ok(value) => Ok(SkbFlags::from_bits_preserve(value)), + Err(error) => Err(error), + } + } +} + +impl Emitable for SkbFlags { + fn buffer_len(&self) -> usize { + size_of::() + } + + fn emit(&self, buffer: &mut [u8]) { + BigEndian::write_u32(buffer, self.bits()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_emit() { + let flags = SkbFlags::CSUMNOTREADY | SkbFlags::GSO; + + let mut buffer = vec![0; size_of::()]; + flags.emit(&mut buffer); + + assert_eq!( + buffer, + [ + 0x00, 0x00, 0x00, + 0x03 // SkbFlags::CSUMNOTREADY | SkbFlags::GSO + ] + ); + } + + #[test] + fn test_parse() { + let buffer: [u8; size_of::()] = [ + 0x00, 0x00, 0x00, + 0x03, // SkbFlags::CSUMNOTREADY | SkbFlags::GSO + ]; + match SkbFlags::parse(&buffer) { + Ok(flags) => { + assert_eq!(flags, SkbFlags::CSUMNOTREADY | SkbFlags::GSO) + } + Err(_) => assert!(false), + } + } +} diff --git a/src/nfqueue/nlas/packet/timestamp.rs b/src/nfqueue/nlas/packet/timestamp.rs new file mode 100644 index 0000000..26e8426 --- /dev/null +++ b/src/nfqueue/nlas/packet/timestamp.rs @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_utils::{buffer, errors::DecodeError, Emitable, Parseable}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TimeStamp { + sec: u64, + usec: u64, +} + +impl TimeStamp { + pub fn new(sec: u64, usec: u64) -> Self { + Self { sec, usec } + } +} + +const TIMESTAMP_LEN: usize = 16; + +buffer!(TimeStampBuffer(TIMESTAMP_LEN) { + sec: (u64, 0..8), + usec: (u64, 8..16), +}); + +impl From<&TimeStampBuffer<&[u8]>> for TimeStamp { + fn from(buffer: &TimeStampBuffer<&[u8]>) -> Self { + TimeStamp::new(u64::from_be(buffer.sec()), u64::from_be(buffer.usec())) + } +} + +impl Parseable<[u8]> for TimeStamp { + fn parse(buffer: &[u8]) -> Result { + match TimeStampBuffer::new_checked(buffer) { + Ok(buffer) => Ok(TimeStamp::from(&buffer)), + Err(error) => Err(error), + } + } +} + +impl Emitable for TimeStamp { + fn buffer_len(&self) -> usize { + TIMESTAMP_LEN + } + + fn emit(&self, buffer: &mut [u8]) { + let mut buffer = TimeStampBuffer::new(buffer); + buffer.set_sec(u64::to_be(self.sec)); + buffer.set_usec(u64::to_be(self.usec)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse() { + let buffer: [u8; TIMESTAMP_LEN] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // sec + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, // usec + ]; + match TimeStamp::parse(&buffer) { + Ok(timestamp) => { + assert_eq!(timestamp.sec, 0x0001020304050607); + assert_eq!(timestamp.usec, 0x08090A0B0C0D0E0F); + } + Err(_) => assert!(false), + } + } + + #[test] + fn test_emit() { + let mut buffer = vec![0; TIMESTAMP_LEN]; + TimeStamp::new(0x0001020304050607, 0x08090A0B0C0D0E0F) + .emit(&mut buffer); + assert_eq!( + buffer, + [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // sec + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, // usec + ] + ); + } +} diff --git a/src/nfqueue/nlas/verdict/mod.rs b/src/nfqueue/nlas/verdict/mod.rs new file mode 100644 index 0000000..907e71a --- /dev/null +++ b/src/nfqueue/nlas/verdict/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +mod nla; +mod verdict_hdr; + +pub use nla::VerdictNla; +pub use verdict_hdr::{VerdictHdr, VerdictType}; diff --git a/src/nfqueue/nlas/verdict/nla.rs b/src/nfqueue/nlas/verdict/nla.rs new file mode 100644 index 0000000..74e6fc2 --- /dev/null +++ b/src/nfqueue/nlas/verdict/nla.rs @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT + +use derive_more::IsVariant; + +use netlink_packet_utils::{ + errors::DecodeError, + nla::{DefaultNla, Nla, NlaBuffer}, + Emitable, Parseable, +}; + +use crate::{constants::NFQA_VERDICT_HDR, nfqueue::nlas::verdict::VerdictHdr}; + +#[derive(Debug, PartialEq, Eq, Clone, IsVariant)] +pub enum VerdictNla { + Verdict(VerdictHdr), + Other(DefaultNla), +} + +impl Nla for VerdictNla { + fn value_len(&self) -> usize { + match self { + VerdictNla::Verdict(attr) => attr.buffer_len(), + VerdictNla::Other(attr) => attr.buffer_len(), + } + } + + fn kind(&self) -> u16 { + match self { + VerdictNla::Verdict(_) => NFQA_VERDICT_HDR, + VerdictNla::Other(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + VerdictNla::Verdict(attr) => attr.emit(buffer), + VerdictNla::Other(attr) => attr.emit_value(buffer), + } + } +} + +impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> + for VerdictNla +{ + fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { + let kind = buf.kind(); + let payload = buf.value(); + match kind { + NFQA_VERDICT_HDR => match VerdictHdr::parse(payload) { + Ok(payload) => Ok(VerdictNla::Verdict(payload)), + Err(error) => Err(error), + }, + _ => match DefaultNla::parse(buf) { + Ok(attr) => Ok(VerdictNla::Other(attr)), + Err(error) => Err(error), + }, + } + } +} diff --git a/src/nfqueue/nlas/verdict/verdict_hdr.rs b/src/nfqueue/nlas/verdict/verdict_hdr.rs new file mode 100644 index 0000000..6c754e5 --- /dev/null +++ b/src/nfqueue/nlas/verdict/verdict_hdr.rs @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_utils::{buffer, DecodeError, Emitable, Parseable}; + +use crate::constants::{ + NF_ACCEPT, NF_DROP, NF_QUEUE, NF_REPEAT, NF_STOLEN, NF_STOP, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum VerdictType { + Drop, + Accept, + Stolen, + Queue, + Repeat, + Stop, + Other(u32), +} + +impl From for u32 { + fn from(verdict: VerdictType) -> Self { + match verdict { + VerdictType::Drop => NF_DROP, + VerdictType::Accept => NF_ACCEPT, + VerdictType::Stolen => NF_STOLEN, + VerdictType::Queue => NF_QUEUE, + VerdictType::Repeat => NF_REPEAT, + VerdictType::Stop => NF_STOP, + VerdictType::Other(verdict) => verdict, + } + } +} + +impl From for VerdictType { + fn from(verdict: u32) -> Self { + match verdict { + NF_DROP => VerdictType::Drop, + NF_ACCEPT => VerdictType::Accept, + NF_STOLEN => VerdictType::Stolen, + NF_QUEUE => VerdictType::Queue, + NF_REPEAT => VerdictType::Repeat, + NF_STOP => VerdictType::Stop, + verdict => VerdictType::Other(verdict), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct VerdictHdr { + verdict: VerdictType, + id: u32, +} + +impl VerdictHdr { + pub fn new(verdict: VerdictType, id: u32) -> Self { + Self { verdict, id } + } +} + +const VERDICT_HDR_LEN: usize = 8; + +buffer!(VerdictBuffer(VERDICT_HDR_LEN) { + verdict: (u32, 0..4), + id: (u32, 4..8), +}); + +impl From<&VerdictBuffer<&[u8]>> for VerdictHdr { + fn from(buffer: &VerdictBuffer<&[u8]>) -> Self { + Self::new( + VerdictType::from(u32::from_be(buffer.verdict())), + u32::from_be(buffer.id()), + ) + } +} + +impl Parseable<[u8]> for VerdictHdr { + fn parse(buffer: &[u8]) -> Result { + match VerdictBuffer::new_checked(buffer) { + Ok(buffer) => Ok(VerdictHdr::from(&buffer)), + Err(error) => Err(error), + } + } +} + +impl Emitable for VerdictHdr { + fn buffer_len(&self) -> usize { + VERDICT_HDR_LEN + } + + fn emit(&self, buffer: &mut [u8]) { + let mut buffer = VerdictBuffer::new(buffer); + buffer.set_verdict(u32::from_be(u32::from(self.verdict))); + buffer.set_id(u32::from_be(self.id)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse() { + let buffer: [u8; VERDICT_HDR_LEN] = [ + 0x00, 0x00, 0x00, 0x01, // NF_ACCEPT 0x00000001 + 0x01, 0x02, 0x03, 0x04, // id 0x01020304 + ]; + match VerdictHdr::parse(&buffer) { + Ok(verdict) => { + assert_eq!(verdict.verdict, VerdictType::Accept); + assert_eq!(verdict.id, 0x01020304); + } + Err(_) => assert!(false), + } + } + + #[test] + fn test_emit() { + let mut buffer = vec![0; VERDICT_HDR_LEN]; + VerdictHdr::new(VerdictType::Accept, 0x01020304).emit(&mut buffer); + assert_eq!( + buffer, + [ + 0x00, 0x00, 0x00, 0x01, // NF_ACCEPT 0x00000001 + 0x01, 0x02, 0x03, 0x04 // id 0x01020304 + ] + ); + } +}