From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 170DB6A3EA for ; Tue, 16 Feb 2021 17:56:48 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id ED54F188EA for ; Tue, 16 Feb 2021 17:56:47 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id D896F188A1 for ; Tue, 16 Feb 2021 17:56:45 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id A21E7461CA for ; Tue, 16 Feb 2021 17:56:45 +0100 (CET) From: Mira Limbeck To: pve-devel@lists.proxmox.com Date: Tue, 16 Feb 2021 17:56:41 +0100 Message-Id: <20210216165642.16600-3-m.limbeck@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20210216165642.16600-1-m.limbeck@proxmox.com> References: <20210216165642.16600-1-m.limbeck@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.241 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods NO_DNS_FOR_FROM 0.379 Envelope sender has no MX or A DNS records RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [conntrack.rs, main.rs, expect.rs] Subject: [pve-devel] [PATCH v3 conntrack-tool 3/4] add expectation support X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 16 Feb 2021 16:56:48 -0000 Expectation support requires net.netfilter.nf_conntrack_helper to be set to 1. In addition the helper modules have to be loaded as well. In the tests nf_conntrack_ftp was used as helper. Signed-off-by: Mira Limbeck --- v3: - split expect functions into their own files - made required functions in conntrack.rs public v2: - mostly the same changes as for patch 1 src/conntrack.rs | 20 ++-- src/expect.rs | 228 +++++++++++++++++++++++++++++++++++++ src/main.rs | 38 +++++-- src/netfilter_conntrack.rs | 44 +++++++ 4 files changed, 306 insertions(+), 24 deletions(-) create mode 100644 src/expect.rs diff --git a/src/conntrack.rs b/src/conntrack.rs index 6abd4a5..248c7a6 100644 --- a/src/conntrack.rs +++ b/src/conntrack.rs @@ -57,7 +57,7 @@ pub struct Conntrack { } impl Conntrack { - fn is_ipv6(&self) -> bool { + pub fn is_ipv6(&self) -> bool { for attr in self.attributes.iter() { if IPV6_ATTRIBUTES.contains(&attr.key) { return true; @@ -67,7 +67,7 @@ impl Conntrack { } } -fn build_nf_conntrack(ct: Conntrack) -> Result<(*mut nf_conntrack, Vec)> { +pub fn build_nf_conntrack(ct: Conntrack) -> Result<(*mut nf_conntrack, Vec)> { let cth = unsafe { nfct_new() }; if cth.is_null() { bail!("Failed to create new conntrack object"); @@ -100,7 +100,7 @@ fn build_nf_conntrack(ct: Conntrack) -> Result<(*mut nf_conntrack, Vec) Ok((cth, strings)) } -fn parse_nf_conntrack(ct: *const nf_conntrack) -> Option { +pub fn parse_nf_conntrack(ct: *const nf_conntrack) -> Option { let mut attributes = Vec::new(); for (attr, ty) in ALL_ATTRIBUTES { if *attr == CTAttr::ID { @@ -145,7 +145,7 @@ fn parse_nf_conntrack(ct: *const nf_conntrack) -> Option { AttrType::U128 => { let val = unsafe { nfct_get_attr(ct, *attr) } as *const u32; let val = unsafe { std::slice::from_raw_parts(val, 4) } - .try_into() + .try_into() .unwrap(); attributes.push(Attr { key: *attr, @@ -156,9 +156,8 @@ fn parse_nf_conntrack(ct: *const nf_conntrack) -> Option { let ptr = unsafe { nfct_get_attr(ct, *attr) }; let cstr = unsafe { std::ffi::CStr::from_ptr(ptr as _) }; let s = cstr.to_bytes(); - let s = unsafe { - CString::from_vec_unchecked(s[0..s.len().min(*len as _)].to_vec()) - }; + let s = + unsafe { CString::from_vec_unchecked(s[0..s.len().min(*len as _)].to_vec()) }; attributes.push(Attr { key: *attr, value: AttrValue::String(s), @@ -191,12 +190,7 @@ pub fn query_all(socket: &mut Socket) -> Result> { Ok(cts) } -fn query_impl( - socket: &mut Socket, - cts: &mut Vec, - seq: u32, - proto: u8, -) -> Result<()> { +fn query_impl(socket: &mut Socket, cts: &mut Vec, seq: u32, proto: u8) -> Result<()> { let mut buf = vec![0u8; *MNL_SOCKET_BUFFER_SIZE as _]; let hdr = build_msg_header( buf.as_mut_ptr() as _, diff --git a/src/expect.rs b/src/expect.rs new file mode 100644 index 0000000..a97c7ef --- /dev/null +++ b/src/expect.rs @@ -0,0 +1,228 @@ +use crate::conntrack; +use crate::conntrack::Conntrack; +use crate::mnl::{IPCTNL_MSG_EXP_GET, IPCTNL_MSG_EXP_NEW, MNL_SOCKET_BUFFER_SIZE}; +use crate::netfilter_conntrack::{ + nfct_destroy, nfexp_attr_is_set, nfexp_destroy, nfexp_get_attr, nfexp_get_attr_u16, + nfexp_get_attr_u32, nfexp_get_attr_u8, nfexp_new, nfexp_nlmsg_build, nfexp_nlmsg_parse, + nfexp_set_attr, nfexp_set_attr_u16, nfexp_set_attr_u32, nfexp_set_attr_u8, ExpAttr, +}; +use crate::socket::Socket; +use crate::utils::build_msg_header; + +use anyhow::{bail, Result}; +use serde::{Deserialize, Serialize}; + +use std::ffi::{CStr, CString}; + +const EXPECT_QUERY_MSG_TYPE: u16 = + ((libc::NFNL_SUBSYS_CTNETLINK_EXP << 8) | IPCTNL_MSG_EXP_GET) as u16; +const EXPECT_QUERY_FLAGS: u16 = (libc::NLM_F_ACK | libc::NLM_F_REQUEST | libc::NLM_F_DUMP) as u16; +const EXPECT_INSERT_MSG_TYPE: u16 = + ((libc::NFNL_SUBSYS_CTNETLINK_EXP << 8) | IPCTNL_MSG_EXP_NEW) as u16; +const EXPECT_INSERT_FLAGS: u16 = + (libc::NLM_F_ACK | libc::NLM_F_REQUEST | libc::NLM_F_CREATE) as u16; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Expect { + attributes: Vec, +} + +impl Expect { + pub fn is_ipv6(&self) -> bool { + for attr in &self.attributes { + if let ExpectAttrValue::CT(ct) = &attr.value { + return ct.is_ipv6(); + } + } + false + } +} + +pub fn query_all(socket: &mut Socket) -> Result> { + let mut exps = Vec::new(); + let seq = socket.seq(); + query_impl(socket, &mut exps, seq, libc::AF_INET as _)?; + let seq = socket.seq(); + query_impl(socket, &mut exps, seq, libc::AF_INET6 as _)?; + Ok(exps) +} + +fn query_impl(socket: &mut Socket, exps: &mut Vec, seq: u32, proto: u8) -> Result<()> { + let mut buf = vec![0u8; *MNL_SOCKET_BUFFER_SIZE as _]; + let hdr = build_msg_header( + buf.as_mut_ptr() as _, + EXPECT_QUERY_MSG_TYPE, + EXPECT_QUERY_FLAGS, + seq, + proto, + ); + + let mut cb = |nlh| { + let exp = unsafe { nfexp_new() }; + unsafe { + nfexp_nlmsg_parse(nlh, exp); + } + + let mut attributes = Vec::new(); + for (attr, ty) in EXPECT_ALL_ATTRIBUTES { + if unsafe { nfexp_attr_is_set(exp, *attr) } == 0 { + continue; + } + match ty { + ExpectAttrType::CT => { + let ct = unsafe { nfexp_get_attr(exp, *attr) }; + if let Some(ct) = conntrack::parse_nf_conntrack(ct as _) { + attributes.push(ExpectAttr { + key: *attr, + value: ExpectAttrValue::CT(ct), + }); + } + } + ExpectAttrType::U8 => { + let val = unsafe { nfexp_get_attr_u8(exp, *attr) }; + attributes.push(ExpectAttr { + key: *attr, + value: ExpectAttrValue::U8(val), + }); + } + ExpectAttrType::U16 => { + let val = unsafe { nfexp_get_attr_u16(exp, *attr) }; + attributes.push(ExpectAttr { + key: *attr, + value: ExpectAttrValue::U16(val), + }); + } + ExpectAttrType::U32 => { + let val = unsafe { nfexp_get_attr_u32(exp, *attr) }; + attributes.push(ExpectAttr { + key: *attr, + value: ExpectAttrValue::U32(val), + }); + } + ExpectAttrType::String(Some(len)) => { + let ptr = unsafe { nfexp_get_attr(exp, *attr) }; + let cstr = unsafe { CStr::from_ptr(ptr as _) }; + let s = cstr.to_bytes(); + let s = unsafe { + CString::from_vec_unchecked(s[0..s.len().min((*len) as _)].to_vec()) + }; + attributes.push(ExpectAttr { + key: *attr, + value: ExpectAttrValue::String(s), + }); + } + ExpectAttrType::String(None) => { + let ptr = unsafe { nfexp_get_attr(exp, *attr) }; + let cstr = unsafe { CStr::from_ptr(ptr as _) }; + let s = cstr.to_bytes(); + let s = unsafe { CString::from_vec_unchecked(s.to_vec()) }; + attributes.push(ExpectAttr { + key: *attr, + value: ExpectAttrValue::String(s), + }); + } + } + } + + exps.push(Expect { attributes }); + }; + socket.send_and_receive(hdr, 0, &mut cb) +} + +pub fn insert(socket: &mut Socket, exp: Expect) -> Result<()> { + let proto = if exp.is_ipv6() { + libc::AF_INET6 as u8 + } else { + libc::AF_INET as u8 + }; + + let mut buf = vec![0u8; *MNL_SOCKET_BUFFER_SIZE as _]; + let hdr = build_msg_header( + buf.as_mut_ptr() as _, + EXPECT_INSERT_MSG_TYPE, + EXPECT_INSERT_FLAGS, + socket.seq(), + proto, + ); + + let exph = unsafe { nfexp_new() }; + if exph.is_null() { + bail!("Failed to create new expect object"); + } + + let mut strings = Vec::new(); + let mut cts = Vec::new(); + for attr in exp.attributes { + match attr.value { + ExpectAttrValue::CT(ct) => unsafe { + let (ct, mut s) = conntrack::build_nf_conntrack(ct)?; + nfexp_set_attr(exph, attr.key, ct as _); + strings.append(&mut s); + cts.push(ct); + }, + ExpectAttrValue::U8(v) => unsafe { + nfexp_set_attr_u8(exph, attr.key, v); + }, + ExpectAttrValue::U16(v) => unsafe { + nfexp_set_attr_u16(exph, attr.key, v); + }, + ExpectAttrValue::U32(v) => unsafe { + nfexp_set_attr_u32(exph, attr.key, v); + }, + ExpectAttrValue::String(v) => unsafe { + nfexp_set_attr(exph, attr.key, v.as_ptr() as _); + strings.push(v); + }, + } + } + + unsafe { + nfexp_nlmsg_build(hdr, exph); + nfexp_destroy(exph); + } + for ct in cts { + unsafe { + nfct_destroy(ct); + } + } + + socket.send_and_receive(hdr, 0, &mut |_| {}) +} + +enum ExpectAttrType { + CT, + U8, + U16, + U32, + String(Option), +} + +#[derive(Debug, Serialize, Deserialize)] +enum ExpectAttrValue { + CT(Conntrack), + U8(u8), + U16(u16), + U32(u32), + String(CString), +} + +#[derive(Debug, Serialize, Deserialize)] +struct ExpectAttr { + #[serde(rename = "type")] + key: ExpAttr, + value: ExpectAttrValue, +} + +const EXPECT_ALL_ATTRIBUTES: &[(ExpAttr, ExpectAttrType)] = &[ + (ExpAttr::MASTER, ExpectAttrType::CT), // conntrack + (ExpAttr::EXPECTED, ExpectAttrType::CT), // conntrack + (ExpAttr::MASK, ExpectAttrType::CT), // conntrack + (ExpAttr::TIMEOUT, ExpectAttrType::U32), // u32 bits + (ExpAttr::ZONE, ExpectAttrType::U16), // u16 bits + (ExpAttr::FLAGS, ExpectAttrType::U32), // u32 bits + (ExpAttr::HELPER_NAME, ExpectAttrType::String(Some(16))), // string 16 bytes max + (ExpAttr::CLASS, ExpectAttrType::U32), // u32 bits + (ExpAttr::NAT_TUPLE, ExpectAttrType::CT), // conntrack + (ExpAttr::NAT_DIR, ExpectAttrType::U8), // u8 bits + (ExpAttr::FN, ExpectAttrType::String(None)), // string +]; diff --git a/src/main.rs b/src/main.rs index 792d487..5ee3451 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,12 +6,14 @@ use std::os::unix::ffi::OsStringExt; use anyhow::{bail, format_err, Result}; -mod socket; mod conntrack; +mod expect; +mod socket; mod utils; -use socket::Socket; use conntrack::Conntrack; +use expect::Expect; +use socket::Socket; fn main() -> Result<()> { let args = std::env::args_os() @@ -42,20 +44,35 @@ fn main() -> Result<()> { } } } + + let exps = expect::query_all(&mut socket) + .map_err(|err| format_err!("Error querying expects: {}", err))?; + + for exp in exps.iter() { + match serde_json::to_string(exp) { + Ok(s) => println!("{}", s), + Err(err) => { + eprintln!("Failed to serialize expect: {}", err); + break; + } + } + } } else if args[1] == "insert" { for line in BufReader::new(stdin()) .lines() .map(|line| line.unwrap_or_else(|_| "".to_string())) { - let ct: Conntrack = match serde_json::from_str(&line) { - Ok(ct) => ct, - Err(err) => { - eprintln!("Failed to deserialize conntrack: {}", err); - break; + if let Ok(ct) = serde_json::from_str::(&line) { + if let Err(err) = conntrack::insert(&mut socket, ct) { + eprintln!("Error inserting conntrack: {}", err); } - }; - if let Err(err) = conntrack::insert(&mut socket, ct) { - eprintln!("Error inserting conntrack: {}", err); + } else if let Ok(exp) = serde_json::from_str::(&line) { + if let Err(err) = expect::insert(&mut socket, exp) { + eprintln!("Error inserting expect: {}", err); + } + } else { + eprintln!("Failed to deserialize input: {}", line); + break; } } } else { @@ -64,4 +81,3 @@ fn main() -> Result<()> { Ok(()) } - diff --git a/src/netfilter_conntrack.rs b/src/netfilter_conntrack.rs index a9e67e4..8a56ad8 100644 --- a/src/netfilter_conntrack.rs +++ b/src/netfilter_conntrack.rs @@ -39,6 +39,25 @@ extern "C" { pub fn nfct_get_attr_u64(ct: *const nf_conntrack, type_: CTAttr) -> u64; pub fn nfct_attr_is_set(ct: *const nf_conntrack, type_: CTAttr) -> libc::c_int; + + // expectation API + pub fn nfexp_new() -> *mut nf_expect; + pub fn nfexp_destroy(exp: *mut nf_expect); + + pub fn nfexp_set_attr(exp: *mut nf_expect, type_: ExpAttr, value: *const libc::c_void); + pub fn nfexp_set_attr_u8(exp: *mut nf_expect, type_: ExpAttr, value: u8); + pub fn nfexp_set_attr_u16(exp: *mut nf_expect, type_: ExpAttr, value: u16); + pub fn nfexp_set_attr_u32(exp: *mut nf_expect, type_: ExpAttr, value: u32); + + pub fn nfexp_get_attr(exp: *const nf_expect, type_: ExpAttr) -> *const libc::c_void; + pub fn nfexp_get_attr_u8(exp: *const nf_expect, type_: ExpAttr) -> u8; + pub fn nfexp_get_attr_u16(exp: *const nf_expect, type_: ExpAttr) -> u16; + pub fn nfexp_get_attr_u32(exp: *const nf_expect, type_: ExpAttr) -> u32; + + pub fn nfexp_attr_is_set(exp: *const nf_expect, type_: ExpAttr) -> libc::c_int; + + pub fn nfexp_nlmsg_parse(nlh: *const libc::nlmsghdr, exp: *mut nf_expect) -> libc::c_int; + pub fn nfexp_nlmsg_build(nlh: *mut libc::nlmsghdr, exp: *const nf_expect) -> libc::c_int; } // set option @@ -166,3 +185,28 @@ pub enum CTAttr { SYNPROXY_TSOFF = 74, /* u32 bits */ MAX = 75, } + +#[repr(C)] +pub struct nf_expect { + _private: [u8; 0], +} + +#[repr(u32)] +#[non_exhaustive] +#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(serde::Deserialize, serde::Serialize)] +#[allow(non_camel_case_types)] +pub enum ExpAttr { + MASTER = 0, /* pointer to conntrack object */ + EXPECTED = 1, /* pointer to conntrack object */ + MASK = 2, /* pointer to conntrack object */ + TIMEOUT = 3, /* u32 bits */ + ZONE = 4, /* u16 bits */ + FLAGS = 5, /* u32 bits */ + HELPER_NAME = 6, /* string (16 bytes max) */ + CLASS = 7, /* u32 bits */ + NAT_TUPLE = 8, /* pointer to conntrack object */ + NAT_DIR = 9, /* u8 bits */ + FN = 10, /* string */ + MAX = 11, +} -- 2.20.1