public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit
@ 2021-02-03 14:25 Mira Limbeck
  2021-02-03 14:25 ` [pve-devel] [PATCH conntrack-tool v2 2/5] add packaging support Mira Limbeck
                   ` (6 more replies)
  0 siblings, 7 replies; 14+ messages in thread
From: Mira Limbeck @ 2021-02-03 14:25 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).

Expectation support, which is necessary for FTP and other protocols, is
not yet implemented.

Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
---
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                 |  14 ++
 src/main.rs                | 488 +++++++++++++++++++++++++++++++++++++
 src/mnl.rs                 | 132 ++++++++++
 src/netfilter_conntrack.rs | 168 +++++++++++++
 4 files changed, 802 insertions(+)
 create mode 100644 Cargo.toml
 create mode 100644 src/main.rs
 create mode 100644 src/mnl.rs
 create mode 100644 src/netfilter_conntrack.rs

diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..3e3851a
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,14 @@
+[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"
+libc = "0.2.69"
+serde = { version = "1.0.106", features = ["derive"] }
+serde_json = "1.0.41"
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..2137556
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,488 @@
+mod mnl;
+mod netfilter_conntrack;
+
+use std::convert::TryInto;
+use std::ffi::CString;
+use std::io::{stdin, BufRead, BufReader};
+use std::os::unix::ffi::OsStringExt;
+use std::ptr::NonNull;
+
+use anyhow::{bail, format_err, Result};
+use mnl::*;
+use netfilter_conntrack::*;
+use serde::{Deserialize, Serialize};
+
+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 mut cts = Vec::new();
+        socket
+            .query_conntracks(&mut cts)
+            .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) = socket.insert_conntrack(ct) {
+                eprintln!("Error inserting conntrack: {}", err);
+            }
+        }
+    } else {
+        bail!("Unknown command: {}", args[1]);
+    }
+
+    Ok(())
+}
+
+extern "C" fn query_cts_cb(nlh: *const libc::nlmsghdr, data_ptr: *mut libc::c_void) -> libc::c_int {
+    let ct = unsafe { nfct_new() };
+    unsafe {
+        nfct_nlmsg_parse(nlh, ct);
+    }
+
+    if let Some(conntrack) = parse_conntrack(ct) {
+        let cts: &mut Vec<Conntrack> = unsafe { &mut *(data_ptr as *mut Vec<Conntrack>) };
+        cts.push(conntrack);
+    }
+
+    unsafe {
+        nfct_destroy(ct);
+    }
+
+    MNL_CB_OK
+}
+
+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;
+
+pub struct Socket {
+    socket: NonNull<mnl_socket>,
+    seq: u32,
+}
+
+impl Socket {
+    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 })
+    }
+
+    fn seq(&mut self) -> u32 {
+        self.seq += 1;
+        self.seq
+    }
+
+    fn query_conntracks(&mut self, cts: &mut Vec<Conntrack>) -> Result<()> {
+        let seq = self.seq();
+        self.query_conntracks_impl(cts, seq, libc::AF_INET as _)?;
+        let seq = self.seq();
+        self.query_conntracks_impl(cts, seq, libc::AF_INET6 as _)?;
+        Ok(())
+    }
+
+    fn query_conntracks_impl(
+        &mut self,
+        cts: &mut Vec<Conntrack>,
+        seq: u32,
+        proto: u8,
+    ) -> Result<()> {
+        let mut buf = [0u8; MNL_SOCKET_DUMP_SIZE as _];
+        let hdr = build_msg_header(
+            buf.as_mut_ptr() as _,
+            CONNTRACK_QUERY_MSG_TYPE,
+            CONNTRACK_QUERY_FLAGS,
+            seq,
+            proto,
+        );
+        self.send_and_receive(hdr, 0, Some(query_cts_cb), cts as *mut Vec<Conntrack> as _)
+    }
+
+    fn insert_conntrack(&mut self, ct: Conntrack) -> Result<()> {
+        let proto = if ct.is_ipv6() {
+            libc::AF_INET6 as u8
+        } else {
+            libc::AF_INET as u8
+        };
+
+        let mut buf = [0u8; MNL_SOCKET_BUFFER_SIZE as _];
+        let hdr = build_msg_header(
+            buf.as_mut_ptr() as _,
+            CONNTRACK_INSERT_MSG_TYPE,
+            CONNTRACK_INSERT_FLAGS,
+            self.seq(),
+            proto,
+        );
+
+        let (cth, _strings) = build_conntrack(ct)?;
+
+        unsafe {
+            nfct_nlmsg_build(hdr, cth);
+            nfct_destroy(cth);
+        }
+
+        self.send_and_receive(hdr, 0, None, std::ptr::null_mut())?;
+
+        Ok(())
+    }
+
+    fn send_and_receive(
+        &mut self,
+        msg: *const libc::nlmsghdr,
+        seq: u32,
+        cb: Option<mnl_cb_t>,
+        data: *mut libc::c_void,
+    ) -> 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, cb, data) };
+            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");
+        }
+    }
+}
+
+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
+}
+
+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,
+    value: AttrValue,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+struct Conntrack {
+    attributes: Vec<Attr>,
+}
+
+impl Conntrack {
+    pub fn is_ipv6(&self) -> bool {
+        for attr in self.attributes.iter() {
+            // only IPV6 attributes have U128 values
+            if let AttrValue::U128(_) = attr.value {
+                return true;
+            }
+        }
+        false
+    }
+}
+
+fn parse_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 })
+}
+
+fn build_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))
+}
+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 */
+];
diff --git a/src/mnl.rs b/src/mnl.rs
new file mode 100644
index 0000000..a23313e
--- /dev/null
+++ b/src/mnl.rs
@@ -0,0 +1,132 @@
+#![allow(dead_code)]
+
+pub const MNL_SOCKET_AUTOPID: libc::c_int = 0;
+pub const MNL_SOCKET_DUMP_SIZE: libc::c_int = 32768;
+pub const MNL_SOCKET_BUFFER_SIZE: libc::c_int = 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,
+}
-- 
2.20.1





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

