diff --git a/libknet/bindings/rust/src/knet_bindings.rs b/libknet/bindings/rust/src/knet_bindings.rs index 0e954bda..1f63d4b7 100644 --- a/libknet/bindings/rust/src/knet_bindings.rs +++ b/libknet/bindings/rust/src/knet_bindings.rs @@ -1,2485 +1,2561 @@ // libknet interface for Rust // Copyright (c) 2021 Red Hat, Inc. // // All rights reserved. // // Author: Christine Caulfield (ccaulfi@redhat.com) // #![allow(clippy::too_many_arguments)] #![allow(clippy::collapsible_else_if)] // For the code generated by bindgen use crate::sys::libknet as ffi; use std::ffi::{CString, CStr}; use std::sync::mpsc::*; use std::ptr::{copy_nonoverlapping, null, null_mut}; use std::sync::Mutex; use std::collections::HashMap; use std::io::{Result, Error, ErrorKind}; use std::os::raw::{c_void, c_char, c_uchar, c_uint}; use std::mem::size_of; use std::net::SocketAddr; use std::fmt; use std::thread::spawn; use std::time::{Duration, SystemTime}; use os_socketaddr::OsSocketAddr; #[derive(Copy, Clone, PartialEq)] /// The ID of a host known to knet. pub struct HostId { host_id: u16, } impl HostId { pub fn new(id: u16) -> HostId { HostId{host_id: id} } pub fn to_u16(self: HostId) -> u16 { self.host_id } } impl fmt::Display for HostId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f,"{}", self.host_id)?; Ok(()) } } pub enum TxRx { Tx = 0, Rx = 1 } impl TxRx { pub fn new (tx_rx: u8) -> TxRx { match tx_rx { 1 => TxRx::Rx, _ => TxRx::Tx } } } impl fmt::Display for TxRx { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { TxRx::Tx => write!(f, "Tx"), TxRx::Rx => write!(f, "Rx"), } } } bitflags! { /// Flags passed into [handle_new] pub struct HandleFlags: u64 { const PRIVILEGED = 1; const NONE = 0; } } bitflags! { /// Flags passed into [link_set_config] pub struct LinkFlags: u64 { const TRAFFICHIPRIO = 1; const NONE = 0; } } /// for passing to [handle_crypto_set_config] pub struct CryptoConfig<'a> { pub crypto_model: String, pub crypto_cipher_type: String, pub crypto_hash_type: String, pub private_key: &'a [u8], } /// for passing to [handle_compress] pub struct CompressConfig { pub compress_model: String, pub compress_threshold: u32, pub compress_level: i32, } /// Return value from packet filter pub enum FilterDecision { Discard, Unicast, Multicast } impl FilterDecision { pub fn to_i32(self: &FilterDecision) -> i32 { match self { FilterDecision::Discard => -1, FilterDecision::Unicast => 0, FilterDecision::Multicast => 1, } } } impl fmt::Display for FilterDecision { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { FilterDecision::Discard => write!(f, "Discard"), FilterDecision::Unicast => write!(f, "Unicast"), FilterDecision::Multicast => write!(f, "Multicast"), } } } // Used to convert a knet_handle_t into one of ours lazy_static! { static ref HANDLE_HASH: Mutex> = Mutex::new(HashMap::new()); } fn get_errno() -> i32 { match Error::last_os_error().raw_os_error() { Some(e) => e, None => libc::EINVAL, } } /// Callback from [handle_enable_sock_notify] pub type SockNotifyFn = fn(private_data: u64, datafd: i32, channel: i8, txrx: TxRx, Result<()>); /// Callback called when packets arrive/are sent [handle_enable_filter] pub type FilterFn = fn(private_data: u64, outdata: &[u8], txrx: TxRx, this_host_id: HostId, src_host_id: HostId, channel: &mut i8, dst_host_ids: &mut Vec) -> FilterDecision; /// Callback called when PMTU changes, see [handle_enable_pmtud_notify] pub type PmtudNotifyFn = fn(private_data: u64, data_mtu: u32); /// Called when the onwire version number for a node changes, see [handle_enable_onwire_ver_notify] pub type OnwireNotifyFn = fn(private_data: u64, onwire_min_ver: u8, onwire_max_ver: u8, onwire_ver: u8); /// Called when a host status changes, see [host_enable_status_change_notify] pub type HostStatusChangeNotifyFn = fn(private_data: u64, host_id: HostId, reachable: bool, remote: bool, external: bool); /// Called when a link status changes, see [link_enable_status_change_notify] pub type LinkStatusChangeNotifyFn = fn(private_data: u64, host_id: HostId, link_id: u8, connected: bool, remote: bool, external: bool); // Called from knet, we work out where to route it to and convert params extern "C" fn rust_sock_notify_fn( private_data: *mut c_void, datafd: i32, channel: i8, tx_rx: u8, error: i32, errorno: i32) { if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) { let res = if error == 0 { Ok(()) } else { Err(Error::from_raw_os_error(errorno)) }; // Call user fn if let Some(f) = h.sock_notify_fn { f(h.sock_notify_private_data, datafd, channel, TxRx::new(tx_rx), res); } } } // Called from knet, we work out where to route it to and convert params extern "C" fn rust_filter_fn( private_data: *mut c_void, outdata: *const c_uchar, outdata_len: isize, tx_rx: u8, this_host_id: u16, src_host_id: u16, channel: *mut i8, dst_host_ids: *mut u16, dst_host_ids_entries: *mut usize) -> i32 { let mut ret : i32 = -1; if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) { // Is there is filter fn? if let Some(f) = h.filter_fn { let data : &[u8] = unsafe { std::slice::from_raw_parts(outdata as *const u8, outdata_len as usize) }; let mut r_channel = unsafe {*channel}; let mut hosts_vec = Vec::::new(); // Call Rust callback ret = f(h.filter_private_data, data, TxRx::new(tx_rx), HostId{host_id: this_host_id}, HostId{host_id: src_host_id}, &mut r_channel, &mut hosts_vec).to_i32(); // Pass back mutable params dst_hosts unsafe { *channel = r_channel; *dst_host_ids_entries = hosts_vec.len(); let mut retvec = dst_host_ids; for i in &hosts_vec { *retvec = i.host_id; retvec = retvec.offset(1); // next entry } } } } ret } // Called from knet, we work out where to route it to and convert params extern "C" fn rust_pmtud_notify_fn( private_data: *mut c_void, data_mtu: u32) { if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) { // Call user fn if let Some(f) = h.pmtud_notify_fn { f(h.pmtud_notify_private_data, data_mtu); } } } // Called from knet, we work out where to route it to and convert params extern "C" fn rust_onwire_notify_fn( private_data: *mut c_void, onwire_min_ver: u8, onwire_max_ver: u8, onwire_ver: u8) { if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) { // Call user fn if let Some(f) = h.onwire_notify_fn { f(h.onwire_notify_private_data, onwire_min_ver, onwire_max_ver, onwire_ver); } } } // Called from knet, we work out where to route it to and convert params extern "C" fn rust_host_status_change_notify_fn( private_data: *mut c_void, host_id: u16, reachable: u8, remote: u8, external: u8) { if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) { // Call user fn if let Some(f) = h.host_status_change_notify_fn { f(h.host_status_change_notify_private_data, HostId{host_id}, crate::u8_to_bool(reachable), crate::u8_to_bool(remote), crate::u8_to_bool(external)); } } } // Called from knet, we work out where to route it to and convert params extern "C" fn rust_link_status_change_notify_fn( private_data: *mut c_void, host_id: u16, link_id: u8, connected: u8, remote: u8, external: u8) { if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) { // Call user fn if let Some(f) = h.link_status_change_notify_fn { f(h.link_status_change_notify_private_data, HostId{host_id}, link_id, crate::u8_to_bool(connected), crate::u8_to_bool(remote), crate::u8_to_bool(external)); } } } // Logging thread fn logging_thread(knet_pipe: i32, sender: Sender) { let mut logbuf = ffi::knet_log_msg {msg: [0; 254], subsystem: 0, msglevel: 0, knet_h: 0 as ffi::knet_handle_t}; // Make it blocking unsafe { libc::fcntl(knet_pipe, libc::F_SETFL, libc::fcntl(knet_pipe, libc::F_GETFL, 0) & !libc::O_NONBLOCK)}; loop { let msglen = unsafe {libc::read(knet_pipe, &mut logbuf as *mut _ as *mut c_void, size_of::())}; if msglen < 1 { unsafe { libc::close(knet_pipe); } // EOF on pipe, handle is closed. return; } if msglen == size_of::() as isize { let rmsg = LogMsg { msg: crate::string_from_bytes_safe(logbuf.msg.as_ptr(), 254), subsystem: SubSystem::new(logbuf.subsystem), level: LogLevel::new(logbuf.msglevel), handle: Handle{knet_handle: logbuf.knet_h as u64}}; if let Err(e) = sender.send(rmsg) { println!("Error sending log message: {}", e); } } } } #[derive(Copy, Clone, PartialEq)] #[repr(transparent)] /// a handle into the knet library, returned from [handle_new] pub struct Handle { knet_handle: u64, } // Private version of knet handle, contains all the callback data so // we only need to access it in the calback functions, making the rest // a bit quicker & neater struct PrivHandle { log_fd: i32, sock_notify_fn: Option, sock_notify_private_data: u64, filter_fn: Option, filter_private_data: u64, pmtud_notify_fn: Option, pmtud_notify_private_data: u64, onwire_notify_fn: Option, onwire_notify_private_data: u64, host_status_change_notify_fn: Option, host_status_change_notify_private_data: u64, link_status_change_notify_fn: Option, link_status_change_notify_private_data: u64, } /// A knet logging message returned down the log_sender channel set in [handle_new] pub struct LogMsg { pub msg: String, pub subsystem: SubSystem, pub level: LogLevel, pub handle: Handle, } /// Initialise the knet library, returns a handle for use with the other API calls pub fn handle_new(host_id: &HostId, log_sender: Option>, default_log_level: LogLevel, flags: HandleFlags) -> Result { // If a log sender was passed, make an FD & thread for knet let log_fd = match log_sender { Some(s) => { let mut pipes = [0i32; 2]; if unsafe {libc::pipe(pipes.as_mut_ptr())} != 0 { return Err(Error::last_os_error()); } spawn(move || logging_thread(pipes[0], s)); pipes[1] }, None => 0 }; let res = unsafe { ffi::knet_handle_new(host_id.host_id, log_fd, default_log_level.to_u8(), flags.bits) }; if res.is_null() { Err(Error::last_os_error()) } else { let rhandle = PrivHandle{log_fd, sock_notify_fn: None, sock_notify_private_data: 0u64, filter_fn: None, filter_private_data: 0u64, pmtud_notify_fn: None, pmtud_notify_private_data: 0u64, onwire_notify_fn: None, onwire_notify_private_data: 0u64, host_status_change_notify_fn: None, host_status_change_notify_private_data: 0u64, link_status_change_notify_fn: None, link_status_change_notify_private_data: 0u64, }; HANDLE_HASH.lock().unwrap().insert(res as u64, rhandle); Ok(Handle{knet_handle: res as u64}) } } /// Finish with knet, frees the handle returned by [handle_new] pub fn handle_free(handle: Handle) -> Result<()> { let res = unsafe { ffi::knet_handle_free(handle.knet_handle as ffi::knet_handle_t) }; if res == 0 { // Close the log fd as knet doesn't "do ownership" and this will shut down // our logging thread. if let Some(h) = HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) { unsafe { libc::close(h.log_fd); }; } HANDLE_HASH.lock().unwrap().remove(&handle.knet_handle); Ok(()) } else { Err(Error::last_os_error()) } } /// Enable notifications of socket state changes, set callback to 'None' to disable pub fn handle_enable_sock_notify(handle: Handle, private_data: u64, sock_notify_fn: Option) -> Result<()> { let res = match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) { Some(h) => { h.sock_notify_private_data = private_data; h.sock_notify_fn = sock_notify_fn; match sock_notify_fn { Some(_f) => unsafe { ffi::knet_handle_enable_sock_notify(handle.knet_handle as ffi::knet_handle_t, handle.knet_handle as *mut c_void, Some(rust_sock_notify_fn)) }, None => unsafe { ffi::knet_handle_enable_sock_notify(handle.knet_handle as ffi::knet_handle_t, handle.knet_handle as *mut c_void, None) }, } }, None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")), }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Add a data FD to knet. if datafd is 0 then knet will allocate one for you. pub fn handle_add_datafd(handle: Handle, datafd: i32, channel: i8) -> Result<(i32, i8)> { let mut c_datafd = datafd; let mut c_channel = channel; let res = unsafe { ffi::knet_handle_add_datafd(handle.knet_handle as ffi::knet_handle_t, &mut c_datafd, &mut c_channel) }; if res == 0 { Ok((c_datafd, c_channel)) } else { Err(Error::last_os_error()) } } /// Remove a datafd from knet pub fn handle_remove_datafd(handle: Handle, datafd: i32) -> Result<()> { let res = unsafe { ffi::knet_handle_remove_datafd(handle.knet_handle as ffi::knet_handle_t, datafd) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Returns the channel associated with data fd pub fn handle_get_channel(handle: Handle, datafd: i32) -> Result { let mut c_channel = 0i8; let res = unsafe { ffi::knet_handle_get_channel(handle.knet_handle as ffi::knet_handle_t, datafd, &mut c_channel) }; if res == 0 { Ok(c_channel) } else { Err(Error::last_os_error()) } } /// Returns the data FD associated with a channel pub fn handle_get_datafd(handle: Handle, channel: i8) -> Result { let mut c_datafd = 0i32; let res = unsafe { ffi::knet_handle_get_datafd(handle.knet_handle as ffi::knet_handle_t, channel, &mut c_datafd) }; if res == 0 { Ok(c_datafd) } else { Err(Error::last_os_error()) } } +#[derive(Copy, Clone, PartialEq)] +pub enum DefragReclaimPolicy { + Average = 0, + Absolute = 1 +} + +impl DefragReclaimPolicy { + pub fn new (policy: u32) -> DefragReclaimPolicy { + { + match policy { + 1 => DefragReclaimPolicy::Absolute, + _ => DefragReclaimPolicy::Average, + } + } + } + pub fn to_u32 (&self) -> u32 { + { + match self { + DefragReclaimPolicy::Absolute => 1, + DefragReclaimPolicy::Average => 0, + } + } + } +} + +impl fmt::Display for DefragReclaimPolicy { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + DefragReclaimPolicy::Absolute => write!(f, "Absolute"), + DefragReclaimPolicy::Average => write!(f, "Average"), + } + } +} + +/// Configure the defrag buffer parameters - applies to all hosts +pub fn handle_set_host_defrag_bufs(handle: Handle, min_defrag_bufs: u16, max_defrag_bufs: u16, + shrink_threshold: u8, + reclaim_policy: DefragReclaimPolicy) -> Result<()> +{ + let res = unsafe { + ffi::knet_handle_set_host_defrag_bufs(handle.knet_handle as ffi::knet_handle_t, + min_defrag_bufs, max_defrag_bufs, + shrink_threshold, + reclaim_policy.to_u32()) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + + +/// Get the defrag buffer parameters. +/// Returns (min_defrag_bufs, max_defrag_bufs, shrink_threshold, usage_samples, usage_samples_timespan, reclaim_policy) +pub fn handle_get_host_defrag_bufs(handle: Handle) -> Result<(u16, u16, u8, DefragReclaimPolicy)> +{ + let mut min_defrag_bufs: u16 = 0; + let mut max_defrag_bufs: u16 = 0; + let mut shrink_threshold: u8 = 0; + let mut reclaim_policy: u32 = 0; + let res = unsafe { + ffi::knet_handle_get_host_defrag_bufs(handle.knet_handle as ffi::knet_handle_t, + &mut min_defrag_bufs, &mut max_defrag_bufs, + &mut shrink_threshold, + &mut reclaim_policy) + }; + if res == 0 { + Ok((min_defrag_bufs, max_defrag_bufs, + shrink_threshold, + DefragReclaimPolicy::new(reclaim_policy))) + } else { + Err(Error::last_os_error()) + } +} + /// Receive messages from knet pub fn recv(handle: Handle, buf: &[u8], channel: i8) -> Result { let res = unsafe { ffi::knet_recv(handle.knet_handle as ffi::knet_handle_t, buf.as_ptr() as *mut c_char, buf.len(), channel) }; if res >= 0 { Ok(res) } else { if get_errno() == libc::EAGAIN { Err(Error::new(ErrorKind::WouldBlock, "Try again")) } else { Err(Error::last_os_error()) } } } /// Send messages knet pub fn send(handle: Handle, buf: &[u8], channel: i8) -> Result { let res = unsafe { ffi::knet_send(handle.knet_handle as ffi::knet_handle_t, buf.as_ptr() as *const c_char, buf.len(), channel) }; if res >= 0 { Ok(res) } else { if get_errno() == libc::EAGAIN { Err(Error::new(ErrorKind::WouldBlock, "Try again")) } else { Err(Error::last_os_error()) } } } /// Send messages to knet and wait till they have gone pub fn send_sync(handle: Handle, buf: &[u8], channel: i8) -> Result<()> { let res = unsafe { ffi::knet_send_sync(handle.knet_handle as ffi::knet_handle_t, buf.as_ptr() as *const c_char, buf.len(), channel) }; if res == 0 { Ok(()) } else { if get_errno() == libc::EAGAIN { Err(Error::new(ErrorKind::WouldBlock, "Try again")) } else { Err(Error::last_os_error()) } } } /// Enable the packet filter. pass 'None' as the callback to disable. pub fn handle_enable_filter(handle: Handle, private_data: u64, filter_fn: Option) -> Result<()> { let res = match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) { Some(h) => { h.filter_private_data = private_data; h.filter_fn = filter_fn; match filter_fn { Some(_f) => unsafe { ffi::knet_handle_enable_filter(handle.knet_handle as ffi::knet_handle_t, handle.knet_handle as *mut c_void, Some(rust_filter_fn)) }, None => unsafe { ffi::knet_handle_enable_filter(handle.knet_handle as ffi::knet_handle_t, handle.knet_handle as *mut c_void, None) }, } }, None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")), }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Set timer resolution pub fn handle_set_threads_timer_res(handle: Handle, timeres: u32) -> Result<()> { let res = unsafe { ffi::knet_handle_set_threads_timer_res(handle.knet_handle as ffi::knet_handle_t, timeres) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Get timer resolution pub fn handle_get_threads_timer_res(handle: Handle) -> Result { let mut c_timeres: u32 = 0; let res = unsafe { ffi::knet_handle_get_threads_timer_res(handle.knet_handle as ffi::knet_handle_t, &mut c_timeres) }; if res == 0 { Ok(c_timeres) } else { Err(Error::last_os_error()) } } /// Starts traffic moving. You must call this before knet will do anything. pub fn handle_setfwd(handle: Handle, enabled: bool) -> Result<()> { let res = unsafe { ffi::knet_handle_setfwd(handle.knet_handle as ffi::knet_handle_t, enabled as c_uint) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Enable access control lists pub fn handle_enable_access_lists(handle: Handle, enabled: bool) -> Result<()> { let res = unsafe { ffi::knet_handle_enable_access_lists(handle.knet_handle as ffi::knet_handle_t, enabled as c_uint) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Set frequency that PMTUd will check for MTU changes. value in milliseconds pub fn handle_pmtud_setfreq(handle: Handle, interval: u32) -> Result<()> { let res = unsafe { ffi::knet_handle_pmtud_setfreq(handle.knet_handle as ffi::knet_handle_t, interval) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Get frequency that PMTUd will check for MTU changes. value in milliseconds pub fn handle_pmtud_getfreq(handle: Handle) -> Result { let mut c_interval = 0u32; let res = unsafe { ffi::knet_handle_pmtud_getfreq(handle.knet_handle as ffi::knet_handle_t, &mut c_interval) }; if res == 0 { Ok(c_interval) } else { Err(Error::last_os_error()) } } /// Get the current MTU pub fn handle_pmtud_get(handle: Handle) -> Result { let mut c_mtu = 0u32; let res = unsafe { ffi::knet_handle_pmtud_get(handle.knet_handle as ffi::knet_handle_t, &mut c_mtu) }; if res == 0 { Ok(c_mtu) } else { Err(Error::last_os_error()) } } /// Set the interface MTU (this should not be necessary) pub fn handle_pmtud_set(handle: Handle, iface_mtu: u32) -> Result<()> { let res = unsafe { ffi::knet_handle_pmtud_set(handle.knet_handle as ffi::knet_handle_t, iface_mtu) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Enable notification of MTU changes pub fn handle_enable_pmtud_notify(handle: Handle, private_data: u64, pmtud_notify_fn: Option) -> Result<()> { let res = match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) { Some(h) => { h.pmtud_notify_private_data = private_data; h.pmtud_notify_fn = pmtud_notify_fn; match pmtud_notify_fn { Some(_f) => unsafe { ffi::knet_handle_enable_pmtud_notify(handle.knet_handle as ffi::knet_handle_t, handle.knet_handle as *mut c_void, Some(rust_pmtud_notify_fn)) }, None => unsafe { ffi::knet_handle_enable_pmtud_notify(handle.knet_handle as ffi::knet_handle_t, handle.knet_handle as *mut c_void, None) }, } }, None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")), }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Configure cryptographic seetings for packets being transmitted pub fn handle_crypto_set_config(handle: Handle, config: &CryptoConfig, config_num: u8) -> Result<()> { let mut crypto_cfg = ffi::knet_handle_crypto_cfg { crypto_model: [0; 16], crypto_cipher_type: [0; 16], crypto_hash_type: [0; 16], private_key: [0; 4096], private_key_len: 0, }; if config.private_key.len() > 4096 { return Err(Error::new(ErrorKind::Other, "key too long")); } crate::string_to_bytes(&config.crypto_model, &mut crypto_cfg.crypto_model)?; crate::string_to_bytes(&config.crypto_cipher_type, &mut crypto_cfg.crypto_cipher_type)?; crate::string_to_bytes(&config.crypto_hash_type, &mut crypto_cfg.crypto_hash_type)?; unsafe { // NOTE param order is 'wrong-way round' from C copy_nonoverlapping(config.private_key.as_ptr(), crypto_cfg.private_key.as_mut_ptr(), config.private_key.len()); } crypto_cfg.private_key_len = config.private_key.len() as u32; let res = unsafe { ffi::knet_handle_crypto_set_config(handle.knet_handle as ffi::knet_handle_t, &mut crypto_cfg, config_num) }; if res == 0 { Ok(()) } else { if res == -2 { Err(Error::new(ErrorKind::Other, "Other cryto error")) } else { Err(Error::last_os_error()) } } } /// Whether to allow or disallow clear-text traffic when crypto is enabled with [handle_crypto_rx_clear_traffic] pub enum RxClearTraffic { Allow = 0, Disallow = 1, } /// Enable or disable clear-text traffic when crypto is enabled pub fn handle_crypto_rx_clear_traffic(handle: Handle, value: RxClearTraffic) -> Result<()> { let c_value : u8 = match value { RxClearTraffic::Allow => 0, RxClearTraffic::Disallow => 1 }; let res = unsafe { ffi::knet_handle_crypto_rx_clear_traffic(handle.knet_handle as ffi::knet_handle_t, c_value) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Tell knet which crypto settings to use pub fn handle_crypto_use_config(handle: Handle, config_num: u8) -> Result<()> { let res = unsafe { ffi::knet_handle_crypto_use_config(handle.knet_handle as ffi::knet_handle_t, config_num) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Set up packet compression pub fn handle_compress(handle: Handle, config: &CompressConfig) -> Result<()> { let mut compress_cfg = ffi::knet_handle_compress_cfg { compress_model: [0; 16], compress_threshold : config.compress_threshold, compress_level : config.compress_level }; if config.compress_model.len() > 16 { return Err(Error::new(ErrorKind::Other, "key too long")); } crate::string_to_bytes(&config.compress_model, &mut compress_cfg.compress_model)?; let res = unsafe { ffi::knet_handle_compress(handle.knet_handle as ffi::knet_handle_t, &mut compress_cfg) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Stats for the knet handle pub type HandleStats = ffi::knet_handle_stats; impl HandleStats { pub fn new() -> HandleStats { HandleStats { size: 0, tx_uncompressed_packets: 0, tx_compressed_packets: 0, tx_compressed_original_bytes: 0, tx_compressed_size_bytes: 0, tx_compress_time_ave: 0, tx_compress_time_min: 0, tx_compress_time_max: 0, tx_failed_to_compress: 0, tx_unable_to_compress: 0, rx_compressed_packets: 0, rx_compressed_original_bytes: 0, rx_compressed_size_bytes: 0, rx_compress_time_ave: 0, rx_compress_time_min: 0, rx_compress_time_max: 0, rx_failed_to_decompress: 0, tx_crypt_packets: 0, tx_crypt_byte_overhead: 0, tx_crypt_time_ave: 0, tx_crypt_time_min: 0, tx_crypt_time_max: 0, rx_crypt_packets: 0, rx_crypt_time_ave: 0, rx_crypt_time_min: 0, rx_crypt_time_max: 0, } } } impl Default for ffi::knet_handle_stats { fn default() -> Self { ffi::knet_handle_stats::new() } } impl fmt::Display for HandleStats { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}, ", self.tx_uncompressed_packets)?; write!(f, "{}, ", self.tx_compressed_packets)?; write!(f, "{}, ", self.tx_compressed_original_bytes)?; write!(f, "{}, ", self.tx_compressed_size_bytes)?; write!(f, "{}, ", self.tx_compress_time_ave)?; write!(f, "{}, ", self.tx_compress_time_min)?; write!(f, "{}, ", self.tx_compress_time_max)?; write!(f, "{}, ", self.tx_failed_to_compress)?; write!(f, "{}, ", self.tx_unable_to_compress)?; write!(f, "{}, ", self.rx_compressed_packets)?; write!(f, "{}, ", self.rx_compressed_original_bytes)?; write!(f, "{}, ", self.rx_compressed_size_bytes)?; write!(f, "{}, ", self.rx_compress_time_ave)?; write!(f, "{}, ", self.rx_compress_time_min)?; write!(f, "{}, ", self.rx_compress_time_max)?; write!(f, "{}, ", self.rx_failed_to_decompress)?; write!(f, "{}, ", self.tx_crypt_packets)?; write!(f, "{}, ", self.tx_crypt_byte_overhead)?; write!(f, "{}, ", self.tx_crypt_time_ave)?; write!(f, "{}, ", self.tx_crypt_time_min)?; write!(f, "{}, ", self.tx_crypt_time_max)?; write!(f, "{}, ", self.rx_crypt_packets)?; write!(f, "{}, ", self.rx_crypt_time_ave)?; write!(f, "{}, ", self.rx_crypt_time_min)?; write!(f, "{}, ", self.rx_crypt_time_max)?; Ok(()) } } /// Return statistics for this knet handle pub fn handle_get_stats(handle: Handle) -> Result { let (res, stats) = unsafe { let mut c_stats = HandleStats::new(); let res = ffi::knet_handle_get_stats(handle.knet_handle as ffi::knet_handle_t, &mut c_stats, size_of::()); (res, c_stats) }; if res == 0 { Ok(stats) } else { Err(Error::last_os_error()) } } /// Tell [handle_clear_stats] whether to cleat all stats or just handle stats pub enum ClearStats { Handle = 1, HandleAndLink = 2, } /// Clear statistics pub fn handle_clear_stats(handle: Handle, clear_options: ClearStats) -> Result<()> { let c_value : i32 = match clear_options { ClearStats::Handle => 1, ClearStats::HandleAndLink => 2 }; let res = unsafe { ffi::knet_handle_clear_stats(handle.knet_handle as ffi::knet_handle_t, c_value) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Crypto info returned from [get_crypto_list] pub struct CryptoInfo { pub name: String, pub properties: u8, // Unused } impl CryptoInfo { pub fn new(c_info: ffi::knet_crypto_info) -> CryptoInfo { let cstr = unsafe {CStr::from_ptr(c_info.name) }; let name = match cstr.to_str() { Ok(s) => s.to_string(), Err(e) => e.to_string(), }; CryptoInfo {properties: 0, name} } } /// Get a list of valid crypto options pub fn get_crypto_list() -> Result> { let mut list_entries: usize = 256; let mut c_list : [ffi::knet_crypto_info; 256] = [ ffi::knet_crypto_info{name: null(), properties: 0u8, pad:[0; 256]}; 256]; let res = unsafe { ffi::knet_get_crypto_list(&mut c_list[0], &mut list_entries) }; if res == 0 { let mut retvec = Vec::::new(); for i in c_list.iter().take(list_entries) { retvec.push(CryptoInfo::new(*i)); } Ok(retvec) } else { Err(Error::last_os_error()) } } /// Compressions types returned from [get_compress_list] pub struct CompressInfo { pub name: String, pub properties: u8, // Unused } impl CompressInfo { pub fn new(c_info: ffi::knet_compress_info) -> CompressInfo { let cstr = unsafe {CStr::from_ptr(c_info.name) }; let name = match cstr.to_str() { Ok(s) => s.to_string(), Err(e) => e.to_string(), }; CompressInfo {properties: 0, name} } } /// Return a list of compression options pub fn get_compress_list() -> Result> { let mut list_entries: usize = 256; let mut c_list : [ffi::knet_compress_info; 256] = [ ffi::knet_compress_info{name: null(), properties: 0u8, pad:[0; 256]}; 256]; let res = unsafe { ffi::knet_get_compress_list(&mut c_list[0], &mut list_entries) }; if res == 0 { let mut retvec = Vec::::new(); for i in c_list.iter().take(list_entries) { retvec.push(CompressInfo::new(*i)); } Ok(retvec) } else { Err(Error::last_os_error()) } } /// Enable callback when the onwire version for a node changes pub fn handle_enable_onwire_ver_notify(handle: Handle, private_data: u64, onwire_notify_fn: Option) -> Result<()> { // This looks a bit different to the other _enable*_notify calls because knet // calls the calback function in the API. Which results in a deadlock with our // own mutex match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) { Some(h) => { h.onwire_notify_private_data = private_data; h.onwire_notify_fn = onwire_notify_fn; }, None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")), }; let res = match onwire_notify_fn { Some(_f) => unsafe { ffi::knet_handle_enable_onwire_ver_notify(handle.knet_handle as ffi::knet_handle_t, handle.knet_handle as *mut c_void, Some(rust_onwire_notify_fn)) }, None => unsafe { ffi::knet_handle_enable_onwire_ver_notify(handle.knet_handle as ffi::knet_handle_t, handle.knet_handle as *mut c_void, None) }, }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Get the onsure version for a node pub fn handle_get_onwire_ver(handle: Handle, host_id: &HostId) -> Result<(u8,u8,u8)> { let mut onwire_min_ver = 0u8; let mut onwire_max_ver = 0u8; let mut onwire_ver = 0u8; let res = unsafe { ffi::knet_handle_get_onwire_ver(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, &mut onwire_min_ver, &mut onwire_max_ver, &mut onwire_ver) }; if res == 0 { Ok((onwire_min_ver, onwire_max_ver, onwire_ver)) } else { Err(Error::last_os_error()) } } /// Set the onsire version for this node pub fn handle_set_onwire_ver(handle: Handle, onwire_ver: u8) -> Result<()> { let res = unsafe { ffi::knet_handle_set_onwire_ver(handle.knet_handle as ffi::knet_handle_t, onwire_ver) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Set the reconnect interval. pub fn handle_set_transport_reconnect_interval(handle: Handle, msecs: u32) -> Result<()> { let res = unsafe { ffi::knet_handle_set_transport_reconnect_interval(handle.knet_handle as ffi::knet_handle_t, msecs) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Get the reconnect interval. pub fn handle_get_transport_reconnect_interval(handle: Handle) -> Result { let mut msecs = 0u32; let res = unsafe { ffi::knet_handle_get_transport_reconnect_interval(handle.knet_handle as ffi::knet_handle_t, &mut msecs) }; if res == 0 { Ok(msecs) } else { Err(Error::last_os_error()) } } /// Add a new host ID pub fn host_add(handle: Handle, host_id: &HostId) -> Result<()> { let res = unsafe { ffi::knet_host_add(handle.knet_handle as ffi::knet_handle_t, host_id.host_id) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Remove a Host ID pub fn host_remove(handle: Handle, host_id: &HostId) -> Result<()> { let res = unsafe { ffi::knet_host_remove(handle.knet_handle as ffi::knet_handle_t, host_id.host_id) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Set the name of a host pub fn host_set_name(handle: Handle, host_id: &HostId, name: &str) -> Result<()> { let c_name = CString::new(name)?; let res = unsafe { ffi::knet_host_set_name(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, c_name.as_ptr()) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } const KNET_MAX_HOST_LEN:usize = 256; const KNET_MAX_PORT_LEN:usize = 6; /// Retrieve the name of a host given its ID pub fn host_get_name_by_host_id(handle: Handle, host_id: &HostId) -> Result { let mut c_name: [c_char; KNET_MAX_HOST_LEN] = [0; KNET_MAX_HOST_LEN]; let res = unsafe { ffi::knet_host_get_name_by_host_id(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, c_name.as_mut_ptr()) }; if res == 0 { crate::string_from_bytes(c_name.as_ptr(), KNET_MAX_HOST_LEN) } else { Err(Error::last_os_error()) } } /// Return the ID of a host given its name pub fn host_get_id_by_host_name(handle: Handle, name: &str) -> Result { let c_name = CString::new(name)?; let mut c_host_id = 0u16; let res = unsafe { ffi::knet_host_get_id_by_host_name(handle.knet_handle as ffi::knet_handle_t, c_name.as_ptr(), &mut c_host_id) }; if res == 0 { Ok(HostId{host_id: c_host_id}) } else { Err(Error::last_os_error()) } } const KNET_MAX_HOST: usize = 65536; /// Return a list of host IDs known to this handle pub fn host_get_host_list(handle: Handle) -> Result> { let mut c_host_ids: [u16; KNET_MAX_HOST] = [0; KNET_MAX_HOST]; let mut c_host_ids_entries: usize = 0; let res = unsafe { ffi::knet_host_get_host_list(handle.knet_handle as ffi::knet_handle_t, &mut c_host_ids[0], &mut c_host_ids_entries) }; if res == 0 { let mut host_vec = Vec::::new(); for i in c_host_ids.iter().take(c_host_ids_entries) { host_vec.push(HostId {host_id: *i}); } Ok(host_vec) } else { Err(Error::last_os_error()) } } /// Link Policies for [host_set_policy] #[derive(Copy, Clone, PartialEq)] pub enum LinkPolicy { Passive, Active, Rr, } impl LinkPolicy{ pub fn new(value: u8) -> LinkPolicy { match value { 2 => LinkPolicy::Rr, 1 => LinkPolicy::Active, _ => LinkPolicy::Passive, } } pub fn to_u8(self: LinkPolicy) -> u8 { match self { LinkPolicy::Passive => 0, LinkPolicy::Active => 1, LinkPolicy::Rr => 2, } } } impl fmt::Display for LinkPolicy { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { LinkPolicy::Passive => write!(f, "Passive"), LinkPolicy::Active => write!(f, "Active"), LinkPolicy::Rr => write!(f, "RR"), } } } /// Set the policy for this host, this only makes sense if multiple links between hosts are configured pub fn host_set_policy(handle: Handle, host_id: &HostId, policy: LinkPolicy) -> Result<()> { let c_value: u8 = policy.to_u8(); let res = unsafe { ffi::knet_host_set_policy(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, c_value) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Return the current link policy for a node pub fn host_get_policy(handle: Handle, host_id: &HostId) -> Result { let mut c_value: u8 = 0; let res = unsafe { ffi::knet_host_get_policy(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, &mut c_value) }; if res == 0 { Ok(LinkPolicy::new(c_value)) } else { Err(Error::last_os_error()) } } /// Current status of a host. remote & reachable are current not used pub struct HostStatus { pub reachable: bool, pub remote: bool, pub external: bool, } impl fmt::Display for HostStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "reachable: {}, ", self.reachable)?; write!(f, "remote: {}, ", self.remote)?; write!(f, "external: {}", self.external)?; Ok(()) } } /// Return the current status of a host pub fn host_get_status(handle: Handle, host_id: &HostId) -> Result { let mut c_value = ffi::knet_host_status { reachable:0, remote:0, external:0}; let res = unsafe { ffi::knet_host_get_status(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, &mut c_value) }; if res == 0 { Ok(HostStatus { reachable: crate::u8_to_bool(c_value.reachable), remote: crate::u8_to_bool(c_value.remote), external: crate::u8_to_bool(c_value.external) }) } else { Err(Error::last_os_error()) } } /// Enable callbacks when the status of a host changes pub fn host_enable_status_change_notify(handle: Handle, private_data: u64, host_status_change_notify_fn: Option) -> Result<()> { let res = match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) { Some(h) => { h.host_status_change_notify_private_data = private_data; h.host_status_change_notify_fn = host_status_change_notify_fn; match host_status_change_notify_fn { Some(_f) => unsafe { ffi::knet_host_enable_status_change_notify(handle.knet_handle as ffi::knet_handle_t, handle.knet_handle as *mut c_void, Some(rust_host_status_change_notify_fn)) }, None => unsafe { ffi::knet_host_enable_status_change_notify(handle.knet_handle as ffi::knet_handle_t, handle.knet_handle as *mut c_void, None) }, } }, None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")), }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Transport types supported in knet pub enum TransportId { Loopback, Udp, Sctp, } impl TransportId { pub fn new(id: u8) -> TransportId { match id { 2 => TransportId::Sctp, 1 => TransportId::Udp, _ => TransportId::Loopback, } } pub fn to_u8(self: &TransportId) -> u8 { match self { TransportId::Loopback => 0, TransportId::Udp => 1, TransportId::Sctp => 2, } } pub fn to_string(self: &TransportId) -> String { match self { TransportId::Udp => "UDP".to_string(), TransportId::Sctp => "SCTP".to_string(), TransportId::Loopback => "Loopback".to_string() } } pub fn from_string(name: String) -> TransportId { match name.as_str() { "UDP" => TransportId::Udp, "SCTP" => TransportId::Sctp, "Loopback" => TransportId::Loopback, _ => TransportId::Loopback, } } } /// Transport info returned from [get_transport_list] pub struct TransportInfo { pub name: String, pub id: TransportId, pub properties: u8, // currently unused } // Controversially implementing name_by_id and id_by_name here impl TransportInfo { pub fn new(c_info: ffi::knet_transport_info) -> TransportInfo { let cstr = unsafe {CStr::from_ptr(c_info.name) }; let name = match cstr.to_str() { Ok(s) => s.to_string(), Err(e) => e.to_string(), }; TransportInfo {properties: 0, id: TransportId::new(c_info.id), name} } } pub fn get_transport_list() -> Result> { let mut list_entries: usize = 256; let mut c_list : [ffi::knet_transport_info; 256] = [ ffi::knet_transport_info{name: null(), id: 0u8, properties: 0u8, pad:[0; 256]}; 256]; let res = unsafe { ffi::knet_get_transport_list(&mut c_list[0], &mut list_entries) }; if res == 0 { let mut retvec = Vec::::new(); for i in c_list.iter().take(list_entries) { retvec.push(TransportInfo::new(*i)); } Ok(retvec) } else { Err(Error::last_os_error()) } } /// Configure a link to a host ID. dst_addr may be None for a dynamic link. pub fn link_set_config(handle: Handle, host_id: &HostId, link_id: u8, transport: TransportId, src_addr: &SocketAddr, dst_addr: Option<&SocketAddr>, flags: LinkFlags) -> Result<()> { // Not really mut, but C is dumb let mut c_srcaddr = make_new_sockaddr_storage(src_addr); // dst_addr can be NULL/None if this is a dynamic link let res = if let Some(dst) = dst_addr { let mut c_dstaddr = make_new_sockaddr_storage(dst); unsafe { ffi::knet_link_set_config(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id, transport.to_u8(), &mut c_srcaddr, &mut c_dstaddr, flags.bits) } } else { unsafe { ffi::knet_link_set_config(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id, transport.to_u8(), &mut c_srcaddr, null_mut(), flags.bits) } }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Return a link's configuration pub fn link_get_config(handle: Handle, host_id: &HostId, link_id: u8) -> Result<(TransportId, Option, Option, LinkFlags)> { let mut c_srcaddr = OsSocketAddr::new(); let mut c_dstaddr = OsSocketAddr::new(); let mut c_transport = 0u8; let mut c_flags = 0u64; let mut c_dynamic = 0u8; let res = unsafe { ffi::knet_link_get_config(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id, &mut c_transport, c_srcaddr.as_mut_ptr() as *mut ffi::sockaddr_storage, c_dstaddr.as_mut_ptr() as *mut ffi::sockaddr_storage, &mut c_dynamic, &mut c_flags) }; if res == 0 { let r_transport = TransportId::new(c_transport); Ok((r_transport, c_srcaddr.into(), c_dstaddr.into(), LinkFlags{bits:c_flags})) } else { Err(Error::last_os_error()) } } /// Clear a link configuration. pub fn link_clear_config(handle: Handle, host_id: &HostId, link_id: u8) -> Result<()> { let res = unsafe { ffi::knet_link_clear_config(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Type of ACL pub enum AclAcceptReject { Accept, Reject, } impl AclAcceptReject { pub fn new(ar: u32) -> AclAcceptReject { match ar { ffi::CHECK_ACCEPT => AclAcceptReject::Accept, ffi::CHECK_REJECT => AclAcceptReject::Reject, _ => AclAcceptReject::Reject, } } pub fn to_u32(self: &AclAcceptReject) -> u32 { match self { AclAcceptReject::Accept => ffi::CHECK_ACCEPT, AclAcceptReject::Reject => ffi::CHECK_REJECT, } } } /// What the ACL should check pub enum AclCheckType { Address, Mask, Range, } impl AclCheckType { pub fn new(ct: u32) -> AclCheckType { match ct { ffi::CHECK_TYPE_ADDRESS => AclCheckType::Address, ffi::CHECK_TYPE_MASK => AclCheckType::Mask, ffi::CHECK_TYPE_RANGE => AclCheckType::Range, _ => AclCheckType::Address, } } pub fn to_u32(self: &AclCheckType) -> u32 { match self { AclCheckType::Address => ffi::CHECK_TYPE_ADDRESS, AclCheckType::Mask => ffi::CHECK_TYPE_MASK, AclCheckType::Range => ffi::CHECK_TYPE_RANGE, } } } // We need to have a zeroed-out stackaddr storage to pass to the ACL APIs // as knet compares the whole sockaddr_storage when using knet_rm_acl() fn make_new_sockaddr_storage(ss: &SocketAddr) -> ffi::sockaddr_storage { // A blank one let mut new_ss = ffi::sockaddr_storage { ss_family: 0, __ss_padding: [0; 118], __ss_align: 0, }; let p_new_ss : *mut ffi::sockaddr_storage = &mut new_ss; // Rust only fills in what it thinks is necessary let c_ss : OsSocketAddr = (*ss).into(); // Copy it unsafe { // Only copy as much as is in the OsSocketAddr copy_nonoverlapping(c_ss.as_ptr(), p_new_ss as *mut libc::sockaddr, 1); } new_ss } /// Add an ACL to a link, adds the ACL to the end of the list. pub fn link_add_acl(handle: Handle, host_id: &HostId, link_id: u8, ss1: &SocketAddr, ss2: &SocketAddr, check_type: AclCheckType, acceptreject: AclAcceptReject) -> Result<()> { // Not really mut, but C is dumb let mut c_ss1 = make_new_sockaddr_storage(ss1); let mut c_ss2 = make_new_sockaddr_storage(ss2); let res = unsafe { ffi::knet_link_add_acl(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id, &mut c_ss1, &mut c_ss2, check_type.to_u32(), acceptreject.to_u32()) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Insert an ACL anywhere in the ACL list for this host/link pub fn link_insert_acl(handle: Handle, host_id: &HostId, link_id: u8, index: i32, ss1: &SocketAddr, ss2: &SocketAddr, check_type: AclCheckType, acceptreject: AclAcceptReject) -> Result<()> { // Not really mut, but C is dumb let mut c_ss1 = make_new_sockaddr_storage(ss1); let mut c_ss2 = make_new_sockaddr_storage(ss2); let res = unsafe { ffi::knet_link_insert_acl(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id, index, &mut c_ss1, &mut c_ss2, check_type.to_u32(), acceptreject.to_u32()) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Remove an ACL for this host/link pub fn link_rm_acl(handle: Handle, host_id: &HostId, link_id: u8, ss1: &SocketAddr, ss2: &SocketAddr, check_type: AclCheckType, acceptreject: AclAcceptReject) -> Result<()> { // Not really mut, but C is dumb let mut c_ss1 = make_new_sockaddr_storage(ss1); let mut c_ss2 = make_new_sockaddr_storage(ss2); let res = unsafe { ffi::knet_link_rm_acl(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id, &mut c_ss1, &mut c_ss2, check_type.to_u32(), acceptreject.to_u32()) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Clear out all ACLs from this host/link pub fn link_clear_acl(handle: Handle, host_id: &HostId, link_id: u8) -> Result<()> { let res = unsafe { ffi::knet_link_clear_acl(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Enable/disable a link (you still need to call [handle_setfwd] for traffic to flow pub fn link_set_enable(handle: Handle, host_id: &HostId, link_id: u8, enable: bool) -> Result<()> { let res = unsafe { ffi::knet_link_set_enable(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id, enable as u32) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Get the 'enabled' status for a link pub fn link_get_enable(handle: Handle, host_id: &HostId, link_id: u8) -> Result { let mut c_enable = 0u32; let res = unsafe { ffi::knet_link_get_enable(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id, &mut c_enable) }; if res == 0 { Ok(crate::u32_to_bool(c_enable)) } else { Err(Error::last_os_error()) } } /// Set the ping timers for a link pub fn link_set_ping_timers(handle: Handle, host_id: &HostId, link_id: u8, interval: i64, timeout: i64, precision: u32) -> Result<()> { let res = unsafe { ffi::knet_link_set_ping_timers(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id, interval, timeout, precision) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Get the ping timers for a link pub fn link_get_ping_timers(handle: Handle, host_id: &HostId, link_id: u8) -> Result<(i64, i64, u32)> { let mut c_interval : ffi::time_t = 0; let mut c_timeout : ffi::time_t = 0; let mut c_precision = 0u32; let res = unsafe { ffi::knet_link_get_ping_timers(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id, &mut c_interval, &mut c_timeout, &mut c_precision) }; if res == 0 { Ok((c_interval as i64, c_timeout as i64, c_precision)) } else { Err(Error::last_os_error()) } } /// Set the pong count for a link pub fn link_set_pong_count(handle: Handle, host_id: &HostId, link_id: u8, pong_count: u8) -> Result<()> { let res = unsafe { ffi::knet_link_set_pong_count(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id, pong_count) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Get the pong count for a link pub fn link_get_pong_count(handle: Handle, host_id: &HostId, link_id: u8) -> Result { let mut c_pong_count = 0u8; let res = unsafe { ffi::knet_link_get_pong_count(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id, &mut c_pong_count) }; if res == 0 { Ok(c_pong_count) } else { Err(Error::last_os_error()) } } /// Set the link priority (only useful with multiple links to a node) pub fn link_set_priority(handle: Handle, host_id: &HostId, link_id: u8, priority: u8) -> Result<()> { let res = unsafe { ffi::knet_link_set_priority(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id, priority) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Get the link priority pub fn link_get_priority(handle: Handle, host_id: &HostId, link_id: u8) -> Result { let mut c_priority = 0u8; let res = unsafe { ffi::knet_link_get_priority(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id, &mut c_priority) }; if res == 0 { Ok(c_priority) } else { Err(Error::last_os_error()) } } const KNET_MAX_LINK: usize = 8; /// Get a list of links for this host pub fn link_get_link_list(handle: Handle, host_id: &HostId) -> Result> { let mut c_link_ids: [u8; KNET_MAX_LINK] = [0; KNET_MAX_LINK]; let mut c_link_ids_entries: usize = 0; let res = unsafe { ffi::knet_link_get_link_list(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, &mut c_link_ids[0], &mut c_link_ids_entries) }; if res == 0 { let mut link_vec = Vec::::new(); for i in c_link_ids.iter().take(c_link_ids_entries) { link_vec.push(*i); } Ok(link_vec) } else { Err(Error::last_os_error()) } } /// Enable callbacks when a link status changes pub fn link_enable_status_change_notify(handle: Handle, private_data: u64, link_status_change_notify_fn: Option) -> Result<()> { let res = match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) { Some(h) => { h.link_status_change_notify_private_data = private_data; h.link_status_change_notify_fn = link_status_change_notify_fn; match link_status_change_notify_fn { Some(_f) => unsafe { ffi::knet_link_enable_status_change_notify(handle.knet_handle as ffi::knet_handle_t, handle.knet_handle as *mut c_void, Some(rust_link_status_change_notify_fn)) }, None => unsafe { ffi::knet_link_enable_status_change_notify(handle.knet_handle as ffi::knet_handle_t, handle.knet_handle as *mut c_void, None) }, } }, None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")), }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Link stats pub struct LinkStats { pub tx_data_packets: u64, pub rx_data_packets: u64, pub tx_data_bytes: u64, pub rx_data_bytes: u64, pub rx_ping_packets: u64, pub tx_ping_packets: u64, pub rx_ping_bytes: u64, pub tx_ping_bytes: u64, pub rx_pong_packets: u64, pub tx_pong_packets: u64, pub rx_pong_bytes: u64, pub tx_pong_bytes: u64, pub rx_pmtu_packets: u64, pub tx_pmtu_packets: u64, pub rx_pmtu_bytes: u64, pub tx_pmtu_bytes: u64, pub tx_total_packets: u64, pub rx_total_packets: u64, pub tx_total_bytes: u64, pub rx_total_bytes: u64, pub tx_total_errors: u64, pub tx_total_retries: u64, pub tx_pmtu_errors: u32, pub tx_pmtu_retries: u32, pub tx_ping_errors: u32, pub tx_ping_retries: u32, pub tx_pong_errors: u32, pub tx_pong_retries: u32, pub tx_data_errors: u32, pub tx_data_retries: u32, pub latency_min: u32, pub latency_max: u32, pub latency_ave: u32, pub latency_samples: u32, pub down_count: u32, pub up_count: u32, pub last_up_times: Vec, pub last_down_times: Vec, } // Quick & Dirty printing impl fmt::Display for LinkStats { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}, ", self.tx_data_packets)?; write!(f, "{}, ", self.rx_data_packets)?; write!(f, "{}, ", self.tx_data_bytes)?; write!(f, "{}, ", self.rx_data_bytes)?; write!(f, "{}, ", self.rx_ping_packets)?; write!(f, "{}, ", self.tx_ping_packets)?; write!(f, "{}, ", self.rx_ping_bytes)?; write!(f, "{}, ", self.tx_ping_bytes)?; write!(f, "{}, ", self.rx_pong_packets)?; write!(f, "{}, ", self.tx_pong_packets)?; write!(f, "{}, ", self.rx_pong_bytes)?; write!(f, "{}, ", self.tx_pong_bytes)?; write!(f, "{}, ", self.rx_pmtu_packets)?; write!(f, "{}, ", self.tx_pmtu_packets)?; write!(f, "{}, ", self.rx_pmtu_bytes)?; write!(f, "{}, ", self.tx_pmtu_bytes)?; write!(f, "{}, ", self.tx_total_packets)?; write!(f, "{}, ", self.rx_total_packets)?; write!(f, "{}, ", self.tx_total_bytes)?; write!(f, "{}, ", self.rx_total_bytes)?; write!(f, "{}, ", self.tx_total_errors)?; write!(f, "{}, ", self.tx_total_retries)?; write!(f, "{}, ", self.tx_pmtu_errors)?; write!(f, "{}, ", self.tx_pmtu_retries)?; write!(f, "{}, ", self.tx_ping_errors)?; write!(f, "{}, ", self.tx_ping_retries)?; write!(f, "{}, ", self.tx_pong_errors)?; write!(f, "{}, ", self.tx_pong_retries)?; write!(f, "{}, ", self.tx_data_errors)?; write!(f, "{}, ", self.tx_data_retries)?; write!(f, "{}, ", self.latency_min)?; write!(f, "{}, ", self.latency_max)?; write!(f, "{}, ", self.latency_ave)?; write!(f, "{}, ", self.latency_samples)?; write!(f, "{}, ", self.down_count)?; write!(f, "{}, ", self.up_count)?; write!(f, "Last up times: ")?; // There's no sensible print for SystemTime in the std library // and I don't want to add dependancies here for printing as it // mostly going to be the client's responsibility, so use the Debug option for i in &self.last_up_times { write!(f, "{:?}", i)?; } write!(f, " Last down times: ")?; for i in &self.last_down_times { write!(f, "{:?}", i)?; } Ok(()) } } // I wish this all wasn't necessary! impl ffi::knet_link_stats { pub fn new() -> ffi::knet_link_stats { ffi::knet_link_stats { tx_data_packets: 0, rx_data_packets: 0, tx_data_bytes: 0, rx_data_bytes: 0, rx_ping_packets: 0, tx_ping_packets: 0, rx_ping_bytes: 0, tx_ping_bytes: 0, rx_pong_packets: 0, tx_pong_packets: 0, rx_pong_bytes: 0, tx_pong_bytes: 0, rx_pmtu_packets: 0, tx_pmtu_packets: 0, rx_pmtu_bytes: 0, tx_pmtu_bytes: 0, tx_total_packets: 0, rx_total_packets: 0, tx_total_bytes: 0, rx_total_bytes: 0, tx_total_errors: 0, tx_total_retries: 0, tx_pmtu_errors: 0, tx_pmtu_retries: 0, tx_ping_errors: 0, tx_ping_retries: 0, tx_pong_errors: 0, tx_pong_retries: 0, tx_data_errors: 0, tx_data_retries: 0, latency_min: 0, latency_max: 0, latency_ave: 0, latency_samples: 0, down_count: 0, up_count: 0, last_up_times: [0; 16], last_down_times: [0; 16], last_up_time_index: 0, last_down_time_index: 0, } } } impl Default for ffi::knet_link_stats { fn default() -> Self { ffi::knet_link_stats::new() } } impl ffi::knet_link_status { pub fn new()-> ffi::knet_link_status { ffi::knet_link_status { size: 0, src_ipaddr : [0; KNET_MAX_HOST_LEN], dst_ipaddr : [0; KNET_MAX_HOST_LEN], src_port : [0; KNET_MAX_PORT_LEN], dst_port : [0; KNET_MAX_PORT_LEN], enabled: 0, connected: 0, dynconnected: 0, pong_last: ffi::timespec{ tv_sec: 0, tv_nsec: 0}, mtu: 0, proto_overhead: 0, stats: ffi::knet_link_stats::new(), } } } impl Default for ffi::knet_link_status { fn default() -> Self { ffi::knet_link_status::new() } } /// Link status (includes a [LinkStats]) pub struct LinkStatus { pub src_ipaddr: String, pub dst_ipaddr: String, pub src_port: String, pub dst_port: String, pub enabled: bool, pub connected: bool, pub dynconnected: bool, pub pong_last: SystemTime, pub mtu: u32, pub proto_overhead: u32, pub stats: LinkStats, } impl LinkStatus { pub fn new(c_stats: ffi::knet_link_status) -> LinkStatus { LinkStatus { src_ipaddr : crate::string_from_bytes_safe(c_stats.src_ipaddr.as_ptr(), KNET_MAX_HOST_LEN), src_port : crate::string_from_bytes_safe(c_stats.src_port.as_ptr(), KNET_MAX_HOST_LEN), dst_ipaddr : crate::string_from_bytes_safe(c_stats.dst_ipaddr.as_ptr(), KNET_MAX_HOST_LEN), dst_port : crate::string_from_bytes_safe(c_stats.dst_port.as_ptr(), KNET_MAX_HOST_LEN), enabled : crate::u8_to_bool(c_stats.enabled), connected : crate::u8_to_bool(c_stats.connected), dynconnected : crate::u8_to_bool(c_stats.dynconnected), pong_last : systemtime_from_timespec(c_stats.pong_last), mtu : c_stats.mtu, proto_overhead : c_stats.proto_overhead, stats : LinkStats::new(c_stats.stats), } } } impl fmt::Display for LinkStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "src_ip_addr: {}:{}, ", self.src_ipaddr, self.src_port)?; write!(f, "dst_ip_addr: {}:{}, ", self.dst_ipaddr, self.src_port)?; write!(f, "enabled: {}, connected: {}, mtu: {}, overhead: {}, ", self.enabled, self.connected, self.mtu, self.proto_overhead)?; write!(f, "stats: {}", self.stats)?; Ok(()) } } fn systemtime_from_time_t(t: u64) -> SystemTime { SystemTime::UNIX_EPOCH+Duration::from_secs(t) } fn systemtime_from_timespec(t: ffi::timespec) -> SystemTime { SystemTime::UNIX_EPOCH+Duration::from_secs(t.tv_sec as u64) +Duration::from_nanos(t.tv_nsec as u64) // TODO may panic?? } fn copy_circular_buffer_of_link_events(num: usize, times: &[ffi::time_t]) -> Vec { let mut times_vec = Vec::::new(); for index in (0 .. num).rev() { if times[index] == 0 { break } times_vec.push(systemtime_from_time_t(times[index] as u64)); // TODO may panic?? } for index in (num+1 .. MAX_LINK_EVENTS).rev() { if times[index] == 0 { break; } times_vec.push(systemtime_from_time_t(times[index] as u64)); // TODO may panic?? } times_vec } const MAX_LINK_EVENTS: usize = 16; impl LinkStats { pub fn new(cstats: ffi::knet_link_stats) -> LinkStats { let up_times = copy_circular_buffer_of_link_events(cstats.last_up_time_index as usize, &cstats.last_up_times); let down_times = copy_circular_buffer_of_link_events(cstats.last_down_time_index as usize, &cstats.last_down_times); LinkStats { tx_data_packets: cstats.tx_data_packets, rx_data_packets: cstats.rx_data_packets, tx_data_bytes: cstats.tx_data_bytes, rx_data_bytes: cstats.rx_data_bytes, rx_ping_packets: cstats.rx_ping_packets, tx_ping_packets: cstats.tx_ping_packets, rx_ping_bytes: cstats.rx_ping_bytes, tx_ping_bytes: cstats.tx_ping_bytes, rx_pong_packets: cstats.rx_pong_packets, tx_pong_packets: cstats.tx_pong_packets, rx_pong_bytes: cstats.rx_pong_bytes, tx_pong_bytes: cstats.tx_pong_bytes, rx_pmtu_packets: cstats.rx_pmtu_packets, tx_pmtu_packets: cstats.tx_pmtu_packets, rx_pmtu_bytes: cstats.rx_pmtu_bytes, tx_pmtu_bytes: cstats.tx_pmtu_bytes, tx_total_packets: cstats.tx_total_packets, rx_total_packets: cstats.rx_total_packets, tx_total_bytes: cstats.tx_total_bytes, rx_total_bytes: cstats.rx_total_bytes, tx_total_errors: cstats.tx_total_errors, tx_total_retries: cstats.tx_total_retries, tx_pmtu_errors: cstats.tx_pmtu_errors, tx_pmtu_retries: cstats.tx_pmtu_retries, tx_ping_errors: cstats.tx_ping_errors, tx_ping_retries: cstats.tx_ping_retries, tx_pong_errors: cstats.tx_pong_errors, tx_pong_retries: cstats.tx_pong_retries, tx_data_errors: cstats.tx_data_errors, tx_data_retries: cstats.tx_data_retries, latency_min: cstats.latency_min, latency_max: cstats.latency_max, latency_ave: cstats.latency_ave, latency_samples: cstats.latency_samples, down_count: cstats.down_count, up_count: cstats.up_count, last_up_times: up_times, last_down_times: down_times, } } } /// Get the status (and stats) of a link pub fn link_get_status(handle: Handle, host_id: &HostId, link_id: u8) -> Result { let (res, stats) = unsafe { let mut c_stats : ffi::knet_link_status = ffi::knet_link_status::new(); let res = ffi::knet_link_get_status(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, link_id, &mut c_stats, size_of::()); (res, c_stats) }; if res == 0 { let r_status = LinkStatus::new(stats); Ok(r_status) } else { Err(Error::last_os_error()) } } /// Get the logging subsystem ID given its name pub fn log_get_subsystem_id(name: &str) -> Result { let c_name = CString::new(name)?; let res = unsafe { ffi::knet_log_get_subsystem_id(c_name.as_ptr()) }; Ok(res) } /// Get the logging subsystem name given its ID pub fn log_get_subsystem_name(id: u8) -> Result { let res = unsafe { ffi::knet_log_get_subsystem_name(id) }; crate::string_from_bytes(res, 256) } /// Get the name of a logging level pub fn log_get_loglevel_id(name: &str) -> Result { let c_name = CString::new(name)?; let res = unsafe { ffi::knet_log_get_loglevel_id(c_name.as_ptr()) }; Ok(res) } /// Get the ID of a logging level, given its name pub fn log_get_loglevel_name(id: u8) -> Result { let res = unsafe { ffi::knet_log_get_loglevel_name(id) }; crate::string_from_bytes(res, 256) } /// Logging levels pub enum LogLevel { Err, Warn, Info, Debug, } impl LogLevel { pub fn new(level: u8) -> LogLevel { match level { 0 => LogLevel::Err, 1 => LogLevel::Warn, 2 => LogLevel::Info, _ => LogLevel::Debug, // 3=Debug, but default anything to it too } } pub fn to_u8(self: &LogLevel) -> u8 { match self { LogLevel::Err => 0, LogLevel::Warn => 1, LogLevel::Info => 2, LogLevel::Debug => 3, } } } impl fmt::Display for LogLevel { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { LogLevel::Err => write!(f, "Err"), LogLevel::Warn => write!(f, "Warn"), LogLevel::Info => write!(f, "Info"), LogLevel::Debug => write!(f, "Debug"), } } } /// Subsystems known to the knet logger pub enum SubSystem { Common, Handle, Host, Listener, Link, Transport, Crypto, Compress, Filter, Dstcache, Heartbeat, Pmtud, Tx, Rx, TranspBase, TranspLoopback, TranspUdp, TranspSctp, NssCrypto, OpensslCrypto, Zlibcomp, Lz4comp, Lz4hccomp, Lzo2comp, Lzmacomp, Bzip2comp, Zstdcomp, Unknown, } impl SubSystem { pub fn to_u8(self: &SubSystem) -> u8 { match self { SubSystem::Common => ffi::KNET_SUB_COMMON, SubSystem::Handle => ffi::KNET_SUB_HANDLE, SubSystem::Host => ffi::KNET_SUB_HOST, SubSystem::Listener => ffi::KNET_SUB_LISTENER, SubSystem::Link => ffi::KNET_SUB_LINK, SubSystem::Transport => ffi::KNET_SUB_TRANSPORT, SubSystem::Crypto => ffi::KNET_SUB_CRYPTO, SubSystem::Compress => ffi::KNET_SUB_COMPRESS, SubSystem::Filter => ffi::KNET_SUB_FILTER, SubSystem::Dstcache => ffi::KNET_SUB_DSTCACHE, SubSystem::Heartbeat => ffi::KNET_SUB_HEARTBEAT, SubSystem::Pmtud => ffi::KNET_SUB_PMTUD, SubSystem::Tx => ffi::KNET_SUB_TX, SubSystem::Rx => ffi::KNET_SUB_RX, SubSystem::TranspBase => ffi::KNET_SUB_TRANSP_BASE, SubSystem::TranspLoopback => ffi::KNET_SUB_TRANSP_LOOPBACK, SubSystem::TranspUdp => ffi::KNET_SUB_TRANSP_UDP, SubSystem::TranspSctp => ffi::KNET_SUB_TRANSP_SCTP, SubSystem::NssCrypto => ffi::KNET_SUB_NSSCRYPTO, SubSystem::OpensslCrypto => ffi::KNET_SUB_OPENSSLCRYPTO, SubSystem::Zlibcomp => ffi::KNET_SUB_ZLIBCOMP, SubSystem::Lz4comp => ffi::KNET_SUB_LZ4COMP, SubSystem::Lz4hccomp => ffi::KNET_SUB_LZ4HCCOMP, SubSystem::Lzo2comp => ffi::KNET_SUB_LZO2COMP, SubSystem::Lzmacomp => ffi::KNET_SUB_LZMACOMP, SubSystem::Bzip2comp => ffi::KNET_SUB_BZIP2COMP, SubSystem::Zstdcomp => ffi::KNET_SUB_ZSTDCOMP, SubSystem::Unknown => ffi::KNET_SUB_UNKNOWN, } } pub fn new(subsys: u8) -> SubSystem { match subsys { 1 => SubSystem::Unknown, 2 => SubSystem::Unknown, _ => SubSystem::Unknown, } } } /// Set the current logging level pub fn log_set_loglevel(handle: Handle, subsystem: SubSystem, level: LogLevel) -> Result<()> { let c_level = level.to_u8(); let c_subsys = subsystem.to_u8(); let res = unsafe { ffi::knet_log_set_loglevel(handle.knet_handle as ffi::knet_handle_t, c_subsys, c_level) }; if res == 0 { Ok(()) } else { Err(Error::last_os_error()) } } /// Get the current logging level pub fn log_get_loglevel(handle: Handle, subsystem: SubSystem) -> Result { let mut c_level:u8 = 0; let c_subsys = subsystem.to_u8(); let res = unsafe { ffi::knet_log_get_loglevel(handle.knet_handle as ffi::knet_handle_t, c_subsys, &mut c_level) }; if res == 0 { Ok(LogLevel::new(c_level)) } else { Err(Error::last_os_error()) } } diff --git a/libknet/bindings/rust/tests/src/bin/knet-test.rs b/libknet/bindings/rust/tests/src/bin/knet-test.rs index 32f688fa..711e69e6 100644 --- a/libknet/bindings/rust/tests/src/bin/knet-test.rs +++ b/libknet/bindings/rust/tests/src/bin/knet-test.rs @@ -1,920 +1,942 @@ // Testing the Knet Rust APIs // // Copyright (c) 2021 Red Hat, Inc. // // All rights reserved. // // Author: Christine Caulfield (ccaulfi@redhat.com) // use knet_bindings::knet_bindings as knet; use std::net::{SocketAddr, IpAddr, Ipv4Addr}; use std::thread::spawn; use std::sync::mpsc::Receiver; use std::sync::mpsc::channel; use std::io::{Result, ErrorKind, Error}; use std::{thread, time}; const CHANNEL: i8 = 1; // Dirty C function to set the plugin path for testing (only) extern { fn set_plugin_path(knet_h: knet::Handle); } // Callbacks fn sock_notify_fn(private_data: u64, datafd: i32, channel: i8, txrx: knet::TxRx, _res: Result<()>) { println!("sock notify called for host {}, datafd: {}, channel: {}, {}", private_data, datafd, channel, txrx); } fn link_notify_fn(private_data: u64, host_id: knet::HostId, link_id: u8, connected: bool, _remote: bool, _external: bool) { println!("link status notify called ({}) for host {}, linkid: {}, connected: {}", private_data, host_id.to_u16(), link_id, connected); } fn host_notify_fn(private_data: u64, host_id: knet::HostId, connected: bool, _remote: bool, _external: bool) { println!("host status notify called ({}) for host {}, connected: {}", private_data, host_id.to_u16(), connected); } fn pmtud_fn(private_data: u64, data_mtu: u32) { println!("PMTUD notify: host {}, MTU:{} ", private_data, data_mtu); } fn onwire_fn(private_data: u64, onwire_min_ver: u8, onwire_max_ver: u8, onwire_ver: u8) { println!("Onwire ver notify for {} : {}/{}/{}", private_data, onwire_min_ver, onwire_max_ver, onwire_ver); } fn filter_fn(private_data: u64, _outdata: &[u8], txrx: knet::TxRx, this_host_id: knet::HostId, src_host_id: knet::HostId, channel: &mut i8, dst_host_ids: &mut Vec) -> knet::FilterDecision { println!("Filter ({}) called {} to {} from {}, channel: {}", private_data, txrx, this_host_id, src_host_id, channel); let dst: u16 = (private_data & 0xFFFF) as u16; match txrx { knet::TxRx::Tx => { dst_host_ids.push(knet::HostId::new(3-dst)); knet::FilterDecision::Unicast } knet::TxRx::Rx => { dst_host_ids.push(this_host_id); knet::FilterDecision::Unicast } } } fn logging_thread(recvr: Receiver) { for i in &recvr { eprintln!("KNET: {}", i.msg); } eprintln!("Logging thread finished"); } fn setup_node(our_hostid: &knet::HostId, other_hostid: &knet::HostId, name: &str) -> Result { let (log_sender, log_receiver) = channel::(); spawn(move || logging_thread(log_receiver)); let knet_handle = match knet::handle_new(our_hostid, Some(log_sender), knet::LogLevel::Debug, knet::HandleFlags::NONE) { Ok(h) => h, Err(e) => { println!("Error from handle_new: {}", e); return Err(e); } }; // Make sure we use the build-tree plugins if LD_LIBRRAY_PATH points to them unsafe { set_plugin_path(knet_handle); } if let Err(e) = knet::host_add(knet_handle, other_hostid) { println!("Error from host_add: {}", e); return Err(e); } if let Err(e) = knet::host_set_name(knet_handle, other_hostid, name) { println!("Error from host_set_name: {}", e); return Err(e); } Ok(knet_handle) } // Called by the ACL tests to get a free port for a dynamic link fn setup_dynamic_link(handle: knet::Handle, hostid: &knet::HostId, link: u8, lowest_port: u16) -> Result<()> { let mut src_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); for p in lowest_port..=65535 { src_addr.set_port(p); if let Err(e) = knet::link_set_config(handle, hostid, link, knet::TransportId::Udp, &src_addr, None, knet::LinkFlags::NONE) { if let Some(os_err) = e.raw_os_error() { if os_err != libc::EADDRINUSE { println!("Error from link_set_config(dyn): {}", e); return Err(e); } // In use - try the next port number } } else { println!("Dynamic link - Using port {}", p); return Ok(()) } } Err(Error::new(ErrorKind::Other, "No ports available")) } // This is the bit that configures two links on two handles that talk to each other // while also making sure they don't clash with anything else on the system fn setup_links(handle1: knet::Handle, hostid1: &knet::HostId, handle2: knet::Handle, hostid2: &knet::HostId) -> Result { let mut src_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); let mut dst_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); for p in 1025..=65534 { src_addr.set_port(p); dst_addr.set_port(p+1); if let Err(e) = knet::link_set_config(handle1, hostid2, 0, knet::TransportId::Udp, &src_addr, Some(&dst_addr), knet::LinkFlags::NONE) { if let Some(os_err) = e.raw_os_error() { if os_err != libc::EADDRINUSE { println!("Error from link_set_config(1): {}", e); return Err(e); } // In use - try the next port number } else { return Err(Error::new(ErrorKind::Other, "Error returned from link_set_config(1) was not an os_error")); } } else { // Now try the other handle if let Err(e) = knet::link_set_config(handle2, hostid1, 0, knet::TransportId::Udp, &dst_addr, Some(&src_addr), knet::LinkFlags::NONE) { if let Some(os_err) = e.raw_os_error() { if os_err != libc::EADDRINUSE { println!("Error from link_set_config(2): {}", e); return Err(e); } else { // In use - clear handle1 and try next pair of ports knet::link_clear_config(handle1, hostid2, 0)?; } } else { return Err(Error::new(ErrorKind::Other, "Error returned from link_set_config(1) was not an os_error")); } } println!("Bound to ports {} & {}",p, p+1); return Ok(p+2) } } Err(Error::new(ErrorKind::Other, "No ports available")) } // Finish configuring links fn configure_link(knet_handle: knet::Handle, our_hostid: &knet::HostId, other_hostid: &knet::HostId) -> Result<()> { if let Err(e) = knet::handle_enable_sock_notify(knet_handle, our_hostid.to_u16() as u64, Some(sock_notify_fn)) { println!("Error from handle_enable_sock_notify: {}", e); return Err(e); } if let Err(e) = knet::link_enable_status_change_notify(knet_handle, our_hostid.to_u16() as u64, Some(link_notify_fn)) { println!("Error from handle_enable_link_notify: {}", e); return Err(e); } if let Err(e) = knet::host_enable_status_change_notify(knet_handle, our_hostid.to_u16() as u64, Some(host_notify_fn)) { println!("Error from handle_enable_host_notify: {}", e); return Err(e); } if let Err(e) = knet::handle_enable_filter(knet_handle, our_hostid.to_u16() as u64, Some(filter_fn)) { println!("Error from handle_enable_filter: {}", e); return Err(e); } if let Err(e) = knet::handle_enable_pmtud_notify(knet_handle, our_hostid.to_u16() as u64, Some(pmtud_fn)) { println!("Error from handle_enable_pmtud_notify: {}", e); return Err(e); } if let Err(e) = knet::handle_enable_onwire_ver_notify(knet_handle, our_hostid.to_u16() as u64, Some(onwire_fn)) { println!("Error from handle_enable_onwire_ver_notify: {}", e); return Err(e); } match knet::handle_add_datafd(knet_handle, 0, CHANNEL) { Ok((fd,chan)) => { println!("Added datafd, fd={}, channel={}", fd, chan); }, Err(e) => { println!("Error from add_datafd: {}", e); return Err(e); } } if let Err(e) = knet::handle_crypto_rx_clear_traffic(knet_handle, knet::RxClearTraffic::Allow) { println!("Error from handle_crypto_rx_clear_traffic: {}", e); return Err(e); } if let Err(e) = knet::link_set_enable(knet_handle, other_hostid, 0, true) { println!("Error from set_link_enable(true): {}", e); return Err(e); } if let Err(e) = knet::link_set_ping_timers(knet_handle, other_hostid, 0, 500, 1000, 1024) { println!("Error from set_link_ping_timers: {}", e); return Err(e); } match knet::link_get_ping_timers(knet_handle, other_hostid, 0) { Ok((a,b,c)) => { if a != 500 || b != 1000 || c != 1024 { println!("get_link_ping_timers returned wrong values {}, {},{} (s/b 500,1000,1024)", a,b,c); return Err(Error::new(ErrorKind::Other, "Error in ping timers")); } }, Err(e) => { println!("Error from set_link_ping_timers: {}", e); return Err(e); } } if let Err(e) = knet::handle_setfwd(knet_handle, true) { println!("Error from setfwd(true): {}", e); return Err(e); } // Check status let data_fd = match knet::handle_get_datafd(knet_handle, CHANNEL) { Ok(f) => { println!("got datafd {} for channel", f); f } Err(e) => { println!("Error from handle_get_datafd: {}", e); return Err(e); } }; match knet::handle_get_channel(knet_handle, data_fd) { Ok(c) => if c != CHANNEL { println!("handle_get_channel returned wrong channel ID: {}, {}",c, CHANNEL); return Err(Error::new(ErrorKind::Other, "Error in handle_get_channel")); } Err(e) => { println!("Error from handle_get_channel: {}", e); return Err(e); } } match knet::link_get_enable(knet_handle, other_hostid, 0) { Ok(b) => if !b { println!("link not enabled (according to link_get_enable()"); }, Err(e) => { println!("Error from link_get_enable: {}", e); return Err(e); } } Ok(()) } fn recv_stuff(handle: knet::Handle, host: knet::HostId) -> Result<()> { let buf = [0u8; 1024]; loop { match knet::recv(handle, &buf, CHANNEL) { Ok(len) => { let recv_len = len as usize; if recv_len == 0 { break; // EOF?? } else { let s = String::from_utf8(buf[0..recv_len].to_vec()).unwrap(); println!("recvd on {}: {} {:?} {} ", host, recv_len, &buf[0..recv_len], s); if s == *"QUIT" { println!("got QUIT on {}, exitting", host); break; } } } Err(e) => { if e.kind() == ErrorKind::WouldBlock { thread::sleep(time::Duration::from_millis(100)); } else { println!("recv failed: {}", e); return Err(e); } } } } Ok(()) } fn close_handle(handle: knet::Handle, remnode: u16) -> Result<()> { let other_hostid = knet::HostId::new(remnode); if let Err(e) = knet::handle_setfwd(handle, false) { println!("Error from setfwd 1 (false): {}", e); return Err(e); } let data_fd = match knet::handle_get_datafd(handle, CHANNEL) { Ok(f) => { println!("got datafd {} for channel", f); f } Err(e) => { println!("Error from handle_get_datafd: {}", e); return Err(e); } }; if let Err(e) = knet::handle_remove_datafd(handle, data_fd) { println!("Error from handle_remove_datafd: {}", e); return Err(e); } if let Err(e) = knet::link_set_enable(handle, &other_hostid, 0, false) { println!("Error from set_link_enable(false): {}", e); return Err(e); } if let Err(e) = knet::link_clear_config(handle, &other_hostid, 0) { println!("clear config failed: {}", e); return Err(e); } if let Err(e) = knet::host_remove(handle, &other_hostid) { println!("host remove failed: {}", e); return Err(e); } if let Err(e) = knet::handle_free(handle) { println!("handle_free failed: {}", e); return Err(e); } Ok(()) } fn set_compress(handle: knet::Handle) -> Result<()> { let compress_config = knet::CompressConfig { compress_model: "zlib".to_string(), compress_threshold : 10, compress_level: 1, }; if let Err(e) = knet::handle_compress(handle, &compress_config) { println!("Error from handle_compress: {}", e); Err(e) } else { Ok(()) } } fn set_crypto(handle: knet::Handle) -> Result<()> { let private_key = [55u8; 2048]; // Add some crypto let crypto_config = knet::CryptoConfig { crypto_model: "openssl".to_string(), crypto_cipher_type: "aes256".to_string(), crypto_hash_type: "sha256".to_string(), private_key: &private_key, }; if let Err(e) = knet::handle_crypto_set_config(handle, &crypto_config, 1) { println!("Error from handle_crypto_set_config: {}", e); return Err(e); } if let Err(e) = knet::handle_crypto_use_config(handle, 1) { println!("Error from handle_crypto_use_config: {}", e); return Err(e); } if let Err(e) = knet::handle_crypto_rx_clear_traffic(handle, knet::RxClearTraffic::Disallow) { println!("Error from handle_crypto_rx_clear_traffic: {}", e); return Err(e); } Ok(()) } fn send_messages(handle: knet::Handle, send_quit: bool) -> Result<()> { let mut buf : [u8; 20] = [b'0'; 20]; for i in 0..10 { buf[i as usize + 1] = i + b'0'; match knet::send(handle, &buf, CHANNEL) { Ok(len) => { if len as usize != buf.len() { println!("sent {} bytes instead of {}", len, buf.len()); } }, Err(e) => { println!("send failed: {}", e); return Err(e); } } } let s = String::from("SYNC TEST").into_bytes(); if let Err(e) = knet::send_sync(handle, &s, CHANNEL) { println!("send_sync failed: {}", e); return Err(e); } if send_quit { // Sleep to allow messages to calm down before we tell the RX thread to quit thread::sleep(time::Duration::from_millis(3000)); let b = String::from("QUIT").into_bytes(); match knet::send(handle, &b, CHANNEL) { Ok(len) => { if len as usize != b.len() { println!("sent {} bytes instead of {}", len, b.len()); } }, Err(e) => { println!("send failed: {}", e); return Err(e); } } } Ok(()) } fn test_link_host_list(handle: knet::Handle) -> Result<()> { match knet::host_get_host_list(handle) { Ok(hosts) => { for i in &hosts { print!("host {}: links: ", i); match knet::link_get_link_list(handle, i) { Ok(ll) => { for j in ll { print!(" {}",j); } }, Err(e) => { println!("link_get_link_list failed: {}", e); return Err(e); } } println!(); } } Err(e) => { println!("link_get_host_list failed: {}", e); return Err(e); } } Ok(()) } // Try some metadata calls fn test_metadata_calls(handle: knet::Handle, host: &knet::HostId) -> Result<()> { if let Err(e) = knet::handle_set_threads_timer_res(handle, 190000) { println!("knet_handle_set_threads_timer_res failed: {:?}", e); return Err(e); } match knet::handle_get_threads_timer_res(handle) { Ok(v) => { if v != 190000 { println!("knet_handle_get_threads_timer_res returned wrong value {}", v); } }, Err(e) => { println!("knet_handle_set_threads_timer_res failed: {:?}", e); return Err(e); } } if let Err(e) = knet::handle_pmtud_set(handle, 1000) { println!("knet_handle_pmtud_set failed: {:?}", e); return Err(e); } match knet::handle_pmtud_get(handle) { Ok(v) => { if v != 1000 { println!("knet_handle_pmtud_get returned wrong value {} (ALLOWED)", v); // Don't fail on this, it might not have been set yet } }, Err(e) => { println!("knet_handle_pmtud_get failed: {:?}", e); return Err(e); } } if let Err(e) = knet::handle_pmtud_setfreq(handle, 1000) { println!("knet_handle_pmtud_setfreq failed: {:?}", e); return Err(e); } match knet::handle_pmtud_getfreq(handle) { Ok(v) => { if v != 1000 { println!("knet_handle_pmtud_getfreq returned wrong value {}", v); } }, Err(e) => { println!("knet_handle_pmtud_getfreq failed: {:?}", e); return Err(e); } } if let Err(e) = knet::handle_set_transport_reconnect_interval(handle, 100) { println!("knet_handle_set_transport_reconnect_interval failed: {:?}", e); return Err(e); } match knet::handle_get_transport_reconnect_interval(handle) { Ok(v) => { if v != 100 { println!("knet_handle_get_transport_reconnect_interval {}", v); } }, Err(e) => { println!("knet_handle_get_transport_reconnect_interval failed: {:?}", e); return Err(e); } } if let Err(e) = knet::link_set_pong_count(handle, host, 0, 4) { println!("knet_link_set_pong_count failed: {:?}", e); return Err(e); } match knet::link_get_pong_count(handle, host, 0) { Ok(v) => { if v != 4 { println!("knet_link_get_pong_count returned wrong value {}", v); } }, Err(e) => { println!("knet_link_get_pong_count failed: {:?}", e); return Err(e); } } if let Err(e) = knet::host_set_policy(handle, host, knet::LinkPolicy::Active) { println!("knet_host_set_policy failed: {:?}", e); return Err(e); } match knet::host_get_policy(handle, host) { Ok(v) => { if v != knet::LinkPolicy::Active { println!("knet_host_get_policy returned wrong value {}", v); } }, Err(e) => { println!("knet_host_get_policy failed: {:?}", e); return Err(e); } } if let Err(e) = knet::link_set_priority(handle, host, 0, 5) { println!("knet_link_set_priority failed: {:?}", e); return Err(e); } match knet::link_get_priority(handle, host, 0) { Ok(v) => { if v != 5 { println!("knet_link_get_priority returned wrong value {}", v); } }, Err(e) => { println!("knet_link_get_priority failed: {:?}", e); return Err(e); } } let name = match knet::host_get_name_by_host_id(handle, host) { Ok(n) => { println!("Returned host name is {}", n); n }, Err(e) => { println!("knet_host_get_name_by_host_id failed: {:?}", e); return Err(e); } }; match knet::host_get_id_by_host_name(handle, &name) { Ok(n) => { println!("Returned host id is {}", n); if n != *host { println!("Returned host id is not 2"); return Err(Error::new(ErrorKind::Other, "Error in get_id_by_host_name")); } }, Err(e) => { println!("knet_host_get_id_by_host_name failed: {:?}", e); return Err(e); } } match knet::link_get_config(handle, host, 0) { Ok((t, s, d, _f)) => { println!("Got link config: {}, {:?}, {:?}", t.to_string(),s,d); }, Err(e) => { println!("knet_link_get_config failed: {:?}", e); return Err(e); } } + if let Err(e) = knet::handle_set_host_defrag_bufs(handle, 4, 32, 25, knet::DefragReclaimPolicy::Absolute) { + println!("handle_config_set_host_defrag_bufs failed: {:?}", e); + return Err(e); + } + match knet::handle_get_host_defrag_bufs(handle) { + Ok((min, max, shrink, policy)) => { + if min != 4 || max != 32 || + shrink != 25 || policy != knet::DefragReclaimPolicy::Absolute { + println!("handle_config_get_host_defrag_bufs returned bad values"); + println!("Got {},{},{},{}. expected 4,32,2,Absolute", min, max, shrink, policy); + } else { + println!("Defrag params correct: {},{},{},{}", min, max, shrink, policy); + } + } + Err(e) => { + println!("handle_config_get_host_defrag_bufs failed: {:?}", e); + return Err(e); + } + } + + + // Can't set this to anything different if let Err(e) = knet::handle_set_onwire_ver(handle, 1) { println!("knet_link_set_onwire_ver failed: {:?}", e); return Err(e); } match knet::handle_get_onwire_ver(handle, host) { Ok((min, max, ver)) => { println!("get_onwire_ver: Got onwire ver: {}/{}/{}", min, max, ver); }, Err(e) => { println!("knet_link_get_onwire_ver failed: {:?}", e); return Err(e); } } // Logging match knet::log_get_subsystem_name(3) { Ok(n) => println!("subsystem name for 3 is {}", n), Err(e) => { println!("knet_log_get_subsystem_name failed: {:?}", e); return Err(e); } } match knet::log_get_subsystem_id("TX") { Ok(n) => println!("subsystem ID for TX is {}", n), Err(e) => { println!("knet_log_get_subsystem_id failed: {:?}", e); return Err(e); } } match knet::log_get_loglevel_id("DEBUG") { Ok(n) => println!("loglevel ID for DEBUG is {}", n), Err(e) => { println!("knet_log_get_loglevel_id failed: {:?}", e); return Err(e); } } match knet::log_get_loglevel_name(1) { Ok(n) => println!("loglevel name for 1 is {}", n), Err(e) => { println!("knet_log_get_loglevel_name failed: {:?}", e); return Err(e); } } if let Err(e) = knet::log_set_loglevel(handle, knet::SubSystem::Handle , knet::LogLevel::Debug) { println!("knet_log_set_loglevel failed: {:?}", e); return Err(e); } match knet::log_get_loglevel(handle, knet::SubSystem::Handle) { Ok(n) => println!("loglevel for Handle is {}", n), Err(e) => { println!("knet_log_get_loglevel failed: {:?}", e); return Err(e); } } Ok(()) } fn test_acl(handle: knet::Handle, host: &knet::HostId, low_port: u16) -> Result<()> { if let Err(e) = knet::handle_enable_access_lists(handle, true) { println!("Error from handle_enable_access_lists: {:?}", e); return Err(e); } // Dynamic link for testing ACL APIs (it never gets used) if let Err(e) = setup_dynamic_link(handle, host, 1, low_port) { println!("Error from link_set_config (dynamic): {}", e); return Err(e); } // These ACLs are nonsense on stilts if let Err(e) = knet::link_add_acl(handle, host, 1, &SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8003_u16), &SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8003_u16), knet::AclCheckType::Address, knet::AclAcceptReject::Reject) { println!("Error from link_add_acl: {:?}", e); return Err(e); } if let Err(e) = knet::link_insert_acl(handle, host, 1, 0, &SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 8004_u16), &SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 8004_u16), knet::AclCheckType::Address, knet::AclAcceptReject::Reject) { println!("Error from link_add_acl: {:?}", e); return Err(e); } if let Err(e) = knet::link_rm_acl(handle, host, 1, &SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8003_u16), &SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8003_u16), knet::AclCheckType::Address, knet::AclAcceptReject::Reject) { println!("Error from link_rm_acl: {:?}", e); return Err(e); } if let Err(e) = knet::link_clear_acl(handle, host, 1) { println!("Error from link_clear_acl: {:?}", e); return Err(e); } // Get rid of this link before it messes things up if let Err(e) = knet::link_clear_config(handle, host, 1) { println!("clear config (dynamic) failed: {}", e); return Err(e); } if let Err(e) = knet::handle_enable_access_lists(handle, false) { println!("Error from handle_enable_access_lists: {:?}", e); return Err(e); } Ok(()) } fn main() -> Result<()> { // Start with some non-handle information match knet::get_crypto_list() { Ok(l) => { print!("Crypto models:"); for i in &l { print!(" {}", i.name); } println!(); } Err(e) => { println!("link_get_crypto_list failed: {:?}", e); return Err(e); } } match knet::get_compress_list() { Ok(l) => { print!("Compress models:"); for i in &l { print!(" {}", i.name); } println!(); } Err(e) => { println!("link_get_compress_list failed: {:?}", e); return Err(e); } } match knet::get_transport_list() { Ok(l) => { print!("Transports:"); for i in &l { print!(" {}", i.name); } println!(); } Err(e) => { println!("link_get_transport_list failed: {:?}", e); return Err(e); } } let host1 = knet::HostId::new(1); let host2 = knet::HostId::new(2); // Now test traffic let handle1 = setup_node(&host1, &host2, "host2")?; let handle2 = setup_node(&host2, &host1, "host1")?; let low_port = setup_links(handle1, &host1, handle2, &host2)?; configure_link(handle1, &host1, &host2)?; configure_link(handle2, &host2, &host1)?; // Copy stuff for the threads let handle1_clone = handle1; let handle2_clone = handle2; let host1_clone = host1; let host2_clone = host2; // Wait for links to start thread::sleep(time::Duration::from_millis(10000)); test_link_host_list(handle1)?; test_link_host_list(handle2)?; // Start recv threads for each handle let thread_handles = vec![ spawn(move || recv_stuff(handle1_clone, host1_clone)), spawn(move || recv_stuff(handle2_clone, host2_clone)) ]; send_messages(handle1, false)?; send_messages(handle2, false)?; thread::sleep(time::Duration::from_millis(3000)); set_crypto(handle1)?; set_crypto(handle2)?; set_compress(handle1)?; set_compress(handle2)?; thread::sleep(time::Duration::from_millis(3000)); send_messages(handle1, true)?; send_messages(handle2, true)?; test_acl(handle1, &host2, low_port)?; // Wait for recv threads to finish for handle in thread_handles { if let Err(error) = handle.join() { println!("thread join error: {:?}", error); } } // Try some statses match knet::handle_get_stats(handle1) { Ok(s) => println!("handle stats: {}", s), Err(e) => { println!("handle_get_stats failed: {:?}", e); return Err(e); } } match knet::host_get_status(handle1, &host2) { Ok(s) => println!("host status: {}", s), Err(e) => { println!("host_get_status failed: {:?}", e); return Err(e); } } match knet::link_get_status(handle1, &host2, 0) { Ok(s) => println!("link status: {}", s), Err(e) => { println!("link_get_status failed: {:?}", e); return Err(e); } } if let Err(e) = knet::handle_clear_stats(handle1, knet::ClearStats::Handle) { println!("handle_clear_stats failed: {:?}", e); return Err(e); } test_metadata_calls(handle1, &knet::HostId::new(2))?; close_handle(handle1, 2)?; close_handle(handle2, 1)?; // Sleep to see if log thread dies thread::sleep(time::Duration::from_millis(3000)); Ok(()) } diff --git a/libknet/handle.c b/libknet/handle.c index 0ab8a532..542b4bff 100644 --- a/libknet/handle.c +++ b/libknet/handle.c @@ -1,784 +1,795 @@ /* * Copyright (C) 2010-2021 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * Federico Simoncelli * * This software licensed under LGPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include #include "internals.h" #include "crypto.h" #include "links.h" #include "compress.h" #include "compat.h" #include "common.h" #include "threads_common.h" #include "threads_heartbeat.h" #include "threads_pmtud.h" #include "threads_dsthandler.h" #include "threads_rx.h" #include "threads_tx.h" #include "transports.h" #include "transport_common.h" #include "logging.h" static int _init_locks(knet_handle_t knet_h) { int savederrno = 0; savederrno = pthread_rwlock_init(&knet_h->global_rwlock, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize list rwlock: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->handle_stats_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize handle stats mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->threads_status_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize threads status mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->pmtud_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize pmtud mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->kmtu_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize kernel_mtu mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_cond_init(&knet_h->pmtud_cond, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize pmtud conditional mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->hb_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize hb_thread mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->tx_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize tx_thread mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->backoff_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize pong timeout backoff mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->tx_seq_num_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize tx_seq_num_mutex mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->onwire_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize onwire_mutex mutex: %s", strerror(savederrno)); goto exit_fail; } return 0; exit_fail: errno = savederrno; return -1; } static void _destroy_locks(knet_handle_t knet_h) { pthread_rwlock_destroy(&knet_h->global_rwlock); pthread_mutex_destroy(&knet_h->pmtud_mutex); pthread_mutex_destroy(&knet_h->kmtu_mutex); pthread_cond_destroy(&knet_h->pmtud_cond); pthread_mutex_destroy(&knet_h->hb_mutex); pthread_mutex_destroy(&knet_h->tx_mutex); pthread_mutex_destroy(&knet_h->backoff_mutex); pthread_mutex_destroy(&knet_h->tx_seq_num_mutex); pthread_mutex_destroy(&knet_h->threads_status_mutex); pthread_mutex_destroy(&knet_h->handle_stats_mutex); pthread_mutex_destroy(&knet_h->onwire_mutex); } static int _init_socks(knet_handle_t knet_h) { int savederrno = 0; if (_init_socketpair(knet_h, knet_h->dstsockfd)) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize internal dstsockpair: %s", strerror(savederrno)); goto exit_fail; } return 0; exit_fail: errno = savederrno; return -1; } static void _close_socks(knet_handle_t knet_h) { _close_socketpair(knet_h, knet_h->dstsockfd); } static int _init_buffers(knet_handle_t knet_h) { int savederrno = 0; int i; size_t bufsize; for (i = 0; i < PCKT_FRAG_MAX; i++) { bufsize = ceil((float)KNET_MAX_PACKET_SIZE / (i + 1)) + KNET_HEADER_ALL_SIZE; knet_h->send_to_links_buf[i] = malloc(bufsize); if (!knet_h->send_to_links_buf[i]) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory datafd to link buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->send_to_links_buf[i], 0, bufsize); } for (i = 0; i < PCKT_RX_BUFS; i++) { knet_h->recv_from_links_buf[i] = malloc(KNET_DATABUFSIZE); if (!knet_h->recv_from_links_buf[i]) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for link to datafd buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->recv_from_links_buf[i], 0, KNET_DATABUFSIZE); } knet_h->recv_from_sock_buf = malloc(KNET_DATABUFSIZE); if (!knet_h->recv_from_sock_buf) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for app to datafd buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->recv_from_sock_buf, 0, KNET_DATABUFSIZE); knet_h->pingbuf = malloc(KNET_HEADER_ALL_SIZE); if (!knet_h->pingbuf) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for hearbeat buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->pingbuf, 0, KNET_HEADER_ALL_SIZE); knet_h->pmtudbuf = malloc(KNET_PMTUD_SIZE_V6 + KNET_HEADER_ALL_SIZE); if (!knet_h->pmtudbuf) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for pmtud buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->pmtudbuf, 0, KNET_PMTUD_SIZE_V6 + KNET_HEADER_ALL_SIZE); for (i = 0; i < PCKT_FRAG_MAX; i++) { bufsize = ceil((float)KNET_MAX_PACKET_SIZE / (i + 1)) + KNET_HEADER_ALL_SIZE + KNET_DATABUFSIZE_CRYPT_PAD; knet_h->send_to_links_buf_crypt[i] = malloc(bufsize); if (!knet_h->send_to_links_buf_crypt[i]) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for crypto datafd to link buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->send_to_links_buf_crypt[i], 0, bufsize); } knet_h->recv_from_links_buf_decrypt = malloc(KNET_DATABUFSIZE_CRYPT); if (!knet_h->recv_from_links_buf_decrypt) { savederrno = errno; log_err(knet_h, KNET_SUB_CRYPTO, "Unable to allocate memory for crypto link to datafd buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->recv_from_links_buf_decrypt, 0, KNET_DATABUFSIZE_CRYPT); knet_h->recv_from_links_buf_crypt = malloc(KNET_DATABUFSIZE_CRYPT); if (!knet_h->recv_from_links_buf_crypt) { savederrno = errno; log_err(knet_h, KNET_SUB_CRYPTO, "Unable to allocate memory for crypto link to datafd buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->recv_from_links_buf_crypt, 0, KNET_DATABUFSIZE_CRYPT); knet_h->pingbuf_crypt = malloc(KNET_DATABUFSIZE_CRYPT); if (!knet_h->pingbuf_crypt) { savederrno = errno; log_err(knet_h, KNET_SUB_CRYPTO, "Unable to allocate memory for crypto hearbeat buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->pingbuf_crypt, 0, KNET_DATABUFSIZE_CRYPT); knet_h->pmtudbuf_crypt = malloc(KNET_DATABUFSIZE_CRYPT); if (!knet_h->pmtudbuf_crypt) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for crypto pmtud buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->pmtudbuf_crypt, 0, KNET_DATABUFSIZE_CRYPT); knet_h->recv_from_links_buf_decompress = malloc(KNET_DATABUFSIZE_COMPRESS); if (!knet_h->recv_from_links_buf_decompress) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for decompress buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->recv_from_links_buf_decompress, 0, KNET_DATABUFSIZE_COMPRESS); knet_h->send_to_links_buf_compress = malloc(KNET_DATABUFSIZE_COMPRESS); if (!knet_h->send_to_links_buf_compress) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for compress buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->send_to_links_buf_compress, 0, KNET_DATABUFSIZE_COMPRESS); memset(knet_h->knet_transport_fd_tracker, 0, sizeof(knet_h->knet_transport_fd_tracker)); for (i = 0; i < KNET_MAX_FDS; i++) { knet_h->knet_transport_fd_tracker[i].transport = KNET_MAX_TRANSPORTS; } return 0; exit_fail: errno = savederrno; return -1; } static void _destroy_buffers(knet_handle_t knet_h) { int i; for (i = 0; i < PCKT_FRAG_MAX; i++) { free(knet_h->send_to_links_buf[i]); free(knet_h->send_to_links_buf_crypt[i]); } for (i = 0; i < PCKT_RX_BUFS; i++) { free(knet_h->recv_from_links_buf[i]); } free(knet_h->recv_from_links_buf_decompress); free(knet_h->send_to_links_buf_compress); free(knet_h->recv_from_sock_buf); free(knet_h->recv_from_links_buf_decrypt); free(knet_h->recv_from_links_buf_crypt); free(knet_h->pingbuf); free(knet_h->pingbuf_crypt); free(knet_h->pmtudbuf); free(knet_h->pmtudbuf_crypt); } static int _init_epolls(knet_handle_t knet_h) { struct epoll_event ev; int savederrno = 0; /* * even if the kernel does dynamic allocation with epoll_ctl * we need to reserve one extra for host to host communication */ knet_h->send_to_links_epollfd = epoll_create(KNET_EPOLL_MAX_EVENTS + 1); if (knet_h->send_to_links_epollfd < 0) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to create epoll datafd to link fd: %s", strerror(savederrno)); goto exit_fail; } knet_h->recv_from_links_epollfd = epoll_create(KNET_EPOLL_MAX_EVENTS); if (knet_h->recv_from_links_epollfd < 0) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to create epoll link to datafd fd: %s", strerror(savederrno)); goto exit_fail; } knet_h->dst_link_handler_epollfd = epoll_create(KNET_EPOLL_MAX_EVENTS); if (knet_h->dst_link_handler_epollfd < 0) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to create epoll dst cache fd: %s", strerror(savederrno)); goto exit_fail; } if (_fdset_cloexec(knet_h->send_to_links_epollfd)) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to set CLOEXEC on datafd to link epoll fd: %s", strerror(savederrno)); goto exit_fail; } if (_fdset_cloexec(knet_h->recv_from_links_epollfd)) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to set CLOEXEC on link to datafd epoll fd: %s", strerror(savederrno)); goto exit_fail; } if (_fdset_cloexec(knet_h->dst_link_handler_epollfd)) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to set CLOEXEC on dst cache epoll fd: %s", strerror(savederrno)); goto exit_fail; } memset(&ev, 0, sizeof(struct epoll_event)); ev.events = EPOLLIN; ev.data.fd = knet_h->dstsockfd[0]; if (epoll_ctl(knet_h->dst_link_handler_epollfd, EPOLL_CTL_ADD, knet_h->dstsockfd[0], &ev)) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to add dstsockfd[0] to epoll pool: %s", strerror(savederrno)); goto exit_fail; } return 0; exit_fail: errno = savederrno; return -1; } static void _close_epolls(knet_handle_t knet_h) { struct epoll_event ev; int i; memset(&ev, 0, sizeof(struct epoll_event)); for (i = 0; i < KNET_DATAFD_MAX; i++) { if (knet_h->sockfd[i].in_use) { epoll_ctl(knet_h->send_to_links_epollfd, EPOLL_CTL_DEL, knet_h->sockfd[i].sockfd[knet_h->sockfd[i].is_created], &ev); if (knet_h->sockfd[i].sockfd[knet_h->sockfd[i].is_created]) { _close_socketpair(knet_h, knet_h->sockfd[i].sockfd); } } } epoll_ctl(knet_h->dst_link_handler_epollfd, EPOLL_CTL_DEL, knet_h->dstsockfd[0], &ev); close(knet_h->send_to_links_epollfd); close(knet_h->recv_from_links_epollfd); close(knet_h->dst_link_handler_epollfd); } static int _start_threads(knet_handle_t knet_h) { int savederrno = 0; pthread_attr_t attr; set_thread_status(knet_h, KNET_THREAD_PMTUD, KNET_THREAD_REGISTERED); savederrno = pthread_attr_init(&attr); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to init pthread attributes: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_attr_setstacksize(&attr, KNET_THREAD_STACK_SIZE); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to set stack size attribute: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_create(&knet_h->pmtud_link_handler_thread, &attr, _handle_pmtud_link_thread, (void *) knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to start pmtud link thread: %s", strerror(savederrno)); goto exit_fail; } set_thread_status(knet_h, KNET_THREAD_DST_LINK, KNET_THREAD_REGISTERED); savederrno = pthread_create(&knet_h->dst_link_handler_thread, &attr, _handle_dst_link_handler_thread, (void *) knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to start dst cache thread: %s", strerror(savederrno)); goto exit_fail; } set_thread_status(knet_h, KNET_THREAD_TX, KNET_THREAD_REGISTERED); savederrno = pthread_create(&knet_h->send_to_links_thread, &attr, _handle_send_to_links_thread, (void *) knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to start datafd to link thread: %s", strerror(savederrno)); goto exit_fail; } set_thread_status(knet_h, KNET_THREAD_RX, KNET_THREAD_REGISTERED); savederrno = pthread_create(&knet_h->recv_from_links_thread, &attr, _handle_recv_from_links_thread, (void *) knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to start link to datafd thread: %s", strerror(savederrno)); goto exit_fail; } set_thread_status(knet_h, KNET_THREAD_HB, KNET_THREAD_REGISTERED); savederrno = pthread_create(&knet_h->heartbt_thread, &attr, _handle_heartbt_thread, (void *) knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to start heartbeat thread: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_attr_destroy(&attr); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to destroy pthread attributes: %s", strerror(savederrno)); /* * Do not return error code. Error is not critical. */ } return 0; exit_fail: errno = savederrno; return -1; } static void _stop_threads(knet_handle_t knet_h) { void *retval; wait_all_threads_status(knet_h, KNET_THREAD_STOPPED); if (knet_h->heartbt_thread) { pthread_cancel(knet_h->heartbt_thread); pthread_join(knet_h->heartbt_thread, &retval); } if (knet_h->send_to_links_thread) { pthread_cancel(knet_h->send_to_links_thread); pthread_join(knet_h->send_to_links_thread, &retval); } if (knet_h->recv_from_links_thread) { pthread_cancel(knet_h->recv_from_links_thread); pthread_join(knet_h->recv_from_links_thread, &retval); } if (knet_h->dst_link_handler_thread) { pthread_cancel(knet_h->dst_link_handler_thread); pthread_join(knet_h->dst_link_handler_thread, &retval); } if (knet_h->pmtud_link_handler_thread) { pthread_cancel(knet_h->pmtud_link_handler_thread); pthread_join(knet_h->pmtud_link_handler_thread, &retval); } } knet_handle_t knet_handle_new(knet_node_id_t host_id, int log_fd, uint8_t default_log_level, uint64_t flags) { knet_handle_t knet_h; int savederrno = 0; struct rlimit cur; if (getrlimit(RLIMIT_NOFILE, &cur) < 0) { return NULL; } if ((log_fd < 0) || ((unsigned int)log_fd >= cur.rlim_max)) { errno = EINVAL; return NULL; } /* * validate incoming request */ if ((log_fd) && (default_log_level > KNET_LOG_DEBUG)) { errno = EINVAL; return NULL; } if (flags > KNET_HANDLE_FLAG_PRIVILEGED * 2 - 1) { errno = EINVAL; return NULL; } /* * allocate handle */ knet_h = malloc(sizeof(struct knet_handle)); if (!knet_h) { errno = ENOMEM; return NULL; } memset(knet_h, 0, sizeof(struct knet_handle)); /* * setting up some handle data so that we can use logging * also when initializing the library global locks * and trackers */ knet_h->flags = flags; /* * copy config in place */ knet_h->host_id = host_id; knet_h->logfd = log_fd; if (knet_h->logfd > 0) { memset(&knet_h->log_levels, default_log_level, KNET_MAX_SUBSYSTEMS); } /* * set internal threads time resolutions */ knet_h->threads_timer_res = KNET_THREADS_TIMER_RES; /* * set pmtud default timers */ knet_h->pmtud_interval = KNET_PMTUD_DEFAULT_INTERVAL; /* * set transports reconnect default timers */ knet_h->reconnect_int = KNET_TRANSPORT_DEFAULT_RECONNECT_INTERVAL; /* * Set the default path for plugins */ knet_h->plugin_path = PLUGINPATH; /* * Set 'min' stats to the maximum value so the * first value we get is always less */ knet_h->stats.tx_compress_time_min = UINT64_MAX; knet_h->stats.rx_compress_time_min = UINT64_MAX; knet_h->stats.tx_crypt_time_min = UINT64_MAX; knet_h->stats.rx_crypt_time_min = UINT64_MAX; /* * set onwire version. See also comments in internals.h * on why we don´t use constants directly across the code. */ knet_h->onwire_ver = KNET_HEADER_ONWIRE_MIN_VER; knet_h->onwire_min_ver = KNET_HEADER_ONWIRE_MIN_VER; knet_h->onwire_max_ver = KNET_HEADER_ONWIRE_MAX_VER; knet_h->onwire_ver_remap = 0; log_info(knet_h, KNET_SUB_HANDLE, "Default onwire version: %u", knet_h->onwire_ver); + /* + * set default buffers + */ + + knet_h->defrag_bufs_min = KNET_MIN_DEFRAG_BUFS_DEFAULT; + knet_h->defrag_bufs_max = KNET_MAX_DEFRAG_BUFS_DEFAULT; + knet_h->defrag_bufs_shrink_threshold = KNET_SHRINK_THRESHOLD_DEFAULT; + knet_h->defrag_bufs_usage_samples = KNET_USAGE_SAMPLES_DEFAULT; + knet_h->defrag_bufs_usage_samples_timespan = KNET_USAGE_SAMPLES_TIMESPAN_DEFAULT; + knet_h->defrag_bufs_reclaim_policy = RECLAIM_POLICY_ABSOLUTE; + /* * init global shared bits */ savederrno = pthread_mutex_lock(&handle_config_mutex); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get handle mutex lock: %s", strerror(savederrno)); free(knet_h); knet_h = NULL; errno = savederrno; return NULL; } if (!handle_list_init) { qb_list_init(&handle_list.head); handle_list_init = 1; } qb_list_add(&knet_h->list, &handle_list.head); /* * init global shlib tracker */ if (_init_shlib_tracker(knet_h) < 0) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to init handle tracker: %s", strerror(savederrno)); errno = savederrno; pthread_mutex_unlock(&handle_config_mutex); goto exit_fail; } pthread_mutex_unlock(&handle_config_mutex); /* * init main locking structures */ if (_init_locks(knet_h)) { savederrno = errno; goto exit_fail; } /* * init sockets */ if (_init_socks(knet_h)) { savederrno = errno; goto exit_fail; } /* * allocate packet buffers */ if (_init_buffers(knet_h)) { savederrno = errno; goto exit_fail; } if (compress_init(knet_h)) { savederrno = errno; goto exit_fail; } /* * create epoll fds */ if (_init_epolls(knet_h)) { savederrno = errno; goto exit_fail; } /* * start transports */ if (start_all_transports(knet_h)) { savederrno = errno; goto exit_fail; } /* * start internal threads */ if (_start_threads(knet_h)) { savederrno = errno; goto exit_fail; } wait_all_threads_status(knet_h, KNET_THREAD_STARTED); errno = 0; return knet_h; exit_fail: knet_handle_free(knet_h); errno = savederrno; return NULL; } int knet_handle_free(knet_handle_t knet_h) { int savederrno = 0; if (!_is_valid_handle(knet_h)) { return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (knet_h->host_head != NULL) { savederrno = EBUSY; log_err(knet_h, KNET_SUB_HANDLE, "Unable to free handle: host(s) or listener(s) are still active: %s", strerror(savederrno)); pthread_rwlock_unlock(&knet_h->global_rwlock); errno = savederrno; return -1; } knet_h->fini_in_progress = 1; pthread_rwlock_unlock(&knet_h->global_rwlock); _stop_threads(knet_h); stop_all_transports(knet_h); _close_epolls(knet_h); _destroy_buffers(knet_h); _close_socks(knet_h); crypto_fini(knet_h, KNET_MAX_CRYPTO_INSTANCES + 1); /* values above MAX_CRYPTO will release all crypto resources */ compress_fini(knet_h, 1); _destroy_locks(knet_h); (void)pthread_mutex_lock(&handle_config_mutex); qb_list_del(&knet_h->list); _fini_shlib_tracker(); pthread_mutex_unlock(&handle_config_mutex); free(knet_h); knet_h = NULL; errno = 0; return 0; } diff --git a/libknet/handle_api.c b/libknet/handle_api.c index 1cf0feff..e45a3c9c 100644 --- a/libknet/handle_api.c +++ b/libknet/handle_api.c @@ -1,592 +1,718 @@ /* * Copyright (C) 2020-2021 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * Federico Simoncelli * * This software licensed under LGPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include "internals.h" #include "crypto.h" +#include "host.h" #include "links.h" #include "common.h" #include "transport_common.h" #include "logging.h" int knet_handle_enable_sock_notify(knet_handle_t knet_h, void *sock_notify_fn_private_data, void (*sock_notify_fn) ( void *private_data, int datafd, int8_t channel, uint8_t tx_rx, int error, int errorno)) { int savederrno = 0; if (!_is_valid_handle(knet_h)) { return -1; } if (!sock_notify_fn) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } knet_h->sock_notify_fn_private_data = sock_notify_fn_private_data; knet_h->sock_notify_fn = sock_notify_fn; log_debug(knet_h, KNET_SUB_HANDLE, "sock_notify_fn enabled"); pthread_rwlock_unlock(&knet_h->global_rwlock); return 0; } int knet_handle_add_datafd(knet_handle_t knet_h, int *datafd, int8_t *channel) { int err = 0, savederrno = 0; int i; struct epoll_event ev; if (!_is_valid_handle(knet_h)) { return -1; } if (datafd == NULL) { errno = EINVAL; return -1; } if (channel == NULL) { errno = EINVAL; return -1; } if (*channel >= KNET_DATAFD_MAX) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (!knet_h->sock_notify_fn) { log_err(knet_h, KNET_SUB_HANDLE, "Adding datafd requires sock notify callback enabled!"); savederrno = EINVAL; err = -1; goto out_unlock; } if (*datafd > 0) { for (i = 0; i < KNET_DATAFD_MAX; i++) { if ((knet_h->sockfd[i].in_use) && (knet_h->sockfd[i].sockfd[0] == *datafd)) { log_err(knet_h, KNET_SUB_HANDLE, "requested datafd: %d already exist in index: %d", *datafd, i); savederrno = EEXIST; err = -1; goto out_unlock; } } } /* * auto allocate a channel */ if (*channel < 0) { for (i = 0; i < KNET_DATAFD_MAX; i++) { if (!knet_h->sockfd[i].in_use) { *channel = i; break; } } if (*channel < 0) { savederrno = EBUSY; err = -1; goto out_unlock; } } else { if (knet_h->sockfd[*channel].in_use) { savederrno = EBUSY; err = -1; goto out_unlock; } } knet_h->sockfd[*channel].is_created = 0; knet_h->sockfd[*channel].is_socket = 0; knet_h->sockfd[*channel].has_error = 0; if (*datafd > 0) { int sockopt; socklen_t sockoptlen = sizeof(sockopt); if (_fdset_cloexec(*datafd)) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_HANDLE, "Unable to set CLOEXEC on datafd: %s", strerror(savederrno)); goto out_unlock; } if (_fdset_nonblock(*datafd)) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_HANDLE, "Unable to set NONBLOCK on datafd: %s", strerror(savederrno)); goto out_unlock; } knet_h->sockfd[*channel].sockfd[0] = *datafd; knet_h->sockfd[*channel].sockfd[1] = 0; if (!getsockopt(knet_h->sockfd[*channel].sockfd[0], SOL_SOCKET, SO_TYPE, &sockopt, &sockoptlen)) { knet_h->sockfd[*channel].is_socket = 1; } } else { if (_init_socketpair(knet_h, knet_h->sockfd[*channel].sockfd)) { savederrno = errno; err = -1; goto out_unlock; } knet_h->sockfd[*channel].is_created = 1; knet_h->sockfd[*channel].is_socket = 1; *datafd = knet_h->sockfd[*channel].sockfd[0]; } memset(&ev, 0, sizeof(struct epoll_event)); ev.events = EPOLLIN; ev.data.fd = knet_h->sockfd[*channel].sockfd[knet_h->sockfd[*channel].is_created]; if (epoll_ctl(knet_h->send_to_links_epollfd, EPOLL_CTL_ADD, knet_h->sockfd[*channel].sockfd[knet_h->sockfd[*channel].is_created], &ev)) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_HANDLE, "Unable to add datafd %d to linkfd epoll pool: %s", knet_h->sockfd[*channel].sockfd[knet_h->sockfd[*channel].is_created], strerror(savederrno)); if (knet_h->sockfd[*channel].is_created) { _close_socketpair(knet_h, knet_h->sockfd[*channel].sockfd); } goto out_unlock; } knet_h->sockfd[*channel].in_use = 1; out_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_handle_remove_datafd(knet_handle_t knet_h, int datafd) { int err = 0, savederrno = 0; int8_t channel = -1; int i; struct epoll_event ev; if (!_is_valid_handle(knet_h)) { return -1; } if (datafd <= 0) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } for (i = 0; i < KNET_DATAFD_MAX; i++) { if ((knet_h->sockfd[i].in_use) && (knet_h->sockfd[i].sockfd[0] == datafd)) { channel = i; break; } } if (channel < 0) { savederrno = EINVAL; err = -1; goto out_unlock; } if (!knet_h->sockfd[channel].has_error) { memset(&ev, 0, sizeof(struct epoll_event)); if (epoll_ctl(knet_h->send_to_links_epollfd, EPOLL_CTL_DEL, knet_h->sockfd[channel].sockfd[knet_h->sockfd[channel].is_created], &ev)) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_HANDLE, "Unable to del datafd %d from linkfd epoll pool: %s", knet_h->sockfd[channel].sockfd[0], strerror(savederrno)); goto out_unlock; } } if (knet_h->sockfd[channel].is_created) { _close_socketpair(knet_h, knet_h->sockfd[channel].sockfd); } memset(&knet_h->sockfd[channel], 0, sizeof(struct knet_sock)); out_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_handle_get_datafd(knet_handle_t knet_h, const int8_t channel, int *datafd) { int err = 0, savederrno = 0; if (!_is_valid_handle(knet_h)) { return -1; } if ((channel < 0) || (channel >= KNET_DATAFD_MAX)) { errno = EINVAL; return -1; } if (datafd == NULL) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (!knet_h->sockfd[channel].in_use) { savederrno = EINVAL; err = -1; goto out_unlock; } *datafd = knet_h->sockfd[channel].sockfd[0]; out_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_handle_get_channel(knet_handle_t knet_h, const int datafd, int8_t *channel) { int err = 0, savederrno = 0; int i; if (!_is_valid_handle(knet_h)) { return -1; } if (datafd <= 0) { errno = EINVAL; return -1; } if (channel == NULL) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } *channel = -1; for (i = 0; i < KNET_DATAFD_MAX; i++) { if ((knet_h->sockfd[i].in_use) && (knet_h->sockfd[i].sockfd[0] == datafd)) { *channel = i; break; } } if (*channel < 0) { savederrno = EINVAL; err = -1; goto out_unlock; } out_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_handle_enable_filter(knet_handle_t knet_h, void *dst_host_filter_fn_private_data, int (*dst_host_filter_fn) ( void *private_data, const unsigned char *outdata, ssize_t outdata_len, uint8_t tx_rx, knet_node_id_t this_host_id, knet_node_id_t src_node_id, int8_t *channel, knet_node_id_t *dst_host_ids, size_t *dst_host_ids_entries)) { int savederrno = 0; if (!_is_valid_handle(knet_h)) { return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } knet_h->dst_host_filter_fn_private_data = dst_host_filter_fn_private_data; knet_h->dst_host_filter_fn = dst_host_filter_fn; if (knet_h->dst_host_filter_fn) { log_debug(knet_h, KNET_SUB_HANDLE, "dst_host_filter_fn enabled"); } else { log_debug(knet_h, KNET_SUB_HANDLE, "dst_host_filter_fn disabled"); } pthread_rwlock_unlock(&knet_h->global_rwlock); errno = 0; return 0; } int knet_handle_setfwd(knet_handle_t knet_h, unsigned int enabled) { int savederrno = 0; if (!_is_valid_handle(knet_h)) { return -1; } if (enabled > 1) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (enabled) { knet_h->enabled = enabled; log_debug(knet_h, KNET_SUB_HANDLE, "Data forwarding is enabled"); } else { /* * notify TX and RX threads to flush the queues */ if (set_thread_flush_queue(knet_h, KNET_THREAD_TX, KNET_THREAD_QUEUE_FLUSH) < 0) { log_debug(knet_h, KNET_SUB_HANDLE, "Unable to request queue flushing for TX thread"); } if (set_thread_flush_queue(knet_h, KNET_THREAD_RX, KNET_THREAD_QUEUE_FLUSH) < 0) { log_debug(knet_h, KNET_SUB_HANDLE, "Unable to request queue flushing for RX thread"); } } pthread_rwlock_unlock(&knet_h->global_rwlock); /* * when disabling data forward, we need to give time to TX and RX * to flush the queues. * * the TX thread is the main leader here. When there is no more * data in the TX queue, we will also close traffic for RX. */ if (!enabled) { /* * this usleep might be unnecessary, but wait_all_threads_flush_queue * adds extra locking delay. * * allow all threads to run free without extra locking interference * and then we switch to a more active wait in case the scheduler * has decided to delay one thread or another */ usleep(knet_h->threads_timer_res * 2); wait_all_threads_flush_queue(knet_h); /* * all threads have done flushing the queue, we can stop data forwarding */ savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } knet_h->enabled = enabled; log_debug(knet_h, KNET_SUB_HANDLE, "Data forwarding is disabled"); pthread_rwlock_unlock(&knet_h->global_rwlock); } errno = 0; return 0; } int knet_handle_get_stats(knet_handle_t knet_h, struct knet_handle_stats *stats, size_t struct_size) { int err = 0, savederrno = 0; if (!_is_valid_handle(knet_h)) { return -1; } if (!stats) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } savederrno = pthread_mutex_lock(&knet_h->handle_stats_mutex); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get mutex lock: %s", strerror(savederrno)); err = -1; goto out_unlock; } if (struct_size > sizeof(struct knet_handle_stats)) { struct_size = sizeof(struct knet_handle_stats); } memmove(stats, &knet_h->stats, struct_size); /* * TX crypt stats only count the data packets sent, so add in the ping/pong/pmtud figures * RX is OK as it counts them before they are sorted. */ stats->tx_crypt_packets += knet_h->stats_extra.tx_crypt_ping_packets + knet_h->stats_extra.tx_crypt_pong_packets + knet_h->stats_extra.tx_crypt_pmtu_packets + knet_h->stats_extra.tx_crypt_pmtu_reply_packets; /* Tell the caller our full size in case they have an old version */ stats->size = sizeof(struct knet_handle_stats); out_unlock: pthread_mutex_unlock(&knet_h->handle_stats_mutex); pthread_rwlock_unlock(&knet_h->global_rwlock); return err; } int knet_handle_clear_stats(knet_handle_t knet_h, int clear_option) { int savederrno = 0; if (!_is_valid_handle(knet_h)) { return -1; } if (clear_option != KNET_CLEARSTATS_HANDLE_ONLY && clear_option != KNET_CLEARSTATS_HANDLE_AND_LINK) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } memset(&knet_h->stats, 0, sizeof(struct knet_handle_stats)); memset(&knet_h->stats_extra, 0, sizeof(struct knet_handle_stats_extra)); if (clear_option == KNET_CLEARSTATS_HANDLE_AND_LINK) { _link_clear_stats(knet_h); } pthread_rwlock_unlock(&knet_h->global_rwlock); return 0; } int knet_handle_enable_access_lists(knet_handle_t knet_h, unsigned int enabled) { int savederrno = 0; if (!_is_valid_handle(knet_h)) { return -1; } if (enabled > 1) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } knet_h->use_access_lists = enabled; if (enabled) { log_debug(knet_h, KNET_SUB_HANDLE, "Links access lists are enabled"); } else { log_debug(knet_h, KNET_SUB_HANDLE, "Links access lists are disabled"); } pthread_rwlock_unlock(&knet_h->global_rwlock); errno = 0; return 0; } + +int knet_handle_get_host_defrag_bufs(knet_handle_t knet_h, + uint16_t *min_defrag_bufs, + uint16_t *max_defrag_bufs, + uint8_t *shrink_threshold, + defrag_bufs_reclaim_policy_t *reclaim_policy) +{ + int savederrno = 0; + + if (!_is_valid_handle(knet_h)) { + return -1; + } + + if (!min_defrag_bufs) { + errno = EINVAL; + return -1; + } + + if (!max_defrag_bufs) { + errno = EINVAL; + return -1; + } + + if (!shrink_threshold) { + errno = EINVAL; + return -1; + } + + if (!reclaim_policy) { + errno = EINVAL; + return -1; + } + + savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); + if (savederrno) { + log_err(knet_h, KNET_SUB_HANDLE, "Unable to get read lock: %s", + strerror(savederrno)); + errno = savederrno; + return -1; + } + + *min_defrag_bufs = knet_h->defrag_bufs_min; + *max_defrag_bufs = knet_h->defrag_bufs_max; + *shrink_threshold = knet_h->defrag_bufs_shrink_threshold; + *reclaim_policy = knet_h->defrag_bufs_reclaim_policy; + + pthread_rwlock_unlock(&knet_h->global_rwlock); + errno = 0; + return 0; +} + +static int _is_power_of_two(uint16_t num) +{ + if ((num != 0) && ((num &(num - 1)) == 0)) { + return 1; + } + return 0; +} + +int knet_handle_set_host_defrag_bufs(knet_handle_t knet_h, + uint16_t min_defrag_bufs, + uint16_t max_defrag_bufs, + uint8_t shrink_threshold, + defrag_bufs_reclaim_policy_t reclaim_policy) +{ + int err = 0, savederrno = 0; + struct knet_host *host; + + if (!_is_valid_handle(knet_h)) { + return -1; + } + + if ((!min_defrag_bufs) || + (!_is_power_of_two(min_defrag_bufs)) || + (min_defrag_bufs > max_defrag_bufs)) { + errno = EINVAL; + return -1; + } + + if ((!max_defrag_bufs) || + (!_is_power_of_two(max_defrag_bufs)) || + (max_defrag_bufs < min_defrag_bufs)) { + errno = EINVAL; + return -1; + } + + if ((!shrink_threshold) || + (shrink_threshold > 50)) { + errno = EINVAL; + return -1; + } + + if (reclaim_policy > 1) { + errno = EINVAL; + return -1; + } + + savederrno = get_global_wrlock(knet_h); + if (savederrno) { + log_err(knet_h, KNET_SUB_HANDLE, "Unable to get write lock: %s", + strerror(savederrno)); + errno = savederrno; + return -1; + } + + /* + * all those parameters are managed by thread_rx in read only context. + * it is safe to change them here because we are in write lock. + */ + knet_h->defrag_bufs_min = min_defrag_bufs; + knet_h->defrag_bufs_max = max_defrag_bufs; + knet_h->defrag_bufs_shrink_threshold = shrink_threshold; + knet_h->defrag_bufs_reclaim_policy = reclaim_policy; + + /* + * reset all stats based on new values + */ + for (host = knet_h->host_head; host != NULL; host = host->next) { + _clear_defrag_bufs_stats(host); + } + + pthread_rwlock_unlock(&knet_h->global_rwlock); + errno = 0; + return err; +} diff --git a/libknet/host.c b/libknet/host.c index 8a0c5e4b..f02ef025 100644 --- a/libknet/host.c +++ b/libknet/host.c @@ -1,794 +1,829 @@ /* * Copyright (C) 2010-2021 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * Federico Simoncelli * * This software licensed under LGPL-2.0+ */ #include "config.h" #include #include #include #include #include #include "host.h" #include "internals.h" #include "logging.h" #include "threads_common.h" static void _host_list_update(knet_handle_t knet_h) { struct knet_host *host; knet_h->host_ids_entries = 0; for (host = knet_h->host_head; host != NULL; host = host->next) { knet_h->host_ids[knet_h->host_ids_entries] = host->host_id; knet_h->host_ids_entries++; } } int knet_host_add(knet_handle_t knet_h, knet_node_id_t host_id) { int savederrno = 0, err = 0; struct knet_host *host = NULL; uint8_t link_idx; if (!_is_valid_handle(knet_h)) { return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HOST, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (knet_h->host_index[host_id]) { err = -1; savederrno = EEXIST; log_err(knet_h, KNET_SUB_HOST, "Unable to add host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } host = malloc(sizeof(struct knet_host)); if (!host) { err = -1; savederrno = errno; log_err(knet_h, KNET_SUB_HOST, "Unable to allocate memory for host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } memset(host, 0, sizeof(struct knet_host)); + host->defrag_bufs = malloc(knet_h->defrag_bufs_min * sizeof(struct knet_host_defrag_buf)); + if (!host->defrag_bufs) { + err = -1; + savederrno = errno; + log_err(knet_h, KNET_SUB_HOST, "Unable to allocate memory for host %u defrag buffers: %s", + host_id, strerror(savederrno)); + goto exit_unlock; + } + + host->allocated_defrag_bufs = knet_h->defrag_bufs_min; + + memset(host->defrag_bufs, 0, host->allocated_defrag_bufs * sizeof(struct knet_host_defrag_buf)); + + log_debug(knet_h, KNET_SUB_HOST, "Allocated %u defrag buffers for host %u", + host->allocated_defrag_bufs, host_id); + /* * set host_id */ host->host_id = host_id; /* * fill up our own data */ if (knet_h->host_id == host->host_id) { host->onwire_ver = knet_h->onwire_ver; host->onwire_max_ver = knet_h->onwire_max_ver; } /* * set default host->name to host_id for logging */ snprintf(host->name, KNET_MAX_HOST_LEN, "%u", host_id); /* * initialize links internal data */ for (link_idx = 0; link_idx < KNET_MAX_LINK; link_idx++) { host->link[link_idx].link_id = link_idx; host->link[link_idx].status.stats.latency_min = UINT32_MAX; } /* * add new host to the index */ knet_h->host_index[host_id] = host; /* * add new host to host list */ if (knet_h->host_head) { host->next = knet_h->host_head; } knet_h->host_head = host; _host_list_update(knet_h); exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); if (err < 0) { + if (host) { + free(host->defrag_bufs); + } free(host); } errno = err ? savederrno : 0; return err; } int knet_host_remove(knet_handle_t knet_h, knet_node_id_t host_id) { int savederrno = 0, err = 0; struct knet_host *host, *removed; uint8_t link_idx; if (!_is_valid_handle(knet_h)) { return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HOST, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_HOST, "Unable to remove host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } /* * if links are configured we cannot release the host */ for (link_idx = 0; link_idx < KNET_MAX_LINK; link_idx++) { if (host->link[link_idx].configured) { err = -1; savederrno = EBUSY; log_err(knet_h, KNET_SUB_HOST, "Unable to remove host %u, links are still configured: %s", host_id, strerror(savederrno)); goto exit_unlock; } } removed = NULL; /* * removing host from list */ if (knet_h->host_head->host_id == host_id) { removed = knet_h->host_head; knet_h->host_head = removed->next; } else { for (host = knet_h->host_head; host->next != NULL; host = host->next) { if (host->next->host_id == host_id) { removed = host->next; host->next = removed->next; break; } } } knet_h->host_index[host_id] = NULL; + + if (removed) { + free(removed->defrag_bufs); + } free(removed); _host_list_update(knet_h); exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_host_set_name(knet_handle_t knet_h, knet_node_id_t host_id, const char *name) { int savederrno = 0, err = 0; struct knet_host *host; if (!_is_valid_handle(knet_h)) { return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HOST, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (!knet_h->host_index[host_id]) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_HOST, "Unable to find host %u to set name: %s", host_id, strerror(savederrno)); goto exit_unlock; } if (!name) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_HOST, "Unable to set name for host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } if (strlen(name) >= KNET_MAX_HOST_LEN) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_HOST, "Requested name for host %u is too long: %s", host_id, strerror(savederrno)); goto exit_unlock; } for (host = knet_h->host_head; host != NULL; host = host->next) { if (!strncmp(host->name, name, KNET_MAX_HOST_LEN)) { err = -1; savederrno = EEXIST; log_err(knet_h, KNET_SUB_HOST, "Duplicated name found on host_id %u", host->host_id); goto exit_unlock; } } snprintf(knet_h->host_index[host_id]->name, KNET_MAX_HOST_LEN, "%s", name); exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_host_get_name_by_host_id(knet_handle_t knet_h, knet_node_id_t host_id, char *name) { int savederrno = 0, err = 0; if (!_is_valid_handle(knet_h)) { return -1; } if (!name) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_HOST, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (!knet_h->host_index[host_id]) { savederrno = EINVAL; err = -1; log_debug(knet_h, KNET_SUB_HOST, "Host %u not found", host_id); goto exit_unlock; } snprintf(name, KNET_MAX_HOST_LEN, "%s", knet_h->host_index[host_id]->name); exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_host_get_id_by_host_name(knet_handle_t knet_h, const char *name, knet_node_id_t *host_id) { int savederrno = 0, err = 0, found = 0; struct knet_host *host; if (!_is_valid_handle(knet_h)) { return -1; } if (!name) { errno = EINVAL; return -1; } if (!host_id) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_HOST, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } for (host = knet_h->host_head; host != NULL; host = host->next) { if (!strncmp(name, host->name, KNET_MAX_HOST_LEN)) { found = 1; *host_id = host->host_id; break; } } if (!found) { savederrno = ENOENT; err = -1; } pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_host_get_host_list(knet_handle_t knet_h, knet_node_id_t *host_ids, size_t *host_ids_entries) { int savederrno = 0; if (!_is_valid_handle(knet_h)) { return -1; } if ((!host_ids) || (!host_ids_entries)) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_HOST, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } memmove(host_ids, knet_h->host_ids, sizeof(knet_h->host_ids)); *host_ids_entries = knet_h->host_ids_entries; pthread_rwlock_unlock(&knet_h->global_rwlock); return 0; } int knet_host_set_policy(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t policy) { int savederrno = 0, err = 0; uint8_t old_policy; if (!_is_valid_handle(knet_h)) { return -1; } if (policy > KNET_LINK_POLICY_RR) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HOST, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (!knet_h->host_index[host_id]) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_HOST, "Unable to set name for host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } old_policy = knet_h->host_index[host_id]->link_handler_policy; knet_h->host_index[host_id]->link_handler_policy = policy; if (_host_dstcache_update_async(knet_h, knet_h->host_index[host_id])) { savederrno = errno; err = -1; knet_h->host_index[host_id]->link_handler_policy = old_policy; log_debug(knet_h, KNET_SUB_HOST, "Unable to update switch cache for host %u: %s", host_id, strerror(savederrno)); } log_debug(knet_h, KNET_SUB_HOST, "Host %u has new switching policy: %u", host_id, policy); exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_host_get_policy(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t *policy) { int savederrno = 0, err = 0; if (!_is_valid_handle(knet_h)) { return -1; } if (!policy) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_HOST, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (!knet_h->host_index[host_id]) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_HOST, "Unable to get name for host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } *policy = knet_h->host_index[host_id]->link_handler_policy; exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_host_get_status(knet_handle_t knet_h, knet_node_id_t host_id, struct knet_host_status *status) { int savederrno = 0, err = 0; struct knet_host *host; if (!_is_valid_handle(knet_h)) { return -1; } if (!status) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_HOST, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_HOST, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } memmove(status, &host->status, sizeof(struct knet_host_status)); exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_host_enable_status_change_notify(knet_handle_t knet_h, void *host_status_change_notify_fn_private_data, void (*host_status_change_notify_fn) ( void *private_data, knet_node_id_t host_id, uint8_t reachable, uint8_t remote, uint8_t external)) { int savederrno = 0; if (!_is_valid_handle(knet_h)) { return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HOST, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } knet_h->host_status_change_notify_fn_private_data = host_status_change_notify_fn_private_data; knet_h->host_status_change_notify_fn = host_status_change_notify_fn; if (knet_h->host_status_change_notify_fn) { log_debug(knet_h, KNET_SUB_HOST, "host_status_change_notify_fn enabled"); } else { log_debug(knet_h, KNET_SUB_HOST, "host_status_change_notify_fn disabled"); } pthread_rwlock_unlock(&knet_h->global_rwlock); errno = 0; return 0; } +void _clear_defrag_bufs_stats(struct knet_host *host) +{ + memset(&host->in_use_defrag_buffers, 0, sizeof(host->in_use_defrag_buffers)); + host->in_use_defrag_buffers_samples = 0; + host->in_use_defrag_buffers_index = 0; +} + static void _clear_cbuffers(struct knet_host *host, seq_num_t rx_seq_num) { int i; memset(host->circular_buffer, 0, KNET_CBUFFER_SIZE); host->rx_seq_num = rx_seq_num; memset(host->circular_buffer_defrag, 0, KNET_CBUFFER_SIZE); - for (i = 0; i < KNET_DEFRAG_BUFFERS; i++) { - memset(&host->defrag_buf[i], 0, sizeof(struct knet_host_defrag_buf)); + for (i = 0; i < host->allocated_defrag_bufs; i++) { + memset(&host->defrag_bufs[i], 0, sizeof(struct knet_host_defrag_buf)); } + _clear_defrag_bufs_stats(host); } static void _reclaim_old_defrag_bufs(knet_handle_t knet_h, struct knet_host *host, seq_num_t seq_num) { seq_num_t head, tail; /* seq_num boundaries */ int i; head = seq_num + 1; - tail = seq_num - (KNET_DEFRAG_BUFFERS + 1); + if (knet_h->defrag_bufs_max > host->allocated_defrag_bufs) { + tail = seq_num - (knet_h->defrag_bufs_max + 1); + } else { + tail = seq_num - (host->allocated_defrag_bufs + 1); + } /* * expire old defrag buffers */ - for (i = 0; i < KNET_DEFRAG_BUFFERS; i++) { - if (host->defrag_buf[i].in_use) { + for (i = 0; i < host->allocated_defrag_bufs; i++) { + if (host->defrag_bufs[i].in_use) { /* * head has done a rollover to 0+ */ if (tail > head) { - if ((host->defrag_buf[i].pckt_seq >= head) && (host->defrag_buf[i].pckt_seq <= tail)) { - host->defrag_buf[i].in_use = 0; + if ((host->defrag_bufs[i].pckt_seq >= head) && (host->defrag_bufs[i].pckt_seq <= tail)) { + host->defrag_bufs[i].in_use = 0; } } else { - if ((host->defrag_buf[i].pckt_seq >= head) || (host->defrag_buf[i].pckt_seq <= tail)){ - host->defrag_buf[i].in_use = 0; + if ((host->defrag_bufs[i].pckt_seq >= head) || (host->defrag_bufs[i].pckt_seq <= tail)){ + host->defrag_bufs[i].in_use = 0; } } } } } /* * check if a given packet seq num is in the circular buffers * defrag_buf = 0 -> use normal cbuf 1 -> use the defrag buffer lookup */ int _seq_num_lookup(knet_handle_t knet_h, struct knet_host *host, seq_num_t seq_num, int defrag_buf, int clear_buf) { size_t head, tail; /* circular buffer indexes */ seq_num_t seq_dist; char *dst_cbuf = host->circular_buffer; char *dst_cbuf_defrag = host->circular_buffer_defrag; seq_num_t *dst_seq_num = &host->rx_seq_num; if (clear_buf) { _clear_cbuffers(host, seq_num); } _reclaim_old_defrag_bufs(knet_h, host, *dst_seq_num); if (seq_num < *dst_seq_num) { seq_dist = (SEQ_MAX - seq_num) + *dst_seq_num; } else { seq_dist = *dst_seq_num - seq_num; } head = seq_num % KNET_CBUFFER_SIZE; if (seq_dist < KNET_CBUFFER_SIZE) { /* seq num is in ring buffer */ if (!defrag_buf) { return (dst_cbuf[head] == 0) ? 1 : 0; } else { return (dst_cbuf_defrag[head] == 0) ? 1 : 0; } } else if (seq_dist <= SEQ_MAX - KNET_CBUFFER_SIZE) { memset(dst_cbuf, 0, KNET_CBUFFER_SIZE); memset(dst_cbuf_defrag, 0, KNET_CBUFFER_SIZE); *dst_seq_num = seq_num; } /* cleaning up circular buffer */ tail = (*dst_seq_num + 1) % KNET_CBUFFER_SIZE; if (tail > head) { memset(dst_cbuf + tail, 0, KNET_CBUFFER_SIZE - tail); memset(dst_cbuf, 0, head + 1); memset(dst_cbuf_defrag + tail, 0, KNET_CBUFFER_SIZE - tail); memset(dst_cbuf_defrag, 0, head + 1); } else { memset(dst_cbuf + tail, 0, head - tail + 1); memset(dst_cbuf_defrag + tail, 0, head - tail + 1); } *dst_seq_num = seq_num; return 1; } void _seq_num_set(struct knet_host *host, seq_num_t seq_num, int defrag_buf) { if (!defrag_buf) { host->circular_buffer[seq_num % KNET_CBUFFER_SIZE] = 1; } else { host->circular_buffer_defrag[seq_num % KNET_CBUFFER_SIZE] = 1; } return; } int _host_dstcache_update_async(knet_handle_t knet_h, struct knet_host *host) { int savederrno = 0; knet_node_id_t host_id = host->host_id; if (sendto(knet_h->dstsockfd[1], &host_id, sizeof(host_id), MSG_DONTWAIT | MSG_NOSIGNAL, NULL, 0) != sizeof(host_id)) { savederrno = errno; log_debug(knet_h, KNET_SUB_HOST, "Unable to write to dstpipefd[1]: %s", strerror(savederrno)); errno = savederrno; return -1; } return 0; } int _host_dstcache_update_sync(knet_handle_t knet_h, struct knet_host *host) { int link_idx; int best_priority = -1; int reachable = 0; if (knet_h->host_id == host->host_id && knet_h->has_loop_link) { host->active_link_entries = 1; return 0; } host->active_link_entries = 0; for (link_idx = 0; link_idx < KNET_MAX_LINK; link_idx++) { if (host->link[link_idx].status.enabled != 1) /* link is not enabled */ continue; if (host->link[link_idx].status.connected != 1) /* link is not enabled */ continue; if (host->link[link_idx].has_valid_mtu != 1) /* link does not have valid MTU */ continue; if (host->link_handler_policy == KNET_LINK_POLICY_PASSIVE) { /* for passive we look for the only active link with higher priority */ if (host->link[link_idx].priority > best_priority) { host->active_links[0] = link_idx; best_priority = host->link[link_idx].priority; } host->active_link_entries = 1; } else { /* for RR and ACTIVE we need to copy all available links */ host->active_links[host->active_link_entries] = link_idx; host->active_link_entries++; } } if (host->link_handler_policy == KNET_LINK_POLICY_PASSIVE) { log_info(knet_h, KNET_SUB_HOST, "host: %u (passive) best link: %u (pri: %u)", host->host_id, host->link[host->active_links[0]].link_id, host->link[host->active_links[0]].priority); } else { log_info(knet_h, KNET_SUB_HOST, "host: %u has %u active links", host->host_id, host->active_link_entries); } /* no active links, we can clean the circular buffers and indexes */ if (!host->active_link_entries) { log_warn(knet_h, KNET_SUB_HOST, "host: %u has no active links", host->host_id); _clear_cbuffers(host, 0); } else { reachable = 1; } if (host->status.reachable != reachable) { host->status.reachable = reachable; if (knet_h->host_status_change_notify_fn) { knet_h->host_status_change_notify_fn( knet_h->host_status_change_notify_fn_private_data, host->host_id, host->status.reachable, host->status.remote, host->status.external); } } return 0; } void _handle_onwire_version(knet_handle_t knet_h, struct knet_host *host, struct knet_header *inbuf) { struct knet_host *tmp_host = NULL; uint8_t onwire_ver = knet_h->onwire_max_ver; int docallback = 0; /* * data we process here are onwire independent * we are in a global read only lock context, so it´s safe to parse host lists * and we can change onwire_ver using the dedicated mutex */ /* * update current host onwire info */ host->onwire_ver = inbuf->kh_version; host->onwire_max_ver = inbuf->kh_max_ver; for (tmp_host = knet_h->host_head; tmp_host != NULL; tmp_host = tmp_host->next) { /* * do not attempt to change protocol till * we see all nodes at least once. */ if (!tmp_host->onwire_max_ver) { return; } /* * ignore nodes were max ver is lower than our min ver * logged as error by thread_rx, we need to make sure to skip it * during onwire_ver calculation. */ if (tmp_host->onwire_max_ver < knet_h->onwire_min_ver) { continue; } /* * use the highest max_ver common to all known nodes */ if (tmp_host->onwire_max_ver < onwire_ver) { onwire_ver = tmp_host->onwire_max_ver; } } if (pthread_mutex_lock(&knet_h->onwire_mutex)) { log_debug(knet_h, KNET_SUB_HOST, "Unable to get onwire mutex lock"); return; } if (knet_h->onwire_force_ver) { onwire_ver = knet_h->onwire_force_ver; } if (knet_h->onwire_ver != onwire_ver) { log_debug(knet_h, KNET_SUB_HOST, "node %u updating onwire version to %u", knet_h->host_id, onwire_ver); knet_h->onwire_ver = onwire_ver; docallback = 1; } pthread_mutex_unlock(&knet_h->onwire_mutex); /* * do the callback outside of locked context and use cached value * to avoid blocking on locking */ if ((docallback) && (knet_h->onwire_ver_notify_fn)) { knet_h->onwire_ver_notify_fn(knet_h->onwire_ver_notify_fn_private_data, knet_h->onwire_min_ver, knet_h->onwire_max_ver, onwire_ver); } } diff --git a/libknet/host.h b/libknet/host.h index ccd5514d..dab0c703 100644 --- a/libknet/host.h +++ b/libknet/host.h @@ -1,23 +1,25 @@ /* * Copyright (C) 2012-2021 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * Federico Simoncelli * * This software licensed under LGPL-2.0+ */ #ifndef __KNET_HOST_H__ #define __KNET_HOST_H__ #include "internals.h" +void _clear_defrag_bufs_stats(struct knet_host *host); + int _seq_num_lookup(knet_handle_t knet_h, struct knet_host *host, seq_num_t seq_num, int defrag_buf, int clear_buf); void _seq_num_set(struct knet_host *host, seq_num_t seq_num, int defrag_buf); int _host_dstcache_update_async(knet_handle_t knet_h, struct knet_host *host); int _host_dstcache_update_sync(knet_handle_t knet_h, struct knet_host *host); void _handle_onwire_version(knet_handle_t knet_h, struct knet_host *host, struct knet_header *inbuf); #endif diff --git a/libknet/internals.h b/libknet/internals.h index 0c282ad9..4e29b06e 100644 --- a/libknet/internals.h +++ b/libknet/internals.h @@ -1,458 +1,473 @@ /* * Copyright (C) 2010-2021 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * Federico Simoncelli * * This software licensed under LGPL-2.0+ */ #ifndef __KNET_INTERNALS_H__ #define __KNET_INTERNALS_H__ /* * NOTE: you shouldn't need to include this header normally */ #include #include #include #include "libknet.h" #include "onwire.h" #include "compat.h" #include "threads_common.h" #define KNET_DATABUFSIZE KNET_MAX_PACKET_SIZE + KNET_HEADER_ALL_SIZE #define KNET_DATABUFSIZE_CRYPT_PAD 1024 #define KNET_DATABUFSIZE_CRYPT KNET_DATABUFSIZE + KNET_DATABUFSIZE_CRYPT_PAD #define KNET_DATABUFSIZE_COMPRESS_PAD 1024 #define KNET_DATABUFSIZE_COMPRESS KNET_DATABUFSIZE + KNET_DATABUFSIZE_COMPRESS_PAD #define KNET_RING_RCVBUFF 8388608 #define PCKT_FRAG_MAX UINT8_MAX #define PCKT_RX_BUFS 512 #define KNET_EPOLL_MAX_EVENTS KNET_DATAFD_MAX + 1 /* * Size of threads stack. Value is choosen by experimenting, how much is needed * to sucesfully finish test suite, and at the time of writing patch it was * ~300KiB. To have some room for future enhancement it is increased * by factor of 3 and rounded. */ #define KNET_THREAD_STACK_SIZE (1024 * 1024) typedef void *knet_transport_link_t; /* per link transport handle */ typedef void *knet_transport_t; /* per knet_h transport handle */ struct knet_transport_ops; /* Forward because of circular dependancy */ struct knet_mmsghdr { struct msghdr msg_hdr; /* Message header */ unsigned int msg_len; /* Number of bytes transmitted */ }; struct knet_link { /* required */ struct sockaddr_storage src_addr; struct sockaddr_storage dst_addr; /* configurable */ unsigned int dynamic; /* see KNET_LINK_DYN_ define above */ uint8_t priority; /* higher priority == preferred for A/P */ unsigned long long ping_interval; /* interval */ unsigned long long pong_timeout; /* timeout */ unsigned long long pong_timeout_adj; /* timeout adjusted for latency */ uint8_t pong_timeout_backoff; /* see link.h for definition */ unsigned int latency_max_samples; /* precision */ uint8_t pong_count; /* how many ping/pong to send/receive before link is up */ uint64_t flags; void *access_list_match_entry_head; /* pointer to access list match_entry list head */ /* status */ struct knet_link_status status; /* internals */ pthread_mutex_t link_stats_mutex; /* used to update link stats */ uint8_t link_id; uint8_t transport; /* #defined constant from API */ knet_transport_link_t transport_link; /* link_info_t from transport */ int outsock; unsigned int configured:1; /* set to 1 if src/dst have been configured transport initialized on this link*/ unsigned int transport_connected:1; /* set to 1 if lower level transport is connected */ uint8_t received_pong; struct timespec ping_last; /* used by PMTUD thread as temp per-link variables and should always contain the onwire_len value! */ uint32_t proto_overhead; /* IP + UDP/SCTP overhead. NOT to be confused with stats.proto_overhead that includes also knet headers and crypto headers */ struct timespec pmtud_last; uint32_t last_ping_size; uint32_t last_good_mtu; uint32_t last_bad_mtu; uint32_t last_sent_mtu; uint32_t last_recv_mtu; uint32_t pmtud_crypto_timeout_multiplier;/* used by PMTUd to adjust timeouts on high loads */ uint8_t has_valid_mtu; }; -#define KNET_DEFRAG_BUFFERS 32 #define KNET_CBUFFER_SIZE 4096 struct knet_host_defrag_buf { char buf[KNET_DATABUFSIZE]; uint8_t in_use; /* 0 buffer is free, 1 is in use */ seq_num_t pckt_seq; /* identify the pckt we are receiving */ uint8_t frag_recv; /* how many frags did we receive */ uint8_t frag_map[PCKT_FRAG_MAX];/* bitmap of what we received? */ uint8_t last_first; /* special case if we receive the last fragment first */ ssize_t frag_size; /* normal frag size (not the last one) */ ssize_t last_frag_size; /* the last fragment might not be aligned with MTU size */ struct timespec last_update; /* keep time of the last pckt */ }; struct knet_host { /* required */ knet_node_id_t host_id; /* configurable */ uint8_t link_handler_policy; char name[KNET_MAX_HOST_LEN]; /* status */ struct knet_host_status status; /* * onwire info */ uint8_t onwire_ver; /* node current onwire version */ uint8_t onwire_max_ver; /* node supports up to this version */ /* internals */ char circular_buffer[KNET_CBUFFER_SIZE]; seq_num_t rx_seq_num; seq_num_t untimed_rx_seq_num; seq_num_t timed_rx_seq_num; uint8_t got_data; /* defrag/reassembly buffers */ - struct knet_host_defrag_buf defrag_buf[KNET_DEFRAG_BUFFERS]; + struct knet_host_defrag_buf *defrag_bufs; + uint16_t allocated_defrag_bufs; + /* track use % of allocated defrag buffers */ + uint8_t in_use_defrag_buffers[UINT8_MAX]; + uint8_t in_use_defrag_buffers_samples; + uint8_t in_use_defrag_buffers_index; char circular_buffer_defrag[KNET_CBUFFER_SIZE]; /* link stuff */ struct knet_link link[KNET_MAX_LINK]; uint8_t active_link_entries; uint8_t active_links[KNET_MAX_LINK]; struct knet_host *next; }; struct knet_sock { int sockfd[2]; /* sockfd[0] will always be application facing * and sockfd[1] internal if sockpair has been created by knet */ int is_socket; /* check if it's a socket for recvmmsg usage */ int is_created; /* knet created this socket and has to clean up on exit/del */ int in_use; /* set to 1 if it's use, 0 if free */ int has_error; /* set to 1 if there were errors reading from the sock * and socket has been removed from epoll */ }; struct knet_fd_trackers { uint8_t transport; /* transport type (UDP/SCTP...) */ uint8_t data_type; /* internal use for transport to define what data are associated * with this fd */ socklen_t sockaddr_len; /* Size of sockaddr_in[6] structure for this socket */ void *data; /* pointer to the data */ }; #define KNET_MAX_FDS KNET_MAX_HOST * KNET_MAX_LINK * 4 #define KNET_MAX_COMPRESS_METHODS UINT8_MAX #define KNET_MAX_CRYPTO_INSTANCES 2 struct knet_handle_stats_extra { uint64_t tx_crypt_pmtu_packets; uint64_t tx_crypt_pmtu_reply_packets; uint64_t tx_crypt_ping_packets; uint64_t tx_crypt_pong_packets; }; + +#define KNET_USAGE_SAMPLES_DEFAULT UINT8_MAX +#define KNET_USAGE_SAMPLES_TIMESPAN_DEFAULT 10 /* seconds */ + struct knet_handle { knet_node_id_t host_id; unsigned int enabled:1; struct knet_sock sockfd[KNET_DATAFD_MAX + 1]; int logfd; uint8_t log_levels[KNET_MAX_SUBSYSTEMS]; int dstsockfd[2]; int send_to_links_epollfd; int recv_from_links_epollfd; int dst_link_handler_epollfd; uint8_t use_access_lists; /* set to 0 for disable, 1 for enable */ unsigned int pmtud_interval; unsigned int manual_mtu; unsigned int data_mtu; /* contains the max data size that we can send onwire * without frags */ struct knet_host *host_head; struct knet_host *host_index[KNET_MAX_HOST]; knet_transport_t transports[KNET_MAX_TRANSPORTS+1]; struct knet_fd_trackers knet_transport_fd_tracker[KNET_MAX_FDS]; /* track status for each fd handled by transports */ struct knet_handle_stats stats; struct knet_handle_stats_extra stats_extra; pthread_mutex_t handle_stats_mutex; /* used to protect handle stats */ uint32_t reconnect_int; knet_node_id_t host_ids[KNET_MAX_HOST]; size_t host_ids_entries; struct knet_header *recv_from_sock_buf; struct knet_header *send_to_links_buf[PCKT_FRAG_MAX]; struct knet_header *recv_from_links_buf[PCKT_RX_BUFS]; struct knet_header *pingbuf; struct knet_header *pmtudbuf; uint8_t threads_status[KNET_THREAD_MAX]; uint8_t threads_flush_queue[KNET_THREAD_MAX]; useconds_t threads_timer_res; pthread_mutex_t threads_status_mutex; pthread_t send_to_links_thread; pthread_t recv_from_links_thread; pthread_t heartbt_thread; pthread_t dst_link_handler_thread; pthread_t pmtud_link_handler_thread; pthread_rwlock_t global_rwlock; /* global config lock */ pthread_mutex_t pmtud_mutex; /* pmtud mutex to handle conditional send/recv + timeout */ pthread_cond_t pmtud_cond; /* conditional for above */ pthread_mutex_t tx_mutex; /* used to protect knet_send_sync and TX thread */ pthread_mutex_t hb_mutex; /* used to protect heartbeat thread and seq_num broadcasting */ pthread_mutex_t backoff_mutex; /* used to protect dst_link->pong_timeout_adj */ pthread_mutex_t kmtu_mutex; /* used to protect kernel_mtu */ pthread_mutex_t onwire_mutex; /* used to protect onwire version */ uint8_t onwire_ver; /* currently agreed onwire version across known nodes */ uint8_t onwire_min_ver; /* min and max are constant and don´t need any mutex protection. */ uint8_t onwire_max_ver; /* we define them as part of internal handle so that we can mingle with them for testing purposes */ uint8_t onwire_force_ver; /* manually configure onwire_ver */ uint8_t onwire_ver_remap; /* when this is on, all mapping will use version 1 for now */ uint32_t kernel_mtu; /* contains the MTU detected by the kernel on a given link */ int pmtud_waiting; int pmtud_running; int pmtud_forcerun; int pmtud_abort; struct crypto_instance *crypto_instance[KNET_MAX_CRYPTO_INSTANCES + 1]; /* store an extra pointer to allow 0|1|2 values without too much magic in the code */ uint8_t crypto_in_use_config; /* crypto config to use for TX */ uint8_t crypto_only; /* allow only crypto (1) or also clear (0) traffic */ size_t sec_block_size; size_t sec_hash_size; size_t sec_salt_size; unsigned char *send_to_links_buf_crypt[PCKT_FRAG_MAX]; unsigned char *recv_from_links_buf_crypt; unsigned char *recv_from_links_buf_decrypt; unsigned char *pingbuf_crypt; unsigned char *pmtudbuf_crypt; int compress_model; int compress_level; size_t compress_threshold; void *compress_int_data[KNET_MAX_COMPRESS_METHODS]; /* for compress method private data */ unsigned char *recv_from_links_buf_decompress; unsigned char *send_to_links_buf_compress; seq_num_t tx_seq_num; pthread_mutex_t tx_seq_num_mutex; + uint16_t defrag_bufs_min; + uint16_t defrag_bufs_max; + uint8_t defrag_bufs_shrink_threshold; + uint8_t defrag_bufs_usage_samples; + uint8_t defrag_bufs_usage_samples_timespan; + defrag_bufs_reclaim_policy_t defrag_bufs_reclaim_policy; + struct timespec defrag_bufs_last_run; uint8_t has_loop_link; uint8_t loop_link; void *dst_host_filter_fn_private_data; int (*dst_host_filter_fn) ( void *private_data, const unsigned char *outdata, ssize_t outdata_len, uint8_t tx_rx, knet_node_id_t this_host_id, knet_node_id_t src_node_id, int8_t *channel, knet_node_id_t *dst_host_ids, size_t *dst_host_ids_entries); void *pmtud_notify_fn_private_data; void (*pmtud_notify_fn) ( void *private_data, unsigned int data_mtu); void *host_status_change_notify_fn_private_data; void (*host_status_change_notify_fn) ( void *private_data, knet_node_id_t host_id, uint8_t reachable, uint8_t remote, uint8_t external); void *link_status_change_notify_fn_private_data; void (*link_status_change_notify_fn) ( void *private_data, knet_node_id_t host_id, uint8_t link_id, uint8_t connected, uint8_t remote, uint8_t external); void *sock_notify_fn_private_data; void (*sock_notify_fn) ( void *private_data, int datafd, int8_t channel, uint8_t tx_rx, int error, int errorno); void *onwire_ver_notify_fn_private_data; void (*onwire_ver_notify_fn) ( void *private_data, uint8_t onwire_min_ver, uint8_t onwire_max_ver, uint8_t onwire_ver); int fini_in_progress; uint64_t flags; struct qb_list_head list; const char *plugin_path; }; struct handle_tracker { struct qb_list_head head; }; /* * lib_config stuff shared across everything */ extern pthread_rwlock_t shlib_rwlock; /* global shared lib load lock */ extern pthread_mutex_t handle_config_mutex; extern struct handle_tracker handle_list; extern uint8_t handle_list_init; int _is_valid_handle(knet_handle_t knet_h); int _init_shlib_tracker(knet_handle_t knet_h); void _fini_shlib_tracker(void); /* * NOTE: every single operation must be implementend * for every protocol. */ /* * for now knet supports only IP protocols (udp/sctp) * in future there might be others like ARP * or TIPC. * keep this around as transport information * to use for access lists and other operations */ #define TRANSPORT_PROTO_LOOPBACK 0 #define TRANSPORT_PROTO_IP_PROTO 1 /* * some transports like SCTP can filter incoming * connections before knet has to process * any packets. * GENERIC_ACL -> packet has to be read and filterted * PROTO_ACL -> transport provides filtering at lower levels * and packet does not need to be processed */ typedef enum { USE_NO_ACL, USE_GENERIC_ACL, USE_PROTO_ACL } transport_acl; /* * make it easier to map values in transports.c */ #define TRANSPORT_PROTO_NOT_CONNECTION_ORIENTED 0 #define TRANSPORT_PROTO_IS_CONNECTION_ORIENTED 1 typedef struct knet_transport_ops { /* * transport generic information */ const char *transport_name; const uint8_t transport_id; const uint8_t built_in; uint8_t transport_protocol; transport_acl transport_acl_type; /* * connection oriented protocols like SCTP * don´t need dst_addr in sendto calls and * on some OSes are considered EINVAL. */ uint8_t transport_is_connection_oriented; uint32_t transport_mtu_overhead; /* * transport init must allocate the new transport * and perform all internal initializations * (threads, lists, etc). */ int (*transport_init)(knet_handle_t knet_h); /* * transport free must releases _all_ resources * allocated by tranport_init */ int (*transport_free)(knet_handle_t knet_h); /* * link operations should take care of all the * sockets and epoll management for a given link/transport set * transport_link_disable should return err = -1 and errno = EBUSY * if listener is still in use, and any other errno in case * the link cannot be disabled. * * set_config/clear_config are invoked in global write lock context */ int (*transport_link_set_config)(knet_handle_t knet_h, struct knet_link *link); int (*transport_link_clear_config)(knet_handle_t knet_h, struct knet_link *link); /* * transport callback for incoming dynamic connections * this is called in global read lock context */ int (*transport_link_dyn_connect)(knet_handle_t knet_h, int sockfd, struct knet_link *link); /* * per transport error handling of recvmmsg * (see _handle_recv_from_links comments for details) */ /* * transport_rx_sock_error is invoked when recvmmsg returns <= 0 * * transport_rx_sock_error is invoked with both global_rdlock */ int (*transport_rx_sock_error)(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno); /* * transport_tx_sock_error is invoked with global_rwlock and * it's invoked when sendto or sendmmsg returns =< 0 * * it should return: * -1 on internal error * 0 ignore error and continue * 1 retry * any sleep or wait action should happen inside the transport code */ int (*transport_tx_sock_error)(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno); /* * this function is called on _every_ received packet * to verify if the packet is data or internal protocol error handling * * it should return: * -1 on error * 0 packet is not data and we should continue the packet process loop * 1 packet is not data and we should STOP the packet process loop * 2 packet is data and should be parsed as such * * transport_rx_is_data is invoked with both global_rwlock * and fd_tracker read lock (from RX thread) */ int (*transport_rx_is_data)(knet_handle_t knet_h, int sockfd, struct knet_mmsghdr *msg); /* * this function is called by links.c when a link down event is recorded * to notify the transport that packets are not going through, and give * transport the opportunity to take actions. */ int (*transport_link_is_down)(knet_handle_t knet_h, struct knet_link *link); } knet_transport_ops_t; struct pretty_names { const char *name; uint8_t val; }; #endif diff --git a/libknet/libknet.h b/libknet/libknet.h index 1ab22ef8..3116080e 100644 --- a/libknet/libknet.h +++ b/libknet/libknet.h @@ -1,2615 +1,2699 @@ /* * Copyright (C) 2010-2021 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * Federico Simoncelli * * This software licensed under LGPL-2.0+ */ #ifndef __LIBKNET_H__ #define __LIBKNET_H__ #include #include #include #include #include /** * @file libknet.h * @brief kronosnet API include file * @copyright Copyright (C) 2010-2021 Red Hat, Inc. All rights reserved. * * Kronosnet is an advanced VPN system for High Availability applications. */ #define KNET_API_VER 2 /* * libknet limits */ /** typedef for a knet node */ typedef uint16_t knet_node_id_t; /* * Maximum number of hosts */ #define KNET_MAX_HOST 65536 /* * Maximum number of links between 2 hosts */ #define KNET_MAX_LINK 8 /* * Maximum packet size that should be written to datafd * see knet_handle_new for details */ #define KNET_MAX_PACKET_SIZE 65536 /* * Buffers used for pretty logging * host is used to store both ip addresses and hostnames */ #define KNET_MAX_HOST_LEN 256 #define KNET_MAX_PORT_LEN 6 /* * Some notifications can be generated either on TX or RX */ #define KNET_NOTIFY_TX 0 #define KNET_NOTIFY_RX 1 /* * Link flags */ /* * Where possible, set traffic priority to high. * On Linux this sets the TOS to INTERACTIVE (6), * see tc-prio(8) for more infomation */ #define KNET_LINK_FLAG_TRAFFICHIPRIO (1ULL << 0) /* * Handle flags */ /* * Use privileged operations during socket setup. */ #define KNET_HANDLE_FLAG_PRIVILEGED (1ULL << 0) /* * threads timer resolution (see knet_handle_set_threads_timer_res below) */ #define KNET_THREADS_TIMER_RES 200000 /** * Opaque handle for this knet connection, created with knet_handle_new() and * freed with knet_handle_free() */ typedef struct knet_handle *knet_handle_t; /* * Handle structs/API calls */ /** * knet_handle_new * * @brief create a new instance of a knet handle * * host_id - Each host in a knet is identified with a unique * ID. when creating a new handle local host_id * must be specified (0 to UINT16_MAX are all valid). * It is the user's responsibility to check that the value * is unique, or bad things might happen. * * log_fd - Write file descriptor. If set to a value > 0, it will be used * to write log packets from libknet to the application. * Setting to 0 will disable logging from libknet. * It is possible to enable logging at any given time (see logging API). * Make sure to either read from this filedescriptor properly and/or * mark it O_NONBLOCK, otherwise if the fd becomes full, libknet could * block. * It is strongly encouraged to use pipes (ex: pipe(2) or pipe2(2)) for * logging fds due to the atomic nature of writes between fds. * See also libknet test suite for reference and guidance. * The caller is responsible for management of the FD. eg. knet will not * close it when knet_handle_free(3) is called * * default_log_level - * If logfd is specified, it will initialize all subsystems to log * at default_log_level value. (see logging API) * * flags - bitwise OR of some of the following flags: * KNET_HANDLE_FLAG_PRIVILEGED: use privileged operations setting up the * communication sockets. If disabled, failure to acquire large * enough socket buffers is ignored but logged. Inadequate buffers * lead to poor performance. * * @return * on success, a new knet_handle_t is returned. * on failure, NULL is returned and errno is set. * knet-specific errno values: * ENAMETOOLONG - socket buffers couldn't be set big enough and KNET_HANDLE_FLAG_PRIVILEGED was specified * ERANGE - buffer size readback returned unexpected type */ knet_handle_t knet_handle_new(knet_node_id_t host_id, int log_fd, uint8_t default_log_level, uint64_t flags); /** * knet_handle_free * * @brief Destroy a knet handle, free all resources * * knet_h - pointer to knet_handle_t * * @return * knet_handle_free returns * 0 on success * -1 on error and errno is set. */ int knet_handle_free(knet_handle_t knet_h); /** * knet_handle_set_threads_timer_res * * @brief Change internal thread timer resolution * * knet_h - pointer to knet_handle_t * * timeres - some threads inside knet will use usleep(timeres) * to check if any activity has to be performed, or wait * for the next cycle. 'timeres' (expressed in nano seconds) * defines this interval, with a default of KNET_THREADS_TIMER_RES * (200000). * The lower this value is, the more often knet will perform * those checks and allows a more (time) precise execution of * some operations (for example ping/pong), at the cost of higher * CPU usage. * Accepted values: * 0 - reset timer res to default * 1 - 999 invalid (as it would cause 100% CPU spinning on some * epoll operations) * 1000 or higher - valid * * Unless you know exactly what you are doing, stay away from * changing the default or seek written and notarized approval * from the knet developer team. * * @return * knet_handle_set_threads_timer_res returns * 0 on success * -1 on error and errno is set. */ int knet_handle_set_threads_timer_res(knet_handle_t knet_h, useconds_t timeres); /** * knet_handle_get_threads_timer_res * * @brief Get internal thread timer resolutions * * knet_h - pointer to knet_handle_t * * timeres - current timer res value * * @return * knet_handle_set_threads_timer_res returns * 0 on success and timerres will contain the current value * -1 on error and errno is set. */ int knet_handle_get_threads_timer_res(knet_handle_t knet_h, useconds_t *timeres); /** * knet_handle_enable_sock_notify * * @brief Register a callback to receive socket events * * knet_h - pointer to knet_handle_t * * sock_notify_fn_private_data * void pointer to data that can be used to identify * the callback. * * sock_notify_fn * A callback function that is invoked every time * a socket in the datafd pool will report an error (-1) * or an end of read (0) (see socket.7). * This function MUST NEVER block or add substantial delays. * The callback is invoked in an internal unlocked area * to allow calls to knet_handle_add_datafd/knet_handle_remove_datafd * to swap/replace the bad fd. * if both err and errno are 0, it means that the socket * has received a 0 byte packet (EOF?). * The callback function must either remove the fd from knet * (by calling knet_handle_remove_fd()) or dup a new fd in its place. * Failure to do this can cause problems. * * @return * knet_handle_enable_sock_notify returns * 0 on success * -1 on error and errno is set. */ int knet_handle_enable_sock_notify(knet_handle_t knet_h, void *sock_notify_fn_private_data, void (*sock_notify_fn) ( void *private_data, int datafd, int8_t channel, uint8_t tx_rx, int error, int errorno)); /* sorry! can't call it errno ;) */ #define KNET_DATAFD_MAX 32 /** * knet_handle_add_datafd * * @brief Install a file descriptor for communication * * IMPORTANT: In order to add datafd to knet, knet_handle_enable_sock_notify * _MUST_ be set and be able to handle both errors (-1) and * 0 bytes read / write from the provided datafd. * On read error (< 0) from datafd, the socket is automatically * removed from polling to avoid spinning on dead sockets. * It is safe to call knet_handle_remove_datafd even on sockets * that have been removed. * * knet_h - pointer to knet_handle_t * * *datafd - read/write file descriptor. * knet will read data here to send to the other hosts * and will write data received from the network. * Each data packet can be of max size KNET_MAX_PACKET_SIZE! * Applications using knet_send/knet_recv will receive a * proper error if the packet size is not within boundaries. * Applications using their own functions to write to the * datafd should NOT write more than KNET_MAX_PACKET_SIZE. * * Please refer to handle.c on how to set up a socketpair. * * datafd can be 0, and knet_handle_add_datafd will create a properly * populated socket pair the same way as ping_test, or a value * higher than 0. A negative number will return an error. * On exit knet_handle_free will take care to cleanup the * socketpair only if they have been created by knet_handle_add_datafd. * * It is possible to pass either sockets or normal fds. * User provided datafd will be marked as non-blocking and close-on-exec. * * *channel - This value is analogous to the tag in VLAN tagging. * A negative value will auto-allocate a channel. * Setting a value between 0 and 31 will try to allocate that * specific channel (unless already in use). * * It is possible to add up to 32 datafds but be aware that each * one of them must have a receiving end on the other host. * * Example: * hostA channel 0 will be delivered to datafd on hostB channel 0 * hostA channel 1 to hostB channel 1. * * Each channel must have a unique file descriptor. * * If your application could have 2 channels on one host and one * channel on another host, then you can use dst_host_filter * to manipulate channel values on TX and RX. * * @return * knet_handle_add_datafd returns * @retval 0 on success, * *datafd will be populated with a socket if the original value was 0 * or if a specific fd was set, the value is untouched. * *channel will be populated with a channel number if the original value * was negative or the value is untouched if a specific channel * was requested. * * @retval -1 on error and errno is set. * *datafd and *channel are untouched or empty. */ int knet_handle_add_datafd(knet_handle_t knet_h, int *datafd, int8_t *channel); /** * knet_handle_remove_datafd * * @brief Remove a file descriptor from knet * * knet_h - pointer to knet_handle_t * * datafd - file descriptor to remove. * NOTE that if the socket/fd was created by knet_handle_add_datafd, * the socket will be closed by libknet. * * @return * knet_handle_remove_datafd returns * 0 on success * -1 on error and errno is set. */ int knet_handle_remove_datafd(knet_handle_t knet_h, int datafd); /** * knet_handle_get_channel * * @brief Get the channel associated with a file descriptor * * knet_h - pointer to knet_handle_t * * datafd - get the channel associated to this datafd * * *channel - will contain the result * * @return * knet_handle_get_channel returns * @retval 0 on success * and *channel will contain the result * @retval -1 on error and errno is set. * and *channel content is meaningless */ int knet_handle_get_channel(knet_handle_t knet_h, const int datafd, int8_t *channel); /** * knet_handle_get_datafd * * @brief Get the file descriptor associated with a channel * * knet_h - pointer to knet_handle_t * * channel - get the datafd associated to this channel * * *datafd - will contain the result * * @return * knet_handle_get_datafd returns * @retval 0 on success * and *datafd will contain the results * @retval -1 on error and errno is set. * and *datafd content is meaningless */ int knet_handle_get_datafd(knet_handle_t knet_h, const int8_t channel, int *datafd); /** * knet_recv * * @brief Receive data from knet nodes * * knet_h - pointer to knet_handle_t * * buff - pointer to buffer to store the received data * * buff_len - buffer length * * channel - channel number * * @return * knet_recv is a commodity function to wrap iovec operations * around a socket. It returns a call to readv(2). */ ssize_t knet_recv(knet_handle_t knet_h, char *buff, const size_t buff_len, const int8_t channel); /** * knet_send * * @brief Send data to knet nodes * * knet_h - pointer to knet_handle_t * * buff - pointer to the buffer of data to send * * buff_len - length of data to send * * channel - channel number * * @return * knet_send is a commodity function to wrap iovec operations * around a socket. It returns a call to writev(2). */ ssize_t knet_send(knet_handle_t knet_h, const char *buff, const size_t buff_len, const int8_t channel); /** * knet_send_sync * * @brief Synchronously send data to knet nodes * * knet_h - pointer to knet_handle_t * * buff - pointer to the buffer of data to send * * buff_len - length of data to send * * channel - data channel to use (see knet_handle_add_datafd(3)) * * All knet RX/TX operations are async for performance reasons. * There are applications that might need a sync version of data * transmission and receive errors in case of failure to deliver * to another host. * knet_send_sync bypasses the whole TX async layer and delivers * data directly to the link layer, and returns errors accordingly. * knet_send_sync sends only one packet to one host at a time. * It does NOT support multiple destinations or multicast packets. * Decision is still based on dst_host_filter_fn. * * @return * knet_send_sync returns 0 on success and -1 on error. * In addition to normal sendmmsg errors, knet_send_sync can fail * due to: * * @retval ECANCELED - data forward is disabled * @retval EFAULT - dst_host_filter fatal error * @retval EINVAL - dst_host_filter did not provide dst_host_ids_entries on unicast pckts * @retval E2BIG - dst_host_filter did return more than one dst_host_ids_entries on unicast pckts * @retval ENOMSG - received unknown message type * @retval EHOSTDOWN - unicast pckt cannot be delivered because dest host is not connected yet * @retval ECHILD - crypto failed * @retval EAGAIN - sendmmsg was unable to send all messages and there was no progress during retry * @retval ENETDOWN - a packet filter was not installed (necessary for knet_send_sync, but not knet_send) */ int knet_send_sync(knet_handle_t knet_h, const char *buff, const size_t buff_len, const int8_t channel); /** * knet_handle_enable_filter * * @brief install a filter to route packets * * knet_h - pointer to knet_handle_t * * dst_host_filter_fn_private_data * void pointer to data that can be used to identify * the callback. * * dst_host_filter_fn - * is a callback function that is invoked every time * a packet hits datafd (see knet_handle_new(3)). * the function allows users to tell libknet where the * packet has to be delivered. * * const unsigned char *outdata - is a pointer to the * current packet * ssize_t outdata_len - length of the above data * uint8_t tx_rx - filter is called on tx or rx * (KNET_NOTIFY_TX, KNET_NOTIFY_RX) * knet_node_id_t this_host_id - host_id processing the packet * knet_node_id_t src_host_id - host_id that generated the * packet * knet_node_id_t *dst_host_ids - array of KNET_MAX_HOST knet_node_id_t * where to store the destinations * size_t *dst_host_ids_entries - number of hosts to send the message * * dst_host_filter_fn should return * -1 on error, packet is discarded. * 0 packet is unicast and should be sent to dst_host_ids and there are * dst_host_ids_entries in the buffer. * 1 packet is broadcast/multicast and is sent all hosts. * contents of dst_host_ids and dst_host_ids_entries are ignored. * * @return * knet_handle_enable_filter returns * 0 on success * -1 on error and errno is set. */ int knet_handle_enable_filter(knet_handle_t knet_h, void *dst_host_filter_fn_private_data, int (*dst_host_filter_fn) ( void *private_data, const unsigned char *outdata, ssize_t outdata_len, uint8_t tx_rx, knet_node_id_t this_host_id, knet_node_id_t src_host_id, int8_t *channel, knet_node_id_t *dst_host_ids, size_t *dst_host_ids_entries)); /** * knet_handle_setfwd * * @brief Start packet forwarding * * knet_h - pointer to knet_handle_t * * enable - set to 1 to allow data forwarding, 0 to disable data forwarding. * * @return * knet_handle_setfwd returns * 0 on success * -1 on error and errno is set. * * By default data forwarding is off and no traffic will pass through knet until * it is set on. */ int knet_handle_setfwd(knet_handle_t knet_h, unsigned int enabled); /** * knet_handle_enable_access_lists * * @brief Enable or disable usage of access lists (default: off) * * knet_h - pointer to knet_handle_t * * enable - set to 1 to use access lists, 0 to disable access_lists. * * @return * knet_handle_enable_access_lists returns * 0 on success * -1 on error and errno is set. * * access lists are bound to links. There are 2 types of links: * 1) point to point, where both source and destinations are well known * at configuration time. * 2) open links, where only the source is known at configuration time. * * knet will automatically generate access lists for point to point links. * * For open links, knet provides 4 API calls to manipulate access lists: * knet_link_add_acl(3), knet_link_rm_acl(3), knet_link_insert_acl(3) * and knet_link_clear_acl(3). * Those API calls will work exclusively on open links as they * are of no use on point to point links. * * knet will not enforce any access list unless specifically enabled by * knet_handle_enable_access_lists(3). * * From a security / programming perspective we recommend: * - create the knet handle * - enable access lists * - configure hosts and links * - configure access lists for open links */ int knet_handle_enable_access_lists(knet_handle_t knet_h, unsigned int enabled); #define KNET_PMTUD_DEFAULT_INTERVAL 60 /** * knet_handle_pmtud_setfreq * * @brief Set the interval between PMTUd scans * * knet_h - pointer to knet_handle_t * * interval - define the interval in seconds between PMTUd scans * range from 1 to 86400 (24h) * * @return * knet_handle_pmtud_setfreq returns * 0 on success * -1 on error and errno is set. * * default interval is 60. */ int knet_handle_pmtud_setfreq(knet_handle_t knet_h, unsigned int interval); /** * knet_handle_pmtud_getfreq * * @brief Get the interval between PMTUd scans * * knet_h - pointer to knet_handle_t * * interval - pointer where to store the current interval value * * @return * knet_handle_pmtud_setfreq returns * 0 on success * -1 on error and errno is set. */ int knet_handle_pmtud_getfreq(knet_handle_t knet_h, unsigned int *interval); /** * knet_handle_enable_pmtud_notify * * @brief install a callback to receive PMTUd changes * * knet_h - pointer to knet_handle_t * * pmtud_notify_fn_private_data * void pointer to data that can be used to identify * the callback. * * pmtud_notify_fn * is a callback function that is invoked every time * a path MTU size change is detected. * The function allows libknet to notify the user * of data MTU, that's the max value that can be send * onwire without fragmentation. The data MTU will always * be lower than real link MTU because it accounts for * protocol overhead, knet packet header and (if configured) * crypto overhead, * This function MUST NEVER block or add substantial delays. * * @return * knet_handle_enable_pmtud_notify returns * 0 on success * -1 on error and errno is set. */ int knet_handle_enable_pmtud_notify(knet_handle_t knet_h, void *pmtud_notify_fn_private_data, void (*pmtud_notify_fn) ( void *private_data, unsigned int data_mtu)); /** * knet_handle_pmtud_set * * @brief Set the current interface MTU * * knet_h - pointer to knet_handle_t * * iface_mtu - current interface MTU, value 0 to 65535. 0 will * re-enable automatic MTU discovery. * In a setup with multiple interfaces, please specify * the lowest MTU between the selected intefaces. * knet will automatically adjust this value for * all headers overhead and set the correct data_mtu. * data_mtu can be retrivied with knet_handle_pmtud_get(3) * or applications will receive a pmtud_notify event * if enabled via knet_handle_enable_pmtud_notify(3). * * @return * knet_handle_pmtud_set returns * 0 on success * -1 on error and errno is set. */ int knet_handle_pmtud_set(knet_handle_t knet_h, unsigned int iface_mtu); /** * knet_handle_pmtud_get * * @brief Get the current data MTU * * knet_h - pointer to knet_handle_t * * data_mtu - pointer where to store data_mtu * * @return * knet_handle_pmtud_get returns * 0 on success * -1 on error and errno is set. */ int knet_handle_pmtud_get(knet_handle_t knet_h, unsigned int *data_mtu); #define KNET_MIN_KEY_LEN 128 #define KNET_MAX_KEY_LEN 4096 /** * Structure passed into knet_handle_set_crypto_config() to determine * the crypto options to use for the current communications handle */ struct knet_handle_crypto_cfg { /** Model to use. nss, openssl, etc */ char crypto_model[16]; /** Cipher type name for encryption. aes 256 etc */ char crypto_cipher_type[16]; /** Hash type for digest. sha512 etc */ char crypto_hash_type[16]; /** Private key */ unsigned char private_key[KNET_MAX_KEY_LEN]; /** Length of private key */ unsigned int private_key_len; }; /** * knet_handle_crypto_set_config * * @brief set up packet cryptographic signing & encryption * * knet_h - pointer to knet_handle_t * * knet_handle_crypto_cfg - * pointer to a knet_handle_crypto_cfg structure * * crypto_model should contain the model name. * Currently "openssl", "nss" and "gcrypt" are supported. * Setting to "none" will disable crypto. * * crypto_cipher_type * should contain the cipher algo name. * It can be set to "none" to disable * encryption. * Currently supported by "nss" model: * "aes128", "aes192" and "aes256". * "openssl" model supports more modes and it strictly * depends on the openssl build. See: EVP_get_cipherbyname * openssl API call for details. * * crypto_hash_type * should contain the hashing algo name. * It can be set to "none" to disable * hashing. * Currently supported by "nss" model: * "md5", "sha1", "sha256", "sha384" and "sha512". * "openssl" model supports more modes and it strictly * depends on the openssl build. See: EVP_get_digestbyname * openssl API call for details. * * private_key will contain the private shared key. * It has to be at least KNET_MIN_KEY_LEN long. * * private_key_len * length of the provided private_key. * * config_num - knet supports 2 concurrent sets of crypto configurations, * to allow runtime change of crypto config and keys. * On RX both configurations will be used sequentially * in an attempt to decrypt/validate a packet (when 2 are available). * Note that this might slow down performance during a reconfiguration. * See also knet_handle_crypto_rx_clear_traffic(3) to enable / disable * processing of clear (unencrypted) traffic. * For TX, the user needs to specify which configuration to use via * knet_handle_crypto_use_config(3). * config_num accepts 0, 1 or 2 as the value. 0 should be used when * all crypto is being disabled. * Calling knet_handle_crypto_set_config(3) twice with * the same config_num will REPLACE the configuration and * NOT activate the second key. If the configuration is currently in use * EBUSY will be returned. See also knet_handle_crypto_use_config(3). * The correct sequence to perform a runtime rekey / reconfiguration * is: * - knet_handle_crypto_set_config(..., 1). -> first time config, will use config1 * - knet_handle_crypto_use_config(..., 1). -> switch TX to config 1 * - knet_handle_crypto_set_config(..., 2). -> install config2 and use it only for RX * - knet_handle_crypto_use_config(..., 2). -> switch TX to config 2 * - knet_handle_crypto_set_config(..., 1). -> with a "none"/"none"/"none" configuration to * release the resources previously allocated * The application is responsible for synchronizing calls on the nodes * to make sure the new config is in place before switching the TX configuration. * Failure to do so will result in knet being unable to talk to some of the nodes. * * Implementation notes/current limitations: * - enabling crypto, will increase latency as packets have * to processed. * - enabling crypto might reduce the overall throughtput * due to crypto data overhead. * - private/public key encryption/hashing is not currently * planned. * - crypto key must be the same for all hosts in the same * knet instance / configX. * - it is safe to call knet_handle_crypto_set_config multiple times at runtime. * The last config will be used. * IMPORTANT: a call to knet_handle_crypto_set_config can fail due to: * 1) failure to obtain locking * 2) errors to initializing the crypto level. * This can happen even in subsequent calls to knet_handle_crypto_set_config(3). * A failure in crypto init will restore the previous crypto configuration if any. * * @return * knet_handle_crypto_set_config returns: * @retval 0 on success * @retval -1 on error and errno is set. * @retval -2 on crypto subsystem initialization error. No errno is provided at the moment (yet). */ int knet_handle_crypto_set_config(knet_handle_t knet_h, struct knet_handle_crypto_cfg *knet_handle_crypto_cfg, uint8_t config_num); #define KNET_CRYPTO_RX_ALLOW_CLEAR_TRAFFIC 0 #define KNET_CRYPTO_RX_DISALLOW_CLEAR_TRAFFIC 1 /** * knet_handle_crypto_rx_clear_traffic * * @brief enable or disable RX processing of clear (unencrypted) traffic * * knet_h - pointer to knet_handle_t * * value - KNET_CRYPTO_RX_ALLOW_CLEAR_TRAFFIC or KNET_CRYPTO_RX_DISALLOW_CLEAR_TRAFFIC * * @return * knet_handle_crypto_use_config returns: * @retval 0 on success * @retval -1 on error and errno is set. */ int knet_handle_crypto_rx_clear_traffic(knet_handle_t knet_h, uint8_t value); /** * knet_handle_crypto_use_config * * @brief specify crypto configuration to use for TX * * knet_h - pointer to knet_handle_t * * config_num - 1|2 use configuration 1 or 2, 0 for clear (unencrypted) traffic. * * @return * knet_handle_crypto_use_config returns: * @retval 0 on success * @retval -1 on error and errno is set. */ int knet_handle_crypto_use_config(knet_handle_t knet_h, uint8_t config_num); #define KNET_COMPRESS_THRESHOLD 100 /** * Structure passed into knet_handle_compress() * to tell knet what type of compression to use * for this communiction */ struct knet_handle_compress_cfg { /** Compression library to use, bzip2 etc... */ char compress_model[16]; /** Threshold. Packets smaller than this will not be compressed */ uint32_t compress_threshold; /** Passed into the compression library as an indication of the level of compression to apply */ int compress_level; }; /** * knet_handle_compress * * @brief Set up packet compression * * knet_h - pointer to knet_handle_t * * knet_handle_compress_cfg - * pointer to a knet_handle_compress_cfg structure * * compress_model contains the model name. * See "compress_level" for the list of accepted values. * Setting the value to "none" disables compression. * * compress_threshold * tells the transmission thread to NOT compress * any packets that are smaller than the value * indicated. Default 100 bytes. * Set to 0 to reset to the default. * Set to 1 to compress everything. * Max accepted value is KNET_MAX_PACKET_SIZE. * * compress_level is the "level" parameter for most models: * zlib: 0 (no compression), 1 (minimal) .. 9 (max compression). * lz4: 1 (max compression)... 9 (fastest compression). * lz4hc: 1 (min compression) ... LZ4HC_MAX_CLEVEL (16) or LZ4HC_CLEVEL_MAX (12) * depending on the version of lz4hc libknet was built with. * lzma: 0 (minimal) .. 9 (max compression) * bzip2: 1 (minimal) .. 9 (max compression) * For lzo2 it selects the algorithm to use: * 1 : lzo1x_1_compress (default) * 11 : lzo1x_1_11_compress * 12 : lzo1x_1_12_compress * 15 : lzo1x_1_15_compress * 999: lzo1x_999_compress * Other values select the default algorithm. * Please refer to the documentation of the respective * compression library for guidance about setting this * value. * * Implementation notes: * - it is possible to enable/disable compression at any time. * - nodes can be using a different compression algorithm at any time. * - knet does NOT implement the compression algorithm directly. it relies * on external libraries for this functionality. Please read * the libraries man pages to figure out which algorithm/compression * level is best for the data you are planning to transmit. * * @return * knet_handle_compress returns * 0 on success * -1 on error and errno is set. EINVAL means that either the model or the * level are not supported. */ int knet_handle_compress(knet_handle_t knet_h, struct knet_handle_compress_cfg *knet_handle_compress_cfg); /** * Detailed stats for this knet handle as returned by knet_handle_get_stats() */ struct knet_handle_stats { /** Size of the structure. set this to sizeof(struct knet_handle_stats) before calling */ size_t size; /** Number of uncompressed packets sent */ uint64_t tx_uncompressed_packets; /** Number of compressed packets sent */ uint64_t tx_compressed_packets; /** Number of bytes sent (as if uncompressed, ie actual data bytes) */ uint64_t tx_compressed_original_bytes; /** Number of bytes sent on the wire after compresion */ uint64_t tx_compressed_size_bytes; /** Average(mean) time take to compress transmitted packets */ uint64_t tx_compress_time_ave; /** Minumum time taken to compress transmitted packets */ uint64_t tx_compress_time_min; /** Maximum time taken to compress transmitted packets */ uint64_t tx_compress_time_max; /** Number of times the compression attempt failed for some reason */ uint64_t tx_failed_to_compress; /** Number of packets where the compressed size was no smaller than the original */ uint64_t tx_unable_to_compress; /** Number of compressed packets received */ uint64_t rx_compressed_packets; /** Number of bytes received - after decompression */ uint64_t rx_compressed_original_bytes; /** Number of compressed bytes received before decompression */ uint64_t rx_compressed_size_bytes; /** Average(mean) time take to decompress received packets */ uint64_t rx_compress_time_ave; /** Minimum time take to decompress received packets */ uint64_t rx_compress_time_min; /** Maximum time take to decompress received packets */ uint64_t rx_compress_time_max; /** Number of times decompression failed */ uint64_t rx_failed_to_decompress; /** Number of encrypted packets sent */ uint64_t tx_crypt_packets; /** Cumulative byte overhead of encrypted traffic */ uint64_t tx_crypt_byte_overhead; /** Average(mean) time take to encrypt packets in usecs */ uint64_t tx_crypt_time_ave; /** Minimum time take to encrypto packets in usecs */ uint64_t tx_crypt_time_min; /** Maximum time take to encrypto packets in usecs */ uint64_t tx_crypt_time_max; /** Number of encrypted packets received */ uint64_t rx_crypt_packets; /** Average(mean) time take to decrypt received packets */ uint64_t rx_crypt_time_ave; /** Minimum time take to decrypt received packets in usecs */ uint64_t rx_crypt_time_min; /** Maximum time take to decrypt received packets in usecs */ uint64_t rx_crypt_time_max; }; /** * knet_handle_get_stats * * @brief Get statistics for compression & crypto * * knet_h - pointer to knet_handle_t * * knet_handle_stats * pointer to a knet_handle_stats structure * * struct_size * size of knet_handle_stats structure to allow * for backwards compatibility. libknet will only * copy this much data into the stats structure * so that older callers will not get overflowed if * new fields are added. * * @return * 0 on success * -1 on error and errno is set. * */ int knet_handle_get_stats(knet_handle_t knet_h, struct knet_handle_stats *stats, size_t struct_size); /* * Tell knet_handle_clear_stats whether to clear just the handle stats * or all of them. */ #define KNET_CLEARSTATS_HANDLE_ONLY 1 #define KNET_CLEARSTATS_HANDLE_AND_LINK 2 /** * knet_handle_clear_stats * * @brief Clear knet stats, link and/or handle * * knet_h - pointer to knet_handle_t * * clear_option - Which stats to clear, must be one of * * KNET_CLEARSTATS_HANDLE_ONLY or * KNET_CLEARSTATS_HANDLE_AND_LINK * * @return * 0 on success * -1 on error and errno is set. * */ int knet_handle_clear_stats(knet_handle_t knet_h, int clear_option); /** * Structure returned from get_crypto_list() containing * information about the installed cryptographic systems */ struct knet_crypto_info { /** Name of the crypto library/ openssl, nss,etc .. */ const char *name; /** Properties - currently unused */ uint8_t properties; /** Currently unused padding */ char pad[256]; }; /** * knet_get_crypto_list * * @brief Get a list of supported crypto libraries * * crypto_list - array of struct knet_crypto_info * * If NULL then only the number of structs is returned in crypto_list_entries * to allow the caller to allocate sufficient space. * libknet does not allow more than 256 crypto methods at the moment. * it is safe to allocate 256 structs to avoid calling * knet_get_crypto_list twice. * * crypto_list_entries - returns the number of structs in crypto_list * * @return * knet_get_crypto_list returns * 0 on success * -1 on error and errno is set. */ int knet_get_crypto_list(struct knet_crypto_info *crypto_list, size_t *crypto_list_entries); /** * Structure returned from get_compress_list() containing * information about the installed compression systems */ struct knet_compress_info { /** Name of the compression type bzip2, lz4, etc.. */ const char *name; /** Properties - currently unused */ uint8_t properties; /** Currently unused padding */ char pad[256]; }; /** * knet_get_compress_list * * @brief Get a list of support compression types * * compress_list - array of struct knet_compress_info * * If NULL then only the number of structs is returned in compress_list_entries * to allow the caller to allocate sufficient space. * libknet does not allow more than 256 compress methods at the moment. * it is safe to allocate 256 structs to avoid calling * knet_get_compress_list twice. * * compress_list_entries - returns the number of structs in compress_list * * @return * knet_get_compress_list returns * 0 on success * -1 on error and errno is set. */ int knet_get_compress_list(struct knet_compress_info *compress_list, size_t *compress_list_entries); /** * knet_handle_enable_onwire_ver_notify * * @brief install a callback to receive onwire changes * * knet_h - pointer to knet_handle_t * * onwire_ver_notify_fn_private_data * void pointer to data that can be used to identify * the callback. * * onwire_ver_notify_fn * is a callback function that is invoked every time * an onwire version change is detected. * The function allows libknet to notify the user * of onwire version changes. * onwire_min_ver - minimum onwire version supported * onwire_max_ver - maximum onwire version supported * onwire_ver - currently onwire version in use * This function MUST NEVER block or add substantial delays. * * NOTE: the callback function will be invoked upon install to * immediately notify the user of the current configuration. * During startup, it is safer to use onwire_min_ver and * onwire_ver on subsequent calls. * * @return * knet_handle_enable_onwire_ver_notify returns * 0 on success * -1 on error and errno is set. */ int knet_handle_enable_onwire_ver_notify(knet_handle_t knet_h, void *onwire_ver_notify_fn_private_data, void (*onwire_ver_notify_fn) ( void *private_data, uint8_t onwire_min_ver, uint8_t onwire_max_ver, uint8_t onwire_ver)); /** * knet_handle_get_onwire_ver * * @brief get onwire protocol version information * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * onwire_min_ver - minimum onwire version supported by local node. * this value is set to 0 for remote nodes. * * onwire_max_ver - maximum onwire version supported by local or * remote node. * * onwire_ver - currently onwire version in use by local or * remote node. * * @return * knet_handle_get_onwire_ver returns * 0 on success * -1 on error and errno is set. */ int knet_handle_get_onwire_ver(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t *onwire_min_ver, uint8_t *onwire_max_ver, uint8_t *onwire_ver); /** * knet_handle_set_onwire_ver * * @brief force onwire protocol version * * knet_h - pointer to knet_handle_t * * onwire_ver - onwire version to use. * reset to 0 to allow knet to detect * automatically the highest version. * * @return * knet_handle_get_onwire_ver returns * 0 on success * -1 on error and errno is set. */ int knet_handle_set_onwire_ver(knet_handle_t knet_h, uint8_t onwire_ver); +/* + * defrag buffer configuration defaults + */ +#define KNET_MIN_DEFRAG_BUFS_DEFAULT 32 +#define KNET_MAX_DEFRAG_BUFS_DEFAULT 1024 +#define KNET_SHRINK_THRESHOLD_DEFAULT 25 + + +/** + * reclaim_policy for defrag buffers + */ +typedef enum { + RECLAIM_POLICY_AVERAGE = 0, + RECLAIM_POLICY_ABSOLUTE = 1 /* default */ +} defrag_bufs_reclaim_policy_t; + +/** + * knet_handle_get_host_defrag_bufs + * + * @brief Return the defrag buffers parameters for hosts + * + * knet_h - pointer to knet_handle_t + * + * min_defrag_bufs - minimum defrag buffers for each host + * + * max_defrag_bufs - maximum defrag buffers for each host + * + * shrink_threshold - define buffer usage threshold in % + * below which buffers will be shrunk. + * This is measured over the last + * usage_samples_timespan, as an + * average of usage_samples. + * + * reclaim_policy - define how % threshold is calculated. + * + * @return + * knet_handle_set_host_defrag_bufs returns + * 0 on success + * -1 on error and errno is set. + */ + +int knet_handle_get_host_defrag_bufs(knet_handle_t knet_h, + uint16_t *min_defrag_bufs, + uint16_t *max_defrag_bufs, + uint8_t *shrink_threshold, + defrag_bufs_reclaim_policy_t *reclaim_policy); + +/** + * knet_handle_set_host_defrag_bufs + * + * @brief configure defrag buffers parameters per host + * + * knet_h - pointer to knet_handle_t + * + * min_defrag_bufs - minimum defrag buffers for each host, + * This should be a power of 2. + * + * max_defrag_bufs - maximum defrag buffers for each host, + * This should be a power of 2. + * + * shrink_threshold - define buffer usage threshold in % + * below which buffers will be shrunk. + * This is measured over the last + * usage_samples_timespan, as an + * average of usage_samples. + * Only values less than or equal to 50% are accepted. + * + * reclaim_policy - define how % threshold is calculated. + * + * @note The defrag buffer parameters are global to a handle + * but the buffers themselves are allocated and shrunk per-host. + * + * @return + * knet_handle_set_host_defrag_bufs returns + * 0 on success + * -1 on error and errno is set. + */ + +int knet_handle_set_host_defrag_bufs(knet_handle_t knet_h, + uint16_t min_defrag_bufs, + uint16_t max_defrag_bufs, + uint8_t shrink_threshold, + defrag_bufs_reclaim_policy_t reclaim_policy); + /* * host structs/API calls */ /** * knet_host_add * * @brief Add a new host ID to knet * * knet_h - pointer to knet_handle_t * * host_id - each host in a knet is identified with a unique ID * (see also knet_handle_new(3)) * * @return * knet_host_add returns: * 0 on success * -1 on error and errno is set. */ int knet_host_add(knet_handle_t knet_h, knet_node_id_t host_id); /** * knet_host_remove * * @brief Remove a host ID from knet * * knet_h - pointer to knet_handle_t * * host_id - each host in a knet is identified with a unique ID * (see also knet_handle_new(3)) * * @return * knet_host_remove returns: * 0 on success * -1 on error and errno is set. */ int knet_host_remove(knet_handle_t knet_h, knet_node_id_t host_id); /** * knet_host_set_name * * @brief Set the name of a knet host * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * name - this name will be used for pretty logging and eventually * search for hosts (see also knet_handle_host_get_name(2) and knet_handle_host_get_id(3)). * Only up to KNET_MAX_HOST_LEN - 1 bytes will be accepted and * name has to be unique for each host. * * @return * knet_host_set_name returns: * 0 on success * -1 on error and errno is set. */ int knet_host_set_name(knet_handle_t knet_h, knet_node_id_t host_id, const char *name); /** * knet_host_get_name_by_host_id * * @brief Get the name of a host given its ID * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * name - pointer to a preallocated buffer of at least size KNET_MAX_HOST_LEN * where the current host name will be stored * (as set by knet_host_set_name or default by knet_host_add) * * @return * knet_host_get_name_by_host_id returns: * 0 on success * -1 on error and errno is set (name is left untouched) */ int knet_host_get_name_by_host_id(knet_handle_t knet_h, knet_node_id_t host_id, char *name); /** * knet_host_get_id_by_host_name * * @brief Get the ID of a host given its name * * knet_h - pointer to knet_handle_t * * name - name to lookup, max len KNET_MAX_HOST_LEN * * host_id - where to store the result * * @return * knet_host_get_id_by_host_name returns: * 0 on success * -1 on error and errno is set. */ int knet_host_get_id_by_host_name(knet_handle_t knet_h, const char *name, knet_node_id_t *host_id); /** * knet_host_get_host_list * * @brief Get a list of hosts known to knet * * knet_h - pointer to knet_handle_t * * host_ids - array of at lest KNET_MAX_HOST size * * host_ids_entries - * number of entries writted in host_ids * * @return * knet_host_get_host_list returns * 0 on success * -1 on error and errno is set. */ int knet_host_get_host_list(knet_handle_t knet_h, knet_node_id_t *host_ids, size_t *host_ids_entries); /* * define switching policies */ #define KNET_LINK_POLICY_PASSIVE 0 #define KNET_LINK_POLICY_ACTIVE 1 #define KNET_LINK_POLICY_RR 2 /** * knet_host_set_policy * * @brief Set the switching policy for a host's links * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * policy - there are currently 3 kind of simple switching policies * based on link configuration. * KNET_LINK_POLICY_PASSIVE - the active link with the highest * priority (highest number) will be used. * if one or more active links share * the same priority, the one with * lowest link_id will be used. * * KNET_LINK_POLICY_ACTIVE - all active links will be used * simultaneously to send traffic. * link priority is ignored. * * KNET_LINK_POLICY_RR - round-robin policy, every packet * will be send on a different active * link. * * @return * knet_host_set_policy returns * 0 on success * -1 on error and errno is set. */ int knet_host_set_policy(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t policy); /** * knet_host_get_policy * * @brief Get the switching policy for a host's links * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * policy - will contain the current configured switching policy. * Default is passive when creating a new host. * * @return * knet_host_get_policy returns * 0 on success * -1 on error and errno is set. */ int knet_host_get_policy(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t *policy); /** * knet_host_enable_status_change_notify * * @brief Install a callback to get host status change events * * knet_h - pointer to knet_handle_t * * host_status_change_notify_fn_private_data - * void pointer to data that can be used to identify * the callback * * host_status_change_notify_fn - * is a callback function that is invoked every time * there is a change in the host status. * host status is identified by: * - reachable, this host can send/receive data to/from host_id * - remote, 0 if the host_id is connected locally or 1 if * the there is one or more knet host(s) in between. * NOTE: re-switching is NOT currently implemented, * but this is ready for future and can avoid * an API/ABI breakage later on. * - external, 0 if the host_id is configured locally or 1 if * it has been added from remote nodes config. * NOTE: dynamic topology is NOT currently implemented, * but this is ready for future and can avoid * an API/ABI breakage later on. * This function MUST NEVER block or add substantial delays. * * @return * knet_host_status_change_notify returns * 0 on success * -1 on error and errno is set. */ int knet_host_enable_status_change_notify(knet_handle_t knet_h, void *host_status_change_notify_fn_private_data, void (*host_status_change_notify_fn) ( void *private_data, knet_node_id_t host_id, uint8_t reachable, uint8_t remote, uint8_t external)); /* * define host status structure for quick lookup * struct is in flux as more stats will be added soon * * reachable host_id can be seen either directly connected * or via another host_id * * remote 0 = node is connected locally, 1 is visible via * via another host_id * * external 0 = node is configured/known locally, * 1 host_id has been received via another host_id */ /** * status of a knet host, returned from knet_host_get_status() */ struct knet_host_status { /** Whether the host is currently reachable */ uint8_t reachable; /** Whether the host is a remote node (not currently implemented) */ uint8_t remote; /** Whether the host is external (not currently implemented) */ uint8_t external; /* add host statistics */ }; /** * knet_host_get_status * * @brief Get the status of a host * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * status - pointer to knet_host_status struct * * @return * knet_handle_pmtud_get returns * 0 on success * -1 on error and errno is set. */ int knet_host_get_status(knet_handle_t knet_h, knet_node_id_t host_id, struct knet_host_status *status); /* * link structs/API calls * * every host allocated/managed by knet_host_* has * KNET_MAX_LINK structures to define the network * paths that connect 2 hosts. * * Each link is identified by a link_id that has a * values between 0 and KNET_MAX_LINK - 1. * * KNOWN LIMITATIONS: * * - let's assume the scenario where two hosts are connected * with any number of links. link_id must match on both sides. * If host_id 0 link_id 0 is configured to connect IP1 to IP2 and * host_id 0 link_id 1 is configured to connect IP3 to IP4, * host_id 1 link_id 0 _must_ connect IP2 to IP1 and likewise * host_id 1 link_id 1 _must_ connect IP4 to IP3. * We might be able to lift this restriction in future, by using * other data to determine src/dst link_id, but for now, deal with it. */ /* * commodity functions to convert strings to sockaddr and viceversa */ /** * knet_strtoaddr * * @brief Convert a hostname string to an address * * host - IPaddr/hostname to convert * be aware only the first IP address will be returned * in case a hostname resolves to multiple IP * * port - port to connect to * * ss - sockaddr_storage where to store the converted data * * sslen - len of the sockaddr_storage * * @return * knet_strtoaddr returns same error codes as getaddrinfo * */ int knet_strtoaddr(const char *host, const char *port, struct sockaddr_storage *ss, socklen_t sslen); /** * knet_addrtostr * * @brief Convert an address to a host name * * ss - sockaddr_storage to convert * * sslen - len of the sockaddr_storage * * host - IPaddr/hostname where to store data * (recommended size: KNET_MAX_HOST_LEN) * * port - port buffer where to store data * (recommended size: KNET_MAX_PORT_LEN) * * @return * knet_strtoaddr returns same error codes as getnameinfo */ int knet_addrtostr(const struct sockaddr_storage *ss, socklen_t sslen, char *addr_buf, size_t addr_buf_size, char *port_buf, size_t port_buf_size); #define KNET_TRANSPORT_LOOPBACK 0 #define KNET_TRANSPORT_UDP 1 #define KNET_TRANSPORT_SCTP 2 #define KNET_MAX_TRANSPORTS UINT8_MAX /* * The Loopback transport is only valid for connections to localhost, the host * with the same node_id specified in knet_handle_new(). Only one link of this * type is allowed. Data sent down a LOOPBACK link will be copied directly from * the knet send datafd to the knet receive datafd so the application must be set * up to take data from that socket at least as often as it is sent or deadlocks * could occur. If used, a LOOPBACK link must be the only link configured to the * local host. */ /** * Transport information returned from knet_get_transport_list() */ struct knet_transport_info { /** Transport name. UDP, SCTP, etc... */ const char *name; /** value that can be used for knet_link_set_config() */ uint8_t id; /** currently unused */ uint8_t properties; /** currently unused */ char pad[256]; }; /** * knet_get_transport_list * * @brief Get a list of the transports support by this build of knet * * transport_list - an array of struct transport_info that must be * at least of size struct transport_info * KNET_MAX_TRANSPORTS * * transport_list_entries - pointer to a size_t where to store how many transports * are available in this build of libknet. * * @return * knet_get_transport_list returns * 0 on success * -1 on error and errno is set. */ int knet_get_transport_list(struct knet_transport_info *transport_list, size_t *transport_list_entries); /** * knet_get_transport_name_by_id * * @brief Get a transport name from its ID number * * transport - one of the KNET_TRANSPORT_xxx constants * * @return * knet_get_transport_name_by_id returns: * * @retval pointer to the name on success or * @retval NULL on error and errno is set. */ const char *knet_get_transport_name_by_id(uint8_t transport); /** * knet_get_transport_id_by_name * * @brief Get a transport ID from its name * * name - transport name (UDP/SCTP/etc) * * @return * knet_get_transport_name_by_id returns: * * @retval KNET_MAX_TRANSPORTS on error and errno is set accordingly * @retval KNET_TRANSPORT_xxx on success. */ uint8_t knet_get_transport_id_by_name(const char *name); #define KNET_TRANSPORT_DEFAULT_RECONNECT_INTERVAL 1000 /** * knet_handle_set_transport_reconnect_interval * * @brief Set the interval between transport attempts to reconnect a failed link * * knet_h - pointer to knet_handle_t * * msecs - milliseconds * * @return * knet_handle_set_transport_reconnect_interval returns * 0 on success * -1 on error and errno is set. */ int knet_handle_set_transport_reconnect_interval(knet_handle_t knet_h, uint32_t msecs); /** * knet_handle_get_transport_reconnect_interval * * @brief Get the interval between transport attempts to reconnect a failed link * * knet_h - pointer to knet_handle_t * * msecs - milliseconds * * @return * knet_handle_get_transport_reconnect_interval returns * 0 on success * -1 on error and errno is set. */ int knet_handle_get_transport_reconnect_interval(knet_handle_t knet_h, uint32_t *msecs); /** * knet_link_set_config * * @brief Configure the link to a host * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * transport - one of the KNET_TRANSPORT_xxx constants * * src_addr - sockaddr_storage that can be either IPv4 or IPv6 * * dst_addr - sockaddr_storage that can be either IPv4 or IPv6 * this can be null if we don't know the incoming * IP address/port and the link will remain quiet * till the node on the other end will initiate a * connection * * flags - KNET_LINK_FLAG_* * * @return * knet_link_set_config returns * 0 on success * -1 on error and errno is set. */ int knet_link_set_config(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, uint8_t transport, struct sockaddr_storage *src_addr, struct sockaddr_storage *dst_addr, uint64_t flags); /** * knet_link_get_config * * @brief Get the link configutation information * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * transport - see knet_link_set_config(3) * * src_addr - sockaddr_storage that can be either IPv4 or IPv6 * * dst_addr - sockaddr_storage that can be either IPv4 or IPv6 * * dynamic - 0 if dst_addr is static or 1 if dst_addr is dynamic. * In case of 1, dst_addr can be NULL and it will be left * untouched. * * flags - KNET_LINK_FLAG_* * * @return * knet_link_get_config returns * 0 on success. * -1 on error and errno is set. */ int knet_link_get_config(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, uint8_t *transport, struct sockaddr_storage *src_addr, struct sockaddr_storage *dst_addr, uint8_t *dynamic, uint64_t *flags); /** * knet_link_clear_config * * @brief Clear link information and disconnect the link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * @return * knet_link_clear_config returns * 0 on success. * -1 on error and errno is set. */ int knet_link_clear_config(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id); /* * Access lists management for open links * see also knet_handle_enable_access_lists(3) */ /** * check_type_t * @brief address type enum for knet access lists * * CHECK_TYPE_ADDRESS is the equivalent of a single entry / IP address. * for example: 10.1.9.3 * and the entry is stored in ss1. ss2 can be NULL. * * CHECK_TYPE_MASK is used to configure network/netmask. * for example: 192.168.0.0/24 * the network is stored in ss1 and the netmask in ss2. * * CHECK_TYPE_RANGE defines a value / range of ip addresses. * for example: 172.16.0.1-172.16.0.10 * the start is stored in ss1 and the end in ss2. * * Please be aware that the above examples refer only to IP based protocols. * Other protocols might use ss1 and ss2 in slightly different ways. * At the moment knet only supports IP based protocol, though that might change * in the future. */ typedef enum { CHECK_TYPE_ADDRESS, CHECK_TYPE_MASK, CHECK_TYPE_RANGE } check_type_t; /** * check_acceptreject_t * * @brief enum for accept/reject in knet access lists * * accept or reject incoming packets defined in the access list entry */ typedef enum { CHECK_ACCEPT, CHECK_REJECT } check_acceptreject_t; /** * knet_link_add_acl * * @brief Add access list entry to an open link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * ss1 / ss2 / type / acceptreject - see typedef definitions for details * * IMPORTANT: the order in which access lists are added is critical and it * is left to the user to add them in the right order. knet * will not attempt to logically sort them. * * For example: * 1 - accept from 10.0.0.0/8 * 2 - reject from 10.0.0.1/32 * * is not the same as: * * 1 - reject from 10.0.0.1/32 * 2 - accept from 10.0.0.0/8 * * In the first example, rule number 2 will never match because * packets from 10.0.0.1 will be accepted by rule number 1. * * @return * knet_link_add_acl returns * 0 on success. * -1 on error and errno is set. */ int knet_link_add_acl(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, struct sockaddr_storage *ss1, struct sockaddr_storage *ss2, check_type_t type, check_acceptreject_t acceptreject); /** * knet_link_insert_acl * * @brief Insert access list entry to an open link at given index * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * index - insert at position "index" where 0 is the first entry and -1 * appends to the current list. * * ss1 / ss2 / type / acceptreject - see typedef definitions for details * * @return * knet_link_insert_acl returns * 0 on success. * -1 on error and errno is set. */ int knet_link_insert_acl(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, int index, struct sockaddr_storage *ss1, struct sockaddr_storage *ss2, check_type_t type, check_acceptreject_t acceptreject); /** * knet_link_rm_acl * * @brief Remove access list entry from an open link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * ss1 / ss2 / type / acceptreject - see typedef definitions for details * * IMPORTANT: the data passed to this API call must match exactly that passed * to knet_link_add_acl(3). * * @return * knet_link_rm_acl returns * 0 on success. * -1 on error and errno is set. */ int knet_link_rm_acl(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, struct sockaddr_storage *ss1, struct sockaddr_storage *ss2, check_type_t type, check_acceptreject_t acceptreject); /** * knet_link_clear_acl * * @brief Remove all access list entries from an open link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * @return * knet_link_clear_acl returns * 0 on success. * -1 on error and errno is set. */ int knet_link_clear_acl(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id); /** * knet_link_set_enable * * @brief Enable traffic on a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * enabled - 0 disable the link, 1 enable the link * * @return * knet_link_set_enable returns * 0 on success * -1 on error and errno is set. */ int knet_link_set_enable(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, unsigned int enabled); /** * knet_link_get_enable * * @brief Find out whether a link is enabled or not * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * enabled - 0 disable the link, 1 enable the link * * @return * knet_link_get_enable returns * 0 on success * -1 on error and errno is set. */ int knet_link_get_enable(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, unsigned int *enabled); #define KNET_LINK_DEFAULT_PING_INTERVAL 1000 /* 1 second */ #define KNET_LINK_DEFAULT_PING_TIMEOUT 2000 /* 2 seconds */ #define KNET_LINK_DEFAULT_PING_PRECISION 2048 /* samples */ /** * knet_link_set_ping_timers * * @brief Set the ping timers for a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * interval - specify the ping interval in milliseconds. * * timeout - if no pong is received within this time, * the link is declared dead, in milliseconds. * NOTE: in future it will be possible to set timeout to 0 * for an autocalculated timeout based on interval, pong_count * and latency. The API already accept 0 as value and it will * return ENOSYS / -1. Once the automatic calculation feature * will be implemented, this call will only return EINVAL * for incorrect values. * * precision - how many values of latency are used to calculate * the average link latency (see also knet_link_get_status(3)) * * @return * knet_link_set_ping_timers returns * 0 on success * -1 on error and errno is set. */ int knet_link_set_ping_timers(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, time_t interval, time_t timeout, unsigned int precision); /** * knet_link_get_ping_timers * * @brief Get the ping timers for a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * interval - ping interval * * timeout - if no pong is received within this time, * the link is declared dead * * precision - how many values of latency are used to calculate * the average link latency (see also knet_link_get_status(3)) * * @return * knet_link_get_ping_timers returns * 0 on success * -1 on error and errno is set. */ int knet_link_get_ping_timers(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, time_t *interval, time_t *timeout, unsigned int *precision); #define KNET_LINK_DEFAULT_PONG_COUNT 5 /** * knet_link_set_pong_count * * @brief Set the pong count for a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * pong_count - how many valid ping/pongs before a link is marked UP. * default: 5, value should be > 0 * * @return * knet_link_set_pong_count returns * 0 on success * -1 on error and errno is set. */ int knet_link_set_pong_count(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, uint8_t pong_count); /** * knet_link_get_pong_count * * @brief Get the pong count for a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * pong_count - how many valid ping/pongs before a link is marked UP. * default: 5, value should be > 0 * * @return * knet_link_get_pong_count returns * 0 on success * -1 on error and errno is set. */ int knet_link_get_pong_count(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, uint8_t *pong_count); /** * knet_link_set_priority * * @brief Set the priority for a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * priority - specify the switching priority for this link * see also knet_host_set_policy * * @return * knet_link_set_priority returns * 0 on success * -1 on error and errno is set. */ int knet_link_set_priority(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, uint8_t priority); /** * knet_link_get_priority * * @brief Get the priority for a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * priority - gather the switching priority for this link * see also knet_host_set_policy * * @return * knet_link_get_priority returns * 0 on success * -1 on error and errno is set. */ int knet_link_get_priority(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, uint8_t *priority); /** * knet_link_get_link_list * * @brief Get a list of links connecting a host * * knet_h - pointer to knet_handle_t * * link_ids - array of at lest KNET_MAX_LINK size * with the list of configured links for a certain host. * * link_ids_entries - * number of entries contained in link_ids * * @return * knet_link_get_link_list returns * 0 on success * -1 on error and errno is set. */ int knet_link_get_link_list(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t *link_ids, size_t *link_ids_entries); /* * define link status structure for quick lookup * * src/dst_{ipaddr,port} strings are filled by * getnameinfo(3) when configuring the link. * if the link is dynamic (see knet_link_set_config(3)) * dst_ipaddr/port will contain ipaddr/port of the currently * connected peer or "Unknown" if it was not possible * to determine the ipaddr/port at runtime. * * enabled see also knet_link_set/get_enable. * * connected the link is connected to a peer and ping/pong traffic * is flowing. * * dynconnected the link has dynamic ip on the other end, and * we can see the other host is sending pings to us. * * pong_last if the link is down, this value tells us how long * ago this link was active. A value of 0 means that the link * has never been active. * * knet_link_stats structure that contains details statistics for the link */ #define MAX_LINK_EVENTS 16 /** * Stats for a knet link * returned from knet_link_get_status() as part of a knet_link_status structure * link stats are 'onwire', ie they indicate the number of actual bytes/packets * sent including overheads, not just data packets. */ struct knet_link_stats { /** Number of data packets sent */ uint64_t tx_data_packets; /** Number of data packets received */ uint64_t rx_data_packets; /** Number of data bytes sent */ uint64_t tx_data_bytes; /** Number of data bytes received */ uint64_t rx_data_bytes; /** Number of ping packets sent */ uint64_t rx_ping_packets; /** Number of ping packets received */ uint64_t tx_ping_packets; /** Number of ping bytes sent */ uint64_t rx_ping_bytes; /** Number of ping bytes received */ uint64_t tx_ping_bytes; /** Number of pong packets sent */ uint64_t rx_pong_packets; /** Number of pong packets received */ uint64_t tx_pong_packets; /** Number of pong bytes sent */ uint64_t rx_pong_bytes; /** Number of pong bytes received */ uint64_t tx_pong_bytes; /** Number of pMTU packets sent */ uint64_t rx_pmtu_packets; /** Number of pMTU packets received */ uint64_t tx_pmtu_packets; /** Number of pMTU bytes sent */ uint64_t rx_pmtu_bytes; /** Number of pMTU bytes received */ uint64_t tx_pmtu_bytes; /* These are only filled in when requested ie. they are not collected in realtime */ /** Total of all packets sent */ uint64_t tx_total_packets; /** Total of all packets received */ uint64_t rx_total_packets; /** Total number of bytes sent */ uint64_t tx_total_bytes; /** Total number of bytes received */ uint64_t rx_total_bytes; /** Total number of errors that occurred while sending */ uint64_t tx_total_errors; /** Total number of retries that occurred while sending */ uint64_t tx_total_retries; /** Total number of errors that occurred while sending pMTU packets */ uint32_t tx_pmtu_errors; /** Total number of retries that occurred while sending pMTU packets */ uint32_t tx_pmtu_retries; /** Total number of errors that occurred while sending ping packets */ uint32_t tx_ping_errors; /** Total number of retries that occurred while sending ping packets */ uint32_t tx_ping_retries; /** Total number of errors that occurred while sending pong packets */ uint32_t tx_pong_errors; /** Total number of retries that occurred while sending pong packets */ uint32_t tx_pong_retries; /** Total number of errors that occurred while sending data packets */ uint32_t tx_data_errors; /** Total number of retries that occurred while sending data packets */ uint32_t tx_data_retries; /** Minimum latency measured in usecs */ uint32_t latency_min; /** Maximum latency measured in usecs */ uint32_t latency_max; /** Average(mean) latency measured in usecs */ uint32_t latency_ave; /** Number of samples used to calculate latency */ uint32_t latency_samples; /** How many times the link has gone down */ uint32_t down_count; /** How many times the link has come up */ uint32_t up_count; /** * A circular buffer of time_t structs collecting the history * of up events on this link. * The index indicates current/last event. * it is safe to walk back the history by decreasing the index */ time_t last_up_times[MAX_LINK_EVENTS]; /** * A circular buffer of time_t structs collecting the history * of down events on this link. * The index indicates current/last event. * it is safe to walk back the history by decreasing the index */ time_t last_down_times[MAX_LINK_EVENTS]; /** Index of last element in the last_up_times[] array */ int8_t last_up_time_index; /** Index of last element in the last_down_times[] array */ int8_t last_down_time_index; /* Always add new stats at the end */ }; /** * Status of a knet link as returned from knet_link_get_status() */ struct knet_link_status { /** Size of the structure for ABI checking, set this to sizeof(knet_link_status) before calling knet_link_get_status() */ size_t size; /** Local IP address as a string*/ char src_ipaddr[KNET_MAX_HOST_LEN]; /** Local IP port as a string */ char src_port[KNET_MAX_PORT_LEN]; /** Remote IP address as a string */ char dst_ipaddr[KNET_MAX_HOST_LEN]; /** Remote IP port as a string*/ char dst_port[KNET_MAX_PORT_LEN]; /** Link is configured and admin enabled for traffic */ uint8_t enabled; /** Link is connected for data (local view) */ uint8_t connected; /** Link has been activated by remote dynip */ uint8_t dynconnected; /** Timestamp of the past pong received */ struct timespec pong_last; /** Currently detected MTU on this link */ unsigned int mtu; /** * Contains the size of the IP protocol, knet headers and * crypto headers (if configured). This value is filled in * ONLY after the first PMTUd run on that given link, * and can change if link configuration or crypto configuration * changes at runtime. * WARNING: in general mtu + proto_overhead might or might * not match the output of ifconfig mtu due to crypto * requirements to pad packets to some specific boundaries. */ unsigned int proto_overhead; /** Link statistics */ struct knet_link_stats stats; }; /** * knet_link_get_status * * @brief Get the status (and statistics) for a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * status - pointer to knet_link_status struct * * struct_size - max size of knet_link_status - allows library to * add fields without ABI change. Returned structure * will be truncated to this length and .size member * indicates the full size. * * @return * knet_link_get_status returns * 0 on success * -1 on error and errno is set. */ int knet_link_get_status(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, struct knet_link_status *status, size_t struct_size); /** * knet_link_enable_status_change_notify * * @brief Install a callback to get a link status change events * * knet_h - pointer to knet_handle_t * * host_status_change_notify_fn_private_data - * void pointer to data that can be used to identify * the callback * * host_status_change_notify_fn - * is a callback function that is invoked every time * there is a change in a link status. * host status is identified by: * - connected, 0 if the link has been disconnected, 1 if the link * is connected. * - remote, 0 if the host_id is connected locally or 1 if * the there is one or more knet host(s) in between. * NOTE: re-switching is NOT currently implemented, * but this is ready for future and can avoid * an API/ABI breakage later on. * - external, 0 if the host_id is configured locally or 1 if * it has been added from remote nodes config. * NOTE: dynamic topology is NOT currently implemented, * but this is ready for future and can avoid * an API/ABI breakage later on. * This function MUST NEVER block or add substantial delays. * * @return * knet_host_status_change_notify returns * 0 on success * -1 on error and errno is set. */ int knet_link_enable_status_change_notify(knet_handle_t knet_h, void *link_status_change_notify_fn_private_data, void (*link_status_change_notify_fn) ( void *private_data, knet_node_id_t host_id, uint8_t link_id, uint8_t connected, uint8_t remote, uint8_t external)); /* * logging structs/API calls */ /* * libknet is composed of several subsystems. In order * to easily distinguish log messages coming from different * places, each subsystem has its own ID. * * 0-19 config/management * 20-39 internal threads * 40-59 transports * 60-69 crypto implementations */ #define KNET_SUB_COMMON 0 /* common.c */ #define KNET_SUB_HANDLE 1 /* handle.c alloc/dealloc config changes */ #define KNET_SUB_HOST 2 /* host add/del/modify */ #define KNET_SUB_LISTENER 3 /* listeners add/del/modify... */ #define KNET_SUB_LINK 4 /* link add/del/modify */ #define KNET_SUB_TRANSPORT 5 /* Transport common */ #define KNET_SUB_CRYPTO 6 /* crypto.c config generic layer */ #define KNET_SUB_COMPRESS 7 /* compress.c config generic layer */ #define KNET_SUB_FILTER 19 /* allocated for users to log from dst_filter */ #define KNET_SUB_DSTCACHE 20 /* switching thread (destination cache handling) */ #define KNET_SUB_HEARTBEAT 21 /* heartbeat thread */ #define KNET_SUB_PMTUD 22 /* Path MTU Discovery thread */ #define KNET_SUB_TX 23 /* send to link thread */ #define KNET_SUB_RX 24 /* recv from link thread */ #define KNET_SUB_TRANSP_BASE 40 /* Base log level for transports */ #define KNET_SUB_TRANSP_LOOPBACK (KNET_SUB_TRANSP_BASE + KNET_TRANSPORT_LOOPBACK) #define KNET_SUB_TRANSP_UDP (KNET_SUB_TRANSP_BASE + KNET_TRANSPORT_UDP) #define KNET_SUB_TRANSP_SCTP (KNET_SUB_TRANSP_BASE + KNET_TRANSPORT_SCTP) #define KNET_SUB_NSSCRYPTO 60 /* crypto_nss.c */ #define KNET_SUB_OPENSSLCRYPTO 61 /* crypto_openssl.c */ #define KNET_SUB_GCRYPTCRYPTO 62 /* crypto_gcrypt.c */ #define KNET_SUB_ZLIBCOMP 70 /* compress_zlib.c */ #define KNET_SUB_LZ4COMP 71 /* compress_lz4.c */ #define KNET_SUB_LZ4HCCOMP 72 /* compress_lz4.c */ #define KNET_SUB_LZO2COMP 73 /* compress_lzo.c */ #define KNET_SUB_LZMACOMP 74 /* compress_lzma.c */ #define KNET_SUB_BZIP2COMP 75 /* compress_bzip2.c */ #define KNET_SUB_ZSTDCOMP 76 /* compress_zstd.c */ #define KNET_SUB_UNKNOWN UINT8_MAX - 1 #define KNET_MAX_SUBSYSTEMS UINT8_MAX /* * Convert between subsystem IDs and names */ /** * knet_log_get_subsystem_name * * @brief Get a logging system name from its numeric ID * * @return * returns internal name of the subsystem or "common" */ const char *knet_log_get_subsystem_name(uint8_t subsystem); /** * knet_log_get_subsystem_id * * @brief Get a logging system ID from its name * * @return * returns internal ID of the subsystem or KNET_SUB_COMMON */ uint8_t knet_log_get_subsystem_id(const char *name); /* * 4 log levels are enough for everybody */ #define KNET_LOG_ERR 0 /* unrecoverable errors/conditions */ #define KNET_LOG_WARN 1 /* recoverable errors/conditions */ #define KNET_LOG_INFO 2 /* info, link up/down, config changes.. */ #define KNET_LOG_DEBUG 3 /* * Convert between log level values and names */ /** * knet_log_get_loglevel_name * * @brief Get a logging level name from its numeric ID * * @return * returns internal name of the log level or "ERROR" for unknown values */ const char *knet_log_get_loglevel_name(uint8_t level); /** * knet_log_get_loglevel_id * * @brief Get a logging level ID from its name * * @return * returns internal log level ID or KNET_LOG_ERR for invalid names */ uint8_t knet_log_get_loglevel_id(const char *name); /* * every log message is composed by a text message * and message level/subsystem IDs. * In order to make debugging easier it is possible to send those packets * straight to stdout/stderr (see knet_bench.c stdout option). */ #define KNET_MAX_LOG_MSG_SIZE 254 #if KNET_MAX_LOG_MSG_SIZE > PIPE_BUF #error KNET_MAX_LOG_MSG_SIZE cannot be bigger than PIPE_BUF for guaranteed system atomic writes #endif /** * Structure of a log message sent to the logging fd */ struct knet_log_msg { /** Text of the log message */ char msg[KNET_MAX_LOG_MSG_SIZE]; /** Subsystem that sent this message. KNET_SUB_* */ uint8_t subsystem; /** Logging level of this message. KNET_LOG_* */ uint8_t msglevel; /** Pointer to the handle generating the log message */ knet_handle_t knet_h; }; /** * knet_log_set_loglevel * * @brief Set the logging level for a subsystem * * knet_h - same as above * * subsystem - same as above * * level - same as above * * knet_log_set_loglevel allows fine control of log levels by subsystem. * See also knet_handle_new for defaults. * * @return * knet_log_set_loglevel returns * 0 on success * -1 on error and errno is set. */ int knet_log_set_loglevel(knet_handle_t knet_h, uint8_t subsystem, uint8_t level); /** * knet_log_get_loglevel * * @brief Get the logging level for a subsystem * * knet_h - same as above * * subsystem - same as above * * level - same as above * * @return * knet_log_get_loglevel returns * 0 on success * -1 on error and errno is set. */ int knet_log_get_loglevel(knet_handle_t knet_h, uint8_t subsystem, uint8_t *level); #endif diff --git a/libknet/tests/api-check.mk b/libknet/tests/api-check.mk index 8850a4ff..6f01565e 100644 --- a/libknet/tests/api-check.mk +++ b/libknet/tests/api-check.mk @@ -1,310 +1,318 @@ # # Copyright (C) 2016-2021 Red Hat, Inc. All rights reserved. # # Authors: Fabio M. Di Nitto # # This software licensed under GPL-2.0+ # api_checks = \ api_knet_handle_new_test \ api_knet_handle_free_test \ api_knet_handle_compress_test \ api_knet_handle_setfwd_test \ api_knet_handle_enable_access_lists_test \ api_knet_handle_enable_filter_test \ api_knet_handle_enable_sock_notify_test \ api_knet_handle_add_datafd_test \ api_knet_handle_remove_datafd_test \ api_knet_handle_get_channel_test \ api_knet_handle_get_datafd_test \ api_knet_handle_get_stats_test \ api_knet_get_crypto_list_test \ api_knet_get_compress_list_test \ api_knet_handle_clear_stats_test \ api_knet_get_transport_list_test \ api_knet_get_transport_name_by_id_test \ api_knet_get_transport_id_by_name_test \ api_knet_handle_set_transport_reconnect_interval_test \ api_knet_handle_get_transport_reconnect_interval_test \ api_knet_recv_test \ api_knet_send_test \ api_knet_send_crypto_test \ api_knet_send_compress_test \ api_knet_send_sync_test \ api_knet_send_loopback_test \ api_knet_handle_pmtud_setfreq_test \ api_knet_handle_pmtud_getfreq_test \ api_knet_handle_enable_pmtud_notify_test \ api_knet_handle_pmtud_get_test \ api_knet_handle_pmtud_set_test \ api_knet_host_add_test \ api_knet_host_remove_test \ api_knet_host_set_name_test \ api_knet_host_get_name_by_host_id_test \ api_knet_host_get_id_by_host_name_test \ api_knet_host_get_host_list_test \ api_knet_host_set_policy_test \ api_knet_host_get_policy_test \ api_knet_host_get_status_test \ api_knet_host_enable_status_change_notify_test \ api_knet_log_get_subsystem_name_test \ api_knet_log_get_subsystem_id_test \ api_knet_log_get_loglevel_name_test \ api_knet_log_get_loglevel_id_test \ api_knet_log_set_loglevel_test \ api_knet_log_get_loglevel_test \ api_knet_strtoaddr_test \ api_knet_addrtostr_test \ api_knet_link_set_config_test \ api_knet_link_clear_config_test \ api_knet_link_get_config_test \ api_knet_link_set_ping_timers_test \ api_knet_link_get_ping_timers_test \ api_knet_link_set_pong_count_test \ api_knet_link_get_pong_count_test \ api_knet_link_set_priority_test \ api_knet_link_get_priority_test \ api_knet_link_set_enable_test \ api_knet_link_get_enable_test \ api_knet_link_get_link_list_test \ api_knet_link_get_status_test \ api_knet_link_enable_status_change_notify_test \ api_knet_handle_set_threads_timer_res_test \ api_knet_handle_get_threads_timer_res_test \ api_knet_link_add_acl_test \ api_knet_link_insert_acl_test \ api_knet_link_rm_acl_test \ api_knet_link_clear_acl_test \ api_knet_handle_crypto_set_config_test \ api_knet_handle_crypto_use_config_test \ api_knet_handle_crypto_rx_clear_traffic_test \ api_knet_handle_enable_onwire_ver_notify_test \ api_knet_handle_get_onwire_ver_test \ - api_knet_handle_set_onwire_ver_test + api_knet_handle_set_onwire_ver_test \ + api_knet_handle_get_host_defrag_bufs_test \ + api_knet_handle_set_host_defrag_bufs_test api_knet_handle_new_test_SOURCES = api_knet_handle_new.c \ test-common.c api_knet_handle_free_test_SOURCES = api_knet_handle_free.c \ test-common.c api_knet_handle_new_limit_test_SOURCES = api_knet_handle_new_limit.c \ test-common.c api_knet_handle_compress_test_SOURCES = api_knet_handle_compress.c \ test-common.c api_knet_handle_setfwd_test_SOURCES = api_knet_handle_setfwd.c \ test-common.c api_knet_handle_enable_access_lists_test_SOURCES = api_knet_handle_enable_access_lists.c \ test-common.c api_knet_handle_enable_filter_test_SOURCES = api_knet_handle_enable_filter.c \ test-common.c api_knet_handle_enable_sock_notify_test_SOURCES = api_knet_handle_enable_sock_notify.c \ test-common.c api_knet_handle_add_datafd_test_SOURCES = api_knet_handle_add_datafd.c \ test-common.c api_knet_handle_remove_datafd_test_SOURCES = api_knet_handle_remove_datafd.c \ test-common.c api_knet_handle_get_channel_test_SOURCES = api_knet_handle_get_channel.c \ test-common.c api_knet_handle_get_datafd_test_SOURCES = api_knet_handle_get_datafd.c \ test-common.c api_knet_handle_get_stats_test_SOURCES = api_knet_handle_get_stats.c \ test-common.c api_knet_get_crypto_list_test_SOURCES = api_knet_get_crypto_list.c \ test-common.c api_knet_get_compress_list_test_SOURCES = api_knet_get_compress_list.c \ test-common.c api_knet_handle_clear_stats_test_SOURCES = api_knet_handle_clear_stats.c \ test-common.c api_knet_get_transport_list_test_SOURCES = api_knet_get_transport_list.c \ test-common.c api_knet_get_transport_name_by_id_test_SOURCES = api_knet_get_transport_name_by_id.c \ test-common.c api_knet_get_transport_id_by_name_test_SOURCES = api_knet_get_transport_id_by_name.c \ test-common.c api_knet_handle_set_transport_reconnect_interval_test_SOURCES = api_knet_handle_set_transport_reconnect_interval.c \ test-common.c api_knet_handle_get_transport_reconnect_interval_test_SOURCES = api_knet_handle_get_transport_reconnect_interval.c \ test-common.c api_knet_recv_test_SOURCES = api_knet_recv.c \ test-common.c api_knet_send_test_SOURCES = api_knet_send.c \ test-common.c api_knet_send_compress_test_SOURCES = api_knet_send_compress.c \ test-common.c api_knet_send_crypto_test_SOURCES = api_knet_send_crypto.c \ test-common.c api_knet_send_loopback_test_SOURCES = api_knet_send_loopback.c \ test-common.c api_knet_send_sync_test_SOURCES = api_knet_send_sync.c \ test-common.c api_knet_handle_pmtud_setfreq_test_SOURCES = api_knet_handle_pmtud_setfreq.c \ test-common.c api_knet_handle_pmtud_getfreq_test_SOURCES = api_knet_handle_pmtud_getfreq.c \ test-common.c api_knet_handle_enable_pmtud_notify_test_SOURCES = api_knet_handle_enable_pmtud_notify.c \ test-common.c api_knet_handle_pmtud_get_test_SOURCES = api_knet_handle_pmtud_get.c \ test-common.c api_knet_handle_pmtud_set_test_SOURCES = api_knet_handle_pmtud_set.c \ test-common.c api_knet_host_add_test_SOURCES = api_knet_host_add.c \ test-common.c api_knet_host_remove_test_SOURCES = api_knet_host_remove.c \ test-common.c api_knet_host_set_name_test_SOURCES = api_knet_host_set_name.c \ test-common.c api_knet_host_get_name_by_host_id_test_SOURCES = api_knet_host_get_name_by_host_id.c \ test-common.c api_knet_host_get_id_by_host_name_test_SOURCES = api_knet_host_get_id_by_host_name.c \ test-common.c api_knet_host_get_host_list_test_SOURCES = api_knet_host_get_host_list.c \ test-common.c api_knet_host_set_policy_test_SOURCES = api_knet_host_set_policy.c \ test-common.c api_knet_host_get_policy_test_SOURCES = api_knet_host_get_policy.c \ test-common.c api_knet_host_get_status_test_SOURCES = api_knet_host_get_status.c \ test-common.c api_knet_host_enable_status_change_notify_test_SOURCES = api_knet_host_enable_status_change_notify.c \ test-common.c api_knet_log_get_subsystem_name_test_SOURCES = api_knet_log_get_subsystem_name.c \ test-common.c api_knet_log_get_subsystem_id_test_SOURCES = api_knet_log_get_subsystem_id.c \ test-common.c api_knet_log_get_loglevel_name_test_SOURCES = api_knet_log_get_loglevel_name.c \ test-common.c api_knet_log_get_loglevel_id_test_SOURCES = api_knet_log_get_loglevel_id.c \ test-common.c api_knet_log_set_loglevel_test_SOURCES = api_knet_log_set_loglevel.c \ test-common.c api_knet_log_get_loglevel_test_SOURCES = api_knet_log_get_loglevel.c \ test-common.c api_knet_strtoaddr_test_SOURCES = api_knet_strtoaddr.c api_knet_addrtostr_test_SOURCES = api_knet_addrtostr.c api_knet_link_set_config_test_SOURCES = api_knet_link_set_config.c \ test-common.c api_knet_link_clear_config_test_SOURCES = api_knet_link_clear_config.c \ test-common.c api_knet_link_get_config_test_SOURCES = api_knet_link_get_config.c \ test-common.c api_knet_link_set_ping_timers_test_SOURCES = api_knet_link_set_ping_timers.c \ test-common.c api_knet_link_get_ping_timers_test_SOURCES = api_knet_link_get_ping_timers.c \ test-common.c api_knet_link_set_pong_count_test_SOURCES = api_knet_link_set_pong_count.c \ test-common.c api_knet_link_get_pong_count_test_SOURCES = api_knet_link_get_pong_count.c \ test-common.c api_knet_link_set_priority_test_SOURCES = api_knet_link_set_priority.c \ test-common.c api_knet_link_get_priority_test_SOURCES = api_knet_link_get_priority.c \ test-common.c api_knet_link_set_enable_test_SOURCES = api_knet_link_set_enable.c \ test-common.c api_knet_link_get_enable_test_SOURCES = api_knet_link_get_enable.c \ test-common.c api_knet_link_get_link_list_test_SOURCES = api_knet_link_get_link_list.c \ test-common.c api_knet_link_get_status_test_SOURCES = api_knet_link_get_status.c \ test-common.c api_knet_link_enable_status_change_notify_test_SOURCES = api_knet_link_enable_status_change_notify.c \ test-common.c api_knet_handle_set_threads_timer_res_test_SOURCES = api_knet_handle_set_threads_timer_res.c \ test-common.c api_knet_handle_get_threads_timer_res_test_SOURCES = api_knet_handle_get_threads_timer_res.c \ test-common.c api_knet_link_add_acl_test_SOURCES = api_knet_link_add_acl.c \ test-common.c api_knet_link_insert_acl_test_SOURCES = api_knet_link_insert_acl.c \ test-common.c api_knet_link_rm_acl_test_SOURCES = api_knet_link_rm_acl.c \ test-common.c api_knet_link_clear_acl_test_SOURCES = api_knet_link_clear_acl.c \ test-common.c api_knet_handle_crypto_set_config_test_SOURCES = api_knet_handle_crypto_set_config.c \ test-common.c api_knet_handle_crypto_use_config_test_SOURCES = api_knet_handle_crypto_use_config.c \ test-common.c api_knet_handle_crypto_rx_clear_traffic_test_SOURCES = api_knet_handle_crypto_rx_clear_traffic.c \ test-common.c api_knet_handle_enable_onwire_ver_notify_test_SOURCES = api_knet_handle_enable_onwire_ver_notify.c \ test-common.c api_knet_handle_get_onwire_ver_test_SOURCES = api_knet_handle_get_onwire_ver.c \ test-common.c api_knet_handle_set_onwire_ver_test_SOURCES = api_knet_handle_set_onwire_ver.c \ test-common.c + +api_knet_handle_get_host_defrag_bufs_test_SOURCES = api_knet_handle_get_host_defrag_bufs.c \ + test-common.c + +api_knet_handle_set_host_defrag_bufs_test_SOURCES = api_knet_handle_set_host_defrag_bufs.c \ + test-common.c diff --git a/libknet/tests/api_knet_handle_get_host_defrag_bufs.c b/libknet/tests/api_knet_handle_get_host_defrag_bufs.c new file mode 100644 index 00000000..7e094e01 --- /dev/null +++ b/libknet/tests/api_knet_handle_get_host_defrag_bufs.c @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2020-2021 Red Hat, Inc. All rights reserved. + * + * Authors: Fabio M. Di Nitto + * + * This software licensed under GPL-2.0+ + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "libknet.h" + +#include "internals.h" +#include "test-common.h" + +static void test(void) +{ + knet_handle_t knet_h; + int logfds[2]; + uint16_t min_defrag_bufs, max_defrag_bufs; + uint8_t shrink_threshold; + defrag_bufs_reclaim_policy_t reclaim_policy; + + printf("Test knet_handle_get_host_defrag_bufs incorrect knet_h\n"); + + if ((!knet_handle_get_host_defrag_bufs(NULL, &min_defrag_bufs, &max_defrag_bufs, &shrink_threshold, &reclaim_policy)) || (errno != EINVAL)) { + printf("knet_handle_get_host_defrag_bufs accepted invalid knet_h or returned incorrect error: %s\n", strerror(errno)); + exit(FAIL); + } + + setup_logpipes(logfds); + + knet_h = knet_handle_start(logfds, KNET_LOG_DEBUG); + + printf("Test knet_handle_get_host_defrag_bufs with invalid min_defrag_bufs\n"); + + if ((!knet_handle_get_host_defrag_bufs(knet_h, NULL, &max_defrag_bufs, &shrink_threshold, &reclaim_policy)) || (errno != EINVAL)) { + printf("knet_handle_get_host_defrag_bufs accepted invalid min_defrag_bufs or returned incorrect error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + printf("Test knet_handle_get_host_defrag_bufs with invalid max_defrag_bufs\n"); + + if ((!knet_handle_get_host_defrag_bufs(knet_h, &min_defrag_bufs, NULL, &shrink_threshold, &reclaim_policy)) || (errno != EINVAL)) { + printf("knet_handle_get_host_defrag_bufs accepted invalid max_defrag_bufs or returned incorrect error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + printf("Test knet_handle_get_host_defrag_bufs with invalid shrink_threshold\n"); + + if ((!knet_handle_get_host_defrag_bufs(knet_h, &min_defrag_bufs, &max_defrag_bufs, NULL, &reclaim_policy)) || (errno != EINVAL)) { + printf("knet_handle_get_host_defrag_bufs accepted invalid shrink_threshold or returned incorrect error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + printf("Test knet_handle_get_host_defrag_bufs with invalid reclaim_policy\n"); + + if ((!knet_handle_get_host_defrag_bufs(knet_h, &min_defrag_bufs, &max_defrag_bufs, &shrink_threshold, NULL)) || (errno != EINVAL)) { + printf("knet_handle_get_host_defrag_bufs accepted invalid reclaim_policy or returned incorrect error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + printf("Test knet_handle_get_host_defrag_bufs with valid data\n"); + + if (knet_handle_get_host_defrag_bufs(knet_h, &min_defrag_bufs, &max_defrag_bufs, &shrink_threshold, &reclaim_policy) < 0) { + printf("knet_handle_get_host_defrag_bufs did not accepted valid data. error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + printf("Test knet_handle_get_host_defrag_bufs default data\n"); + + if ((min_defrag_bufs != KNET_MIN_DEFRAG_BUFS_DEFAULT) || + (max_defrag_bufs != KNET_MAX_DEFRAG_BUFS_DEFAULT) || + (shrink_threshold != KNET_SHRINK_THRESHOLD_DEFAULT) || + (reclaim_policy != RECLAIM_POLICY_ABSOLUTE)) { + printf("knet_handle_get_host_defrag_bufs returned incorrect default data\n"); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + knet_h->defrag_bufs_reclaim_policy = RECLAIM_POLICY_AVERAGE; + + printf("Test knet_handle_get_host_defrag_bufs with reclaim_policy override\n"); + + if (knet_handle_get_host_defrag_bufs(knet_h, &min_defrag_bufs, &max_defrag_bufs, &shrink_threshold, &reclaim_policy) < 0) { + printf("knet_handle_get_host_defrag_bufs did not accepted valid data. error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + if ((min_defrag_bufs != KNET_MIN_DEFRAG_BUFS_DEFAULT) || + (max_defrag_bufs != KNET_MAX_DEFRAG_BUFS_DEFAULT) || + (shrink_threshold != KNET_SHRINK_THRESHOLD_DEFAULT) || + (reclaim_policy != RECLAIM_POLICY_AVERAGE)) { + printf("knet_handle_get_host_defrag_bufs returned incorrect default data\n"); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); +} + +int main(int argc, char *argv[]) +{ + test(); + + return PASS; +} diff --git a/libknet/tests/api_knet_handle_set_host_defrag_bufs.c b/libknet/tests/api_knet_handle_set_host_defrag_bufs.c new file mode 100644 index 00000000..883703ab --- /dev/null +++ b/libknet/tests/api_knet_handle_set_host_defrag_bufs.c @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2020-2021 Red Hat, Inc. All rights reserved. + * + * Authors: Fabio M. Di Nitto + * + * This software licensed under GPL-2.0+ + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "libknet.h" + +#include "internals.h" +#include "test-common.h" + +static void test(void) +{ + knet_handle_t knet_h; + int logfds[2]; + uint16_t min_defrag_bufs = KNET_MIN_DEFRAG_BUFS_DEFAULT, max_defrag_bufs = KNET_MAX_DEFRAG_BUFS_DEFAULT; + uint8_t shrink_threshold = KNET_SHRINK_THRESHOLD_DEFAULT; + defrag_bufs_reclaim_policy_t reclaim_policy = RECLAIM_POLICY_ABSOLUTE; + + printf("Test knet_handle_set_host_defrag_bufs incorrect knet_h\n"); + + if ((!knet_handle_set_host_defrag_bufs(NULL, min_defrag_bufs, max_defrag_bufs, shrink_threshold, reclaim_policy)) || (errno != EINVAL)) { + printf("knet_handle_set_host_defrag_bufs accepted invalid knet_h or returned incorrect error: %s\n", strerror(errno)); + exit(FAIL); + } + + setup_logpipes(logfds); + + knet_h = knet_handle_start(logfds, KNET_LOG_DEBUG); + + printf("Test knet_handle_set_host_defrag_bufs with invalid min_defrag_bufs (0)\n"); + + if ((!knet_handle_set_host_defrag_bufs(knet_h, 0, max_defrag_bufs, shrink_threshold, reclaim_policy)) || (errno != EINVAL)) { + printf("knet_handle_set_host_defrag_bufs accepted invalid min_defrag_bufs or returned incorrect error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + printf("Test knet_handle_set_host_defrag_bufs with invalid min_defrag_bufs (3 - not power of 2)\n"); + + if ((!knet_handle_set_host_defrag_bufs(knet_h, 3, max_defrag_bufs, shrink_threshold, reclaim_policy)) || (errno != EINVAL)) { + printf("knet_handle_set_host_defrag_bufs accepted invalid min_defrag_bufs or returned incorrect error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + printf("Test knet_handle_set_host_defrag_bufs with invalid min_defrag_bufs (> max_defrag_bufs)\n"); + + if ((!knet_handle_set_host_defrag_bufs(knet_h, max_defrag_bufs * 2, max_defrag_bufs, shrink_threshold, reclaim_policy)) || (errno != EINVAL)) { + printf("knet_handle_set_host_defrag_bufs accepted invalid min_defrag_bufs or returned incorrect error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + printf("Test knet_handle_set_host_defrag_bufs with invalid max_defrag_bufs (0)\n"); + + if ((!knet_handle_set_host_defrag_bufs(knet_h, min_defrag_bufs, 0, shrink_threshold, reclaim_policy)) || (errno != EINVAL)) { + printf("knet_handle_set_host_defrag_bufs accepted invalid max_defrag_bufs or returned incorrect error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + printf("Test knet_handle_set_host_defrag_bufs with invalid max_defrag_bufs (min_defrag_bufs + 1)\n"); + + if ((!knet_handle_set_host_defrag_bufs(knet_h, min_defrag_bufs, min_defrag_bufs + 1, shrink_threshold, reclaim_policy)) || (errno != EINVAL)) { + printf("knet_handle_set_host_defrag_bufs accepted invalid max_defrag_bufs or returned incorrect error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + printf("Test knet_handle_set_host_defrag_bufs with invalid max_defrag_bufs (< min_defrag_bufs)\n"); + + if ((!knet_handle_set_host_defrag_bufs(knet_h, min_defrag_bufs, min_defrag_bufs / 2, shrink_threshold, reclaim_policy)) || (errno != EINVAL)) { + printf("knet_handle_set_host_defrag_bufs accepted invalid max_defrag_bufs or returned incorrect error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + + printf("Test knet_handle_set_host_defrag_bufs with invalid shrink_threshold (0)\n"); + + if ((!knet_handle_set_host_defrag_bufs(knet_h, min_defrag_bufs, max_defrag_bufs, 0, reclaim_policy)) || (errno != EINVAL)) { + printf("knet_handle_set_host_defrag_bufs accepted invalid shrink_threshold or returned incorrect error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + printf("Test knet_handle_set_host_defrag_bufs with invalid shrink_threshold (51)\n"); + + if ((!knet_handle_set_host_defrag_bufs(knet_h, min_defrag_bufs, max_defrag_bufs, 51, reclaim_policy)) || (errno != EINVAL)) { + printf("knet_handle_set_host_defrag_bufs accepted invalid shrink_threshold or returned incorrect error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + printf("Test knet_handle_set_host_defrag_bufs with invalid reclaim_policy (20)\n"); + + if ((!knet_handle_set_host_defrag_bufs(knet_h, min_defrag_bufs, max_defrag_bufs, shrink_threshold, 20)) || (errno != EINVAL)) { + printf("knet_handle_set_host_defrag_bufs accepted invalid usage_samples_timespan or returned incorrect error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + printf("Test knet_handle_set_host_defrag_bufs with valid data (defaults)\n"); + + if (knet_handle_set_host_defrag_bufs(knet_h, min_defrag_bufs, max_defrag_bufs, shrink_threshold, reclaim_policy) < 0) { + printf("knet_handle_set_host_defrag_bufs did not accepted valid data. error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + printf("Test knet_handle_set_host_defrag_bufs default data\n"); + + if ((knet_h->defrag_bufs_min != KNET_MIN_DEFRAG_BUFS_DEFAULT) || + (knet_h->defrag_bufs_max != KNET_MAX_DEFRAG_BUFS_DEFAULT) || + (knet_h->defrag_bufs_shrink_threshold != KNET_SHRINK_THRESHOLD_DEFAULT) || + (knet_h->defrag_bufs_reclaim_policy != RECLAIM_POLICY_ABSOLUTE)) { + printf("knet_handle_set_host_defrag_bufs set incorrect default data\n"); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + printf("Test knet_handle_set_host_defrag_bufs with reclaim_policy override\n"); + + if (knet_handle_set_host_defrag_bufs(knet_h, min_defrag_bufs, max_defrag_bufs, shrink_threshold, RECLAIM_POLICY_AVERAGE) < 0) { + printf("knet_handle_set_host_defrag_bufs did not accepted valid data. error: %s\n", strerror(errno)); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + flush_logs(logfds[0], stdout); + + if ((knet_h->defrag_bufs_min != KNET_MIN_DEFRAG_BUFS_DEFAULT) || + (knet_h->defrag_bufs_max != KNET_MAX_DEFRAG_BUFS_DEFAULT) || + (knet_h->defrag_bufs_shrink_threshold != KNET_SHRINK_THRESHOLD_DEFAULT) || + (knet_h->defrag_bufs_reclaim_policy != RECLAIM_POLICY_AVERAGE)) { + printf("knet_handle_set_host_defrag_bufs set incorrect reclaim_policy override\n"); + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); + exit(FAIL); + } + + knet_handle_free(knet_h); + flush_logs(logfds[0], stdout); + close_logpipes(logfds); +} + +int main(int argc, char *argv[]) +{ + test(); + + return PASS; +} diff --git a/libknet/tests/knet_bench.c b/libknet/tests/knet_bench.c index 644e796c..ee3fa2e4 100644 --- a/libknet/tests/knet_bench.c +++ b/libknet/tests/knet_bench.c @@ -1,1389 +1,1394 @@ /* * Copyright (C) 2016-2021 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "libknet.h" #include "compat.h" #include "internals.h" #include "netutils.h" #include "transport_common.h" #include "test-common.h" #define MAX_NODES 128 static int senderid = -1; static int thisnodeid = -1; static knet_handle_t knet_h; static int datafd = 0; static int8_t channel = 0; static int globallistener = 0; static int continous = 0; static int show_stats = 0; static struct sockaddr_storage allv4; static struct sockaddr_storage allv6; static int broadcast_test = 1; static pthread_t rx_thread = (pthread_t)NULL; static char *rx_buf[PCKT_FRAG_MAX]; static int wait_for_perf_rx = 0; static char *compresscfg = NULL; static char *cryptocfg = NULL; static int machine_output = 0; static int use_access_lists = 0; static int use_pckt_verification = 0; static int bench_shutdown_in_progress = 0; static pthread_mutex_t shutdown_mutex = PTHREAD_MUTEX_INITIALIZER; #define TEST_PING 0 #define TEST_PING_AND_DATA 1 #define TEST_PERF_BY_SIZE 2 #define TEST_PERF_BY_TIME 3 static int test_type = TEST_PING; #define TEST_START 2 #define TEST_STOP 4 #define TEST_COMPLETE 6 #define ONE_GIGABYTE 1073741824 static uint64_t perf_by_size_size = 1 * ONE_GIGABYTE; static uint64_t perf_by_time_secs = 10; static uint32_t force_packet_size = 0; struct node { int nodeid; int links; uint8_t transport[KNET_MAX_LINK]; struct sockaddr_storage address[KNET_MAX_LINK]; }; struct pckt_ver { uint32_t len; uint32_t chksum; }; static void print_help(void) { printf("knet_bench usage:\n"); printf(" -h print this help (no really)\n"); printf(" -d enable debug logs (default INFO)\n"); printf(" -f enable use of access lists (default: off)\n"); printf(" -c [implementation]:[crypto]:[hashing] crypto configuration. (default disabled)\n"); printf(" Example: -c nss:aes128:sha1\n"); printf(" -z [implementation]:[level]:[threshold] compress configuration. (default disabled)\n"); printf(" Example: -z zlib:5:100\n"); printf(" -p [active|passive|rr] (default: passive)\n"); printf(" -P [UDP|SCTP] (default: UDP) protocol (transport) to use for all links\n"); printf(" -t [nodeid] This nodeid (required)\n"); printf(" -n [nodeid],[proto]/[link1_ip],[link2_..] Other nodes information (at least one required)\n"); printf(" Example: -n 1,192.168.8.1,SCTP/3ffe::8:1,UDP/172...\n"); printf(" can be repeated up to %d and should contain also the localnode info\n", MAX_NODES); printf(" -b [port] baseport (default: 50000)\n"); printf(" -l enable global listener on 0.0.0.0/:: (default: off, incompatible with -o)\n"); printf(" -o enable baseport offset per nodeid\n"); printf(" -m change PMTUd interval in seconds (default: 60)\n"); printf(" -w dont wait for all nodes to be up before starting the test (default: wait)\n"); printf(" -T [ping|ping_data|perf-by-size|perf-by-time]\n"); printf(" test type (default: ping)\n"); printf(" ping: will wait for all hosts to join the knet network, sleep 5 seconds and quit\n"); printf(" ping_data: will wait for all hosts to join the knet network, sends some data to all nodes and quit\n"); printf(" perf-by-size: will wait for all hosts to join the knet network,\n"); printf(" perform a series of benchmarks by transmitting a known\n"); printf(" size/quantity of packets and measuring the time, then quit\n"); printf(" perf-by-time: will wait for all hosts to join the knet network,\n"); printf(" perform a series of benchmarks by transmitting a known\n"); printf(" size of packets for a given amount of time (10 seconds)\n"); printf(" and measuring the quantity of data transmitted, then quit\n"); printf(" -s nodeid that will generate traffic for benchmarks\n"); printf(" -S [size|seconds] when used in combination with -T perf-by-size it indicates how many GB of traffic to generate for the test. (default: 1GB)\n"); printf(" when used in combination with -T perf-by-time it indicates how many Seconds of traffic to generate for the test. (default: 10 seconds)\n"); printf(" -x force packet size for perf-by-time or perf-by-size\n"); printf(" -C repeat the test continously (default: off)\n"); printf(" -X[XX] show stats at the end of the run (default: 1)\n"); printf(" 1: show handle stats, 2: show summary link stats\n"); printf(" 3: show detailed link stats\n"); printf(" -a enable machine parsable output (default: off).\n"); printf(" -v enable packet verification for performance tests (default: off).\n"); } static void parse_nodes(char *nodesinfo[MAX_NODES], int onidx, int port, struct node nodes[MAX_NODES], int *thisidx) { int i; char *temp = NULL; char port_str[10]; memset(port_str, 0, sizeof(port_str)); sprintf(port_str, "%d", port); for (i = 0; i < onidx; i++) { nodes[i].nodeid = atoi(strtok(nodesinfo[i], ",")); if ((nodes[i].nodeid < 0) || (nodes[i].nodeid > KNET_MAX_HOST)) { printf("Invalid nodeid: %d (0 - %d)\n", nodes[i].nodeid, KNET_MAX_HOST); exit(FAIL); } if (thisnodeid == nodes[i].nodeid) { *thisidx = i; } while((temp = strtok(NULL, ","))) { char *slash = NULL; uint8_t transport; if (nodes[i].links == KNET_MAX_LINK) { printf("Too many links configured. Max %d\n", KNET_MAX_LINK); exit(FAIL); } slash = strstr(temp, "/"); if (slash) { memset(slash, 0, 1); transport = knet_get_transport_id_by_name(temp); if (transport == KNET_MAX_TRANSPORTS) { printf("Unknown transport: %s\n", temp); exit(FAIL); } nodes[i].transport[nodes[i].links] = transport; temp = slash + 1; } else { nodes[i].transport[nodes[i].links] = KNET_TRANSPORT_UDP; } if (knet_strtoaddr(temp, port_str, &nodes[i].address[nodes[i].links], sizeof(struct sockaddr_storage)) < 0) { printf("Unable to convert %s to sockaddress\n", temp); exit(FAIL); } nodes[i].links++; } } if (knet_strtoaddr("0.0.0.0", port_str, &allv4, sizeof(struct sockaddr_storage)) < 0) { printf("Unable to convert 0.0.0.0 to sockaddress\n"); exit(FAIL); } if (knet_strtoaddr("::", port_str, &allv6, sizeof(struct sockaddr_storage)) < 0) { printf("Unable to convert :: to sockaddress\n"); exit(FAIL); } for (i = 1; i < onidx; i++) { if (nodes[0].links != nodes[i].links) { printf("knet_bench does not support unbalanced link configuration\n"); exit(FAIL); } } return; } static int private_data; static void sock_notify(void *pvt_data, int local_datafd, int8_t local_channel, uint8_t tx_rx, int error, int errorno) { printf("[info]: error (%d - %d - %s) from socket: %d\n", error, errorno, strerror(errno), local_datafd); return; } static int ping_dst_host_filter(void *pvt_data, const unsigned char *outdata, ssize_t outdata_len, uint8_t tx_rx, knet_node_id_t this_host_id, knet_node_id_t src_host_id, int8_t *dst_channel, knet_node_id_t *dst_host_ids, size_t *dst_host_ids_entries) { if (broadcast_test) { return 1; } if (tx_rx == KNET_NOTIFY_TX) { memmove(&dst_host_ids[0], outdata, 2); } else { dst_host_ids[0] = this_host_id; } *dst_host_ids_entries = 1; return 0; } static void setup_knet(int argc, char *argv[]) { int logfd = 0; int rv; char *policystr = NULL, *protostr = NULL; char *othernodeinfo[MAX_NODES]; struct node nodes[MAX_NODES]; int thisidx = -1; int onidx = 0; int debug = KNET_LOG_INFO; int port = 50000, portoffset = 0; int thisport = 0, otherport = 0; int thisnewport = 0, othernewport = 0; struct sockaddr_in *so_in; struct sockaddr_in6 *so_in6; struct sockaddr_storage *src; int i, link_idx, allnodesup = 0; int policy = KNET_LINK_POLICY_PASSIVE, policyfound = 0; int protocol = KNET_TRANSPORT_UDP, protofound = 0; int wait = 1; int pmtud_interval = 60; struct knet_handle_crypto_cfg knet_handle_crypto_cfg; char *cryptomodel = NULL, *cryptotype = NULL, *cryptohash = NULL; struct knet_handle_compress_cfg knet_handle_compress_cfg; memset(nodes, 0, sizeof(nodes)); while ((rv = getopt(argc, argv, "aCT:S:s:lvdfom:wb:t:n:c:p:x:X::P:z:h")) != EOF) { switch(rv) { case 'h': print_help(); exit(PASS); break; case 'a': machine_output = 1; break; case 'd': debug = KNET_LOG_DEBUG; break; case 'f': use_access_lists = 1; break; case 'c': if (cryptocfg) { printf("Error: -c can only be specified once\n"); exit(FAIL); } cryptocfg = optarg; break; case 'p': if (policystr) { printf("Error: -p can only be specified once\n"); exit(FAIL); } if (optarg) { policystr = optarg; if (!strcmp(policystr, "active")) { policy = KNET_LINK_POLICY_ACTIVE; policyfound = 1; } /* * we can't use rr because clangs can't compile * an array of 3 strings, one of which is 2 bytes long */ if (!strcmp(policystr, "round-robin")) { policy = KNET_LINK_POLICY_RR; policyfound = 1; } if (!strcmp(policystr, "passive")) { policy = KNET_LINK_POLICY_PASSIVE; policyfound = 1; } } if (!policyfound) { printf("Error: invalid policy %s specified. -p accepts active|passive|rr\n", policystr); exit(FAIL); } break; case 'P': if (protostr) { printf("Error: -P can only be specified once\n"); exit(FAIL); } if (optarg) { protostr = optarg; if (!strcmp(protostr, "UDP")) { protocol = KNET_TRANSPORT_UDP; protofound = 1; } if (!strcmp(protostr, "SCTP")) { protocol = KNET_TRANSPORT_SCTP; protofound = 1; } } if (!protofound) { printf("Error: invalid protocol %s specified. -P accepts udp|sctp\n", policystr); exit(FAIL); } break; case 't': if (thisnodeid >= 0) { printf("Error: -t can only be specified once\n"); exit(FAIL); } thisnodeid = atoi(optarg); if ((thisnodeid < 0) || (thisnodeid > 65536)) { printf("Error: -t nodeid out of range %d (1 - 65536)\n", thisnodeid); exit(FAIL); } break; case 'n': if (onidx == MAX_NODES) { printf("Error: too many other nodes. Max %d\n", MAX_NODES); exit(FAIL); } othernodeinfo[onidx] = optarg; onidx++; break; case 'b': port = atoi(optarg); if ((port < 1) || (port > 65536)) { printf("Error: port %d out of range (1 - 65536)\n", port); exit(FAIL); } break; case 'o': if (globallistener) { printf("Error: -l cannot be used with -o\n"); exit(FAIL); } portoffset = 1; break; case 'm': pmtud_interval = atoi(optarg); if (pmtud_interval < 1) { printf("Error: pmtud interval %d out of range (> 0)\n", pmtud_interval); exit(FAIL); } break; case 'l': if (portoffset) { printf("Error: -o cannot be used with -l\n"); exit(FAIL); } globallistener = 1; break; case 'w': wait = 0; break; case 's': if (senderid >= 0) { printf("Error: -s can only be specified once\n"); exit(FAIL); } senderid = atoi(optarg); if ((senderid < 0) || (senderid > 65536)) { printf("Error: -s nodeid out of range %d (1 - 65536)\n", senderid); exit(FAIL); } break; case 'T': if (optarg) { if (!strcmp("ping", optarg)) { test_type = TEST_PING; } if (!strcmp("ping_data", optarg)) { test_type = TEST_PING_AND_DATA; } if (!strcmp("perf-by-size", optarg)) { test_type = TEST_PERF_BY_SIZE; } if (!strcmp("perf-by-time", optarg)) { test_type = TEST_PERF_BY_TIME; } } else { printf("Error: -T requires an option\n"); exit(FAIL); } break; case 'S': perf_by_size_size = (uint64_t)atoi(optarg) * ONE_GIGABYTE; perf_by_time_secs = (uint64_t)atoi(optarg); break; case 'x': force_packet_size = (uint32_t)atoi(optarg); if ((force_packet_size < 64) || (force_packet_size > 65536)) { printf("Unsupported packet size %u (accepted 64 - 65536)\n", force_packet_size); exit(FAIL); } break; case 'v': use_pckt_verification = 1; break; case 'C': continous = 1; break; case 'X': if (optarg) { show_stats = atoi(optarg); } else { show_stats = 1; } break; case 'z': if (compresscfg) { printf("Error: -c can only be specified once\n"); exit(FAIL); } compresscfg = optarg; break; default: break; } } if (thisnodeid < 0) { printf("Who am I?!? missing -t from command line?\n"); exit(FAIL); } if (onidx < 1) { printf("no other nodes configured?!? missing -n from command line\n"); exit(FAIL); } parse_nodes(othernodeinfo, onidx, port, nodes, &thisidx); if (thisidx < 0) { printf("no config for this node found\n"); exit(FAIL); } if (senderid >= 0) { for (i=0; i < onidx; i++) { if (senderid == nodes[i].nodeid) { break; } } if (i == onidx) { printf("Unable to find senderid in nodelist\n"); exit(FAIL); } } if (((test_type == TEST_PERF_BY_SIZE) || (test_type == TEST_PERF_BY_TIME)) && (senderid < 0)) { printf("Error: performance test requires -s to be set (for now)\n"); exit(FAIL); } logfd = start_logging(stdout); knet_h = knet_handle_new(thisnodeid, logfd, debug, 0); if (!knet_h) { printf("Unable to knet_handle_new: %s\n", strerror(errno)); exit(FAIL); } if (knet_handle_enable_access_lists(knet_h, use_access_lists) < 0) { printf("Unable to knet_handle_enable_access_lists: %s\n", strerror(errno)); exit(FAIL); } + if (knet_handle_set_host_defrag_bufs(knet_h, 4, KNET_MAX_DEFRAG_BUFS_DEFAULT, KNET_SHRINK_THRESHOLD_DEFAULT, RECLAIM_POLICY_ABSOLUTE) < 0) { + printf("Unable to knet_handle_set_host_defrag_bufs: %s\n", strerror(errno)); + exit(FAIL); + } + if (cryptocfg) { memset(&knet_handle_crypto_cfg, 0, sizeof(knet_handle_crypto_cfg)); cryptomodel = strtok(cryptocfg, ":"); cryptotype = strtok(NULL, ":"); cryptohash = strtok(NULL, ":"); if (cryptomodel) { strncpy(knet_handle_crypto_cfg.crypto_model, cryptomodel, sizeof(knet_handle_crypto_cfg.crypto_model) - 1); } if (cryptotype) { strncpy(knet_handle_crypto_cfg.crypto_cipher_type, cryptotype, sizeof(knet_handle_crypto_cfg.crypto_cipher_type) - 1); } if (cryptohash) { strncpy(knet_handle_crypto_cfg.crypto_hash_type, cryptohash, sizeof(knet_handle_crypto_cfg.crypto_hash_type) - 1); } knet_handle_crypto_cfg.private_key_len = KNET_MAX_KEY_LEN; if (knet_handle_crypto_set_config(knet_h, &knet_handle_crypto_cfg, 1)) { printf("Unable to set crypto config\n"); exit(FAIL); } if (knet_handle_crypto_use_config(knet_h, 1)) { printf("Unable to use crypto config\n"); exit(FAIL); } if (knet_handle_crypto_rx_clear_traffic(knet_h, KNET_CRYPTO_RX_DISALLOW_CLEAR_TRAFFIC)) { printf("Unable to disable clear traffic on RX\n"); exit(FAIL); } } if (compresscfg) { memset(&knet_handle_compress_cfg, 0, sizeof(struct knet_handle_compress_cfg)); snprintf(knet_handle_compress_cfg.compress_model, 16, "%s", strtok(compresscfg, ":")); knet_handle_compress_cfg.compress_level = atoi(strtok(NULL, ":")); knet_handle_compress_cfg.compress_threshold = atoi(strtok(NULL, ":")); if (knet_handle_compress(knet_h, &knet_handle_compress_cfg)) { printf("Unable to configure compress\n"); exit(FAIL); } } if (knet_handle_enable_sock_notify(knet_h, &private_data, sock_notify) < 0) { printf("knet_handle_enable_sock_notify failed: %s\n", strerror(errno)); knet_handle_free(knet_h); exit(FAIL); } datafd = 0; channel = -1; if (knet_handle_add_datafd(knet_h, &datafd, &channel) < 0) { printf("knet_handle_add_datafd failed: %s\n", strerror(errno)); knet_handle_free(knet_h); exit(FAIL); } if (knet_handle_pmtud_setfreq(knet_h, pmtud_interval) < 0) { printf("knet_handle_pmtud_setfreq failed: %s\n", strerror(errno)); knet_handle_free(knet_h); exit(FAIL); } for (i=0; i < onidx; i++) { if (i == thisidx) { continue; } if (knet_host_add(knet_h, nodes[i].nodeid) < 0) { printf("knet_host_add failed: %s\n", strerror(errno)); exit(FAIL); } if (knet_host_set_policy(knet_h, nodes[i].nodeid, policy) < 0) { printf("knet_host_set_policy failed: %s\n", strerror(errno)); exit(FAIL); } for (link_idx = 0; link_idx < nodes[i].links; link_idx++) { if (portoffset) { if (nodes[thisidx].address[link_idx].ss_family == AF_INET) { so_in = (struct sockaddr_in *)&nodes[thisidx].address[link_idx]; thisport = ntohs(so_in->sin_port); thisnewport = thisport + nodes[i].nodeid; so_in->sin_port = (htons(thisnewport)); so_in = (struct sockaddr_in *)&nodes[i].address[link_idx]; otherport = ntohs(so_in->sin_port); othernewport = otherport + nodes[thisidx].nodeid; so_in->sin_port = (htons(othernewport)); } else { so_in6 = (struct sockaddr_in6 *)&nodes[thisidx].address[link_idx]; thisport = ntohs(so_in6->sin6_port); thisnewport = thisport + nodes[i].nodeid; so_in6->sin6_port = (htons(thisnewport)); so_in6 = (struct sockaddr_in6 *)&nodes[i].address[link_idx]; otherport = ntohs(so_in6->sin6_port); othernewport = otherport + nodes[thisidx].nodeid; so_in6->sin6_port = (htons(othernewport)); } } if (!globallistener) { src = &nodes[thisidx].address[link_idx]; } else { if (nodes[thisidx].address[link_idx].ss_family == AF_INET) { src = &allv4; } else { src = &allv6; } } /* * -P overrides per link protocol configuration */ if (protofound) { nodes[i].transport[link_idx] = protocol; } if (knet_link_set_config(knet_h, nodes[i].nodeid, link_idx, nodes[i].transport[link_idx], src, &nodes[i].address[link_idx], 0) < 0) { printf("Unable to configure link: %s\n", strerror(errno)); exit(FAIL); } if (portoffset) { if (nodes[thisidx].address[link_idx].ss_family == AF_INET) { so_in = (struct sockaddr_in *)&nodes[thisidx].address[link_idx]; so_in->sin_port = (htons(thisport)); so_in = (struct sockaddr_in *)&nodes[i].address[link_idx]; so_in->sin_port = (htons(otherport)); } else { so_in6 = (struct sockaddr_in6 *)&nodes[thisidx].address[link_idx]; so_in6->sin6_port = (htons(thisport)); so_in6 = (struct sockaddr_in6 *)&nodes[i].address[link_idx]; so_in6->sin6_port = (htons(otherport)); } } if (knet_link_set_enable(knet_h, nodes[i].nodeid, link_idx, 1) < 0) { printf("knet_link_set_enable failed: %s\n", strerror(errno)); exit(FAIL); } if (knet_link_set_ping_timers(knet_h, nodes[i].nodeid, link_idx, 1000, 10000, 2048) < 0) { printf("knet_link_set_ping_timers failed: %s\n", strerror(errno)); exit(FAIL); } if (knet_link_set_pong_count(knet_h, nodes[i].nodeid, link_idx, 2) < 0) { printf("knet_link_set_pong_count failed: %s\n", strerror(errno)); exit(FAIL); } } } if (knet_handle_enable_filter(knet_h, NULL, ping_dst_host_filter)) { printf("Unable to enable dst_host_filter: %s\n", strerror(errno)); exit(FAIL); } if (knet_handle_setfwd(knet_h, 1) < 0) { printf("knet_handle_setfwd failed: %s\n", strerror(errno)); exit(FAIL); } if (wait) { while(!allnodesup) { allnodesup = 1; for (i=0; i < onidx; i++) { if (i == thisidx) { continue; } if (knet_h->host_index[nodes[i].nodeid]->status.reachable != 1) { printf("[info]: waiting host %d to be reachable\n", nodes[i].nodeid); allnodesup = 0; } } if (!allnodesup) { sleep(1); } } sleep(1); } } /* * calculate weak chksum (stole from corosync for debugging purposes) */ static uint32_t compute_chsum(const unsigned char *data, uint32_t data_len) { unsigned int i; unsigned int checksum = 0; for (i = 0; i < data_len; i++) { if (checksum & 1) { checksum |= 0x10000; } checksum = ((checksum >> 1) + (unsigned char)data[i]) & 0xffff; } return (checksum); } static void *_rx_thread(void *args) { int rx_epoll; struct epoll_event ev; struct epoll_event events[KNET_EPOLL_MAX_EVENTS]; struct sockaddr_storage address[PCKT_FRAG_MAX]; struct knet_mmsghdr msg[PCKT_FRAG_MAX]; struct iovec iov_in[PCKT_FRAG_MAX]; int i, msg_recv; struct timespec clock_start, clock_end; unsigned long long time_diff = 0; uint64_t rx_pkts = 0; uint64_t rx_bytes = 0; unsigned int current_pckt_size = 0; for (i = 0; i < PCKT_FRAG_MAX; i++) { rx_buf[i] = malloc(KNET_MAX_PACKET_SIZE); if (!rx_buf[i]) { printf("RXT: Unable to malloc!\nHALTING RX THREAD!\n"); return NULL; } memset(rx_buf[i], 0, KNET_MAX_PACKET_SIZE); iov_in[i].iov_base = (void *)rx_buf[i]; iov_in[i].iov_len = KNET_MAX_PACKET_SIZE; memset(&msg[i].msg_hdr, 0, sizeof(struct msghdr)); msg[i].msg_hdr.msg_name = &address[i]; msg[i].msg_hdr.msg_namelen = sizeof(struct sockaddr_storage); msg[i].msg_hdr.msg_iov = &iov_in[i]; msg[i].msg_hdr.msg_iovlen = 1; } rx_epoll = epoll_create(KNET_EPOLL_MAX_EVENTS + 1); if (rx_epoll < 0) { printf("RXT: Unable to create epoll!\nHALTING RX THREAD!\n"); return NULL; } memset(&ev, 0, sizeof(struct epoll_event)); ev.events = EPOLLIN; ev.data.fd = datafd; if (epoll_ctl(rx_epoll, EPOLL_CTL_ADD, datafd, &ev)) { printf("RXT: Unable to add datafd to epoll\nHALTING RX THREAD!\n"); return NULL; } memset(&clock_start, 0, sizeof(clock_start)); memset(&clock_end, 0, sizeof(clock_start)); while (!bench_shutdown_in_progress) { if (epoll_wait(rx_epoll, events, KNET_EPOLL_MAX_EVENTS, 1) >= 1) { msg_recv = _recvmmsg(datafd, &msg[0], PCKT_FRAG_MAX, MSG_DONTWAIT | MSG_NOSIGNAL); if (msg_recv < 0) { printf("[info]: RXT: error from recvmmsg: %s\n", strerror(errno)); } switch(test_type) { case TEST_PING_AND_DATA: for (i = 0; i < msg_recv; i++) { if (msg[i].msg_len == 0) { printf("[info]: RXT: received 0 bytes message?\n"); } printf("[info]: received %u bytes message: %s\n", msg[i].msg_len, (char *)msg[i].msg_hdr.msg_iov->iov_base); } break; case TEST_PERF_BY_TIME: case TEST_PERF_BY_SIZE: for (i = 0; i < msg_recv; i++) { if (msg[i].msg_len < 64) { if (msg[i].msg_len == 0) { printf("[info]: RXT: received 0 bytes message?\n"); } if (msg[i].msg_len == TEST_START) { if (clock_gettime(CLOCK_MONOTONIC, &clock_start) != 0) { printf("[info]: unable to get start time!\n"); } } if (msg[i].msg_len == TEST_STOP) { double average_rx_mbytes; double average_rx_pkts; double time_diff_sec; if (clock_gettime(CLOCK_MONOTONIC, &clock_end) != 0) { printf("[info]: unable to get end time!\n"); } timespec_diff(clock_start, clock_end, &time_diff); /* * adjust for sleep(2) between sending the last data and TEST_STOP */ time_diff = time_diff - 2000000000llu; /* * convert to seconds */ time_diff_sec = (double)time_diff / 1000000000llu; average_rx_mbytes = (double)((rx_bytes / time_diff_sec) / (1024 * 1024)); average_rx_pkts = (double)(rx_pkts / time_diff_sec); if (!machine_output) { printf("[perf] execution time: %8.4f secs Average speed: %8.4f MB/sec %8.4f pckts/sec (size: %u total: %" PRIu64 ")\n", time_diff_sec, average_rx_mbytes, average_rx_pkts, current_pckt_size, rx_pkts); } else { printf("[perf],%.4f,%u,%" PRIu64 ",%.4f,%.4f\n", time_diff_sec, current_pckt_size, rx_pkts, average_rx_mbytes, average_rx_pkts); } rx_pkts = 0; rx_bytes = 0; current_pckt_size = 0; } if (msg[i].msg_len == TEST_COMPLETE) { wait_for_perf_rx = 1; } continue; } if (use_pckt_verification) { struct pckt_ver *recv_pckt = (struct pckt_ver *)msg[i].msg_hdr.msg_iov->iov_base; uint32_t chksum; if (msg[i].msg_len != recv_pckt->len) { printf("Wrong packet len received: %u expected: %u!\n", msg[i].msg_len, recv_pckt->len); exit(FAIL); } chksum = compute_chsum((const unsigned char *)msg[i].msg_hdr.msg_iov->iov_base + sizeof(struct pckt_ver), msg[i].msg_len - sizeof(struct pckt_ver)); if (recv_pckt->chksum != chksum){ printf("Wrong packet checksum received: %u expected: %u!\n", recv_pckt->chksum, chksum); exit(FAIL); } } rx_pkts++; rx_bytes = rx_bytes + msg[i].msg_len; current_pckt_size = msg[i].msg_len; } break; } } } epoll_ctl(rx_epoll, EPOLL_CTL_DEL, datafd, &ev); close(rx_epoll); return NULL; } static void setup_data_txrx_common(void) { if (!rx_thread) { if (knet_handle_enable_filter(knet_h, NULL, ping_dst_host_filter)) { printf("Unable to enable dst_host_filter: %s\n", strerror(errno)); exit(FAIL); } printf("[info]: setting up rx thread\n"); if (pthread_create(&rx_thread, 0, _rx_thread, NULL)) { printf("Unable to start rx thread\n"); exit(FAIL); } } } static void stop_rx_thread(void) { void *retval; int i; if (rx_thread) { printf("[info]: shutting down rx thread\n"); sleep(2); pthread_cancel(rx_thread); pthread_join(rx_thread, &retval); for (i = 0; i < PCKT_FRAG_MAX; i ++) { free(rx_buf[i]); } } } static void send_ping_data(void) { char buf[65535]; ssize_t len; memset(&buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf), "Hello world!"); if (compresscfg) { len = sizeof(buf); } else { len = strlen(buf); } if (knet_send(knet_h, buf, len, channel) != len) { printf("[info]: Error sending hello world: %s\n", strerror(errno)); } sleep(1); } static int send_messages(struct knet_mmsghdr *msg, int msgs_to_send) { int sent_msgs, prev_sent, progress, total_sent; total_sent = 0; sent_msgs = 0; prev_sent = 0; progress = 1; retry: errno = 0; sent_msgs = _sendmmsg(datafd, 0, &msg[0], msgs_to_send, MSG_NOSIGNAL); if (sent_msgs < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { usleep(KNET_THREADS_TIMER_RES / 16); goto retry; } printf("[info]: Unable to send messages: %s\n", strerror(errno)); return -1; } total_sent = total_sent + sent_msgs; if ((sent_msgs >= 0) && (sent_msgs < msgs_to_send)) { if ((sent_msgs) || (progress)) { msgs_to_send = msgs_to_send - sent_msgs; prev_sent = prev_sent + sent_msgs; if (sent_msgs) { progress = 1; } else { progress = 0; } goto retry; } if (!progress) { printf("[info]: Unable to send more messages after retry\n"); } } return total_sent; } static int setup_send_buffers_common(struct knet_mmsghdr *msg, struct iovec *iov_out, char *tx_buf[]) { int i; for (i = 0; i < PCKT_FRAG_MAX; i++) { tx_buf[i] = malloc(KNET_MAX_PACKET_SIZE); if (!tx_buf[i]) { printf("TXT: Unable to malloc!\n"); return -1; } memset(tx_buf[i], i, KNET_MAX_PACKET_SIZE); iov_out[i].iov_base = (void *)tx_buf[i]; memset(&msg[i].msg_hdr, 0, sizeof(struct msghdr)); msg[i].msg_hdr.msg_iov = &iov_out[i]; msg[i].msg_hdr.msg_iovlen = 1; } return 0; } static void send_perf_data_by_size(void) { char *tx_buf[PCKT_FRAG_MAX]; struct knet_mmsghdr msg[PCKT_FRAG_MAX]; struct iovec iov_out[PCKT_FRAG_MAX]; char ctrl_message[16]; int sent_msgs; int i; uint64_t total_pkts_to_tx; uint64_t packets_to_send; uint32_t packetsize = 64; setup_send_buffers_common(msg, iov_out, tx_buf); while (packetsize <= KNET_MAX_PACKET_SIZE) { if (force_packet_size) { packetsize = force_packet_size; } for (i = 0; i < PCKT_FRAG_MAX; i++) { iov_out[i].iov_len = packetsize; if (use_pckt_verification) { struct pckt_ver *tx_pckt = (struct pckt_ver *)&iov_out[i].iov_base; tx_pckt->len = iov_out[i].iov_len; tx_pckt->chksum = compute_chsum((const unsigned char *)iov_out[i].iov_base + sizeof(struct pckt_ver), iov_out[i].iov_len - sizeof(struct pckt_ver)); } } total_pkts_to_tx = perf_by_size_size / packetsize; printf("[info]: testing with %u packet size. total bytes to transfer: %" PRIu64 " (%" PRIu64 " packets)\n", packetsize, perf_by_size_size, total_pkts_to_tx); memset(ctrl_message, 0, sizeof(ctrl_message)); knet_send(knet_h, ctrl_message, TEST_START, channel); while (total_pkts_to_tx > 0) { if (total_pkts_to_tx >= PCKT_FRAG_MAX) { packets_to_send = PCKT_FRAG_MAX; } else { packets_to_send = total_pkts_to_tx; } sent_msgs = send_messages(&msg[0], packets_to_send); if (sent_msgs < 0) { printf("Something went wrong, aborting\n"); exit(FAIL); } total_pkts_to_tx = total_pkts_to_tx - sent_msgs; } sleep(2); knet_send(knet_h, ctrl_message, TEST_STOP, channel); if ((packetsize == KNET_MAX_PACKET_SIZE) || (force_packet_size)) { break; } /* * Use a multiplier that can always divide properly a GB * into smaller chunks without worry about boundaries */ packetsize *= 4; if (packetsize > KNET_MAX_PACKET_SIZE) { packetsize = KNET_MAX_PACKET_SIZE; } } knet_send(knet_h, ctrl_message, TEST_COMPLETE, channel); for (i = 0; i < PCKT_FRAG_MAX; i++) { free(tx_buf[i]); } } /* For sorting the node list into order */ static int node_compare(const void *aptr, const void *bptr) { uint16_t a,b; a = *(uint16_t *)aptr; b = *(uint16_t *)bptr; return a > b; } static void display_stats(int level) { struct knet_handle_stats handle_stats; struct knet_link_status link_status; struct knet_link_stats total_link_stats; knet_node_id_t host_list[KNET_MAX_HOST]; uint8_t link_list[KNET_MAX_LINK]; unsigned int i,j; size_t num_hosts, num_links; if (knet_handle_get_stats(knet_h, &handle_stats, sizeof(handle_stats)) < 0) { perror("[info]: failed to get knet handle stats"); return; } if (compresscfg || cryptocfg) { printf("\n"); printf("[stat]: handle stats\n"); printf("[stat]: ------------\n"); if (compresscfg) { printf("[stat]: tx_uncompressed_packets: %" PRIu64 "\n", handle_stats.tx_uncompressed_packets); printf("[stat]: tx_compressed_packets: %" PRIu64 "\n", handle_stats.tx_compressed_packets); printf("[stat]: tx_compressed_original_bytes: %" PRIu64 "\n", handle_stats.tx_compressed_original_bytes); printf("[stat]: tx_compressed_size_bytes: %" PRIu64 "\n", handle_stats.tx_compressed_size_bytes ); printf("[stat]: tx_compress_time_ave: %" PRIu64 "\n", handle_stats.tx_compress_time_ave); printf("[stat]: tx_compress_time_min: %" PRIu64 "\n", handle_stats.tx_compress_time_min); printf("[stat]: tx_compress_time_max: %" PRIu64 "\n", handle_stats.tx_compress_time_max); printf("[stat]: tx_failed_to_compress: %" PRIu64 "\n", handle_stats.tx_failed_to_compress); printf("[stat]: tx_unable_to_compress: %" PRIu64 "\n", handle_stats.tx_unable_to_compress); printf("[stat]: rx_compressed_packets: %" PRIu64 "\n", handle_stats.rx_compressed_packets); printf("[stat]: rx_compressed_original_bytes: %" PRIu64 "\n", handle_stats.rx_compressed_original_bytes); printf("[stat]: rx_compressed_size_bytes: %" PRIu64 "\n", handle_stats.rx_compressed_size_bytes); printf("[stat]: rx_compress_time_ave: %" PRIu64 "\n", handle_stats.rx_compress_time_ave); printf("[stat]: rx_compress_time_min: %" PRIu64 "\n", handle_stats.rx_compress_time_min); printf("[stat]: rx_compress_time_max: %" PRIu64 "\n", handle_stats.rx_compress_time_max); printf("[stat]: rx_failed_to_decompress: %" PRIu64 "\n", handle_stats.rx_failed_to_decompress); printf("\n"); } if (cryptocfg) { printf("[stat]: tx_crypt_packets: %" PRIu64 "\n", handle_stats.tx_crypt_packets); printf("[stat]: tx_crypt_byte_overhead: %" PRIu64 "\n", handle_stats.tx_crypt_byte_overhead); printf("[stat]: tx_crypt_time_ave: %" PRIu64 "\n", handle_stats.tx_crypt_time_ave); printf("[stat]: tx_crypt_time_min: %" PRIu64 "\n", handle_stats.tx_crypt_time_min); printf("[stat]: tx_crypt_time_max: %" PRIu64 "\n", handle_stats.tx_crypt_time_max); printf("[stat]: rx_crypt_packets: %" PRIu64 "\n", handle_stats.rx_crypt_packets); printf("[stat]: rx_crypt_time_ave: %" PRIu64 "\n", handle_stats.rx_crypt_time_ave); printf("[stat]: rx_crypt_time_min: %" PRIu64 "\n", handle_stats.rx_crypt_time_min); printf("[stat]: rx_crypt_time_max: %" PRIu64 "\n", handle_stats.rx_crypt_time_max); printf("\n"); } } if (level < 2) { return; } memset(&total_link_stats, 0, sizeof(struct knet_link_stats)); if (knet_host_get_host_list(knet_h, host_list, &num_hosts) < 0) { perror("[info]: cannot get host list for stats"); return; } /* Print in host ID order */ qsort(host_list, num_hosts, sizeof(uint16_t), node_compare); for (j=0; j 2) { printf("\n"); printf("[stat]: Node %d Link %d\n", host_list[j], link_list[i]); printf("[stat]: tx_data_packets: %" PRIu64 "\n", link_status.stats.tx_data_packets); printf("[stat]: rx_data_packets: %" PRIu64 "\n", link_status.stats.rx_data_packets); printf("[stat]: tx_data_bytes: %" PRIu64 "\n", link_status.stats.tx_data_bytes); printf("[stat]: rx_data_bytes: %" PRIu64 "\n", link_status.stats.rx_data_bytes); printf("[stat]: rx_ping_packets: %" PRIu64 "\n", link_status.stats.rx_ping_packets); printf("[stat]: tx_ping_packets: %" PRIu64 "\n", link_status.stats.tx_ping_packets); printf("[stat]: rx_ping_bytes: %" PRIu64 "\n", link_status.stats.rx_ping_bytes); printf("[stat]: tx_ping_bytes: %" PRIu64 "\n", link_status.stats.tx_ping_bytes); printf("[stat]: rx_pong_packets: %" PRIu64 "\n", link_status.stats.rx_pong_packets); printf("[stat]: tx_pong_packets: %" PRIu64 "\n", link_status.stats.tx_pong_packets); printf("[stat]: rx_pong_bytes: %" PRIu64 "\n", link_status.stats.rx_pong_bytes); printf("[stat]: tx_pong_bytes: %" PRIu64 "\n", link_status.stats.tx_pong_bytes); printf("[stat]: rx_pmtu_packets: %" PRIu64 "\n", link_status.stats.rx_pmtu_packets); printf("[stat]: tx_pmtu_packets: %" PRIu64 "\n", link_status.stats.tx_pmtu_packets); printf("[stat]: rx_pmtu_bytes: %" PRIu64 "\n", link_status.stats.rx_pmtu_bytes); printf("[stat]: tx_pmtu_bytes: %" PRIu64 "\n", link_status.stats.tx_pmtu_bytes); printf("[stat]: tx_total_packets: %" PRIu64 "\n", link_status.stats.tx_total_packets); printf("[stat]: rx_total_packets: %" PRIu64 "\n", link_status.stats.rx_total_packets); printf("[stat]: tx_total_bytes: %" PRIu64 "\n", link_status.stats.tx_total_bytes); printf("[stat]: rx_total_bytes: %" PRIu64 "\n", link_status.stats.rx_total_bytes); printf("[stat]: tx_total_errors: %" PRIu64 "\n", link_status.stats.tx_total_errors); printf("[stat]: tx_total_retries: %" PRIu64 "\n", link_status.stats.tx_total_retries); printf("[stat]: tx_pmtu_errors: %" PRIu32 "\n", link_status.stats.tx_pmtu_errors); printf("[stat]: tx_pmtu_retries: %" PRIu32 "\n", link_status.stats.tx_pmtu_retries); printf("[stat]: tx_ping_errors: %" PRIu32 "\n", link_status.stats.tx_ping_errors); printf("[stat]: tx_ping_retries: %" PRIu32 "\n", link_status.stats.tx_ping_retries); printf("[stat]: tx_pong_errors: %" PRIu32 "\n", link_status.stats.tx_pong_errors); printf("[stat]: tx_pong_retries: %" PRIu32 "\n", link_status.stats.tx_pong_retries); printf("[stat]: tx_data_errors: %" PRIu32 "\n", link_status.stats.tx_data_errors); printf("[stat]: tx_data_retries: %" PRIu32 "\n", link_status.stats.tx_data_retries); printf("[stat]: latency_min: %" PRIu32 "\n", link_status.stats.latency_min); printf("[stat]: latency_max: %" PRIu32 "\n", link_status.stats.latency_max); printf("[stat]: latency_ave: %" PRIu32 "\n", link_status.stats.latency_ave); printf("[stat]: latency_samples: %" PRIu32 "\n", link_status.stats.latency_samples); printf("[stat]: down_count: %" PRIu32 "\n", link_status.stats.down_count); printf("[stat]: up_count: %" PRIu32 "\n", link_status.stats.up_count); } } } printf("\n"); printf("[stat]: Total link stats\n"); printf("[stat]: ----------------\n"); printf("[stat]: tx_data_packets: %" PRIu64 "\n", total_link_stats.tx_data_packets); printf("[stat]: rx_data_packets: %" PRIu64 "\n", total_link_stats.rx_data_packets); printf("[stat]: tx_data_bytes: %" PRIu64 "\n", total_link_stats.tx_data_bytes); printf("[stat]: rx_data_bytes: %" PRIu64 "\n", total_link_stats.rx_data_bytes); printf("[stat]: rx_ping_packets: %" PRIu64 "\n", total_link_stats.rx_ping_packets); printf("[stat]: tx_ping_packets: %" PRIu64 "\n", total_link_stats.tx_ping_packets); printf("[stat]: rx_ping_bytes: %" PRIu64 "\n", total_link_stats.rx_ping_bytes); printf("[stat]: tx_ping_bytes: %" PRIu64 "\n", total_link_stats.tx_ping_bytes); printf("[stat]: rx_pong_packets: %" PRIu64 "\n", total_link_stats.rx_pong_packets); printf("[stat]: tx_pong_packets: %" PRIu64 "\n", total_link_stats.tx_pong_packets); printf("[stat]: rx_pong_bytes: %" PRIu64 "\n", total_link_stats.rx_pong_bytes); printf("[stat]: tx_pong_bytes: %" PRIu64 "\n", total_link_stats.tx_pong_bytes); printf("[stat]: rx_pmtu_packets: %" PRIu64 "\n", total_link_stats.rx_pmtu_packets); printf("[stat]: tx_pmtu_packets: %" PRIu64 "\n", total_link_stats.tx_pmtu_packets); printf("[stat]: rx_pmtu_bytes: %" PRIu64 "\n", total_link_stats.rx_pmtu_bytes); printf("[stat]: tx_pmtu_bytes: %" PRIu64 "\n", total_link_stats.tx_pmtu_bytes); printf("[stat]: tx_total_packets: %" PRIu64 "\n", total_link_stats.tx_total_packets); printf("[stat]: rx_total_packets: %" PRIu64 "\n", total_link_stats.rx_total_packets); printf("[stat]: tx_total_bytes: %" PRIu64 "\n", total_link_stats.tx_total_bytes); printf("[stat]: rx_total_bytes: %" PRIu64 "\n", total_link_stats.rx_total_bytes); printf("[stat]: tx_total_errors: %" PRIu64 "\n", total_link_stats.tx_total_errors); printf("[stat]: tx_total_retries: %" PRIu64 "\n", total_link_stats.tx_total_retries); printf("[stat]: tx_pmtu_errors: %" PRIu32 "\n", total_link_stats.tx_pmtu_errors); printf("[stat]: tx_pmtu_retries: %" PRIu32 "\n", total_link_stats.tx_pmtu_retries); printf("[stat]: tx_ping_errors: %" PRIu32 "\n", total_link_stats.tx_ping_errors); printf("[stat]: tx_ping_retries: %" PRIu32 "\n", total_link_stats.tx_ping_retries); printf("[stat]: tx_pong_errors: %" PRIu32 "\n", total_link_stats.tx_pong_errors); printf("[stat]: tx_pong_retries: %" PRIu32 "\n", total_link_stats.tx_pong_retries); printf("[stat]: tx_data_errors: %" PRIu32 "\n", total_link_stats.tx_data_errors); printf("[stat]: tx_data_retries: %" PRIu32 "\n", total_link_stats.tx_data_retries); printf("[stat]: down_count: %" PRIu32 "\n", total_link_stats.down_count); printf("[stat]: up_count: %" PRIu32 "\n", total_link_stats.up_count); } static void send_perf_data_by_time(void) { char *tx_buf[PCKT_FRAG_MAX]; struct knet_mmsghdr msg[PCKT_FRAG_MAX]; struct iovec iov_out[PCKT_FRAG_MAX]; char ctrl_message[16]; int sent_msgs; int i; uint32_t packetsize = 64; struct timespec clock_start, clock_end; unsigned long long time_diff = 0; setup_send_buffers_common(msg, iov_out, tx_buf); memset(&clock_start, 0, sizeof(clock_start)); memset(&clock_end, 0, sizeof(clock_start)); while (packetsize <= KNET_MAX_PACKET_SIZE) { if (force_packet_size) { packetsize = force_packet_size; } for (i = 0; i < PCKT_FRAG_MAX; i++) { iov_out[i].iov_len = packetsize; if (use_pckt_verification) { struct pckt_ver *tx_pckt = (struct pckt_ver *)iov_out[i].iov_base; tx_pckt->len = iov_out[i].iov_len; tx_pckt->chksum = compute_chsum((const unsigned char *)iov_out[i].iov_base + sizeof(struct pckt_ver), iov_out[i].iov_len - sizeof(struct pckt_ver)); } } printf("[info]: testing with %u bytes packet size for %" PRIu64 " seconds.\n", packetsize, perf_by_time_secs); memset(ctrl_message, 0, sizeof(ctrl_message)); knet_send(knet_h, ctrl_message, TEST_START, channel); if (clock_gettime(CLOCK_MONOTONIC, &clock_start) != 0) { printf("[info]: unable to get start time!\n"); } time_diff = 0; while (time_diff < (perf_by_time_secs * 1000000000llu)) { sent_msgs = send_messages(&msg[0], PCKT_FRAG_MAX); if (sent_msgs < 0) { printf("Something went wrong, aborting\n"); exit(FAIL); } if (clock_gettime(CLOCK_MONOTONIC, &clock_end) != 0) { printf("[info]: unable to get end time!\n"); } timespec_diff(clock_start, clock_end, &time_diff); } sleep(2); knet_send(knet_h, ctrl_message, TEST_STOP, channel); if ((packetsize == KNET_MAX_PACKET_SIZE) || (force_packet_size)) { break; } /* * Use a multiplier that can always divide properly a GB * into smaller chunks without worry about boundaries */ packetsize *= 4; if (packetsize > KNET_MAX_PACKET_SIZE) { packetsize = KNET_MAX_PACKET_SIZE; } } knet_send(knet_h, ctrl_message, TEST_COMPLETE, channel); for (i = 0; i < PCKT_FRAG_MAX; i++) { free(tx_buf[i]); } } static void cleanup_all(void) { if (pthread_mutex_lock(&shutdown_mutex)) { return; } if (bench_shutdown_in_progress) { pthread_mutex_unlock(&shutdown_mutex); return; } bench_shutdown_in_progress = 1; pthread_mutex_unlock(&shutdown_mutex); if (rx_thread) { stop_rx_thread(); } knet_handle_stop(knet_h); } static void sigint_handler(int signum) { printf("[info]: cleaning up... got signal: %d\n", signum); cleanup_all(); exit(PASS); } int main(int argc, char *argv[]) { if (signal(SIGINT, sigint_handler) == SIG_ERR) { printf("Unable to configure SIGINT handler\n"); exit(FAIL); } setup_knet(argc, argv); setup_data_txrx_common(); sleep(5); restart: switch(test_type) { default: case TEST_PING: /* basic ping, no data */ sleep(5); break; case TEST_PING_AND_DATA: send_ping_data(); break; case TEST_PERF_BY_SIZE: if (senderid == thisnodeid) { send_perf_data_by_size(); } else { printf("[info]: waiting for perf rx thread to finish\n"); while(!wait_for_perf_rx) { sleep(1); } } break; case TEST_PERF_BY_TIME: if (senderid == thisnodeid) { send_perf_data_by_time(); } else { printf("[info]: waiting for perf rx thread to finish\n"); while(!wait_for_perf_rx) { sleep(1); } } break; } if (continous) { goto restart; } if (show_stats) { display_stats(show_stats); } cleanup_all(); return PASS; } diff --git a/libknet/threads_rx.c b/libknet/threads_rx.c index fcc384d2..3ba09c71 100644 --- a/libknet/threads_rx.c +++ b/libknet/threads_rx.c @@ -1,967 +1,1178 @@ /* * Copyright (C) 2012-2021 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * Federico Simoncelli * * This software licensed under LGPL-2.0+ */ #include "config.h" #include +#include #include #include #include #include #include "compat.h" #include "compress.h" #include "crypto.h" #include "host.h" #include "links.h" #include "links_acl.h" #include "logging.h" #include "transports.h" #include "transport_common.h" #include "threads_common.h" #include "threads_heartbeat.h" #include "threads_pmtud.h" #include "threads_rx.h" #include "netutils.h" #include "onwire_v1.h" /* * RECV */ /* * return 1 if a > b * return -1 if b > a * return 0 if they are equal */ static inline int _timecmp(struct timespec a, struct timespec b) { if (a.tv_sec != b.tv_sec) { if (a.tv_sec > b.tv_sec) { return 1; } else { return -1; } } else { if (a.tv_nsec > b.tv_nsec) { return 1; } else if (a.tv_nsec < b.tv_nsec) { return -1; } else { return 0; } } } /* - * this functions needs to return an index (0 to 7) + * calculate use % of defrag buffers per host + * and if % is <= knet_h->defrag_bufs_shrink_threshold for the last second, then half the size + */ + +static void _shrink_defrag_buffers(knet_handle_t knet_h) +{ + struct knet_host *host; + struct knet_host_defrag_buf *new_bufs = NULL; + struct timespec now; + unsigned long long time_diff; /* nanoseconds */ + uint16_t i, x, in_use_bufs; + uint32_t sum; + + /* + * first run. + */ + if ((knet_h->defrag_bufs_last_run.tv_sec == 0) && + (knet_h->defrag_bufs_last_run.tv_nsec == 0)) { + clock_gettime(CLOCK_MONOTONIC, &knet_h->defrag_bufs_last_run); + return; + } + + clock_gettime(CLOCK_MONOTONIC, &now); + + timespec_diff(knet_h->defrag_bufs_last_run, now, &time_diff); + + if (time_diff < (((unsigned long long)knet_h->defrag_bufs_usage_samples_timespan * 1000000000) / knet_h->defrag_bufs_usage_samples)) { + return; + } + + /* + * record the last run + */ + memmove(&knet_h->defrag_bufs_last_run, &now, sizeof(struct timespec)); + + /* + * do the real work: + */ + for (host = knet_h->host_head; host != NULL; host = host->next) { + + /* + * Update buffer usage stats. We do this for all nodes. + */ + in_use_bufs = 0; + + for (i = 0; i < host->allocated_defrag_bufs; i++) { + if (host->defrag_bufs[i].in_use) { + in_use_bufs++; + } + } + + /* + * record only % + */ + host->in_use_defrag_buffers[host->in_use_defrag_buffers_index] = (in_use_bufs * 100 / host->allocated_defrag_bufs); + host->in_use_defrag_buffers_index++; + + /* + * make sure to stay within buffer + */ + if (host->in_use_defrag_buffers_index == knet_h->defrag_bufs_usage_samples) { + host->in_use_defrag_buffers_index = 0; + } + + /* + * only allow shrinking if we have enough samples + */ + if (host->in_use_defrag_buffers_samples < knet_h->defrag_bufs_usage_samples) { + host->in_use_defrag_buffers_samples++; + continue; + } + + /* + * only allow shrinking if in use bufs are <= knet_h->defrag_bufs_shrink_threshold% + */ + if (knet_h->defrag_bufs_reclaim_policy == RECLAIM_POLICY_AVERAGE) { + sum = 0; + for (i = 0; i < knet_h->defrag_bufs_usage_samples; i++) { + sum += host->in_use_defrag_buffers[i]; + } + sum = sum / knet_h->defrag_bufs_usage_samples; + + if (sum > knet_h->defrag_bufs_shrink_threshold) { + continue; + } + } else { + sum = 0; + for (i = 0; i < knet_h->defrag_bufs_usage_samples; i++) { + if (host->in_use_defrag_buffers[i] > knet_h->defrag_bufs_shrink_threshold) { + sum = 1; + } + } + + if (sum) { + continue; + } + } + + /* + * only allow shrinking if allocated bufs > min_defrag_bufs + */ + if (host->allocated_defrag_bufs == knet_h->defrag_bufs_min) { + continue; + } + + /* + * compat all the in_use buffers at the beginning. + * we the checks above, we are 100% sure they fit + */ + x = 0; + for (i = 0; i < host->allocated_defrag_bufs; i++) { + if (host->defrag_bufs[i].in_use) { + memmove(&host->defrag_bufs[x], &host->defrag_bufs[i], sizeof(struct knet_host_defrag_buf)); + x++; + } + } + + /* + * memory allocation is not critical. it just means the system is under + * memory pressure and we will need to wait our turn to free memory... how odd :) + */ + new_bufs = realloc(host->defrag_bufs, sizeof(struct knet_host_defrag_buf) * (host->allocated_defrag_bufs / 2)); + if (!new_bufs) { + log_err(knet_h, KNET_SUB_RX, "Unable to decrease defrag buffers for host %u: %s", + host->host_id, strerror(errno)); + continue; + } + + host->defrag_bufs = new_bufs; + host->allocated_defrag_bufs = host->allocated_defrag_bufs / 2; + + /* + * clear buffer use stats. Old ones are no good for new one + */ + _clear_defrag_bufs_stats(host); + + log_debug(knet_h, KNET_SUB_RX, "Defrag buffers for host %u decreased from %u to: %u", + host->host_id, host->allocated_defrag_bufs * 2, host->allocated_defrag_bufs); + } +} + +/* + * check if we can double the defrag buffers. + * + * return 0 if we cannot reallocate + * return 1 if we have more buffers + */ + +static int _realloc_defrag_buffers(knet_handle_t knet_h, struct knet_host *src_host) +{ + struct knet_host_defrag_buf *new_bufs = NULL; + int i; + + /* + * max_defrag_bufs is a power of 2 + * allocated_defrag_bufs doubles on each iteration. + * Sooner or later (and hopefully never) allocated with be == to max. + */ + if (src_host->allocated_defrag_bufs < knet_h->defrag_bufs_max) { + new_bufs = realloc(src_host->defrag_bufs, + src_host->allocated_defrag_bufs * 2 * sizeof(struct knet_host_defrag_buf)); + if (!new_bufs) { + log_err(knet_h, KNET_SUB_RX, "Unable to increase defrag buffers for host %u: %s", + src_host->host_id, strerror(errno)); + return 0; + } + + /* + * keep the math simple here between arrays, pointers and what not. + * Init each buffer individually. + */ + for (i = src_host->allocated_defrag_bufs; i < src_host->allocated_defrag_bufs * 2; i++) { + memset(&new_bufs[i], 0, sizeof(struct knet_host_defrag_buf)); + } + + src_host->allocated_defrag_bufs = src_host->allocated_defrag_bufs * 2; + + src_host->defrag_bufs = new_bufs; + + /* + * clear buffer use stats. Old ones are no good for new one + */ + _clear_defrag_bufs_stats(src_host); + + log_debug(knet_h, KNET_SUB_RX, "Defrag buffers for host %u increased from %u to: %u", + src_host->host_id, src_host->allocated_defrag_bufs / 2, src_host->allocated_defrag_bufs); + + return 1; + } + + return 0; +} + +/* + * this functions needs to return an index * to a knet_host_defrag_buf. (-1 on errors) */ static int _find_pckt_defrag_buf(knet_handle_t knet_h, struct knet_host *src_host, seq_num_t seq_num) { int i, oldest; + uint16_t cur_allocated_defrag_bufs = src_host->allocated_defrag_bufs; /* * check if there is a buffer already in use handling the same seq_num */ - for (i = 0; i < KNET_DEFRAG_BUFFERS; i++) { - if (src_host->defrag_buf[i].in_use) { - if (src_host->defrag_buf[i].pckt_seq == seq_num) { + + for (i = 0; i < src_host->allocated_defrag_bufs; i++) { + if (src_host->defrag_bufs[i].in_use) { + if (src_host->defrag_bufs[i].pckt_seq == seq_num) { return i; } } } /* * If there is no buffer that's handling the current seq_num * either it's new or it's been reclaimed already. * check if it's been reclaimed/seen before using the defrag circular * buffer. If the pckt has been seen before, the buffer expired (ETIME) * and there is no point to try to defrag it again. */ + if (!_seq_num_lookup(knet_h, src_host, seq_num, 1, 0)) { errno = ETIME; return -1; } /* * register the pckt as seen */ + _seq_num_set(src_host, seq_num, 1); /* * see if there is a free buffer */ - for (i = 0; i < KNET_DEFRAG_BUFFERS; i++) { - if (!src_host->defrag_buf[i].in_use) { + + for (i = 0; i < src_host->allocated_defrag_bufs; i++) { + if (!src_host->defrag_bufs[i].in_use) { return i; } } + /* + * check if we can increase num of buffers + */ + + if (_realloc_defrag_buffers(knet_h, src_host)) { + return cur_allocated_defrag_bufs + 1; + } + /* * at this point, there are no free buffers, the pckt is new * and we need to reclaim a buffer, and we will take the one * with the oldest timestamp. It's as good as any. */ oldest = 0; - for (i = 0; i < KNET_DEFRAG_BUFFERS; i++) { - if (_timecmp(src_host->defrag_buf[i].last_update, src_host->defrag_buf[oldest].last_update) < 0) { + for (i = 0; i < src_host->allocated_defrag_bufs; i++) { + if (_timecmp(src_host->defrag_bufs[i].last_update, src_host->defrag_bufs[oldest].last_update) < 0) { oldest = i; } } - src_host->defrag_buf[oldest].in_use = 0; + src_host->defrag_bufs[oldest].in_use = 0; + return oldest; } static int _pckt_defrag(knet_handle_t knet_h, struct knet_host *src_host, seq_num_t seq_num, unsigned char *data, ssize_t *len, uint8_t frags, uint8_t frag_seq) { struct knet_host_defrag_buf *defrag_buf; int defrag_buf_idx; defrag_buf_idx = _find_pckt_defrag_buf(knet_h, src_host, seq_num); if (defrag_buf_idx < 0) { return 1; } - defrag_buf = &src_host->defrag_buf[defrag_buf_idx]; + defrag_buf = &src_host->defrag_bufs[defrag_buf_idx]; /* * if the buf is not is use, then make sure it's clean */ if (!defrag_buf->in_use) { memset(defrag_buf, 0, sizeof(struct knet_host_defrag_buf)); defrag_buf->in_use = 1; defrag_buf->pckt_seq = seq_num; } /* * update timestamp on the buffer */ clock_gettime(CLOCK_MONOTONIC, &defrag_buf->last_update); /* * check if we already received this fragment */ if (defrag_buf->frag_map[frag_seq]) { /* * if we have received this fragment and we didn't clear the buffer * it means that we don't have all fragments yet */ return 1; } /* * we need to handle the last packet with gloves due to its different size */ if (frag_seq == frags) { defrag_buf->last_frag_size = *len; /* * in the event when the last packet arrives first, * we still don't know the offset vs the other fragments (based on MTU), * so we store the fragment at the end of the buffer where it's safe * and take a copy of the len so that we can restore its offset later. * remember we can't use the local MTU for this calculation because pMTU * can be asymettric between the same hosts. */ if (!defrag_buf->frag_size) { defrag_buf->last_first = 1; memmove(defrag_buf->buf + (KNET_MAX_PACKET_SIZE - *len), data, *len); } } else { defrag_buf->frag_size = *len; } if (defrag_buf->frag_size) { memmove(defrag_buf->buf + ((frag_seq - 1) * defrag_buf->frag_size), data, *len); } defrag_buf->frag_recv++; defrag_buf->frag_map[frag_seq] = 1; /* * check if we received all the fragments */ if (defrag_buf->frag_recv == frags) { /* * special case the last pckt */ if (defrag_buf->last_first) { memmove(defrag_buf->buf + ((frags - 1) * defrag_buf->frag_size), defrag_buf->buf + (KNET_MAX_PACKET_SIZE - defrag_buf->last_frag_size), defrag_buf->last_frag_size); } /* * recalculate packet lenght */ *len = ((frags - 1) * defrag_buf->frag_size) + defrag_buf->last_frag_size; /* * copy the pckt back in the user data */ memmove(data, defrag_buf->buf, *len); /* * free this buffer */ defrag_buf->in_use = 0; return 0; } return 1; } static int _handle_data_stats(knet_handle_t knet_h, struct knet_link *src_link, ssize_t len, uint64_t decrypt_time) { int stats_err; /* data stats at the top for consistency with TX */ src_link->status.stats.rx_data_packets++; src_link->status.stats.rx_data_bytes += len; if (decrypt_time) { stats_err = pthread_mutex_lock(&knet_h->handle_stats_mutex); if (stats_err < 0) { log_err(knet_h, KNET_SUB_RX, "Unable to get mutex lock: %s", strerror(stats_err)); return -1; } /* Only update the crypto overhead for data packets. Mainly to be consistent with TX */ if (decrypt_time < knet_h->stats.rx_crypt_time_min) { knet_h->stats.rx_crypt_time_min = decrypt_time; } if (decrypt_time > knet_h->stats.rx_crypt_time_max) { knet_h->stats.rx_crypt_time_max = decrypt_time; } knet_h->stats.rx_crypt_time_ave = (knet_h->stats.rx_crypt_time_ave * knet_h->stats.rx_crypt_packets + decrypt_time) / (knet_h->stats.rx_crypt_packets+1); knet_h->stats.rx_crypt_packets++; pthread_mutex_unlock(&knet_h->handle_stats_mutex); } return 0; } static int _decompress_data(knet_handle_t knet_h, uint8_t decompress_type, unsigned char *data, ssize_t *len, ssize_t header_size) { int err = 0, stats_err = 0; if (decompress_type) { ssize_t decmp_outlen = KNET_DATABUFSIZE_COMPRESS; struct timespec start_time; struct timespec end_time; uint64_t decompress_time; clock_gettime(CLOCK_MONOTONIC, &start_time); err = decompress(knet_h, decompress_type, data, *len - header_size, knet_h->recv_from_links_buf_decompress, &decmp_outlen); clock_gettime(CLOCK_MONOTONIC, &end_time); timespec_diff(start_time, end_time, &decompress_time); stats_err = pthread_mutex_lock(&knet_h->handle_stats_mutex); if (stats_err < 0) { log_err(knet_h, KNET_SUB_RX, "Unable to get mutex lock: %s", strerror(stats_err)); return -1; } if (!err) { /* Collect stats */ if (decompress_time < knet_h->stats.rx_compress_time_min) { knet_h->stats.rx_compress_time_min = decompress_time; } if (decompress_time > knet_h->stats.rx_compress_time_max) { knet_h->stats.rx_compress_time_max = decompress_time; } knet_h->stats.rx_compress_time_ave = (knet_h->stats.rx_compress_time_ave * knet_h->stats.rx_compressed_packets + decompress_time) / (knet_h->stats.rx_compressed_packets+1); knet_h->stats.rx_compressed_packets++; knet_h->stats.rx_compressed_original_bytes += decmp_outlen; knet_h->stats.rx_compressed_size_bytes += *len - KNET_HEADER_SIZE; memmove(data, knet_h->recv_from_links_buf_decompress, decmp_outlen); *len = decmp_outlen + header_size; } else { knet_h->stats.rx_failed_to_decompress++; pthread_mutex_unlock(&knet_h->handle_stats_mutex); log_warn(knet_h, KNET_SUB_COMPRESS, "Unable to decompress packet (%d): %s", err, strerror(errno)); return -1; } pthread_mutex_unlock(&knet_h->handle_stats_mutex); } return 0; } static int _check_destination(knet_handle_t knet_h, struct knet_header *inbuf, unsigned char *data, ssize_t len, ssize_t header_size, int8_t *channel) { knet_node_id_t dst_host_ids[KNET_MAX_HOST]; size_t dst_host_ids_entries = 0; int bcast = 1; size_t host_idx; int found = 0; if (knet_h->dst_host_filter_fn) { bcast = knet_h->dst_host_filter_fn( knet_h->dst_host_filter_fn_private_data, data, len - header_size, KNET_NOTIFY_RX, knet_h->host_id, inbuf->kh_node, channel, dst_host_ids, &dst_host_ids_entries); if (bcast < 0) { log_debug(knet_h, KNET_SUB_RX, "Error from dst_host_filter_fn: %d", bcast); return -1; } if ((!bcast) && (!dst_host_ids_entries)) { log_debug(knet_h, KNET_SUB_RX, "Message is unicast but no dst_host_ids_entries"); return -1; } /* check if we are dst for this packet */ if (!bcast) { if (dst_host_ids_entries > KNET_MAX_HOST) { log_debug(knet_h, KNET_SUB_RX, "dst_host_filter_fn returned too many destinations"); return -1; } for (host_idx = 0; host_idx < dst_host_ids_entries; host_idx++) { if (dst_host_ids[host_idx] == knet_h->host_id) { found = 1; break; } } if (!found) { log_debug(knet_h, KNET_SUB_RX, "Packet is not for us"); return -1; } } } return 0; } static int _deliver_data(knet_handle_t knet_h, unsigned char *data, ssize_t len, ssize_t header_size, int8_t channel) { struct iovec iov_out[1]; ssize_t outlen = 0; memset(iov_out, 0, sizeof(iov_out)); retry: iov_out[0].iov_base = (void *) data + outlen; iov_out[0].iov_len = len - (outlen + header_size); outlen = writev(knet_h->sockfd[channel].sockfd[knet_h->sockfd[channel].is_created], iov_out, 1); if ((outlen > 0) && (outlen < (ssize_t)iov_out[0].iov_len)) { log_debug(knet_h, KNET_SUB_RX, "Unable to send all data to the application in one go. Expected: %zu Sent: %zd\n", iov_out[0].iov_len, outlen); goto retry; } if (outlen <= 0) { knet_h->sock_notify_fn(knet_h->sock_notify_fn_private_data, knet_h->sockfd[channel].sockfd[0], channel, KNET_NOTIFY_RX, outlen, errno); return -1; } if ((size_t)outlen != iov_out[0].iov_len) { return -1; } return 0; } static void _process_data(knet_handle_t knet_h, struct knet_host *src_host, struct knet_link *src_link, struct knet_header *inbuf, ssize_t len, uint64_t decrypt_time) { int8_t channel; uint8_t decompress_type = 0; ssize_t header_size; seq_num_t seq_num; uint8_t frags, frag_seq; unsigned char *data; if (_handle_data_stats(knet_h, src_link, len, decrypt_time) < 0) { return; } /* * register host is sending data. Required to determine if we need * to reset circular buffers. (see onwire_v1.c) */ src_host->got_data = 1; if (knet_h->onwire_ver_remap) { get_data_header_info_v1(knet_h, inbuf, &header_size, &channel, &seq_num, &decompress_type, &frags, &frag_seq); data = get_data_v1(knet_h, inbuf); } else { switch (inbuf->kh_version) { case 1: get_data_header_info_v1(knet_h, inbuf, &header_size, &channel, &seq_num, &decompress_type, &frags, &frag_seq); data = get_data_v1(knet_h, inbuf); break; default: log_warn(knet_h, KNET_SUB_RX, "processing data onwire version %u not supported", inbuf->kh_version); return; break; } } if (!_seq_num_lookup(knet_h, src_host, seq_num, 0, 0)) { if (src_host->link_handler_policy != KNET_LINK_POLICY_ACTIVE) { log_debug(knet_h, KNET_SUB_RX, "Packet has already been delivered"); } return; } if (frags > 1) { /* * len as received from the socket also includes extra stuff * that the defrag code doesn't care about. So strip it * here and readd only for repadding once we are done * defragging * * the defrag code assumes that data packets have all the same size * except the last one that might be smaller. * */ len = len - header_size; if (_pckt_defrag(knet_h, src_host, seq_num, data, &len, frags, frag_seq)) { return; } len = len + header_size; } if (_decompress_data(knet_h, decompress_type, data, &len, header_size) < 0) { return; } if (!src_host->status.reachable) { log_debug(knet_h, KNET_SUB_RX, "Source host %u not reachable yet. Discarding packet.", src_host->host_id); return; } if (knet_h->enabled != 1) /* data forward is disabled */ return; if (_check_destination(knet_h, inbuf, data, len, header_size, &channel) < 0) { return; } if (!knet_h->sockfd[channel].in_use) { log_debug(knet_h, KNET_SUB_RX, "received packet for channel %d but there is no local sock connected", channel); return; } if (_deliver_data(knet_h, data, len, header_size, channel) < 0) { return; } _seq_num_set(src_host, seq_num, 0); } static struct knet_header *_decrypt_packet(knet_handle_t knet_h, struct knet_header *inbuf, ssize_t *len, uint64_t *decrypt_time) { int try_decrypt = 0; int i = 0; struct timespec start_time; struct timespec end_time; ssize_t outlen; for (i = 1; i <= KNET_MAX_CRYPTO_INSTANCES; i++) { if (knet_h->crypto_instance[i]) { try_decrypt = 1; break; } } if ((!try_decrypt) && (knet_h->crypto_only == KNET_CRYPTO_RX_DISALLOW_CLEAR_TRAFFIC)) { log_debug(knet_h, KNET_SUB_RX, "RX thread configured to accept only crypto packets, but no crypto configs are configured!"); return NULL; } if (try_decrypt) { clock_gettime(CLOCK_MONOTONIC, &start_time); if (crypto_authenticate_and_decrypt(knet_h, (unsigned char *)inbuf, *len, knet_h->recv_from_links_buf_decrypt, &outlen) < 0) { log_debug(knet_h, KNET_SUB_RX, "Unable to decrypt/auth packet"); if (knet_h->crypto_only == KNET_CRYPTO_RX_DISALLOW_CLEAR_TRAFFIC) { return NULL; } log_debug(knet_h, KNET_SUB_RX, "Attempting to process packet as clear data"); } else { clock_gettime(CLOCK_MONOTONIC, &end_time); timespec_diff(start_time, end_time, decrypt_time); *len = outlen; inbuf = (struct knet_header *)knet_h->recv_from_links_buf_decrypt; } } return inbuf; } static int _packet_checks(knet_handle_t knet_h, struct knet_header *inbuf, ssize_t len) { if (len < (ssize_t)(KNET_HEADER_SIZE + 1)) { log_debug(knet_h, KNET_SUB_RX, "Packet is too short: %ld", (long)len); return -1; } /* * old versions of knet did not advertise max_ver and max_ver is set to 0. */ if (!inbuf->kh_max_ver) { inbuf->kh_max_ver = 1; } /* * if the node joining max version is lower than the min version * then we reject the node */ if (inbuf->kh_max_ver < knet_h->onwire_min_ver) { log_warn(knet_h, KNET_SUB_RX, "Received packet version %u from node %u, lower than currently minimal supported onwire version. Rejecting.", inbuf->kh_version, inbuf->kh_node); return -1; } /* * if the node joining with version higher than our max version * then we reject the node */ if (inbuf->kh_version > knet_h->onwire_max_ver) { log_warn(knet_h, KNET_SUB_RX, "Received packet version %u from node %u, higher than currently maximum supported onwire version. Rejecting.", inbuf->kh_version, inbuf->kh_node); return -1; } /* * if the node joining with version lower than the current in use version * then we reject the node * * NOTE: should we make this configurable and support downgrades? */ if ((!knet_h->onwire_force_ver) && (inbuf->kh_version < knet_h->onwire_ver) && (inbuf->kh_max_ver > inbuf->kh_version)) { log_warn(knet_h, KNET_SUB_RX, "Received packet version %u from node %u, lower than currently in use onwire version. Rejecting.", inbuf->kh_version, inbuf->kh_node); return -1; } return 0; } static void _handle_dynip(knet_handle_t knet_h, struct knet_host *src_host, struct knet_link *src_link, int sockfd, const struct knet_mmsghdr *msg) { if (src_link->dynamic == KNET_LINK_DYNIP) { if (cmpaddr(&src_link->dst_addr, msg->msg_hdr.msg_name) != 0) { log_debug(knet_h, KNET_SUB_RX, "host: %u link: %u appears to have changed ip address", src_host->host_id, src_link->link_id); memmove(&src_link->dst_addr, msg->msg_hdr.msg_name, sizeof(struct sockaddr_storage)); if (knet_addrtostr(&src_link->dst_addr, sockaddr_len(&src_link->dst_addr), src_link->status.dst_ipaddr, KNET_MAX_HOST_LEN, src_link->status.dst_port, KNET_MAX_PORT_LEN) != 0) { log_debug(knet_h, KNET_SUB_RX, "Unable to resolve ???"); snprintf(src_link->status.dst_ipaddr, KNET_MAX_HOST_LEN - 1, "Unknown!!!"); snprintf(src_link->status.dst_port, KNET_MAX_PORT_LEN - 1, "??"); } else { log_info(knet_h, KNET_SUB_RX, "host: %u link: %u new connection established from: %s %s", src_host->host_id, src_link->link_id, src_link->status.dst_ipaddr, src_link->status.dst_port); } } /* * transport has already accepted the connection here * otherwise we would not be receiving packets */ transport_link_dyn_connect(knet_h, sockfd, src_link); } } /* * processing incoming packets vs access lists */ static int _check_rx_acl(knet_handle_t knet_h, struct knet_link *src_link, const struct knet_mmsghdr *msg) { if (knet_h->use_access_lists) { if (!check_validate(knet_h, src_link, msg->msg_hdr.msg_name)) { char src_ipaddr[KNET_MAX_HOST_LEN]; char src_port[KNET_MAX_PORT_LEN]; memset(src_ipaddr, 0, KNET_MAX_HOST_LEN); memset(src_port, 0, KNET_MAX_PORT_LEN); if (knet_addrtostr(msg->msg_hdr.msg_name, sockaddr_len(msg->msg_hdr.msg_name), src_ipaddr, KNET_MAX_HOST_LEN, src_port, KNET_MAX_PORT_LEN) < 0) { log_debug(knet_h, KNET_SUB_RX, "Packet rejected: unable to resolve host/port"); } else { log_debug(knet_h, KNET_SUB_RX, "Packet rejected from %s/%s", src_ipaddr, src_port); } return 0; } } return 1; } static void _parse_recv_from_links(knet_handle_t knet_h, int sockfd, const struct knet_mmsghdr *msg) { int savederrno = 0, stats_err = 0; struct knet_host *src_host; struct knet_link *src_link; uint64_t decrypt_time = 0; struct knet_header *inbuf = msg->msg_hdr.msg_iov->iov_base; ssize_t len = msg->msg_len; int i, found_link = 0; inbuf = _decrypt_packet(knet_h, inbuf, &len, &decrypt_time); if (!inbuf) { return; } inbuf->kh_node = ntohs(inbuf->kh_node); if (_packet_checks(knet_h, inbuf, len) < 0) { return; } /* * determine source host */ src_host = knet_h->host_index[inbuf->kh_node]; if (src_host == NULL) { /* host not found */ log_debug(knet_h, KNET_SUB_RX, "Unable to find source host for this packet"); return; } /* * deteremine source link */ if (inbuf->kh_type == KNET_HEADER_TYPE_PING) { _handle_onwire_version(knet_h, src_host, inbuf); if (knet_h->onwire_ver_remap) { src_link = get_link_from_pong_v1(knet_h, src_host, inbuf); } else { switch (inbuf->kh_version) { case 1: src_link = get_link_from_pong_v1(knet_h, src_host, inbuf); break; default: log_warn(knet_h, KNET_SUB_RX, "Parsing ping onwire version %u not supported", inbuf->kh_version); return; break; } } if (!_check_rx_acl(knet_h, src_link, msg)) { return; } _handle_dynip(knet_h, src_host, src_link, sockfd, msg); } else { /* all other packets */ for (i = 0; i < KNET_MAX_LINK; i++) { src_link = &src_host->link[i]; if (cmpaddr(&src_link->dst_addr, msg->msg_hdr.msg_name) == 0) { found_link = 1; break; } } if (found_link) { /* * this check is currently redundant.. Keep it here for now */ if (!_check_rx_acl(knet_h, src_link, msg)) { return; } } else { log_debug(knet_h, KNET_SUB_RX, "Unable to determine source link for data packet. Discarding packet."); return; } } stats_err = pthread_mutex_lock(&src_link->link_stats_mutex); if (stats_err) { log_err(knet_h, KNET_SUB_RX, "Unable to get stats mutex lock for host %u link %u: %s", src_host->host_id, src_link->link_id, strerror(savederrno)); return; } switch (inbuf->kh_type) { case KNET_HEADER_TYPE_DATA: _process_data(knet_h, src_host, src_link, inbuf, len, decrypt_time); break; case KNET_HEADER_TYPE_PING: process_ping(knet_h, src_host, src_link, inbuf, len); break; case KNET_HEADER_TYPE_PONG: process_pong(knet_h, src_host, src_link, inbuf, len); break; case KNET_HEADER_TYPE_PMTUD: src_link->status.stats.rx_pmtu_packets++; src_link->status.stats.rx_pmtu_bytes += len; /* Unlock so we don't deadlock with tx_mutex */ pthread_mutex_unlock(&src_link->link_stats_mutex); process_pmtud(knet_h, src_link, inbuf); return; /* Don't need to unlock link_stats_mutex */ break; case KNET_HEADER_TYPE_PMTUD_REPLY: src_link->status.stats.rx_pmtu_packets++; src_link->status.stats.rx_pmtu_bytes += len; /* pmtud_mutex can't be acquired while we hold a link_stats_mutex (ordering) */ pthread_mutex_unlock(&src_link->link_stats_mutex); process_pmtud_reply(knet_h, src_link, inbuf); return; break; default: pthread_mutex_unlock(&src_link->link_stats_mutex); return; break; } pthread_mutex_unlock(&src_link->link_stats_mutex); } static void _handle_recv_from_links(knet_handle_t knet_h, int sockfd, struct knet_mmsghdr *msg) { int err, savederrno; int i, msg_recv, transport; if (pthread_rwlock_rdlock(&knet_h->global_rwlock) != 0) { log_debug(knet_h, KNET_SUB_RX, "Unable to get global read lock"); return; } if (_is_valid_fd(knet_h, sockfd) < 1) { /* * this is normal if a fd got an event and before we grab the read lock * and the link is removed by another thread */ goto exit_unlock; } transport = knet_h->knet_transport_fd_tracker[sockfd].transport; /* * reset msg_namelen to buffer size because after recvmmsg * each msg_namelen will contain sizeof sockaddr_in or sockaddr_in6 */ for (i = 0; i < PCKT_RX_BUFS; i++) { msg[i].msg_hdr.msg_namelen = knet_h->knet_transport_fd_tracker[sockfd].sockaddr_len; } msg_recv = _recvmmsg(sockfd, &msg[0], PCKT_RX_BUFS, MSG_DONTWAIT | MSG_NOSIGNAL); savederrno = errno; /* * WARNING: man page for recvmmsg is wrong. Kernel implementation here: * recvmmsg can return: * -1 on error * 0 if the previous run of recvmmsg recorded an error on the socket * N number of messages (see exception below). * * If there is an error from recvmsg after receiving a frame or more, the recvmmsg * loop is interrupted, error recorded in the socket (getsockopt(SO_ERROR) and * it will be visibile in the next run. * * Need to be careful how we handle errors at this stage. * * error messages need to be handled on a per transport/protocol base * at this point we have different layers of error handling * - msg_recv < 0 -> error from this run * msg_recv = 0 -> error from previous run and error on socket needs to be cleared * - per-transport message data * example: msg[i].msg_hdr.msg_flags & MSG_NOTIFICATION or msg_len for SCTP == EOF, * but for UDP it is perfectly legal to receive a 0 bytes message.. go figure * - NOTE: on SCTP MSG_NOTIFICATION we get msg_recv == PCKT_FRAG_MAX messages and no * errno set. That means the error api needs to be able to abort the loop below. */ if (msg_recv <= 0) { transport_rx_sock_error(knet_h, transport, sockfd, msg_recv, savederrno); goto exit_unlock; } for (i = 0; i < msg_recv; i++) { err = transport_rx_is_data(knet_h, transport, sockfd, &msg[i]); /* * TODO: make this section silent once we are confident * all protocols packet handlers are good */ switch(err) { case KNET_TRANSPORT_RX_ERROR: /* on error */ log_debug(knet_h, KNET_SUB_RX, "Transport reported error parsing packet"); goto exit_unlock; break; case KNET_TRANSPORT_RX_NOT_DATA_CONTINUE: /* packet is not data and we should continue the packet process loop */ log_debug(knet_h, KNET_SUB_RX, "Transport reported no data, continue"); break; case KNET_TRANSPORT_RX_NOT_DATA_STOP: /* packet is not data and we should STOP the packet process loop */ log_debug(knet_h, KNET_SUB_RX, "Transport reported no data, stop"); goto exit_unlock; break; case KNET_TRANSPORT_RX_IS_DATA: /* packet is data and should be parsed as such */ _parse_recv_from_links(knet_h, sockfd, &msg[i]); break; case KNET_TRANSPORT_RX_OOB_DATA_CONTINUE: log_debug(knet_h, KNET_SUB_RX, "Transport is processing sock OOB data, continue"); break; case KNET_TRANSPORT_RX_OOB_DATA_STOP: log_debug(knet_h, KNET_SUB_RX, "Transport has completed processing sock OOB data, stop"); goto exit_unlock; break; } } exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); } void *_handle_recv_from_links_thread(void *data) { int i, nev; knet_handle_t knet_h = (knet_handle_t) data; struct epoll_event events[KNET_EPOLL_MAX_EVENTS]; struct sockaddr_storage address[PCKT_RX_BUFS]; struct knet_mmsghdr msg[PCKT_RX_BUFS]; struct iovec iov_in[PCKT_RX_BUFS]; set_thread_status(knet_h, KNET_THREAD_RX, KNET_THREAD_STARTED); memset(&msg, 0, sizeof(msg)); memset(&events, 0, sizeof(events)); for (i = 0; i < PCKT_RX_BUFS; i++) { iov_in[i].iov_base = (void *)knet_h->recv_from_links_buf[i]; iov_in[i].iov_len = KNET_DATABUFSIZE; memset(&msg[i].msg_hdr, 0, sizeof(struct msghdr)); msg[i].msg_hdr.msg_name = &address[i]; msg[i].msg_hdr.msg_namelen = sizeof(struct sockaddr_storage); /* Real value filled in before actual use */ msg[i].msg_hdr.msg_iov = &iov_in[i]; msg[i].msg_hdr.msg_iovlen = 1; } while (!shutdown_in_progress(knet_h)) { nev = epoll_wait(knet_h->recv_from_links_epollfd, events, KNET_EPOLL_MAX_EVENTS, knet_h->threads_timer_res / 1000); /* * the RX threads only need to notify that there has been at least * one successful run after queue flush has been requested. * See setfwd in handle.c */ if (get_thread_flush_queue(knet_h, KNET_THREAD_RX) == KNET_THREAD_QUEUE_FLUSH) { set_thread_flush_queue(knet_h, KNET_THREAD_RX, KNET_THREAD_QUEUE_FLUSHED); } /* * we use timeout to detect if thread is shutting down */ if (nev == 0) { continue; } for (i = 0; i < nev; i++) { _handle_recv_from_links(knet_h, events[i].data.fd, msg); } + + _shrink_defrag_buffers(knet_h); } set_thread_status(knet_h, KNET_THREAD_RX, KNET_THREAD_STOPPED); return NULL; } ssize_t knet_recv(knet_handle_t knet_h, char *buff, const size_t buff_len, const int8_t channel) { int savederrno = 0; ssize_t err = 0; struct iovec iov_in; if (!_is_valid_handle(knet_h)) { return -1; } if (buff == NULL) { errno = EINVAL; return -1; } if (buff_len <= 0) { errno = EINVAL; return -1; } if (buff_len > KNET_MAX_PACKET_SIZE) { errno = EINVAL; return -1; } if (channel < 0) { errno = EINVAL; return -1; } if (channel >= KNET_DATAFD_MAX) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (!knet_h->sockfd[channel].in_use) { savederrno = EINVAL; err = -1; goto out_unlock; } memset(&iov_in, 0, sizeof(iov_in)); iov_in.iov_base = (void *)buff; iov_in.iov_len = buff_len; err = readv(knet_h->sockfd[channel].sockfd[0], &iov_in, 1); savederrno = errno; out_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } diff --git a/man/Makefile.am b/man/Makefile.am index 1d75ac4d..0936d34a 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -1,151 +1,153 @@ # # Copyright (C) 2017-2021 Red Hat, Inc. All rights reserved. # # Authors: Fabio M. Di Nitto # Federico Simoncelli # # This software licensed under GPL-2.0+ # MAINTAINERCLEANFILES = Makefile.in include $(top_srcdir)/build-aux/check.mk EXTRA_DIST = \ api-to-man-page-coverage if BUILD_MAN knet_man3_MANS = \ knet_addrtostr.3 \ knet_handle_add_datafd.3 \ knet_handle_clear_stats.3 \ knet_handle_compress.3 \ knet_handle_enable_filter.3 \ knet_handle_enable_pmtud_notify.3 \ knet_handle_enable_sock_notify.3 \ knet_handle_free.3 \ knet_handle_get_channel.3 \ knet_get_compress_list.3 \ knet_get_crypto_list.3 \ knet_handle_get_datafd.3 \ knet_handle_get_stats.3 \ knet_get_transport_id_by_name.3 \ knet_get_transport_list.3 \ knet_get_transport_name_by_id.3 \ knet_handle_get_transport_reconnect_interval.3 \ knet_handle_new.3 \ knet_handle_pmtud_get.3 \ knet_handle_pmtud_set.3 \ knet_handle_pmtud_getfreq.3 \ knet_handle_pmtud_setfreq.3 \ knet_handle_remove_datafd.3 \ knet_handle_setfwd.3 \ knet_handle_set_transport_reconnect_interval.3 \ knet_host_add.3 \ knet_host_enable_status_change_notify.3 \ knet_host_get_host_list.3 \ knet_host_get_id_by_host_name.3 \ knet_host_get_name_by_host_id.3 \ knet_host_get_policy.3 \ knet_host_get_status.3 \ knet_host_remove.3 \ knet_host_set_name.3 \ knet_host_set_policy.3 \ knet_link_clear_config.3 \ knet_link_get_config.3 \ knet_link_get_enable.3 \ knet_link_get_link_list.3 \ knet_link_get_ping_timers.3 \ knet_link_get_pong_count.3 \ knet_link_get_priority.3 \ knet_link_get_status.3 \ knet_link_set_config.3 \ knet_link_set_enable.3 \ knet_link_set_ping_timers.3 \ knet_link_set_pong_count.3 \ knet_link_set_priority.3 \ knet_log_get_loglevel.3 \ knet_log_get_loglevel_id.3 \ knet_log_get_loglevel_name.3 \ knet_log_get_subsystem_id.3 \ knet_log_get_subsystem_name.3 \ knet_log_set_loglevel.3 \ knet_recv.3 \ knet_send.3 \ knet_send_sync.3 \ knet_strtoaddr.3 \ knet_handle_set_threads_timer_res.3 \ knet_handle_get_threads_timer_res.3 \ knet_link_enable_status_change_notify.3 \ knet_handle_enable_access_lists.3 \ knet_link_add_acl.3 \ knet_link_insert_acl.3 \ knet_link_rm_acl.3 \ knet_link_clear_acl.3 \ knet_handle_crypto_set_config.3 \ knet_handle_crypto_use_config.3 \ knet_handle_crypto_rx_clear_traffic.3 \ knet_handle_enable_onwire_ver_notify.3 \ knet_handle_get_onwire_ver.3 \ - knet_handle_set_onwire_ver.3 + knet_handle_set_onwire_ver.3 \ + knet_handle_get_host_defrag_bufs.3 \ + knet_handle_set_host_defrag_bufs.3 if BUILD_LIBNOZZLE nozzle_man3_MANS = \ nozzle_add_ip.3 \ nozzle_close.3 \ nozzle_del_ip.3 \ nozzle_get_fd.3 \ nozzle_get_handle_by_name.3 \ nozzle_get_ips.3 \ nozzle_get_mac.3 \ nozzle_get_mtu.3 \ nozzle_get_name_by_handle.3 \ nozzle_open.3 \ nozzle_reset_mac.3 \ nozzle_reset_mtu.3 \ nozzle_run_updown.3 \ nozzle_set_down.3 \ nozzle_set_mac.3 \ nozzle_set_mtu.3 \ nozzle_set_up.3 endif man3_MANS = $(knet_man3_MANS) $(nozzle_man3_MANS) $(MANS): doxyfile-knet.stamp doxyfile-nozzle.stamp # export LSAN_OPTIONS unconditionally for now. # there is no urgency to fix doxygen2man for leaks or bad memory access # since it's a one-shot tool and doesn't affect runtime. doxyfile-knet.stamp: Doxyfile-knet $(top_srcdir)/libknet/libknet.h $(DOXYGEN) Doxyfile-knet 2>&1 | $(EGREP) -v 'warning.*macro definition' LSAN_OPTIONS="exitcode=0" $(DOXYGEN2MAN) -m -P -o $(builddir) -s 3 -p @PACKAGE_NAME@ -H "Kronosnet Programmer's Manual" \ $$($(UTC_DATE_AT)$(SOURCE_EPOCH) +"-D %F -Y %Y") -d $(builddir)/xml-knet/ libknet_8h.xml touch doxyfile-knet.stamp doxyfile-nozzle.stamp: Doxyfile-nozzle $(top_srcdir)/libnozzle/libnozzle.h if BUILD_LIBNOZZLE $(DOXYGEN) Doxyfile-nozzle 2>&1 | $(EGREP) -v 'warning.*macro definition' LSAN_OPTIONS="exitcode=0" $(DOXYGEN2MAN) -m -P -o $(builddir) -s 3 -p @PACKAGE_NAME@ -H "Kronosnet Programmer's Manual" \ $$($(UTC_DATE_AT)$(SOURCE_EPOCH) +"-D %F -Y %Y") -d $(builddir)/xml-nozzle/ libnozzle_8h.xml endif touch doxyfile-nozzle.stamp noinst_SCRIPTS = api-to-man-page-coverage check-local: check-api-to-man-page-coverage-libknet check-api-to-man-page-coverage-libnozzle check-api-to-man-page-coverage-libnozzle: if BUILD_LIBNOZZLE $(srcdir)/api-to-man-page-coverage $(top_srcdir) nozzle endif check-api-to-man-page-coverage-libknet: $(srcdir)/api-to-man-page-coverage $(top_srcdir) knet endif clean-local: rm -rf doxyfile*.stamp xml* *.3