public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH v3 conntrack-tool 1/4] initial commit
@ 2021-02-16 16:56 Mira Limbeck
  2021-02-16 16:56 ` [pve-devel] [PATCH v3 conntrack-tool 2/4] add packaging support Mira Limbeck
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Mira Limbeck @ 2021-02-16 16:56 UTC (permalink / raw)
  To: pve-devel

Dumping conntrack information and importing conntrack information works
for IPv4 and IPv6. No filtering is supported for now. pve-conntrack-tool
will always return both IPv4 and IPv6 conntracks together.

Conntracks are serialized as JSON and printed on STDOUT line by line
with one line containing one conntrack. When inserting data is read
from STDIN line by line and expected to be one JSON object per line
representing the conntrack.

Currently some conntrack attributes are not supported. These are
HELPER_INFO, CONNLABELS and CONNLABELS_MASK. The reason for this is that
handling of variable length attributes does not seem to be correctly
implemented in libnetfilter_conntrack. To fix this we would probably have
to use libmnl directly.

Conntracks containing protonum 2 (IGMP) are ignored in the dump as
they can't be inserted using libnetfilter_conntrack (conntrack-tools'
conntrack also exhibits the same behavior).

Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
---
v3:
 - split the functionality from Socket and into their own files
 - fixed MNL_SOCKET_BUFFER_SIZE to check configured page size at runtime
   (added lazy_static dependency)
 - changed is_ipv6 function to check the attribute keys instead of values
 - merged v2 patch 5 into patch 1 and 3 and fixed calling the closure in
   the callback
v2:
 - changed Conntracks to Socket
 - reworked a lot of the code for less code duplication
 - reduced usage of 'unsafe'
 - added/changed things based on @Wobu's suggestions (off-list)

 Cargo.toml                 |  15 ++
 src/conntrack.rs           | 338 +++++++++++++++++++++++++++++++++++++
 src/main.rs                |  67 ++++++++
 src/mnl.rs                 | 142 ++++++++++++++++
 src/netfilter_conntrack.rs | 168 ++++++++++++++++++
 src/socket.rs              | 104 ++++++++++++
 src/utils.rs               |  26 +++
 7 files changed, 860 insertions(+)
 create mode 100644 Cargo.toml
 create mode 100644 src/conntrack.rs
 create mode 100644 src/main.rs
 create mode 100644 src/mnl.rs
 create mode 100644 src/netfilter_conntrack.rs
 create mode 100644 src/socket.rs
 create mode 100644 src/utils.rs

diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..4936ec7
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "pve-conntrack-tool"
+version = "1.0.0"
+authors = ["Mira Limbeck <m.limbeck@proxmox.com>"]
+edition = "2018"
+license = "AGPL-3"
+
+exclude = [ "build", "debian" ]
+
+[dependencies]
+anyhow = "1.0.26"
+lazy_static = "1.4.0"
+libc = "0.2.79"
+serde = { version = "1.0.106", features = ["derive"] }
+serde_json = "1.0.41"
diff --git a/src/conntrack.rs b/src/conntrack.rs
new file mode 100644
index 0000000..6abd4a5
--- /dev/null
+++ b/src/conntrack.rs
@@ -0,0 +1,338 @@
+use crate::mnl::{IPCTNL_MSG_CT_GET, IPCTNL_MSG_CT_NEW, MNL_SOCKET_BUFFER_SIZE};
+use crate::netfilter_conntrack::{
+    nf_conntrack, nfct_attr_is_set, nfct_destroy, nfct_get_attr, nfct_get_attr_u16,
+    nfct_get_attr_u32, nfct_get_attr_u64, nfct_get_attr_u8, nfct_new, nfct_nlmsg_build,
+    nfct_nlmsg_parse, nfct_set_attr, nfct_set_attr_l, nfct_set_attr_u16, nfct_set_attr_u32,
+    nfct_set_attr_u64, nfct_set_attr_u8, CTAttr,
+};
+use crate::socket::Socket;
+use crate::utils::build_msg_header;
+
+use anyhow::{bail, Result};
+use serde::{Deserialize, Serialize};
+
+use std::convert::TryInto;
+use std::ffi::CString;
+
+const CONNTRACK_QUERY_MSG_TYPE: u16 =
+    ((libc::NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET) as u16;
+const CONNTRACK_QUERY_FLAGS: u16 =
+    (libc::NLM_F_ACK | libc::NLM_F_REQUEST | libc::NLM_F_DUMP) as u16;
+const CONNTRACK_INSERT_MSG_TYPE: u16 =
+    ((libc::NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_NEW) as u16;
+const CONNTRACK_INSERT_FLAGS: u16 =
+    (libc::NLM_F_ACK | libc::NLM_F_REQUEST | libc::NLM_F_CREATE) as u16;
+
+enum AttrType {
+    U8,
+    U16,
+    U32,
+    U64,
+    U128,
+    String(Option<u32>),
+    VarLen,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+enum AttrValue {
+    U8(u8),
+    U16(u16),
+    U32(u32),
+    U64(u64),
+    U128([u32; 4]),
+    String(CString),
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+struct Attr {
+    #[serde(rename = "type")]
+    key: CTAttr,
+    #[serde(flatten)]
+    value: AttrValue,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Conntrack {
+    attributes: Vec<Attr>,
+}
+
+impl Conntrack {
+    fn is_ipv6(&self) -> bool {
+        for attr in self.attributes.iter() {
+            if IPV6_ATTRIBUTES.contains(&attr.key) {
+                return true;
+            }
+        }
+        false
+    }
+}
+
+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");
+    }
+
+    let mut strings = Vec::new();
+    for attr in ct.attributes {
+        match attr.value {
+            AttrValue::U8(v) => unsafe {
+                nfct_set_attr_u8(cth, attr.key, v);
+            },
+            AttrValue::U16(v) => unsafe {
+                nfct_set_attr_u16(cth, attr.key, v);
+            },
+            AttrValue::U32(v) => unsafe {
+                nfct_set_attr_u32(cth, attr.key, v);
+            },
+            AttrValue::U64(v) => unsafe {
+                nfct_set_attr_u64(cth, attr.key, v);
+            },
+            AttrValue::U128(v) => unsafe {
+                nfct_set_attr_l(cth, attr.key, v.as_ptr() as _, std::mem::size_of_val(&v));
+            },
+            AttrValue::String(v) => unsafe {
+                nfct_set_attr(cth, attr.key, v.as_ptr() as _);
+                strings.push(v);
+            },
+        }
+    }
+    Ok((cth, strings))
+}
+
+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 {
+            continue;
+        }
+        if unsafe { nfct_attr_is_set(ct, *attr) } == 0 {
+            continue;
+        }
+        // check for IGMP and skip it as we can't insert it again
+        if unsafe { nfct_get_attr_u8(ct, CTAttr::ORIG_L4PROTO) } == 2 {
+            return None;
+        }
+        match ty {
+            AttrType::U8 => {
+                let val = unsafe { nfct_get_attr_u8(ct, *attr) };
+                attributes.push(Attr {
+                    key: *attr,
+                    value: AttrValue::U8(val),
+                });
+            }
+            AttrType::U16 => {
+                let val = unsafe { nfct_get_attr_u16(ct, *attr) };
+                attributes.push(Attr {
+                    key: *attr,
+                    value: AttrValue::U16(val),
+                });
+            }
+            AttrType::U32 => {
+                let val = unsafe { nfct_get_attr_u32(ct, *attr) };
+                attributes.push(Attr {
+                    key: *attr,
+                    value: AttrValue::U32(val),
+                });
+            }
+            AttrType::U64 => {
+                let val = unsafe { nfct_get_attr_u64(ct, *attr) };
+                attributes.push(Attr {
+                    key: *attr,
+                    value: AttrValue::U64(val),
+                });
+            }
+            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()
+                    .unwrap();
+                attributes.push(Attr {
+                    key: *attr,
+                    value: AttrValue::U128(val),
+                });
+            }
+            AttrType::String(Some(len)) => {
+                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())
+                };
+                attributes.push(Attr {
+                    key: *attr,
+                    value: AttrValue::String(s),
+                });
+            }
+            AttrType::String(None) => {
+                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.to_vec()) };
+                attributes.push(Attr {
+                    key: *attr,
+                    value: AttrValue::String(s),
+                });
+            }
+            // ignore VarLen case for now
+            AttrType::VarLen => {}
+        }
+    }
+
+    Some(Conntrack { attributes })
+}
+
+pub fn query_all(socket: &mut Socket) -> Result<Vec<Conntrack>> {
+    let mut cts = Vec::new();
+    let seq = socket.seq();
+    query_impl(socket, &mut cts, seq, libc::AF_INET as _)?;
+    let seq = socket.seq();
+    query_impl(socket, &mut cts, seq, libc::AF_INET6 as _)?;
+    Ok(cts)
+}
+
+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 _,
+        CONNTRACK_QUERY_MSG_TYPE,
+        CONNTRACK_QUERY_FLAGS,
+        seq,
+        proto,
+    );
+    let mut cb = |nlh| {
+        let ct = unsafe { nfct_new() };
+        unsafe {
+            nfct_nlmsg_parse(nlh, ct);
+        }
+
+        if let Some(conntrack) = parse_nf_conntrack(ct) {
+            cts.push(conntrack);
+        }
+
+        unsafe {
+            nfct_destroy(ct);
+        }
+    };
+    socket.send_and_receive(hdr, 0, &mut cb)
+}
+
+pub fn insert(socket: &mut Socket, ct: Conntrack) -> Result<()> {
+    let proto = if ct.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 _,
+        CONNTRACK_INSERT_MSG_TYPE,
+        CONNTRACK_INSERT_FLAGS,
+        socket.seq(),
+        proto,
+    );
+
+    let (cth, _strings) = build_nf_conntrack(ct)?;
+
+    unsafe {
+        nfct_nlmsg_build(hdr, cth);
+        nfct_destroy(cth);
+    }
+
+    socket.send_and_receive(hdr, 0, &mut |_| {})
+}
+
+const ALL_ATTRIBUTES: &[(CTAttr, AttrType)] = &[
+    (CTAttr::ORIG_IPV4_SRC, AttrType::U32),        /* u32 bits */
+    (CTAttr::ORIG_IPV4_DST, AttrType::U32),        /* u32 bits */
+    (CTAttr::REPL_IPV4_SRC, AttrType::U32),        /* u32 bits */
+    (CTAttr::REPL_IPV4_DST, AttrType::U32),        /* u32 bits */
+    (CTAttr::ORIG_IPV6_SRC, AttrType::U128),       /* u128 bits */
+    (CTAttr::ORIG_IPV6_DST, AttrType::U128),       /* u128 bits */
+    (CTAttr::REPL_IPV6_SRC, AttrType::U128),       /* u128 bits */
+    (CTAttr::REPL_IPV6_DST, AttrType::U128),       /* u128 bits */
+    (CTAttr::ORIG_PORT_SRC, AttrType::U16),        /* u16 bits */
+    (CTAttr::ORIG_PORT_DST, AttrType::U16),        /* u16 bits */
+    (CTAttr::REPL_PORT_SRC, AttrType::U16),        /* u16 bits */
+    (CTAttr::REPL_PORT_DST, AttrType::U16),        /* u16 bits */
+    (CTAttr::ICMP_TYPE, AttrType::U8),             /* u8 bits */
+    (CTAttr::ICMP_CODE, AttrType::U8),             /* u8 bits */
+    (CTAttr::ICMP_ID, AttrType::U16),              /* u16 bits */
+    (CTAttr::ORIG_L3PROTO, AttrType::U8),          /* u8 bits */
+    (CTAttr::REPL_L3PROTO, AttrType::U8),          /* u8 bits */
+    (CTAttr::ORIG_L4PROTO, AttrType::U8),          /* u8 bits */
+    (CTAttr::REPL_L4PROTO, AttrType::U8),          /* u8 bits */
+    (CTAttr::TCP_STATE, AttrType::U8),             /* u8 bits */
+    (CTAttr::SNAT_IPV4, AttrType::U32),            /* u32 bits */
+    (CTAttr::DNAT_IPV4, AttrType::U32),            /* u32 bits */
+    (CTAttr::SNAT_PORT, AttrType::U16),            /* u16 bits */
+    (CTAttr::DNAT_PORT, AttrType::U16),            /* u16 bits */
+    (CTAttr::TIMEOUT, AttrType::U32),              /* u32 bits */
+    (CTAttr::MARK, AttrType::U32),                 /* u32 bits */
+    (CTAttr::ORIG_COUNTER_PACKETS, AttrType::U64), /* u64 bits */
+    (CTAttr::REPL_COUNTER_PACKETS, AttrType::U64), /* u64 bits */
+    (CTAttr::ORIG_COUNTER_BYTES, AttrType::U64),   /* u64 bits */
+    (CTAttr::REPL_COUNTER_BYTES, AttrType::U64),   /* u64 bits */
+    (CTAttr::USE, AttrType::U32),                  /* u32 bits */
+    (CTAttr::ID, AttrType::U32),                   /* u32 bits */
+    (CTAttr::STATUS, AttrType::U32),               /* u32 bits  */
+    (CTAttr::TCP_FLAGS_ORIG, AttrType::U8),        /* u8 bits */
+    (CTAttr::TCP_FLAGS_REPL, AttrType::U8),        /* u8 bits */
+    (CTAttr::TCP_MASK_ORIG, AttrType::U8),         /* u8 bits */
+    (CTAttr::TCP_MASK_REPL, AttrType::U8),         /* u8 bits */
+    (CTAttr::MASTER_IPV4_SRC, AttrType::U32),      /* u32 bits */
+    (CTAttr::MASTER_IPV4_DST, AttrType::U32),      /* u32 bits */
+    (CTAttr::MASTER_IPV6_SRC, AttrType::U128),     /* u128 bits */
+    (CTAttr::MASTER_IPV6_DST, AttrType::U128),     /* u128 bits */
+    (CTAttr::MASTER_PORT_SRC, AttrType::U16),      /* u16 bits */
+    (CTAttr::MASTER_PORT_DST, AttrType::U16),      /* u16 bits */
+    (CTAttr::MASTER_L3PROTO, AttrType::U8),        /* u8 bits */
+    (CTAttr::MASTER_L4PROTO, AttrType::U8),        /* u8 bits */
+    (CTAttr::SECMARK, AttrType::U32),              /* u32 bits */
+    (CTAttr::ORIG_NAT_SEQ_CORRECTION_POS, AttrType::U32), /* u32 bits */
+    (CTAttr::ORIG_NAT_SEQ_OFFSET_BEFORE, AttrType::U32), /* u32 bits */
+    (CTAttr::ORIG_NAT_SEQ_OFFSET_AFTER, AttrType::U32), /* u32 bits */
+    (CTAttr::REPL_NAT_SEQ_CORRECTION_POS, AttrType::U32), /* u32 bits */
+    (CTAttr::REPL_NAT_SEQ_OFFSET_BEFORE, AttrType::U32), /* u32 bits */
+    (CTAttr::REPL_NAT_SEQ_OFFSET_AFTER, AttrType::U32), /* u32 bits */
+    (CTAttr::SCTP_STATE, AttrType::U8),            /* u8 bits */
+    (CTAttr::SCTP_VTAG_ORIG, AttrType::U32),       /* u32 bits */
+    (CTAttr::SCTP_VTAG_REPL, AttrType::U32),       /* u32 bits */
+    (CTAttr::HELPER_NAME, AttrType::String(Some(30))), /* string (30 bytes max) */
+    (CTAttr::DCCP_STATE, AttrType::U8),            /* u8 bits */
+    (CTAttr::DCCP_ROLE, AttrType::U8),             /* u8 bits */
+    (CTAttr::DCCP_HANDSHAKE_SEQ, AttrType::U64),   /* u64 bits */
+    (CTAttr::TCP_WSCALE_ORIG, AttrType::U8),       /* u8 bits */
+    (CTAttr::TCP_WSCALE_REPL, AttrType::U8),       /* u8 bits */
+    (CTAttr::ZONE, AttrType::U16),                 /* u16 bits */
+    (CTAttr::SECCTX, AttrType::String(None)),      /* string */
+    (CTAttr::TIMESTAMP_START, AttrType::U64),      /* u64 bits; linux >= 2.6.38 */
+    (CTAttr::TIMESTAMP_STOP, AttrType::U64),       /* u64 bits; linux >= 2.6.38 */
+    (CTAttr::HELPER_INFO, AttrType::VarLen),       /* variable length */
+    (CTAttr::CONNLABELS, AttrType::VarLen),        /* variable length */
+    (CTAttr::CONNLABELS_MASK, AttrType::VarLen),   /* variable length */
+    (CTAttr::ORIG_ZONE, AttrType::U16),            /* u16 bits */
+    (CTAttr::REPL_ZONE, AttrType::U16),            /* u16 bits */
+    (CTAttr::SNAT_IPV6, AttrType::U128),           /* u128 bits */
+    (CTAttr::DNAT_IPV6, AttrType::U128),           /* u128 bits */
+    (CTAttr::SYNPROXY_ISN, AttrType::U32),         /* u32 bits */
+    (CTAttr::SYNPROXY_ITS, AttrType::U32),         /* u32 bits */
+    (CTAttr::SYNPROXY_TSOFF, AttrType::U32),       /* u32 bits */
+];
+
+const IPV6_ATTRIBUTES: &[CTAttr] = &[
+    CTAttr::ORIG_IPV6_SRC,
+    CTAttr::ORIG_IPV6_DST,
+    CTAttr::REPL_IPV6_SRC,
+    CTAttr::REPL_IPV6_DST,
+    CTAttr::MASTER_IPV6_SRC,
+    CTAttr::MASTER_IPV6_DST,
+    CTAttr::SNAT_IPV6,
+    CTAttr::DNAT_IPV6,
+];
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..792d487
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,67 @@
+mod mnl;
+mod netfilter_conntrack;
+
+use std::io::{stdin, BufRead, BufReader};
+use std::os::unix::ffi::OsStringExt;
+
+use anyhow::{bail, format_err, Result};
+
+mod socket;
+mod conntrack;
+mod utils;
+
+use socket::Socket;
+use conntrack::Conntrack;
+
+fn main() -> Result<()> {
+    let args = std::env::args_os()
+        .map(|os| String::from_utf8(os.into_vec()))
+        .try_fold(Vec::new(), |mut args, s| match s {
+            Ok(s) => {
+                args.push(s);
+                Ok(args)
+            }
+            Err(err) => bail!("Invalid UTF8 argument: {}", err),
+        })?;
+    if args.len() != 2 {
+        bail!("Either 'dump' or 'insert' command required.");
+    }
+
+    let mut socket = Socket::open()?;
+
+    if args[1] == "dump" {
+        let cts = conntrack::query_all(&mut socket)
+            .map_err(|err| format_err!("Error querying conntracks: {}", err))?;
+
+        for ct in cts.iter() {
+            match serde_json::to_string(ct) {
+                Ok(s) => println!("{}", s),
+                Err(err) => {
+                    eprintln!("Failed to serialize conntrack: {}", 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 Err(err) = conntrack::insert(&mut socket, ct) {
+                eprintln!("Error inserting conntrack: {}", err);
+            }
+        }
+    } else {
+        bail!("Unknown command: {}", args[1]);
+    }
+
+    Ok(())
+}
+
diff --git a/src/mnl.rs b/src/mnl.rs
new file mode 100644
index 0000000..737f59a
--- /dev/null
+++ b/src/mnl.rs
@@ -0,0 +1,142 @@
+#![allow(dead_code)]
+use lazy_static::lazy_static;
+
+pub const MNL_SOCKET_AUTOPID: libc::c_int = 0;
+pub const MNL_SOCKET_DUMP_SIZE: libc::c_int = 32768;
+lazy_static! {
+    pub static ref MNL_SOCKET_BUFFER_SIZE: libc::c_long = {
+        let pagesize = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
+        if pagesize == -1 {
+            eprintln!("Error retrieving page size: {}", std::io::Error::last_os_error());
+            std::process::exit(1);
+        }
+        std::cmp::min(pagesize, 8192)
+    };
+}
+
+pub const MNL_CB_ERROR: libc::c_int = -1;
+pub const MNL_CB_STOP: libc::c_int = 0;
+pub const MNL_CB_OK: libc::c_int = 1;
+
+#[repr(C)]
+pub struct mnl_socket {
+    _private: [u8; 0],
+}
+
+pub const IPCTNL_MSG_CT_NEW: libc::c_int = 0;
+pub const IPCTNL_MSG_CT_GET: libc::c_int = 1;
+pub const IPCTNL_MSG_CT_DELETE: libc::c_int = 2;
+pub const IPCTNL_MSG_CT_GET_CTRZERO: libc::c_int = 3;
+pub const IPCTNL_MSG_CT_GET_STATS_CPU: libc::c_int = 4;
+pub const IPCTNL_MSG_CT_GET_STATS: libc::c_int = 5;
+pub const IPCTNL_MSG_CT_GET_DYING: libc::c_int = 6;
+pub const IPCTNL_MSG_CT_GET_UNCONFIRMED: libc::c_int = 7;
+pub const IPCTNL_MSG_MAX: libc::c_int = 8;
+
+pub const IPCTNL_MSG_EXP_NEW: libc::c_int = 0;
+pub const IPCTNL_MSG_EXP_GET: libc::c_int = 1;
+pub const IPCTNL_MSG_EXP_DELETE: libc::c_int = 2;
+pub const IPCTNL_MSG_EXP_GET_STATS_CPU: libc::c_int = 3;
+pub const IPCTNL_MSG_EXP_MAX: libc::c_int = 4;
+
+#[link(name = "mnl")]
+extern "C" {
+    pub fn mnl_socket_open(bus: libc::c_int) -> *mut mnl_socket;
+    pub fn mnl_socket_bind(
+        nl: *const mnl_socket,
+        groups: libc::c_uint,
+        pid: libc::pid_t,
+    ) -> libc::c_int;
+    pub fn mnl_socket_close(nl: *mut mnl_socket) -> libc::c_int;
+    pub fn mnl_socket_get_portid(nl: *mut mnl_socket) -> libc::c_uint;
+    pub fn mnl_socket_sendto(
+        nl: *mut mnl_socket,
+        buf: *const libc::c_void,
+        len: libc::size_t,
+    ) -> libc::ssize_t;
+    pub fn mnl_socket_recvfrom(
+        nl: *mut mnl_socket,
+        buf: *mut libc::c_void,
+        bufsiz: libc::size_t,
+    ) -> libc::ssize_t;
+
+    pub fn mnl_nlmsg_put_header(buf: *mut libc::c_void) -> *mut libc::nlmsghdr;
+    pub fn mnl_nlmsg_put_extra_header(
+        nlh: *mut libc::nlmsghdr,
+        size: libc::size_t,
+    ) -> *mut libc::c_void;
+    pub fn mnl_nlmsg_get_payload(nlh: *const libc::nlmsghdr) -> *mut libc::c_void;
+
+    pub fn mnl_cb_run(
+        buf: *const libc::c_void,
+        numbytes: libc::size_t,
+        seq: libc::c_uint,
+        portid: libc::c_uint,
+        cb_data: Option<mnl_cb_t>,
+        data: *mut libc::c_void,
+    ) -> libc::c_int;
+
+    pub fn mnl_attr_parse(
+        nlh: *const libc::nlmsghdr,
+        offset: libc::c_uint,
+        cb: mnl_attr_cb_t,
+        data: *mut libc::c_void,
+    ) -> libc::c_int;
+    pub fn mnl_attr_get_type(attr: *const libc::nlattr) -> u16;
+    pub fn mnl_attr_type_valid(attr: *const libc::nlattr, maxtype: u16) -> libc::c_int;
+
+    pub fn mnl_nlmsg_batch_start(buf: *mut libc::c_void, limit: libc::size_t) -> *mut mnl_nlmsg_batch;
+    pub fn mnl_nlmsg_batch_stop(b: *mut mnl_nlmsg_batch);
+    pub fn mnl_nlmsg_batch_next(b: *mut mnl_nlmsg_batch) -> bool;
+    pub fn mnl_nlmsg_batch_current(b: *mut mnl_nlmsg_batch) -> *mut libc::c_void;
+    pub fn mnl_nlmsg_batch_head(b: *mut mnl_nlmsg_batch) -> *mut libc::c_void;
+    pub fn mnl_nlmsg_batch_size(b: *mut mnl_nlmsg_batch) -> libc::size_t;
+}
+
+#[allow(non_camel_case_types)]
+pub type mnl_cb_t = extern "C" fn(nlh: *const libc::nlmsghdr, data: *mut libc::c_void) -> libc::c_int;
+#[allow(non_camel_case_types)]
+pub type mnl_attr_cb_t =
+    extern "C" fn(attr: *const libc::nlattr, data: *mut libc::c_void) -> libc::c_int;
+
+#[repr(C)]
+pub struct nfgenmsg {
+    pub nfgen_family: u8,
+    pub version: u8,
+    pub res_id: u16, // TODO any better solution for __be16?
+}
+
+pub const CTA_UNSPEC: u16 = 0;
+pub const CTA_TUPLE_ORIG: u16 = 1;
+pub const CTA_TUPLE_REPLY: u16 = 2;
+pub const CTA_STATUS: u16 = 3;
+pub const CTA_PROTOINFO: u16 = 4;
+pub const CTA_HELP: u16 = 5;
+pub const CTA_NAT_SRC: u16 = 6;
+pub const CTA_NAT: u16 = CTA_NAT_SRC; // backwards compatibility
+pub const CTA_TIMEOUT: u16 = 7;
+pub const CTA_MARK: u16 = 8;
+pub const CTA_COUNTERS_ORIG: u16 = 9;
+pub const CTA_COUNTERS_REPLY: u16 = 10;
+pub const CTA_USE: u16 = 11;
+pub const CTA_ID: u16 = 12;
+pub const CTA_NAT_DST: u16 = 13;
+pub const CTA_TUPLE_MASTER: u16 = 14;
+pub const CTA_SEQ_ADJ_ORIG: u16 = 15;
+pub const CTA_NAT_SEQ_ADJ_ORIG: u16 = CTA_SEQ_ADJ_ORIG;
+pub const CTA_SEQ_ADJ_REPLY: u16 = 16;
+pub const CTA_NAT_SEQ_ADJ_REPLY: u16 = CTA_SEQ_ADJ_REPLY;
+pub const CTA_SECMARK: u16 = 17; // obsolete
+pub const CTA_ZONE: u16 = 18;
+pub const CTA_SECCTX: u16 = 19;
+pub const CTA_TIMESTAMP: u16 = 20;
+pub const CTA_MARK_MASK: u16 = 21;
+pub const CTA_LABELS: u16 = 22;
+pub const CTA_LABELS_MASK: u16 = 23;
+pub const CTA_SYNPROXY: u16 = 24;
+pub const CTA_MAX: u16 = CTA_SYNPROXY;
+
+#[repr(C)]
+pub struct mnl_nlmsg_batch {
+    _private: [u8; 0],
+}
diff --git a/src/netfilter_conntrack.rs b/src/netfilter_conntrack.rs
new file mode 100644
index 0000000..a9e67e4
--- /dev/null
+++ b/src/netfilter_conntrack.rs
@@ -0,0 +1,168 @@
+#![allow(dead_code)]
+
+#[repr(C)]
+pub struct nf_conntrack {
+    _private: [u8; 0],
+}
+
+#[link(name = "netfilter_conntrack")]
+extern "C" {
+    pub fn nfct_new() -> *mut nf_conntrack;
+    pub fn nfct_destroy(ct: *mut nf_conntrack);
+
+    pub fn nfct_nlmsg_build(nlh: *mut libc::nlmsghdr, ct: *const nf_conntrack) -> libc::c_int;
+    pub fn nfct_nlmsg_parse(nlh: *const libc::nlmsghdr, ct: *mut nf_conntrack) -> libc::c_int;
+
+    pub fn nfct_snprintf(
+        buf: *mut libc::c_char,
+        size: libc::c_uint,
+        ct: *const nf_conntrack,
+        msg_type: libc::c_uint,
+        out_type: libc::c_uint,
+        out_flags: libc::c_uint,
+    ) -> libc::c_int;
+
+    pub fn nfct_setobjopt(ct: *mut nf_conntrack, option: libc::c_uint) -> libc::c_int;
+    pub fn nfct_getobjopt(ct: *const nf_conntrack, option: libc::c_uint) -> libc::c_int;
+
+    pub fn nfct_set_attr(ct: *mut nf_conntrack, type_: CTAttr, value: *const libc::c_void);
+    pub fn nfct_set_attr_u8(ct: *mut nf_conntrack, type_: CTAttr, value: u8);
+    pub fn nfct_set_attr_u16(ct: *mut nf_conntrack, type_: CTAttr, value: u16);
+    pub fn nfct_set_attr_u32(ct: *mut nf_conntrack, type_: CTAttr, value: u32);
+    pub fn nfct_set_attr_u64(ct: *mut nf_conntrack, type_: CTAttr, value: u64);
+    pub fn nfct_set_attr_l(ct: *mut nf_conntrack, type_: CTAttr, value: *const libc::c_void, len: libc::size_t);
+
+    pub fn nfct_get_attr(ct: *const nf_conntrack, type_: CTAttr) -> *const libc::c_void;
+    pub fn nfct_get_attr_u8(ct: *const nf_conntrack, type_: CTAttr) -> u8;
+    pub fn nfct_get_attr_u16(ct: *const nf_conntrack, type_: CTAttr) -> u16;
+    pub fn nfct_get_attr_u32(ct: *const nf_conntrack, type_: CTAttr) -> u32;
+    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;
+}
+
+// set option
+pub const NFCT_SOPT_UNDO_SNAT: u32 = 0;
+pub const NFCT_SOPT_UNDO_DNAT: u32 = 1;
+pub const NFCT_SOPT_UNDO_SPAT: u32 = 2;
+pub const NFCT_SOPT_UNDO_DPAT: u32 = 3;
+pub const NFCT_SOPT_SETUP_ORIGINAL: u32 = 4;
+pub const NFCT_SOPT_SETUP_REPLY: u32 = 5;
+
+// get option
+pub const NFCT_GOPT_IS_SNAT: u32 = 0;
+pub const NFCT_GOPT_IS_DNAT: u32 = 1;
+pub const NFCT_GOPT_IS_SPAT: u32 = 2;
+pub const NFCT_GOPT_IS_DPAT: u32 = 3;
+
+// output type
+pub const NFCT_O_PLAIN: u32 = 0;
+pub const NFCT_O_DEFAULT: u32 = NFCT_O_PLAIN;
+pub const NFCT_O_XML: u32 = 1;
+pub const NFCT_O_MAX: u32 = 2;
+
+// output flags
+pub const NFCT_OF_SHOW_LAYER3_BIT: u32 = 0;
+pub const NFCT_OF_SHOW_LAYER3: u32 = 1 << NFCT_OF_SHOW_LAYER3_BIT;
+pub const NFCT_OF_TIME_BIT: u32 = 1;
+pub const NFCT_OF_TIME: u32 = 1 << NFCT_OF_TIME_BIT;
+pub const NFCT_OF_ID_BIT: u32 = 2;
+pub const NFCT_OF_ID: u32 = 1 << NFCT_OF_ID_BIT;
+pub const NFCT_OF_TIMESTAMP_BIT: u32 = 3;
+pub const NFCT_OF_TIMESTAMP: u32 = 1 << NFCT_OF_TIMESTAMP_BIT;
+
+// message type
+pub const NFCT_T_UNKNOWN: u32 = 0;
+pub const NFCT_T_NEW_BIT: u32 = 0;
+pub const NFCT_T_NEW: u32 = 1 << NFCT_T_NEW_BIT;
+pub const NFCT_T_UPDATE_BIT: u32 = 1;
+pub const NFCT_T_UPDATE: u32 = 1 << NFCT_T_UPDATE_BIT;
+pub const NFCT_T_DESTROY_BIT: u32 = 2;
+pub const NFCT_T_DESTROY: u32 = 1 << NFCT_T_DESTROY_BIT;
+pub const NFCT_T_ALL: u32 = NFCT_T_NEW | NFCT_T_UPDATE | NFCT_T_DESTROY;
+pub const NFCT_T_ERROR_BIT: u32 = 31;
+pub const NFCT_T_ERROR: u32 = 1 << NFCT_T_ERROR_BIT;
+
+#[repr(u32)]
+#[non_exhaustive]
+#[derive(Debug, Copy, Clone, PartialEq)]
+#[derive(serde::Deserialize, serde::Serialize)]
+#[allow(non_camel_case_types)]
+pub enum CTAttr {
+    ORIG_IPV4_SRC = 0,			/* u32 bits */
+    ORIG_IPV4_DST = 1,			/* u32 bits */
+    REPL_IPV4_SRC = 2,		        /* u32 bits */
+    REPL_IPV4_DST = 3,			/* u32 bits */
+    ORIG_IPV6_SRC = 4,			/* u128 bits */
+    ORIG_IPV6_DST = 5,			/* u128 bits */
+    REPL_IPV6_SRC = 6,			/* u128 bits */
+    REPL_IPV6_DST = 7,			/* u128 bits */
+    ORIG_PORT_SRC = 8,			/* u16 bits */
+    ORIG_PORT_DST = 9,			/* u16 bits */
+    REPL_PORT_SRC = 10,			/* u16 bits */
+    REPL_PORT_DST = 11,			/* u16 bits */
+    ICMP_TYPE = 12,			/* u8 bits */
+    ICMP_CODE = 13,			/* u8 bits */
+    ICMP_ID = 14,			/* u16 bits */
+    ORIG_L3PROTO = 15,			/* u8 bits */
+    REPL_L3PROTO = 16,			/* u8 bits */
+    ORIG_L4PROTO = 17,			/* u8 bits */
+    REPL_L4PROTO = 18,			/* u8 bits */
+    TCP_STATE = 19,			/* u8 bits */
+    SNAT_IPV4 = 20,			/* u32 bits */
+    DNAT_IPV4 = 21,			/* u32 bits */
+    SNAT_PORT = 22,			/* u16 bits */
+    DNAT_PORT = 23,			/* u16 bits */
+    TIMEOUT = 24,			/* u32 bits */
+    MARK = 25,				/* u32 bits */
+    ORIG_COUNTER_PACKETS = 26,		/* u64 bits */
+    REPL_COUNTER_PACKETS = 27,		/* u64 bits */
+    ORIG_COUNTER_BYTES = 28,		/* u64 bits */
+    REPL_COUNTER_BYTES = 29,		/* u64 bits */
+    USE = 30,				/* u32 bits */
+    ID = 31,				/* u32 bits */
+    STATUS = 32,			/* u32 bits  */
+    TCP_FLAGS_ORIG = 33,		/* u8 bits */
+    TCP_FLAGS_REPL = 34,		/* u8 bits */
+    TCP_MASK_ORIG = 35,			/* u8 bits */
+    TCP_MASK_REPL = 36,		        /* u8 bits */
+    MASTER_IPV4_SRC = 37,		/* u32 bits */
+    MASTER_IPV4_DST = 38,		/* u32 bits */
+    MASTER_IPV6_SRC = 39,		/* u128 bits */
+    MASTER_IPV6_DST = 40,		/* u128 bits */
+    MASTER_PORT_SRC = 41,		/* u16 bits */
+    MASTER_PORT_DST = 42,		/* u16 bits */
+    MASTER_L3PROTO = 43,		/* u8 bits */
+    MASTER_L4PROTO = 44,		/* u8 bits */
+    SECMARK = 45,			/* u32 bits */
+    ORIG_NAT_SEQ_CORRECTION_POS = 46,	/* u32 bits */
+    ORIG_NAT_SEQ_OFFSET_BEFORE = 47,	/* u32 bits */
+    ORIG_NAT_SEQ_OFFSET_AFTER = 48,	/* u32 bits */
+    REPL_NAT_SEQ_CORRECTION_POS = 49,	/* u32 bits */
+    REPL_NAT_SEQ_OFFSET_BEFORE = 50,	/* u32 bits */
+    REPL_NAT_SEQ_OFFSET_AFTER = 51,	/* u32 bits */
+    SCTP_STATE = 52,			/* u8 bits */
+    SCTP_VTAG_ORIG = 53,		/* u32 bits */
+    SCTP_VTAG_REPL = 54,		/* u32 bits */
+    HELPER_NAME = 55,			/* string (30 bytes max) */
+    DCCP_STATE = 56,			/* u8 bits */
+    DCCP_ROLE = 57,			/* u8 bits */
+    DCCP_HANDSHAKE_SEQ = 58,		/* u64 bits */
+    TCP_WSCALE_ORIG = 59,		/* u8 bits */
+    TCP_WSCALE_REPL = 60,		/* u8 bits */
+    ZONE = 61,				/* u16 bits */
+    SECCTX = 62,			/* string */
+    TIMESTAMP_START = 63,		/* u64 bits, linux >= 2.6.38 */
+    TIMESTAMP_STOP = 64,		/* u64 bits, linux >= 2.6.38 */
+    HELPER_INFO = 65,			/* variable length */
+    CONNLABELS = 66,			/* variable length */
+    CONNLABELS_MASK = 67,		/* variable length */
+    ORIG_ZONE = 68,			/* u16 bits */
+    REPL_ZONE = 69,			/* u16 bits */
+    SNAT_IPV6 = 70,			/* u128 bits */
+    DNAT_IPV6 = 71,			/* u128 bits */
+    SYNPROXY_ISN = 72,			/* u32 bits */
+    SYNPROXY_ITS = 73,			/* u32 bits */
+    SYNPROXY_TSOFF = 74,		/* u32 bits */
+    MAX = 75,
+}
diff --git a/src/socket.rs b/src/socket.rs
new file mode 100644
index 0000000..0167c1e
--- /dev/null
+++ b/src/socket.rs
@@ -0,0 +1,104 @@
+use crate::mnl::{
+    mnl_cb_run, mnl_socket, mnl_socket_bind, mnl_socket_close, mnl_socket_get_portid,
+    mnl_socket_open, mnl_socket_recvfrom, mnl_socket_sendto, MNL_CB_OK, MNL_CB_STOP,
+    MNL_SOCKET_AUTOPID, MNL_SOCKET_DUMP_SIZE,
+};
+
+use anyhow::{bail, Result};
+
+use std::ptr::NonNull;
+
+pub struct Socket {
+    socket: NonNull<mnl_socket>,
+    seq: u32,
+}
+
+impl Socket {
+    pub fn open() -> Result<Self> {
+        let socket = unsafe { mnl_socket_open(libc::NETLINK_NETFILTER) };
+        let socket = match NonNull::new(socket) {
+            Some(s) => s,
+            None => {
+                let err = std::io::Error::last_os_error();
+                bail!("Failed to open MNL socket: {}", err)
+            }
+        };
+
+        let res = unsafe { mnl_socket_bind(socket.as_ptr(), 0, MNL_SOCKET_AUTOPID) };
+        if res < 0 {
+            let err = std::io::Error::last_os_error();
+            bail!("Failed to bind MNL socket: {}", err);
+        }
+
+        Ok(Self { socket, seq: 0 })
+    }
+
+    pub fn seq(&mut self) -> u32 {
+        self.seq += 1;
+        self.seq
+    }
+
+    pub fn send_and_receive(
+        &mut self,
+        msg: *const libc::nlmsghdr,
+        seq: u32,
+        mut cb: &mut dyn FnMut(*const libc::nlmsghdr),
+    ) -> Result<()> {
+        let res =
+            unsafe { mnl_socket_sendto(self.socket.as_ptr(), msg as _, (*msg).nlmsg_len as _) };
+        if res == -1 {
+            let err = std::io::Error::last_os_error();
+            bail!("Failed to send message: {}", err);
+        }
+
+        let portid = unsafe { mnl_socket_get_portid(self.socket.as_ptr()) };
+        let mut buffer = [0u8; MNL_SOCKET_DUMP_SIZE as _];
+
+        loop {
+            let res = unsafe {
+                mnl_socket_recvfrom(
+                    self.socket.as_ptr(),
+                    buffer.as_mut_ptr() as _,
+                    MNL_SOCKET_DUMP_SIZE as _,
+                )
+            };
+            if res == -1 {
+                let err = std::io::Error::last_os_error();
+                bail!("Failed to read message: {}", err);
+            }
+
+            let res = unsafe {
+                mnl_cb_run(
+                    buffer.as_ptr() as _,
+                    res as _,
+                    seq,
+                    portid,
+                    Some(callback),
+                    &mut cb as *mut _ as _,
+                )
+            };
+            if res == -1 {
+                let err = std::io::Error::last_os_error();
+                bail!("Failed to run callback: {}", err);
+            } else if res <= MNL_CB_STOP {
+                break;
+            }
+        }
+        Ok(())
+    }
+}
+
+impl Drop for Socket {
+    fn drop(&mut self) {
+        let res = unsafe { mnl_socket_close(self.socket.as_ptr()) };
+        if res < 0 {
+            eprintln!("Error closing socket");
+        }
+    }
+}
+
+extern "C" fn callback(nlh: *const libc::nlmsghdr, data_ptr: *mut libc::c_void) -> libc::c_int {
+    let cb = unsafe { &mut *(data_ptr as *mut &mut dyn FnMut(*const libc::nlmsghdr)) };
+    cb(nlh);
+    MNL_CB_OK
+}
diff --git a/src/utils.rs b/src/utils.rs
new file mode 100644
index 0000000..f4d0853
--- /dev/null
+++ b/src/utils.rs
@@ -0,0 +1,26 @@
+use crate::mnl::{mnl_nlmsg_put_extra_header, mnl_nlmsg_put_header, nfgenmsg};
+
+pub fn build_msg_header(
+    buf: *mut libc::c_void,
+    ty: u16,
+    flags: u16,
+    seq: u32,
+    proto: u8,
+) -> *mut libc::nlmsghdr {
+    let nlh = unsafe { mnl_nlmsg_put_header(buf) };
+    unsafe {
+        (*nlh).nlmsg_type = ty;
+        (*nlh).nlmsg_flags = flags;
+        (*nlh).nlmsg_seq = seq;
+    }
+
+    let nfh = unsafe {
+        mnl_nlmsg_put_extra_header(nlh, std::mem::size_of::<nfgenmsg>()) as *mut nfgenmsg
+    };
+    unsafe {
+        (*nfh).nfgen_family = proto;
+        (*nfh).version = libc::NFNETLINK_V0 as _;
+        (*nfh).res_id = 0;
+    }
+    nlh
+}
-- 
2.20.1





^ permalink raw reply	[flat|nested] 5+ messages in thread

* [pve-devel] [PATCH v3 conntrack-tool 2/4] add packaging support
  2021-02-16 16:56 [pve-devel] [PATCH v3 conntrack-tool 1/4] initial commit Mira Limbeck
@ 2021-02-16 16:56 ` Mira Limbeck
  2021-02-16 16:56 ` [pve-devel] [PATCH v3 conntrack-tool 3/4] add expectation support Mira Limbeck
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Mira Limbeck @ 2021-02-16 16:56 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
---
v3:
 - updated package versions
 - changed build dir and added temporary build dir for debcargo
 - removed build-profile 'nodoc' (required creating debian/SOURCE file)

 .cargo/config        |  5 ++++
 Makefile             | 68 ++++++++++++++++++++++++++++++++++++++++++++
 debian/changelog     |  5 ++++
 debian/copyright     | 16 +++++++++++
 debian/debcargo.toml | 17 +++++++++++
 debian/docs          |  1 +
 6 files changed, 112 insertions(+)
 create mode 100644 .cargo/config
 create mode 100644 Makefile
 create mode 100644 debian/changelog
 create mode 100644 debian/copyright
 create mode 100644 debian/debcargo.toml
 create mode 100644 debian/docs

diff --git a/.cargo/config b/.cargo/config
new file mode 100644
index 0000000..3b5b6e4
--- /dev/null
+++ b/.cargo/config
@@ -0,0 +1,5 @@
+[source]
+[source.debian-packages]
+directory = "/usr/share/cargo/registry"
+[source.crates-io]
+replace-with = "debian-packages"
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..9e6eada
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,68 @@
+include /usr/share/dpkg/pkg-info.mk
+include /usr/share/dpkg/architecture.mk
+
+PACKAGE=pve-conntrack-tool
+BUILDDIR ?= $(PACKAGE)-$(DEB_VERSION_UPSTREAM)
+BUILDDIR_TMP ?= $(BUILDDIR).tmp
+
+GITVERSION:=$(shell git rev-parse HEAD)
+
+DEB=${PACKAGE}_${DEB_VERSION_UPSTREAM_REVISION}_${DEB_BUILD_ARCH}.deb
+DSC=rust-${PACKAGE}_${DEB_VERSION_UPSTREAM_REVISION}.dsc
+
+ifeq ($(BUILD_MODE), release)
+CARGO_BUILD_ARGS += --release
+COMPILEDIR := target/release
+else
+COMPILEDIR := target/debug
+endif
+
+all: cargo-build $(SUBDIRS)
+
+.PHONY: cargo-build
+cargo-build:
+	cargo build $(CARGO_BUILD_ARGS)
+
+.PHONY: build
+build:
+	rm -rf $(BUILDDIR) $(BUILDDIR_TMP); mkdir $(BUILDDIR_TMP)
+	debcargo package \
+	  --config debian/debcargo.toml \
+	  --changelog-ready \
+	  --no-overlay-write-back \
+	  --directory $(BUILDDIR_TMP) \
+	  $(PACKAGE) \
+	  $(shell dpkg-parsechangelog -l debian/changelog -SVersion | sed -e 's/-.*//')
+	rm -f $(BUILDDIR_TMP)/Cargo.lock
+	find $(BUILDDIR_TMP)/debian -name "*.hint" -delete
+	echo "git clone git://git.proxmox.com/git/pve-conntrack-tool.git\\ngit checkout $(shell git rev-parse HEAD)" > $(BUILDDIR_TMP)/debian/SOURCE
+	mv $(BUILDDIR_TMP) $(BUILDDIR)
+
+
+.PHONY: deb
+deb: $(DEB)
+$(DEB): build
+	cd $(BUILDDIR); dpkg-buildpackage -b -us -uc --no-pre-clean
+	lintian $(DEB)
+
+.PHONY: dsc
+dsc: $(DSC)
+$(DSC): build
+	cd $(BUILDDIR); dpkg-buildpackage -S -us -uc -d -nc
+	lintian $(DSC)
+
+.PHONY: dinstall
+dinstall: ${DEB}
+	dpkg -i ${DEB}
+
+.PHONY: upload
+upload: ${DEB} ${DBG_DEB}
+	tar cf - ${DEB} ${DBG_DEB}| ssh -X repoman@repo.proxmox.com -- upload --product pve --dist buster --arch ${DEB_BUILD_ARCH}
+
+.PHONY: distclean
+distclean: clean
+
+.PHONY: clean
+clean:
+	rm -rf *.deb ${PACKAGE}-* *.buildinfo *.changes *.dsc rust-${PACKAGE}_*.tar.?z $(BUILDDIR) $(BUILDDIR_TMP)
+	find . -name '*~' -exec rm {} ';'
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..1e6f246
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+rust-pve-conntrack-tool (1.0.0-1) pve; urgency=medium
+
+  * Initial release.
+
+ -- Mira Limbeck <mira@nena.proxmox.com>  Thu, 11 Feb 2021 14:20:59 +0100
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..5661ef6
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,16 @@
+Copyright (C) 2021 Proxmox Server Solutions GmbH
+
+This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
diff --git a/debian/debcargo.toml b/debian/debcargo.toml
new file mode 100644
index 0000000..66c3e07
--- /dev/null
+++ b/debian/debcargo.toml
@@ -0,0 +1,17 @@
+overlay = "."
+crate_src_path = ".."
+
+[source]
+maintainer = "Proxmox Support Team <support@proxmox.com>"
+section = "admin"
+homepage = "http://www.proxmox.com"
+vcs_git = "git://git.proxmox.com/git/pve-conntrack-tool.git"
+vcs_browser = "https://git.proxmox.com/?p=pve-conntrack-tool.git;a=summary"
+
+[package]
+summary = "PVE Conntrack Tool"
+description = "Tool to dump and import conntracks"
+
+[packages.bin]
+build-depends = ["libmnl-dev", "libnetfilter-conntrack-dev"]
+depends = ["libmnl0", "libnetfilter-conntrack3"]
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..8696672
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1 @@
+debian/SOURCE
-- 
2.20.1





^ permalink raw reply	[flat|nested] 5+ messages in thread

* [pve-devel] [PATCH v3 conntrack-tool 3/4] add expectation support
  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
  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
  3 siblings, 0 replies; 5+ messages in thread
From: Mira Limbeck @ 2021-02-16 16:56 UTC (permalink / raw)
  To: pve-devel

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





^ permalink raw reply	[flat|nested] 5+ messages in thread

* [pve-devel] [PATCH v3 conntrack-tool 4/4] add additional bindings
  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 ` [pve-devel] [PATCH v3 conntrack-tool 3/4] add expectation support Mira Limbeck