* [pve-devel] [PATCH conntrack-tool v2 2/5] add packaging support
  2021-02-03 14:25 [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit Mira Limbeck
@ 2021-02-03 14:25 ` Mira Limbeck
  2021-02-04  7:08   ` Thomas Lamprecht
  2021-02-03 14:25 ` [pve-devel] [PATCH conntrack-tool v2 3/5] add expectation support Mira Limbeck
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 14+ messages in thread
From: Mira Limbeck @ 2021-02-03 14:25 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
---
v2:
 - unchanged

 .cargo/config        |  5 ++++
 Makefile             | 63 ++++++++++++++++++++++++++++++++++++++++++++
 debian/changelog     |  5 ++++
 debian/copyright     | 16 +++++++++++
 debian/debcargo.toml | 17 ++++++++++++
 debian/docs          |  1 +
 6 files changed, 107 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..8ea2f8a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,63 @@
+include /usr/share/dpkg/pkg-info.mk
+include /usr/share/dpkg/architecture.mk
+
+PACKAGE=pve-conntrack-tool
+
+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 build
+	debcargo package \
+	  --config debian/debcargo.toml \
+	  --changelog-ready \
+	  --no-overlay-write-back \
+	  --directory build \
+	  $(PACKAGE) \
+	  $(shell dpkg-parsechangelog -l debian/changelog -SVersion | sed -e 's/-.*//')
+	rm build/Cargo.lock
+	find build/debian -name "*.hint" -delete
+
+.PHONY: deb
+deb: $(DEB)
+$(DEB): build
+	cd build; dpkg-buildpackage -b -us -uc --no-pre-clean --build-profiles=nodoc
+	lintian $(DEB)
+
+.PHONY: dsc
+dsc: $(DSC)
+$(DSC): build
+	cd build; 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 build/
+	find . -name '*~' -exec rm {} ';'
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..700e739
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+rust-pve-conntrack-tool (1.0.0-1) pve; urgency=medium
+
+  * Initial release.
+
+ -- Mira Limbeck <m.limbeck@proxmox.com>  Thu, 08 Oct 2020 14:04:19 +0200
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..4d6f36a
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,16 @@
+Copyright (C) 2020 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] 14+ messages in thread

* [pve-devel] [PATCH conntrack-tool v2 3/5] add expectation support
  2021-02-03 14:25 [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit Mira Limbeck
  2021-02-03 14:25 ` [pve-devel] [PATCH conntrack-tool v2 2/5] add packaging support Mira Limbeck
@ 2021-02-03 14:25 ` Mira Limbeck
  2021-02-03 14:25 ` [pve-devel] [PATCH conntrack-tool v2 4/5] add additional bindings Mira Limbeck
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 14+ messages in thread
From: Mira Limbeck @ 2021-02-03 14:25 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.

Together with expectation support, string attribute support is also
added. Some functions which are conntrack specific are renamed to
contain 'conntrack' in their names.

Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
---
v2:
 - mostly the same changes as for patch 1

 src/main.rs                | 249 +++++++++++++++++++++++++++++++++++--
 src/netfilter_conntrack.rs |  44 +++++++
 2 files changed, 285 insertions(+), 8 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 2137556..79779ff 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -43,20 +43,37 @@ fn main() -> Result<()> {
                 }
             }
         }
+
+        let mut exps = Vec::new();
+        socket
+            .query_expects(&mut exps)
+            .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) = socket.insert_conntrack(ct) {
+                    eprintln!("Error inserting conntrack: {}", err);
                 }
-            };
-            if let Err(err) = socket.insert_conntrack(ct) {
-                eprintln!("Error inserting conntrack: {}", err);
+            } else if let Ok(exp) = serde_json::from_str::<Expect>(&line) {
+                if let Err(err) = socket.insert_expect(exp) {
+                    eprintln!("Error inserting expect: {}", err);
+                }
+            } else {
+                eprintln!("Failed to deserialize input: {}", line);
+                break;
             }
         }
     } else {
@@ -92,6 +109,13 @@ 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;
+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;
 
 pub struct Socket {
     socket: NonNull<mnl_socket>,
@@ -176,6 +200,88 @@ impl Socket {
         Ok(())
     }
 
+    fn query_expects(&mut self, exps: &mut Vec<Expect>) -> Result<()> {
+        let seq = self.seq();
+        self.query_expects_impl(exps, seq, libc::AF_INET as _)?;
+        let seq = self.seq();
+        self.query_expects_impl(exps, seq, libc::AF_INET6 as _)?;
+        Ok(())
+    }
+
+    fn query_expects_impl(&mut self, exps: &mut Vec<Expect>, seq: u32, proto: u8) -> Result<()> {
+        let mut buf = [0u8; MNL_SOCKET_DUMP_SIZE as _];
+        let hdr = build_msg_header(
+            buf.as_mut_ptr() as _,
+            EXPECT_QUERY_MSG_TYPE,
+            EXPECT_QUERY_FLAGS,
+            seq,
+            proto,
+        );
+        self.send_and_receive(hdr, 0, Some(query_exp_cb), exps as *mut Vec<Expect> as _)
+    }
+
+    fn insert_expect(&mut self, exp: Expect) -> Result<()> {
+        let proto = if exp.is_ipv6() {
+            libc::AF_INET6 as u8
+        } else {
+            libc::AF_INET as u8
+        };
+
+        let mut buf = [0u8; MNL_SOCKET_BUFFER_SIZE as _];
+        let hdr = build_msg_header(
+            buf.as_mut_ptr() as _,
+            EXPECT_INSERT_MSG_TYPE,
+            EXPECT_INSERT_FLAGS,
+            self.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) = build_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);
+            }
+        }
+
+        self.send_and_receive(hdr, 0, None, std::ptr::null_mut())?;
+
+        Ok(())
+    }
+
     fn send_and_receive(
         &mut self,
         msg: *const libc::nlmsghdr,
@@ -409,6 +515,7 @@ fn build_conntrack(ct: Conntrack) -> Result<(*mut nf_conntrack, Vec<CString>)> {
     }
     Ok((cth, strings))
 }
