all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Mira Limbeck <m.limbeck@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH v3 conntrack-tool 3/4] add expectation support
Date: Tue, 16 Feb 2021 17:56:41 +0100	[thread overview]
Message-ID: <20210216165642.16600-3-m.limbeck@proxmox.com> (raw)
In-Reply-To: <20210216165642.16600-1-m.limbeck@proxmox.com>

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 <m.limbeck@proxmox.com>
---
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<CString>)> {
+pub fn build_nf_conntrack(ct: Conntrack) -> Result<(*mut nf_conntrack, Vec<CString>)> {
     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<CString>)
     Ok((cth, strings))
 }
 
-fn parse_nf_conntrack(ct: *const nf_conntrack) -> Option<Conntrack> {
+pub fn parse_nf_conntrack(ct: *const nf_conntrack) -> Option<Conntrack> {
     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<Conntrack> {
             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<Conntrack> {
                 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<Vec<Conntrack>> {
     Ok(cts)
 }
 
-fn query_impl(
-    socket: &mut Socket,
-    cts: &mut Vec<Conntrack>,
-    seq: u32,
-    proto: u8,
-) -> Result<()> {
+fn query_impl(socket: &mut Socket, cts: &mut Vec<Conntrack>, 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<ExpectAttr>,
+}
+
+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<Vec<Expect>> {
+    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<Expect>, 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<u32>),
+}
+
+#[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::<Conntrack>(&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::<Expect>(&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





  parent reply	other threads:[~2021-02-16 16:56 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-02-16 16:56 [pve-devel] [PATCH v3 conntrack-tool 1/4] initial commit Mira Limbeck
2021-02-16 16:56 ` [pve-devel] [PATCH v3 conntrack-tool 2/4] add packaging support Mira Limbeck
2021-02-16 16:56 ` Mira Limbeck [this message]
2021-02-16 16:56 ` [pve-devel] [PATCH v3 conntrack-tool 4/4] add additional bindings Mira Limbeck
2021-04-06 10:19 ` [pve-devel] [PATCH v3 conntrack-tool 1/4] initial commit Dominic Jäger

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210216165642.16600-3-m.limbeck@proxmox.com \
    --to=m.limbeck@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal