Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F3151648
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
110 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/libknet/bindings/rust/src/knet_bindings.rs b/libknet/bindings/rust/src/knet_bindings.rs
index 8878cb7b..01262186 100644
--- a/libknet/bindings/rust/src/knet_bindings.rs
+++ b/libknet/bindings/rust/src/knet_bindings.rs
@@ -1,2556 +1,2556 @@
// libknet interface for Rust
// Copyright (C) 2021-2023 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, Eq)]
/// 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<HashMap<u64, PrivHandle>> = 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<HostId>) -> 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::<HostId>::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<LogMsg>)
{
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::<ffi::knet_log_msg>())};
if msglen < 1 {
unsafe { libc::close(knet_pipe); }
// EOF on pipe, handle is closed.
return;
}
if msglen == size_of::<ffi::knet_log_msg>() 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);
+ println!("Error sending log message: {e}");
}
}
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[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<SockNotifyFn>,
sock_notify_private_data: u64,
filter_fn: Option<FilterFn>,
filter_private_data: u64,
pmtud_notify_fn: Option<PmtudNotifyFn>,
pmtud_notify_private_data: u64,
onwire_notify_fn: Option<OnwireNotifyFn>,
onwire_notify_private_data: u64,
host_status_change_notify_fn: Option<HostStatusChangeNotifyFn>,
host_status_change_notify_private_data: u64,
link_status_change_notify_fn: Option<LinkStatusChangeNotifyFn>,
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<Sender<LogMsg>>,
default_log_level: LogLevel,
flags: HandleFlags) -> Result<Handle>
{
// 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<SockNotifyFn>) -> Result<()>
{
if let Some(h) = HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) {
h.sock_notify_private_data = private_data;
h.sock_notify_fn = sock_notify_fn;
let res = 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)
},
};
if res == 0 {
return Ok(());
} else {
return Err(Error::last_os_error());
}
}
Err(Error::new(ErrorKind::Other, "Rust handle not found"))
}
/// 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<i8>
{
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<i32>
{
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, Eq)]
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<isize>
{
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<isize>
{
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<FilterFn>) -> Result<()>
{
if let Some(h) = HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) {
h.filter_private_data = private_data;
h.filter_fn = filter_fn;
let res = 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)
},
};
if res == 0 {
return Ok(());
} else {
return Err(Error::last_os_error());
}
};
Err(Error::new(ErrorKind::Other, "Rust handle not found"))
}
/// 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<u32>
{
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<u32>
{
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<u32>
{
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<PmtudNotifyFn>) -> Result<()>
{
if let Some(h) = HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) {
h.pmtud_notify_private_data = private_data;
h.pmtud_notify_fn = pmtud_notify_fn;
let res = 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)
},
};
if res == 0 {
return Ok(());
} else {
return Err(Error::last_os_error());
}
}
Err(Error::new(ErrorKind::Other, "Rust handle not found"))
}
/// 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<HandleStats>
{
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::<HandleStats>());
(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<Vec<CryptoInfo>>
{
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::<CryptoInfo>::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<Vec<CompressInfo>>
{
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::<CompressInfo>::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<OnwireNotifyFn>) -> Result<()>
{
// This looks a bit different to the other _enable*_notify calls because knet
// calls the callback function in the API. Which results in a deadlock with our
// own mutex
if let Some(h) = HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) {
h.onwire_notify_private_data = private_data;
h.onwire_notify_fn = onwire_notify_fn;
} else {
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<u32>
{
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<String>
{
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<HostId>
{
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<Vec<HostId>>
{
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::<HostId>::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, Eq)]
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<LinkPolicy>
{
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<HostStatus>
{
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<HostStatusChangeNotifyFn>) -> Result<()>
{
if let Some(h) = HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) {
h.host_status_change_notify_private_data = private_data;
h.host_status_change_notify_fn = host_status_change_notify_fn;
let res = 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)
},
};
if res == 0 {
return Ok(());
} else {
return Err(Error::last_os_error());
}
}
Err(Error::new(ErrorKind::Other, "Rust handle not found"))
}
/// 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<Vec<TransportInfo>>
{
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::<TransportInfo>::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<SocketAddr>, Option<SocketAddr>, 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
#[cfg(any(target_os="freebsd"))]
let mut new_ss = ffi::sockaddr_storage {
ss_family: 0,
ss_len: 0,
__ss_pad1: [0; 6],
__ss_align: 0,
__ss_pad2: [0; 112],
};
#[cfg(any(target_os="linux"))]
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<bool>
{
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, c_timeout, 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<u8>
{
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<u8>
{
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<Vec<u8>>
{
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::<u8>::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<LinkStatusChangeNotifyFn>) -> Result<()>
{
if let Some(h) = HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) {
h.link_status_change_notify_private_data = private_data;
h.link_status_change_notify_fn = link_status_change_notify_fn;
let res = 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)
},
};
if res == 0 {
return Ok(());
} else {
return Err(Error::last_os_error());
}
}
Err(Error::new(ErrorKind::Other, "Rust handle not found"))
}
/// 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<SystemTime>,
pub last_down_times: Vec<SystemTime>,
}
// 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, "{i:?}")?;
}
write!(f, " Last down times: ")?;
for i in &self.last_down_times {
- write!(f, "{:?}", i)?;
+ 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<SystemTime>
{
let mut times_vec = Vec::<SystemTime>::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<LinkStatus>
{
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::<ffi::knet_link_status>());
(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<u8>
{
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<String>
{
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<u8>
{
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<String>
{
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<LogLevel>
{
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 c5d1a6c1..0158e7ce 100644
--- a/libknet/bindings/rust/tests/src/bin/knet-test.rs
+++ b/libknet/bindings/rust/tests/src/bin/knet-test.rs
@@ -1,974 +1,971 @@
// Testing the Knet Rust APIs
//
// Copyright (C) 2021-2023 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};
use std::env;
const CHANNEL: i8 = 1;
// Dirty C function to set the plugin path for testing (only)
extern {
fn set_plugin_path(knet_h: knet::Handle);
}
fn is_memcheck() -> bool
{
match env::var("KNETMEMCHECK") {
Ok(s) => {
s == "yes"
}
Err(_) => false
}
}
// Probably this will never happen, but just-in-case
fn is_helgrind() -> bool
{
match env::var("KNETHELGRIND") {
Ok(s) => {
s == "yes"
}
Err(_) => false
}
}
fn get_scaled_tmo(millis: u64) -> time::Duration
{
if is_memcheck() || is_helgrind() {
println!("Running under valgrind, increasing timer from {} to {}", millis, millis*16);
time::Duration::from_millis(millis * 16)
} else {
time::Duration::from_millis(millis)
}
}
// 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);
+ println!("sock notify called for host {private_data}, datafd: {datafd}, channel: {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);
+ println!("PMTUD notify: host {private_data}, MTU:{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);
+ 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::HostId>) -> knet::FilterDecision
{
- println!("Filter ({}) called {} to {} from {}, channel: {}",
- private_data, txrx, this_host_id, src_host_id, channel);
+ println!("Filter ({private_data}) called {txrx} to {this_host_id} from {src_host_id}, channel: {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<knet::LogMsg>)
{
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<knet::Handle>
{
let (log_sender, log_receiver) = channel::<knet::LogMsg>();
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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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<u16>
{
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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ println!("Added datafd, fd={fd}, channel={chan}");
},
Err(e) => {
- println!("Error from add_datafd: {}", 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);
+ 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);
+ 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);
+ 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);
+ println!("get_link_ping_timers returned wrong values {a}, {b},{c} (s/b 500,1000,1024)");
return Err(Error::new(ErrorKind::Other, "Error in ping timers"));
}
},
Err(e) => {
- println!("Error from set_link_ping_timers: {}", 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);
+ 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);
+ println!("got datafd {f} for channel");
f
}
Err(e) => {
- println!("Error from handle_get_datafd: {}", 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);
+ 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);
+ 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);
+ 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);
+ println!("got QUIT on {host}, exitting");
break;
}
}
}
Err(e) => {
if e.kind() == ErrorKind::WouldBlock {
thread::sleep(get_scaled_tmo(100));
} else {
- println!("recv failed: {}", e);
+ 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);
+ 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);
+ println!("got datafd {f} for channel");
f
}
Err(e) => {
- println!("Error from handle_get_datafd: {}", 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);
+ 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);
+ 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);
+ println!("clear config failed: {e}");
return Err(e);
}
if let Err(e) = knet::host_remove(handle, &other_hostid) {
- println!("host remove failed: {}", e);
+ println!("host remove failed: {e}");
return Err(e);
}
if let Err(e) = knet::handle_free(handle) {
- println!("handle_free failed: {}", e);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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(get_scaled_tmo(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);
+ 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);
+ print!("host {i}: links: ");
match knet::link_get_link_list(handle, i) {
Ok(ll) => {
for j in ll {
- print!(" {}",j);
+ print!(" {j}");
}
},
Err(e) => {
- println!("link_get_link_list failed: {}", e);
+ println!("link_get_link_list failed: {e}");
return Err(e);
}
}
println!();
}
}
Err(e) => {
- println!("link_get_host_list failed: {}", 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);
+ 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);
+ println!("knet_handle_get_threads_timer_res returned wrong value {v}");
}
},
Err(e) => {
- println!("knet_handle_set_threads_timer_res failed: {:?}", 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);
+ 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);
+ println!("knet_handle_pmtud_get returned wrong value {v} (ALLOWED)");
// Don't fail on this, it might not have been set yet
}
},
Err(e) => {
- println!("knet_handle_pmtud_get failed: {:?}", 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);
+ 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);
+ println!("knet_handle_pmtud_getfreq returned wrong value {v}");
}
},
Err(e) => {
- println!("knet_handle_pmtud_getfreq failed: {:?}", 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);
+ 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);
+ println!("knet_handle_get_transport_reconnect_interval {v}");
}
},
Err(e) => {
- println!("knet_handle_get_transport_reconnect_interval failed: {:?}", 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);
+ 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);
+ println!("knet_link_get_pong_count returned wrong value {v}");
}
},
Err(e) => {
- println!("knet_link_get_pong_count failed: {:?}", 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);
+ 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);
+ println!("knet_host_get_policy returned wrong value {v}");
}
},
Err(e) => {
- println!("knet_host_get_policy failed: {:?}", 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);
+ 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);
+ println!("knet_link_get_priority returned wrong value {v}");
}
},
Err(e) => {
- println!("knet_link_get_priority failed: {:?}", 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);
+ println!("Returned host name is {n}");
n
},
Err(e) => {
- println!("knet_host_get_name_by_host_id failed: {:?}", 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ println!("Got {min},{max},{shrink},{policy}. expected 4,32,2,Absolute");
} else {
- println!("Defrag params correct: {},{},{},{}", min, max, shrink, policy);
+ println!("Defrag params correct: {min},{max},{shrink},{policy}");
}
}
Err(e) => {
- println!("handle_config_get_host_defrag_bufs failed: {:?}", 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);
+ 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);
+ println!("get_onwire_ver: Got onwire ver: {min}/{max}/{ver}");
},
Err(e) => {
- println!("knet_link_get_onwire_ver failed: {:?}", 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),
+ Ok(n) => println!("subsystem name for 3 is {n}"),
Err(e) => {
- println!("knet_log_get_subsystem_name failed: {:?}", 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),
+ Ok(n) => println!("subsystem ID for TX is {n}"),
Err(e) => {
- println!("knet_log_get_subsystem_id failed: {:?}", 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),
+ Ok(n) => println!("loglevel ID for DEBUG is {n}"),
Err(e) => {
- println!("knet_log_get_loglevel_id failed: {:?}", 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),
+ Ok(n) => println!("loglevel name for 1 is {n}"),
Err(e) => {
- println!("knet_log_get_loglevel_name failed: {:?}", 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);
+ 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),
+ Ok(n) => println!("loglevel for Handle is {n}"),
Err(e) => {
- println!("knet_log_get_loglevel failed: {:?}", 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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(get_scaled_tmo(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(get_scaled_tmo(3000));
set_crypto(handle1)?;
set_crypto(handle2)?;
set_compress(handle1)?;
set_compress(handle2)?;
thread::sleep(get_scaled_tmo(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);
+ println!("thread join error: {error:?}");
}
}
// Try some statses
match knet::handle_get_stats(handle1) {
- Ok(s) => println!("handle stats: {}", s),
+ Ok(s) => println!("handle stats: {s}"),
Err(e) => {
- println!("handle_get_stats failed: {:?}", e);
+ println!("handle_get_stats failed: {e:?}");
return Err(e);
}
}
match knet::host_get_status(handle1, &host2) {
- Ok(s) => println!("host status: {}", s),
+ Ok(s) => println!("host status: {s}"),
Err(e) => {
- println!("host_get_status failed: {:?}", e);
+ println!("host_get_status failed: {e:?}");
return Err(e);
}
}
match knet::link_get_status(handle1, &host2, 0) {
- Ok(s) => println!("link status: {}", s),
+ Ok(s) => println!("link status: {s}"),
Err(e) => {
- println!("link_get_status failed: {:?}", 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);
+ 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(get_scaled_tmo(3000));
Ok(())
}
diff --git a/libnozzle/bindings/rust/tests/src/bin/nozzle-test.rs b/libnozzle/bindings/rust/tests/src/bin/nozzle-test.rs
index 79638c86..acd957c7 100644
--- a/libnozzle/bindings/rust/tests/src/bin/nozzle-test.rs
+++ b/libnozzle/bindings/rust/tests/src/bin/nozzle-test.rs
@@ -1,245 +1,245 @@
// Testing the Nozzle Rust APIs
//
// Copyright (C) 2021-2023 Red Hat, Inc.
//
// All rights reserved.
//
// Author: Christine Caulfield (ccaulfi@redhat.com)
//
use nozzle_bindings::nozzle_bindings as nozzle;
use std::io::{Result, Error, ErrorKind, BufWriter, Write};
use std::fmt::Write as fmtwrite;
use std::{thread, time};
use std::fs::File;
use std::fs;
use tempfile::tempdir;
const SKIP: i32 = 77;
fn main() -> Result<()>
{
// We must be root
if unsafe { libc::geteuid() != 0 } {
std::process::exit(SKIP);
}
// Run in a random tmpdir so we don't clash with other instances
let tmp_path = tempdir()?;
let tmp_dir = match tmp_path.path().to_str() {
Some(td) => td,
None => {
println!("Error creating temp path for running");
return Err(Error::new(ErrorKind::Other, "Error creating temp path"));
}
};
std::env::set_current_dir(tmp_dir)?;
// Let the OS generate a tap name
let mut nozzle_name = String::from("");
let handle = match nozzle::open(&mut nozzle_name, tmp_dir) {
Ok(h) => {
- println!("Opened device {}", nozzle_name);
+ println!("Opened device {nozzle_name}");
h
},
Err(e) => {
- println!("Error from open: {}", e);
+ println!("Error from open: {e}");
return Err(e);
}
};
// Get default state for checking reset_* calls later
let saved_mtu = match nozzle::get_mtu(handle) {
Ok(m) => m,
Err(e) => {
- println!("Error from get_mtu: {}", e);
+ println!("Error from get_mtu: {e}");
return Err(e);
}
};
let saved_mac = match nozzle::get_mac(handle) {
Ok(m) => m,
Err(e) => {
- println!("Error from get_mac: {}", e);
+ println!("Error from get_mac: {e}");
return Err(e);
}
};
// Play with APIs
if let Err(e) = nozzle::add_ip(handle, "192.160.100.1", "24") {
- println!("Error from add_ip: {}", e);
+ println!("Error from add_ip: {e}");
return Err(e);
}
if let Err(e) = nozzle::add_ip(handle, "192.160.100.2", "24") {
- println!("Error from add_ip2: {}", e);
+ println!("Error from add_ip2: {e}");
return Err(e);
}
if let Err(e) = nozzle::add_ip(handle, "192.160.100.3", "24") {
- println!("Error from add_ip3: {}", e);
+ println!("Error from add_ip3: {e}");
return Err(e);
}
if let Err(e) = nozzle::set_mac(handle, "AA:00:04:00:22:01") {
- println!("Error from set_mac: {}", e);
+ println!("Error from set_mac: {e}");
return Err(e);
}
if let Err(e) = nozzle::set_mtu(handle, 157) {
- println!("Error from set_mtu: {}", e);
+ println!("Error from set_mtu: {e}");
return Err(e);
}
if let Err(e) = nozzle::set_up(handle) {
- println!("Error from set_up: {}", e);
+ println!("Error from set_up: {e}");
return Err(e);
}
// Create the 'up' script so we can test the run_updown() function,
let up_path = std::path::Path::new("up.d");
if let Err(e) = fs::create_dir_all(up_path) {
- eprintln!("Error creating up.d directory: {:?}", e);
+ eprintln!("Error creating up.d directory: {e:?}");
return Err(e);
}
let mut up_filename = String::new();
- if let Err(e) = write!(up_filename, "up.d/{}", nozzle_name) {
- eprintln!("Error making up.d filename: {:?}", e);
+ if let Err(e) = write!(up_filename, "up.d/{nozzle_name}") {
+ eprintln!("Error making up.d filename: {e:?}");
return Err(Error::new(ErrorKind::Other, "Error making up.d filename"));
}
match File::create(&up_filename) {
Err(e) => {
println!("Cannot create up.d file {}: {}", &up_filename, e);
return Err(e);
}
Ok(fl) => {
let mut f = BufWriter::new(fl);
writeln!(f, "#!/bin/sh\necho 'This is a test of an \"Up\" script'")?;
}
}
// A grotty way to do chmod, but normally this would be distributed by the sysadmin
unsafe {
let up_cstring = std::ffi::CString::new(up_filename.clone()).unwrap();
libc::chmod(up_cstring.as_ptr(), 0o700);
}
match nozzle::run_updown(handle, nozzle::Action::Up) {
- Ok(s) => println!("Returned from Up script: {}", s),
+ Ok(s) => println!("Returned from Up script: {s}"),
Err(e) => {
- println!("Error from run_updown: {}", e);
+ println!("Error from run_updown: {e}");
return Err(e);
}
}
// Tidy up after ourself - remove the up.d/tapX file
fs::remove_file(&up_filename)?;
fs::remove_dir("up.d")?;
match nozzle::get_ips(handle) {
Ok(ips) => {
print!("Got IPs:");
for i in ips {
- print!(" {}", i);
+ print!(" {i}");
}
println!();
},
Err(e) => {
- println!("Error from get_ips: {}", e);
+ println!("Error from get_ips: {e}");
return Err(e);
}
}
match nozzle::get_mtu(handle) {
- Ok(m) => println!("Got mtu: {}", m),
+ Ok(m) => println!("Got mtu: {m}"),
Err(e) => {
- println!("Error from get_ips: {}", e);
+ println!("Error from get_ips: {e}");
return Err(e);
}
}
match nozzle::get_mac(handle) {
- Ok(m) => println!("Got mac: {}", m),
+ Ok(m) => println!("Got mac: {m}"),
Err(e) => {
- println!("Error from get_ips: {}", e);
+ println!("Error from get_ips: {e}");
return Err(e);
}
}
match nozzle::get_fd(handle) {
- Ok(f) => println!("Got FD: {}", f),
+ Ok(f) => println!("Got FD: {f}"),
Err(e) => {
- println!("Error from get_fd: {}", e);
+ println!("Error from get_fd: {e}");
return Err(e);
}
}
match nozzle::get_handle_by_name(&nozzle_name) {
Ok(h) => if h != handle {
return Err(Error::new(ErrorKind::Other, "get_handle_by_name returned wrong value"));
}
Err(e) => {
- println!("Error from get_ips: {}", e);
+ println!("Error from get_ips: {e}");
return Err(e);
}
}
match nozzle::get_name_by_handle(handle) {
Ok(n) => if n != nozzle_name {
- println!("n: {}, nozzle_name: {}", n, nozzle_name);
+ println!("n: {n}, nozzle_name: {nozzle_name}");
return Err(Error::new(ErrorKind::Other, "get_name_by_handle returned wrong name"));
}
Err(e) => {
- println!("Error from get_ips: {}", e);
+ println!("Error from get_ips: {e}");
return Err(e);
}
}
// Wait a little while in case user wants to check with 'ip' command
thread::sleep(time::Duration::from_millis(1000));
if let Err(e) = nozzle::del_ip(handle, "192.160.100.3", "24") {
- println!("Error from del_ip: {}", e);
+ println!("Error from del_ip: {e}");
return Err(e);
}
if let Err(e) = nozzle::reset_mtu(handle) {
- println!("Error from reset_mtu: {}", e);
+ println!("Error from reset_mtu: {e}");
return Err(e);
}
match nozzle::get_mtu(handle) {
Ok(m) => {
if m != saved_mtu {
- println!("Got default MTU of {}, not {}", m, saved_mtu);
+ println!("Got default MTU of {m}, not {saved_mtu}");
}
}
Err(e) => {
- println!("Error from get_ips: {}", e);
+ println!("Error from get_ips: {e}");
return Err(e);
}
}
if let Err(e) = nozzle::reset_mac(handle) {
- println!("Error from reset_mac: {}", e);
+ println!("Error from reset_mac: {e}");
return Err(e);
}
match nozzle::get_mac(handle) {
Ok(m) => {
if m != saved_mac {
- println!("Got default MAC of {}, not {}", m, saved_mac);
+ println!("Got default MAC of {m}, not {saved_mac}");
}
}
Err(e) => {
- println!("Error from get_ips: {}", e);
+ println!("Error from get_ips: {e}");
return Err(e);
}
}
if let Err(e) = nozzle::set_down(handle){
- println!("Error from set_down: {}", e);
+ println!("Error from set_down: {e}");
return Err(e);
}
if let Err(e) = nozzle::close(handle) {
- println!("Error from open: {}", e);
+ println!("Error from open: {e}");
return Err(e);
}
Ok(())
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Feb 24, 8:05 AM (22 h, 10 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1460600
Default Alt Text
(110 KB)
Attached To
Mode
rK kronosnet
Attached
Detach File
Event Timeline
Log In to Comment