+
 const ALL_ATTRIBUTES: &[(CTAttr, AttrType)] = &[
     (CTAttr::ORIG_IPV4_SRC, AttrType::U32),        /* u32 bits */
     (CTAttr::ORIG_IPV4_DST, AttrType::U32),        /* u32 bits */
@@ -486,3 +593,129 @@ const ALL_ATTRIBUTES: &[(CTAttr, AttrType)] = &[
     (CTAttr::SYNPROXY_ITS, AttrType::U32),         /* u32 bits */
     (CTAttr::SYNPROXY_TSOFF, AttrType::U32),       /* u32 bits */
 ];
+
+extern "C" fn query_exp_cb(nlh: *const libc::nlmsghdr, data_ptr: *mut libc::c_void) -> libc::c_int {
+    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) = parse_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 { 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(ExpectAttr {
+                    key: *attr,
+                    value: ExpectAttrValue::String(s),
+                });
+            }
+            ExpectAttrType::String(None) => {
+                let ptr = unsafe { nfexp_get_attr(exp, *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(ExpectAttr {
+                    key: *attr,
+                    value: ExpectAttrValue::String(s),
+                });
+            }
+        }
+    }
+
+    let exps: &mut Vec<Expect> = unsafe { &mut *(data_ptr as *mut Vec<Expect>) };
+    exps.push(Expect { attributes });
+
+    MNL_CB_OK
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+struct Expect {
+    attributes: Vec<ExpectAttr>,
+}
+
+impl Expect {
+    fn is_ipv6(&self) -> bool {
+        for attr in &self.attributes {
+            if let ExpectAttrValue::CT(ct) = &attr.value {
+                return ct.is_ipv6();
+            }
+        }
+        false
+    }
+}
+
+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/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] 14+ messages in thread

* [pve-devel] [PATCH conntrack-tool v2 4/5] add additional bindings
  2021-02-03 14:25 [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit Mira Limbeck
  2021-02-03 14:25 ` [pve-devel] [PATCH conntrack-tool v2 2/5] add packaging support Mira Limbeck
  2021-02-03 14:25 ` [pve-devel] [PATCH conntrack-tool v2 3/5] add expectation support Mira Limbeck
@ 2021-02-03 14:25 ` Mira Limbeck
  2021-02-03 14:25 ` [pve-devel] [PATCH conntrack-tool v2 5/5] replace C callback with closures Mira Limbeck
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 14+ messages in thread
From: Mira Limbeck @ 2021-02-03 14:25 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
---
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 a23313e..dc897f9 100644
--- a/src/mnl.rs
+++ b/src/mnl.rs
@@ -56,6 +56,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,
@@ -66,6 +72,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,
@@ -75,16 +92,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] 14+ messages in thread