@ 2021-02-16 16:56 ` Mira Limbeck
  2021-04-06 10:19 ` [pve-devel] [PATCH v3 conntrack-tool 1/4] initial commit Dominic Jäger
  3 siblings, 0 replies; 5+ messages in thread
From: Mira Limbeck @ 2021-02-16 16:56 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
---
v3:
 - unchanged
v2:
 - new addition, can be ignored as it only adds batch and print function
   bindings which are not used in the code currently

 src/mnl.rs | 31 +++++++++++++++++++++++++++++--
 1 file changed, 29 insertions(+), 2 deletions(-)

diff --git a/src/mnl.rs b/src/mnl.rs
index 737f59a..fa2b5fb 100644
--- a/src/mnl.rs
+++ b/src/mnl.rs
@@ -66,6 +66,12 @@ extern "C" {
         size: libc::size_t,
     ) -> *mut libc::c_void;
     pub fn mnl_nlmsg_get_payload(nlh: *const libc::nlmsghdr) -> *mut libc::c_void;
+    pub fn mnl_nlmsg_fprintf(
+        fd: *mut libc::FILE,
+        data: *const libc::c_void,
+        datalen: libc::size_t,
+        extra_header_size: libc::size_t,
+    );
 
     pub fn mnl_cb_run(
         buf: *const libc::c_void,
@@ -76,6 +82,17 @@ extern "C" {
         data: *mut libc::c_void,
     ) -> libc::c_int;
 
+    pub fn mnl_cb_run2(
+        buf: *const libc::c_void,
+        numbytes: libc::size_t,
+        seq: libc::c_uint,
+        portid: libc::c_uint,
+        cb_data: Option<mnl_cb_t>,
+        data: *const libc::c_void,
+        cb_ctl_array: *const *const mnl_cb_t,
+        cb_ctl_array_len: libc::c_uint,
+    ) -> libc::c_int;
+
     pub fn mnl_attr_parse(
         nlh: *const libc::nlmsghdr,
         offset: libc::c_uint,
@@ -85,16 +102,26 @@ extern "C" {
     pub fn mnl_attr_get_type(attr: *const libc::nlattr) -> u16;
     pub fn mnl_attr_type_valid(attr: *const libc::nlattr, maxtype: u16) -> libc::c_int;
 
-    pub fn mnl_nlmsg_batch_start(buf: *mut libc::c_void, limit: libc::size_t) -> *mut mnl_nlmsg_batch;
+    pub fn mnl_nlmsg_batch_start(
+        buf: *mut libc::c_void,
+        limit: libc::size_t,
+    ) -> *mut mnl_nlmsg_batch;
     pub fn mnl_nlmsg_batch_stop(b: *mut mnl_nlmsg_batch);
     pub fn mnl_nlmsg_batch_next(b: *mut mnl_nlmsg_batch) -> bool;
     pub fn mnl_nlmsg_batch_current(b: *mut mnl_nlmsg_batch) -> *mut libc::c_void;
     pub fn mnl_nlmsg_batch_head(b: *mut mnl_nlmsg_batch) -> *mut libc::c_void;
     pub fn mnl_nlmsg_batch_size(b: *mut mnl_nlmsg_batch) -> libc::size_t;
+    pub fn mnl_nlmsg_batch_reset(b: *mut mnl_nlmsg_batch);
+    pub fn mnl_nlmsg_batch_is_empty(b: *mut mnl_nlmsg_batch) -> bool;
+
+    pub fn mnl_nlmsg_ok(nlh: *const libc::nlmsghdr, len: libc::c_int) -> bool;
+    pub fn mnl_nlmsg_next(nlh: *const libc::nlmsghdr, len: *mut libc::c_int)
+        -> *mut libc::nlmsghdr;
 }
 
 #[allow(non_camel_case_types)]
-pub type mnl_cb_t = extern "C" fn(nlh: *const libc::nlmsghdr, data: *mut libc::c_void) -> libc::c_int;
+pub type mnl_cb_t =
+    extern "C" fn(nlh: *const libc::nlmsghdr, data: *mut libc::c_void) -> libc::c_int;
 #[allow(non_camel_case_types)]
 pub type mnl_attr_cb_t =
     extern "C" fn(attr: *const libc::nlattr, data: *mut libc::c_void) -> libc::c_int;
-- 
2.20.1





^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [pve-devel] [PATCH v3 conntrack-tool 1/4] initial commit
  2021-02-16 16:56 [pve-devel] [PATCH v3 conntrack-tool 1/4] initial commit Mira Limbeck
                   ` (2 preceding siblings ...)
  2021-02-16 16:56 ` [pve-devel] [PATCH v3 conntrack-tool 4/4] add additional bindings Mira Limbeck
@ 2021-04-06 10:19 ` Dominic Jäger
  3 siblings, 0 replies; 5+ messages in thread
From: Dominic Jäger @ 2021-04-06 10:19 UTC (permalink / raw)
  To: Proxmox VE development discussion

On Tue, Feb 16, 2021 at 05:56:39PM +0100, Mira Limbeck wrote:
> Dumping conntrack information and importing conntrack information works
> for IPv4 and IPv6. No filtering is supported for now. pve-conntrack-tool
> will always return both IPv4 and IPv6 conntracks together.
> 
> Conntracks are serialized as JSON and printed on STDOUT line by line
> with one line containing one conntrack. When inserting data is read
> from STDIN line by line and expected to be one JSON object per line
> representing the conntrack.

When comparing conntrack -L and pve-conntrack-tool dump gave equivalent
outputs.  It might be a bit confusing that the tool uses converted values, e.g.
for ports. But I think this shouldn't matter as it's internal.

With firewall enabled on both nodes and cluster

> cat /etc/pve/firewall/cluster.fw /etc/pve/nodes/pveA/host.fw /etc/pve/nodes/pveB/host.fw | grep enable
> enable: 1
> enable: 1
> enable: 1

and tcp_loose deactivated on both nodes

> sysctl net.netfilter.nf_conntrack_tcp_loose 
> net.netfilter.nf_conntrack_tcp_loose = 0

I could copy test flow entries like
> conntrack -I -p tcp -t 60 --src 127.0.0.1 --dst 1.1.1.1 --state LISTEN --sport 80 --dport 55555
> pve-conntrack-tool dump | ssh root@192.168.25.147 pve-conntrack-tool insert

from node A to B

>conntrack -L | grep 55555
>tcp      6 52 SYN_SENT2 src=127.0.0.1 dst=1.1.1.1 sport=80 dport=55555 [UNREPLIED] src=1.1.1.1 dst=127.0.0.1 sport=55555 dport=80 mark=0 use=1

and looking at the number of flow entries it seems the other flow entries have been copied, too.


What still confuses me a little is live migration. Not sure if I'm doing this right.

Without the new option
> qm migrate 150 pveB --online

the SSH connection to the migrating guest broke (OK, I guess) but after
reconnecting the old flow entries were still there? Shouldn't they vanish?

With the new option
> qm migrate 150 pveA --online --migrate-conntracks

the SSH connection to the guest sometimes remained working and sometimes not
(and the entries survived).

Tested-by: Dominic Jäger <d.jaeger@proxmox.com>




^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2021-04-06 10:19 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [pve-devel] [PATCH v3 conntrack-tool 3/4] add expectation support Mira Limbeck
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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal