Skip to content

Add support for NFQUEUE messages #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ target
vendor/

*.swp
.vscode
181 changes: 181 additions & 0 deletions examples/nfqueue.rs
Original file line number Diff line number Diff line change
@@ -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<NetfilterMessage>) -> &[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 =
<NetlinkMessage<NetfilterMessage>>::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 =
<NetlinkMessage<NetfilterMessage>>::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 =
<NetlinkMessage<NetfilterMessage>>::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 =
<NetlinkMessage<NetfilterMessage>>::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;
}
}
}
}
5 changes: 5 additions & 0 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
NETFILTER_HEADER_LEN,
},
nflog::NfLogMessage,
nfqueue::NfQueueMessage,
};
use anyhow::Context;
use netlink_packet_utils::{
Expand Down Expand Up @@ -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,
Expand Down
69 changes: 69 additions & 0 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ pub mod constants;
mod message;
pub use message::{NetfilterHeader, NetfilterMessage, NetfilterMessageInner};
pub mod nflog;
pub mod nfqueue;
17 changes: 16 additions & 1 deletion src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -62,6 +64,7 @@ impl<T: AsRef<[u8]>> Parseable<NetfilterHeaderBuffer<T>> for NetfilterHeader {
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum NetfilterMessageInner {
NfLog(NfLogMessage),
NfQueue(NfQueueMessage),
Other {
subsys: u8,
message_type: u8,
Expand All @@ -75,10 +78,17 @@ impl From<NfLogMessage> for NetfilterMessageInner {
}
}

impl From<NfQueueMessage> 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()
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -115,13 +126,17 @@ impl NetfilterMessage {
pub fn subsys(&self) -> u8 {
match self.inner {
NetfilterMessageInner::NfLog(_) => NfLogMessage::SUBSYS,
NetfilterMessageInner::NfQueue(_) => NfQueueMessage::SUBSYS,
NetfilterMessageInner::Other { subsys, .. } => subsys,
}
}

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,
}
}
Expand Down
Loading