* [pve-devel] [PATCH conntrack-tool v2 5/5] replace C callback with closures
  2021-02-03 14:25 [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit Mira Limbeck
                   ` (2 preceding siblings ...)
  2021-02-03 14:25 ` [pve-devel] [PATCH conntrack-tool v2 4/5] add additional bindings Mira Limbeck
@ 2021-02-03 14:25 ` Mira Limbeck
  2021-02-03 14:25 ` [pve-devel] [PATCH qemu-server v2] copy conntrack information on migration Mira Limbeck
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 14+ messages in thread
From: Mira Limbeck @ 2021-02-03 14:25 UTC (permalink / raw)
  To: pve-devel

Internally we still have to use a C callback, but all it does is forward
to the closure we pass to it.

Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
---
v2:
 - new addition

 src/main.rs | 208 +++++++++++++++++++++++++++-------------------------
 1 file changed, 109 insertions(+), 99 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 79779ff..0930f92 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -83,24 +83,6 @@ fn main() -> Result<()> {
     Ok(())
 }
 
-extern "C" fn query_cts_cb(nlh: *const libc::nlmsghdr, data_ptr: *mut libc::c_void) -> libc::c_int {
-    let ct = unsafe { nfct_new() };
-    unsafe {
-        nfct_nlmsg_parse(nlh, ct);
-    }
-
-    if let Some(conntrack) = parse_conntrack(ct) {
-        let cts: &mut Vec<Conntrack> = unsafe { &mut *(data_ptr as *mut Vec<Conntrack>) };
-        cts.push(conntrack);
-    }
-
-    unsafe {
-        nfct_destroy(ct);
-    }
-
-    MNL_CB_OK
-}
-
 const CONNTRACK_QUERY_MSG_TYPE: u16 =
     ((libc::NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET) as u16;
 const CONNTRACK_QUERY_FLAGS: u16 =
@@ -169,7 +151,20 @@ impl Socket {
             seq,
             proto,
         );
-        self.send_and_receive(hdr, 0, Some(query_cts_cb), cts as *mut Vec<Conntrack> as _)
+        self.send_and_receive(hdr, 0, |nlh| {
+            let ct = unsafe { nfct_new() };
+            unsafe {
+                nfct_nlmsg_parse(nlh, ct);
+            }
+
+            if let Some(conntrack) = parse_conntrack(ct) {
+                cts.push(conntrack);
+            }
+
+            unsafe {
+                nfct_destroy(ct);
+            }
+        })
     }
 
     fn insert_conntrack(&mut self, ct: Conntrack) -> Result<()> {
@@ -195,7 +190,7 @@ impl Socket {
             nfct_destroy(cth);
         }
 
-        self.send_and_receive(hdr, 0, None, std::ptr::null_mut())?;
+        self.send_and_receive(hdr, 0, |_| {})?;
 
         Ok(())
     }
@@ -208,7 +203,12 @@ impl Socket {
         Ok(())
     }
 
-    fn query_expects_impl(&mut self, exps: &mut Vec<Expect>, seq: u32, proto: u8) -> Result<()> {
+    fn query_expects_impl(
+        &mut self,
+        exps: &mut Vec<Expect>,
+        seq: u32,
+        proto: u8,
+    ) -> Result<()> {
         let mut buf = [0u8; MNL_SOCKET_DUMP_SIZE as _];
         let hdr = build_msg_header(
             buf.as_mut_ptr() as _,
@@ -217,7 +217,75 @@ impl Socket {
             seq,
             proto,
         );
-        self.send_and_receive(hdr, 0, Some(query_exp_cb), exps as *mut Vec<Expect> as _)
+        self.send_and_receive(hdr, 0, |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) = parse_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 { 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(ExpectAttr {
+                            key: *attr,
+                            value: ExpectAttrValue::String(s),
+                        });
+                    }
+                    ExpectAttrType::String(None) => {
+                        let ptr = unsafe { nfexp_get_attr(exp, *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(ExpectAttr {
+                            key: *attr,
+                            value: ExpectAttrValue::String(s),
+                        });
+                    }
+                }
+            }
+
+            exps.push(Expect { attributes });
+        })
     }
 
     fn insert_expect(&mut self, exp: Expect) -> Result<()> {
@@ -277,17 +345,16 @@ impl Socket {
             }
         }
 
-        self.send_and_receive(hdr, 0, None, std::ptr::null_mut())?;
+        self.send_and_receive(hdr, 0, |_| {})?;
 
         Ok(())
     }
 
-    fn send_and_receive(
+    fn send_and_receive<CB: FnMut(*const libc::nlmsghdr)>(
         &mut self,
         msg: *const libc::nlmsghdr,
         seq: u32,
-        cb: Option<mnl_cb_t>,
-        data: *mut libc::c_void,
+        mut cb: CB,
     ) -> Result<()> {
         let res =
             unsafe { mnl_socket_sendto(self.socket.as_ptr(), msg as _, (*msg).nlmsg_len as _) };
@@ -312,7 +379,16 @@ impl Socket {
                 bail!("Failed to read message: {}", err);
             }
 
-            let res = unsafe { mnl_cb_run(buffer.as_ptr() as _, res as _, seq, portid, cb, data) };
+            let res = unsafe {
+                mnl_cb_run(
+                    buffer.as_ptr() as _,
+                    res as _,
+                    seq,
+                    portid,
+                    Some(callback),
+                    &mut &mut cb as *mut &mut CB as _,
+                )
+            };
             if res == -1 {
                 let err = std::io::Error::last_os_error();
                 bail!("Failed to run callback: {}", err);
@@ -333,6 +409,12 @@ impl Drop for Socket {
     }
 }
 
+extern "C" fn callback(nlh: *const libc::nlmsghdr, data_ptr: *mut libc::c_void) -> libc::c_int {
+    let cb = unsafe { *(data_ptr as *mut &dyn Fn(*const libc::nlmsghdr)) };
+    cb(nlh);
+    MNL_CB_OK
+}
+
 fn build_msg_header(
     buf: *mut libc::c_void,
     ty: u16,
@@ -594,78 +676,6 @@ const ALL_ATTRIBUTES: &[(CTAttr, AttrType)] = &[
     (CTAttr::SYNPROXY_TSOFF, AttrType::U32),       /* u32 bits */
 ];
 
-extern "C" fn query_exp_cb(nlh: *const libc::nlmsghdr, data_ptr: *mut libc::c_void) -> libc::c_int {
-    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) = parse_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 { 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(ExpectAttr {
-                    key: *attr,
-                    value: ExpectAttrValue::String(s),
-                });
-            }
-            ExpectAttrType::String(None) => {
-                let ptr = unsafe { nfexp_get_attr(exp, *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(ExpectAttr {
-                    key: *attr,
-                    value: ExpectAttrValue::String(s),
-                });
-            }
-        }
-    }
-
-    let exps: &mut Vec<Expect> = unsafe { &mut *(data_ptr as *mut Vec<Expect>) };
-    exps.push(Expect { attributes });
-
-    MNL_CB_OK
-}
-
 #[derive(Debug, Serialize, Deserialize)]
 struct Expect {
     attributes: Vec<ExpectAttr>,
-- 
2.20.1





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

* [pve-devel] [PATCH qemu-server v2] copy conntrack information on migration
  2021-02-03 14:25 [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit Mira Limbeck
                   ` (3 preceding siblings ...)
  2021-02-03 14:25 ` [pve-devel] [PATCH conntrack-tool v2 5/5] replace C callback with closures Mira Limbeck
@ 2021-02-03 14:25 ` Mira Limbeck
  2021-02-05  8:11   ` Fabian Ebner
  2021-02-04  8:07 ` [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit Thomas Lamprecht
  2021-02-04 10:16 ` Mira Limbeck
  6 siblings, 1 reply; 14+ messages in thread
From: Mira Limbeck @ 2021-02-03 14:25 UTC (permalink / raw)
  To: pve-devel

Requires the pve-conntrack-tool. On migration the conntrack information
from the source node is dumped and sent to the target node where it is
then inserted.
This helps with open connections during migration when the firewall is active.

A new 'migrate-conntracks' option is added to the migrate_vm API call.

Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
---
v2:
 - added the migrate-conntracks option so that it only copies conntrack
   information when requested

 PVE/API2/Qemu.pm   | 5 +++++
 PVE/QemuMigrate.pm | 5 +++++
 2 files changed, 10 insertions(+)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index 3571f5e..8c4336b 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -3556,6 +3556,11 @@ __PACKAGE__->register_method({
 		minimum => '0',
 		default => 'migrate limit from datacenter or storage config',
 	    },
+	    'migrate-conntracks' => {
+		description => "Migrate connection tracking info.",
+		type => 'boolean',
+		optional => 1,
+	    }
 	},
     },
     returns => {
diff --git a/PVE/QemuMigrate.pm b/PVE/QemuMigrate.pm
index 5c019fc..2ccef2a 100644
--- a/PVE/QemuMigrate.pm
+++ b/PVE/QemuMigrate.pm
@@ -1087,6 +1087,11 @@ sub phase2 {
 	    die "unable to parse migration status '$stat->{status}' - aborting\n";
 	}
     }
+
+    if ($self->{opts}->{'migrate-conntracks'}) {
+	$self->log('info', 'copy conntrack information');
+	PVE::Tools::run_command([['/usr/bin/pve-conntrack-tool', 'dump'], [@{$self->{rem_ssh}}, '/usr/bin/pve-conntrack-tool', 'insert']]);
+    }
 }
 
 sub phase2_cleanup {
-- 
2.20.1





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

* Re: [pve-devel] [PATCH conntrack-tool v2 2/5] add packaging support
  2021-02-03 14:25 ` [pve-devel] [PATCH conntrack-tool v2 2/5] add packaging support Mira Limbeck
@ 2021-02-04  7:08   ` Thomas Lamprecht
  2021-02-04  9:55     ` Mira Limbeck
  0 siblings, 1 reply; 14+ messages in thread
From: Thomas Lamprecht @ 2021-02-04  7:08 UTC (permalink / raw)
  To: Proxmox VE development discussion, Mira Limbeck

On 03.02.21 15:25, Mira Limbeck wrote:
> Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
> ---
> v2:
>  - unchanged

Some nits/comments inline.
You probably adapted this from proxmox-backup so my comments may hold true
there too.

> 
>  .cargo/config        |  5 ++++
>  Makefile             | 63 ++++++++++++++++++++++++++++++++++++++++++++
>  debian/changelog     |  5 ++++
>  debian/copyright     | 16 +++++++++++
>  debian/debcargo.toml | 17 ++++++++++++
>  debian/docs          |  1 +
>  6 files changed, 107 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..8ea2f8a
> --- /dev/null
> +++ b/Makefile
> @@ -0,0 +1,63 @@
> +include /usr/share/dpkg/pkg-info.mk
> +include /usr/share/dpkg/architecture.mk
> +
> +PACKAGE=pve-conntrack-tool
> +
> +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:

We normally try to use a more Debian conform build directory, like:

BUILDDIR ?= ${PACKAGE}-${DEB_VERSION_UPSTREAM}

This way one can also easily set their own via a make parameter.


> +	rm -rf build
> +	debcargo package \
> +	  --config debian/debcargo.toml \
> +	  --changelog-ready \
> +	  --no-overlay-write-back \
> +	  --directory build \
> +	  $(PACKAGE) \
> +	  $(shell dpkg-parsechangelog -l debian/changelog -SVersion | sed -e 's/-.*//')
> +	rm build/Cargo.lock
> +	find build/debian -name "*.hint" -delete

can we do all in a temporary directory and do a rename at the end? making this
target actin a bit more "atomic".

> +
> +.PHONY: deb
> +deb: $(DEB)
> +$(DEB): build
> +	cd build; dpkg-buildpackage -b -us -uc --no-pre-clean --build-profiles=nodoc

is the `nodoc` actually required? Is it always generated by debcargo, as I do not
see any d/control like reference to it.

> +	lintian $(DEB)
> +
> +.PHONY: dsc
> +dsc: $(DSC)
> +$(DSC): build
> +	cd build; 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 build/
> +	find . -name '*~' -exec rm {} ';'
> diff --git a/debian/changelog b/debian/changelog
> new file mode 100644
> index 0000000..700e739
> --- /dev/null
> +++ b/debian/changelog
> @@ -0,0 +1,5 @@
> +rust-pve-conntrack-tool (1.0.0-1) pve; urgency=medium
> +
> +  * Initial release.
> +
> + -- Mira Limbeck <m.limbeck@proxmox.com>  Thu, 08 Oct 2020 14:04:19 +0200
> diff --git a/debian/copyright b/debian/copyright
> new file mode 100644
> index 0000000..4d6f36a
> --- /dev/null
> +++ b/debian/copyright
> @@ -0,0 +1,16 @@
> +Copyright (C) 2020 Proxmox Server Solutions GmbH

2020 - 2021

> +
> +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
> 





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

* Re: [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit
  2021-02-03 14:25 [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit Mira Limbeck
                   ` (4 preceding siblings ...)
  2021-02-03 14:25 ` [pve-devel] [PATCH qemu-server v2] copy conntrack information on migration Mira Limbeck
@ 2021-02-04  8:07 ` Thomas Lamprecht
  2021-02-04 10:15   ` Mira Limbeck
  2021-02-04 10:16 ` Mira Limbeck
  6 siblings, 1 reply; 14+ messages in thread
From: Thomas Lamprecht @ 2021-02-04  8:07 UTC (permalink / raw)
  To: Proxmox VE development discussion, Mira Limbeck

On 03.02.21 15:25, 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.
> 
> 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).
> 
> Expectation support, which is necessary for FTP and other protocols, is
> not yet implemented.
> 
> Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
> ---
> 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                 |  14 ++
>  src/main.rs                | 488 +++++++++++++++++++++++++++++++++++++
>  src/mnl.rs                 | 132 ++++++++++
>  src/netfilter_conntrack.rs | 168 +++++++++++++
>  4 files changed, 802 insertions(+)
>  create mode 100644 Cargo.toml
>  create mode 100644 src/main.rs
>  create mode 100644 src/mnl.rs
>  create mode 100644 src/netfilter_conntrack.rs
> 

I take a (very) quick look at it and the code itself seems quite sensible.

One higher level question though, would it makes sense do have the whole
plumbing and general socket interfacing in it's own library crate (or sub
workspace or something like that) and the binary here separate and as
plain user of that create.

That way we could additionally publish it on crates.io, could be helpful
form some people (even if conntrack/nl is certainly a bit of a niche).

What do you think about that?




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

* Re: [pve-devel] [PATCH conntrack-tool v2 2/5] add packaging support
  2021-02-04  7:08   ` Thomas Lamprecht
@ 2021-02-04  9:55     ` Mira Limbeck
  0 siblings, 0 replies; 14+ messages in thread
From: Mira Limbeck @ 2021-02-04  9:55 UTC (permalink / raw)
  To: Thomas Lamprecht, Proxmox VE development discussion

On 2/4/21 8:08 AM, Thomas Lamprecht wrote:
> On 03.02.21 15:25, Mira Limbeck wrote:
>> Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
>> ---
>> v2:
>>   - unchanged
> Some nits/comments inline.
> You probably adapted this from proxmox-backup so my comments may hold true
> there too.
pmg-log-tracker actually
>
>>   .cargo/config        |  5 ++++
>>   Makefile             | 63 ++++++++++++++++++++++++++++++++++++++++++++
>>   debian/changelog     |  5 ++++
>>   debian/copyright     | 16 +++++++++++
>>   debian/debcargo.toml | 17 ++++++++++++
>>   debian/docs          |  1 +
>>   6 files changed, 107 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..8ea2f8a
>> --- /dev/null
>> +++ b/Makefile
>> @@ -0,0 +1,63 @@
>> +include /usr/share/dpkg/pkg-info.mk
>> +include /usr/share/dpkg/architecture.mk
>> +
>> +PACKAGE=pve-conntrack-tool
>> +
>> +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:
> We normally try to use a more Debian conform build directory, like:
>
> BUILDDIR ?= ${PACKAGE}-${DEB_VERSION_UPSTREAM}
>
> This way one can also easily set their own via a make parameter.
>
>
>> +	rm -rf build
>> +	debcargo package \
>> +	  --config debian/debcargo.toml \
>> +	  --changelog-ready \
>> +	  --no-overlay-write-back \
>> +	  --directory build \
>> +	  $(PACKAGE) \
>> +	  $(shell dpkg-parsechangelog -l debian/changelog -SVersion | sed -e 's/-.*//')
>> +	rm build/Cargo.lock
>> +	find build/debian -name "*.hint" -delete
> can we do all in a temporary directory and do a rename at the end? making this
> target actin a bit more "atomic".
Yes, will change that in v3
>
>> +
>> +.PHONY: deb
>> +deb: $(DEB)
>> +$(DEB): build
>> +	cd build; dpkg-buildpackage -b -us -uc --no-pre-clean --build-profiles=nodoc
> is the `nodoc` actually required? Is it always generated by debcargo, as I do not
> see any d/control like reference to it.
>
>> +	lintian $(DEB)
>> +
>> +.PHONY: dsc
>> +dsc: $(DSC)
>> +$(DSC): build
>> +	cd build; 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 build/
>> +	find . -name '*~' -exec rm {} ';'
>> diff --git a/debian/changelog b/debian/changelog
>> new file mode 100644
>> index 0000000..700e739
>> --- /dev/null
>> +++ b/debian/changelog
>> @@ -0,0 +1,5 @@
>> +rust-pve-conntrack-tool (1.0.0-1) pve; urgency=medium
>> +
>> +  * Initial release.
>> +
>> + -- Mira Limbeck <m.limbeck@proxmox.com>  Thu, 08 Oct 2020 14:04:19 +0200
>> diff --git a/debian/copyright b/debian/copyright
>> new file mode 100644
>> index 0000000..4d6f36a
>> --- /dev/null
>> +++ b/debian/copyright
>> @@ -0,0 +1,16 @@
>> +Copyright (C) 2020 Proxmox Server Solutions GmbH
> 2020 - 2021

Haven't touched the patch at all since the first series, sorry about 
that. Will fix all these things in v3.

>
>> +
>> +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
>>




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

* Re: [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit
  2021-02-04  8:07 ` [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit Thomas Lamprecht
@ 2021-02-04 10:15   ` Mira Limbeck
  2021-02-04 10:20     ` Thomas Lamprecht
  0 siblings, 1 reply; 14+ messages in thread
From: Mira Limbeck @ 2021-02-04 10:15 UTC (permalink / raw)
  To: Thomas Lamprecht, Proxmox VE development discussion

On 2/4/21 9:07 AM, Thomas Lamprecht wrote:
> On 03.02.21 15:25, 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.
>>
>> 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).
>>
>> Expectation support, which is necessary for FTP and other protocols, is
>> not yet implemented.
>>
>> Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
>> ---
>> 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                 |  14 ++
>>   src/main.rs                | 488 +++++++++++++++++++++++++++++++++++++
>>   src/mnl.rs                 | 132 ++++++++++
>>   src/netfilter_conntrack.rs | 168 +++++++++++++
>>   4 files changed, 802 insertions(+)
>>   create mode 100644 Cargo.toml
>>   create mode 100644 src/main.rs
>>   create mode 100644 src/mnl.rs
>>   create mode 100644 src/netfilter_conntrack.rs
>>
> I take a (very) quick look at it and the code itself seems quite sensible.
>
> One higher level question though, would it makes sense do have the whole
> plumbing and general socket interfacing in it's own library crate (or sub
> workspace or something like that) and the binary here separate and as
> plain user of that create.
>
> That way we could additionally publish it on crates.io, could be helpful
> form some people (even if conntrack/nl is certainly a bit of a niche).
>
> What do you think about that?

The bindings are not complete, I only added what I needed during 
development and sometimes a bit more.

We would have to remove the query_(conntracks|expects) and 
insert_(conntrack|expect) functions from the Socket then.


For libmnl there are already 2 crates available which provide a wrapper 
around the low level bindings: https://github.com/mullvad/mnl-rs and 
https://crates.io/crates/crslmnl which are more complete.

For netlink itself there are also some crates: 
https://crates.io/keywords/netlink

But I could not find any bindings for libnetfilter_conntrack.






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

* Re: [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit
  2021-02-03 14:25 [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit Mira Limbeck
                   ` (5 preceding siblings ...)
  2021-02-04  8:07 ` [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit Thomas Lamprecht
@ 2021-02-04 10:16 ` Mira Limbeck
  6 siblings, 0 replies; 14+ messages in thread
From: Mira Limbeck @ 2021-02-04 10:16 UTC (permalink / raw)
  To: pve-devel

Forgot to change this, it is actually the minimum of 
sysconf(_SC_PAGESIZE) and 8192. Will fix this in v3.

On 2/3/21 3:25 PM, Mira Limbeck wrote:
> +pub const MNL_SOCKET_BUFFER_SIZE: libc::c_int = 8192;




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

* Re: [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit
  2021-02-04 10:15   ` Mira Limbeck
@ 2021-02-04 10:20     ` Thomas Lamprecht
  0 siblings, 0 replies; 14+ messages in thread
From: Thomas Lamprecht @ 2021-02-04 10:20 UTC (permalink / raw)
  To: Mira Limbeck, Proxmox VE development discussion

On 04.02.21 11:15, Mira Limbeck wrote:
> On 2/4/21 9:07 AM, Thomas Lamprecht wrote:
>> On 03.02.21 15:25, 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.
>>>
>>> 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).
>>>
>>> Expectation support, which is necessary for FTP and other protocols, is
>>> not yet implemented.
>>>
>>> Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
>>> ---
>>> 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                 |  14 ++
>>>   src/main.rs                | 488 +++++++++++++++++++++++++++++++++++++
>>>   src/mnl.rs                 | 132 ++++++++++
>>>   src/netfilter_conntrack.rs | 168 +++++++++++++
>>>   4 files changed, 802 insertions(+)
>>>   create mode 100644 Cargo.toml
>>>   create mode 100644 src/main.rs
>>>   create mode 100644 src/mnl.rs
>>>   create mode 100644 src/netfilter_conntrack.rs
>>>
>> I take a (very) quick look at it and the code itself seems quite sensible.
>>
>> One higher level question though, would it makes sense do have the whole
>> plumbing and general socket interfacing in it's own library crate (or sub
>> workspace or something like that) and the binary here separate and as
>> plain user of that create.
>>
>> That way we could additionally publish it on crates.io, could be helpful
>> form some people (even if conntrack/nl is certainly a bit of a niche).
>>
>> What do you think about that?
> 
> The bindings are not complete, I only added what I needed during development and sometimes a bit more.
> 
> We would have to remove the query_(conntracks|expects) and insert_(conntrack|expect) functions from the Socket then.
> 
> 
> For libmnl there are already 2 crates available which provide a wrapper around the low level bindings: https://github.com/mullvad/mnl-rs and https://crates.io/crates/crslmnl which are more complete.
> 
> For netlink itself there are also some crates: https://crates.io/keywords/netlink
> 
> But I could not find any bindings for libnetfilter_conntrack.
> 

OK, yeah just an idea.

But, I'd still like to see more code getting moved out from main.rs in it's own module,
ideally with only the relevant stuff being "pub".




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

* Re: [pve-devel] [PATCH qemu-server v2] copy conntrack information on migration
  2021-02-03 14:25 ` [pve-devel] [PATCH qemu-server v2] copy conntrack information on migration Mira Limbeck
@ 2021-02-05  8:11   ` Fabian Ebner
  2021-02-05  9:51     ` Mira Limbeck
  0 siblings, 1 reply; 14+ messages in thread
From: Fabian Ebner @ 2021-02-05  8:11 UTC (permalink / raw)
  To: pve-devel, m.limbeck

Am 03.02.21 um 15:25 schrieb Mira Limbeck:
> Requires the pve-conntrack-tool. On migration the conntrack information
> from the source node is dumped and sent to the target node where it is
> then inserted.
> This helps with open connections during migration when the firewall is active.
> 
> A new 'migrate-conntracks' option is added to the migrate_vm API call.
> 
> Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
> ---
> v2:
>   - added the migrate-conntracks option so that it only copies conntrack
>     information when requested
> 
>   PVE/API2/Qemu.pm   | 5 +++++
>   PVE/QemuMigrate.pm | 5 +++++
>   2 files changed, 10 insertions(+)
> 
> diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
> index 3571f5e..8c4336b 100644
> --- a/PVE/API2/Qemu.pm
> +++ b/PVE/API2/Qemu.pm
> @@ -3556,6 +3556,11 @@ __PACKAGE__->register_method({
>   		minimum => '0',
>   		default => 'migrate limit from datacenter or storage config',
>   	    },
> +	    'migrate-conntracks' => {
> +		description => "Migrate connection tracking info.",
> +		type => 'boolean',
> +		optional => 1,
> +	    }
>   	},
>       },
>       returns => {
> diff --git a/PVE/QemuMigrate.pm b/PVE/QemuMigrate.pm
> index 5c019fc..2ccef2a 100644
> --- a/PVE/QemuMigrate.pm
> +++ b/PVE/QemuMigrate.pm
> @@ -1087,6 +1087,11 @@ sub phase2 {
>   	    die "unable to parse migration status '$stat->{status}' - aborting\n";
>   	}
>       }
> +
> +    if ($self->{opts}->{'migrate-conntracks'}) {
> +	$self->log('info', 'copy conntrack information');
> +	PVE::Tools::run_command([['/usr/bin/pve-conntrack-tool', 'dump'], [@{$self->{rem_ssh}}, '/usr/bin/pve-conntrack-tool', 'insert']]);

Just wondering if having an eval here and still continue with the 
migration if there's a problem would make sense? OTOH one can argue that 
if the option is set and the connection information cannot be copied, 
it's better to abort.

> +    }
>   }
>   
>   sub phase2_cleanup {
> 




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

* Re: [pve-devel] [PATCH qemu-server v2] copy conntrack information on migration
  2021-02-05  8:11   ` Fabian Ebner
@ 2021-02-05  9:51     ` Mira Limbeck
  0 siblings, 0 replies; 14+ messages in thread
From: Mira Limbeck @ 2021-02-05  9:51 UTC (permalink / raw)
  To: Fabian Ebner, pve-devel

On 2/5/21 9:11 AM, Fabian Ebner wrote:
> Am 03.02.21 um 15:25 schrieb Mira Limbeck:
>> Requires the pve-conntrack-tool. On migration the conntrack information
>> from the source node is dumped and sent to the target node where it is
>> then inserted.
>> This helps with open connections during migration when the firewall 
>> is active.
>>
>> A new 'migrate-conntracks' option is added to the migrate_vm API call.
>>
>> Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
>> ---
>> v2:
>>   - added the migrate-conntracks option so that it only copies conntrack
>>     information when requested
>>
>>   PVE/API2/Qemu.pm   | 5 +++++
>>   PVE/QemuMigrate.pm | 5 +++++
>>   2 files changed, 10 insertions(+)
>>
>> diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
>> index 3571f5e..8c4336b 100644
>> --- a/PVE/API2/Qemu.pm
>> +++ b/PVE/API2/Qemu.pm
>> @@ -3556,6 +3556,11 @@ __PACKAGE__->register_method({
>>           minimum => '0',
>>           default => 'migrate limit from datacenter or storage config',
>>           },
>> +        'migrate-conntracks' => {
>> +        description => "Migrate connection tracking info.",
>> +        type => 'boolean',
>> +        optional => 1,
>> +        }
>>       },
>>       },
>>       returns => {
>> diff --git a/PVE/QemuMigrate.pm b/PVE/QemuMigrate.pm
>> index 5c019fc..2ccef2a 100644
>> --- a/PVE/QemuMigrate.pm
>> +++ b/PVE/QemuMigrate.pm
>> @@ -1087,6 +1087,11 @@ sub phase2 {
>>           die "unable to parse migration status '$stat->{status}' - 
>> aborting\n";
>>       }
>>       }
>> +
>> +    if ($self->{opts}->{'migrate-conntracks'}) {
>> +    $self->log('info', 'copy conntrack information');
>> +    PVE::Tools::run_command([['/usr/bin/pve-conntrack-tool', 
>> 'dump'], [@{$self->{rem_ssh}}, '/usr/bin/pve-conntrack-tool', 
>> 'insert']]);
>
> Just wondering if having an eval here and still continue with the 
> migration if there's a problem would make sense? OTOH one can argue 
> that if the option is set and the connection information cannot be 
> copied, it's better to abort.
>
I'm in favor of aborting, but perhaps eval and die on error with a 
custom error message?
>> +    }
>>   }
>>     sub phase2_cleanup {
>>




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

end of thread, other threads:[~2021-02-05  9:51 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-02-03 14:25 [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit Mira Limbeck
2021-02-03 14:25 ` [pve-devel] [PATCH conntrack-tool v2 2/5] add packaging support Mira Limbeck
2021-02-04  7:08   ` Thomas Lamprecht
2021-02-04  9:55     ` Mira Limbeck
2021-02-03 14:25 ` [pve-devel] [PATCH conntrack-tool v2 3/5] add expectation support Mira Limbeck
2021-02-03 14:25 ` [pve-devel] [PATCH conntrack-tool v2 4/5] add additional bindings Mira Limbeck
2021-02-03 14:25 ` [pve-devel] [PATCH conntrack-tool v2 5/5] replace C callback with closures Mira Limbeck
2021-02-03 14:25 ` [pve-devel] [PATCH qemu-server v2] copy conntrack information on migration Mira Limbeck
2021-02-05  8:11   ` Fabian Ebner
2021-02-05  9:51     ` Mira Limbeck
2021-02-04  8:07 ` [pve-devel] [PATCH conntrack-tool v2 1/5] initial commit Thomas Lamprecht
2021-02-04 10:15   ` Mira Limbeck
2021-02-04 10:20     ` Thomas Lamprecht
2021-02-04 10:16 ` Mira Limbeck

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