Merge pull request #5 from blockworks-foundation/implementing_pacing
Implement pacing, adding option to change cc algorithm
This commit is contained in:
commit
611fef8b1a
|
@ -2165,6 +2165,18 @@ dependencies = [
|
||||||
"mio",
|
"mio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.27.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.5.0",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"memoffset",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
|
@ -2763,9 +2775,11 @@ dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"boring",
|
"boring",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"mio",
|
"mio",
|
||||||
"mio_channel",
|
"mio_channel",
|
||||||
|
"nix",
|
||||||
"quic-geyser-common",
|
"quic-geyser-common",
|
||||||
"quic-geyser-quiche-utils",
|
"quic-geyser-quiche-utils",
|
||||||
"quiche",
|
"quiche",
|
||||||
|
|
|
@ -278,7 +278,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use quic_geyser_server::{configure_server::configure_server, quiche_server_loop::server_loop};
|
use quic_geyser_server::quiche_server_loop::server_loop;
|
||||||
use solana_sdk::{account::Account, pubkey::Pubkey};
|
use solana_sdk::{account::Account, pubkey::Pubkey};
|
||||||
|
|
||||||
use quic_geyser_common::{
|
use quic_geyser_common::{
|
||||||
|
@ -371,9 +371,8 @@ mod tests {
|
||||||
// server loop
|
// server loop
|
||||||
let (server_send_queue, rx_sent_queue) = mpsc::channel::<ChannelMessage>();
|
let (server_send_queue, rx_sent_queue) = mpsc::channel::<ChannelMessage>();
|
||||||
let _server_loop_jh = std::thread::spawn(move || {
|
let _server_loop_jh = std::thread::spawn(move || {
|
||||||
let config = configure_server(QuicParameters::default()).unwrap();
|
|
||||||
if let Err(e) = server_loop(
|
if let Err(e) = server_loop(
|
||||||
config,
|
QuicParameters::default(),
|
||||||
socket_addr,
|
socket_addr,
|
||||||
rx_sent_queue,
|
rx_sent_queue,
|
||||||
CompressionType::Lz4Fast(8),
|
CompressionType::Lz4Fast(8),
|
||||||
|
|
|
@ -5,8 +5,9 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::{
|
use crate::{
|
||||||
compression::CompressionType,
|
compression::CompressionType,
|
||||||
defaults::{
|
defaults::{
|
||||||
DEFAULT_ACK_EXPONENT, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_MAX_ACK_DELAY,
|
DEFAULT_ACK_EXPONENT, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_ENABLE_PACING,
|
||||||
DEFAULT_MAX_NB_CONNECTIONS, DEFAULT_MAX_RECIEVE_WINDOW_SIZE, DEFAULT_MAX_STREAMS,
|
DEFAULT_MAX_ACK_DELAY, DEFAULT_MAX_NB_CONNECTIONS, DEFAULT_MAX_RECIEVE_WINDOW_SIZE,
|
||||||
|
DEFAULT_MAX_STREAMS, DEFAULT_USE_CC_BBR,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -62,6 +63,8 @@ pub struct QuicParameters {
|
||||||
pub max_number_of_connections: u64,
|
pub max_number_of_connections: u64,
|
||||||
pub max_ack_delay: u64,
|
pub max_ack_delay: u64,
|
||||||
pub ack_exponent: u64,
|
pub ack_exponent: u64,
|
||||||
|
pub enable_pacing: bool,
|
||||||
|
pub use_cc_bbr: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for QuicParameters {
|
impl Default for QuicParameters {
|
||||||
|
@ -73,6 +76,8 @@ impl Default for QuicParameters {
|
||||||
max_number_of_connections: DEFAULT_MAX_NB_CONNECTIONS,
|
max_number_of_connections: DEFAULT_MAX_NB_CONNECTIONS,
|
||||||
max_ack_delay: DEFAULT_MAX_ACK_DELAY,
|
max_ack_delay: DEFAULT_MAX_ACK_DELAY,
|
||||||
ack_exponent: DEFAULT_ACK_EXPONENT,
|
ack_exponent: DEFAULT_ACK_EXPONENT,
|
||||||
|
enable_pacing: DEFAULT_ENABLE_PACING,
|
||||||
|
use_cc_bbr: DEFAULT_USE_CC_BBR,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@ pub const MAX_ALLOWED_PARTIAL_RESPONSES: u64 = DEFAULT_MAX_STREAMS - 1;
|
||||||
pub const DEFAULT_MAX_RECIEVE_WINDOW_SIZE: u64 = 256 * 1024 * 1024; // 256 MBs
|
pub const DEFAULT_MAX_RECIEVE_WINDOW_SIZE: u64 = 256 * 1024 * 1024; // 256 MBs
|
||||||
pub const DEFAULT_CONNECTION_TIMEOUT: u64 = 10;
|
pub const DEFAULT_CONNECTION_TIMEOUT: u64 = 10;
|
||||||
pub const DEFAULT_MAX_NB_CONNECTIONS: u64 = 10;
|
pub const DEFAULT_MAX_NB_CONNECTIONS: u64 = 10;
|
||||||
pub const DEFAULT_MAX_ACK_DELAY: u64 = 100;
|
pub const DEFAULT_MAX_ACK_DELAY: u64 = 200;
|
||||||
pub const DEFAULT_ACK_EXPONENT: u64 = 3;
|
pub const DEFAULT_ACK_EXPONENT: u64 = 3;
|
||||||
pub const ALPN_GEYSER_PROTOCOL_ID: &[u8] = b"geyser";
|
pub const ALPN_GEYSER_PROTOCOL_ID: &[u8] = b"geyser";
|
||||||
pub const MAX_DATAGRAM_SIZE: usize = 1350; // MAX: 65527
|
pub const MAX_DATAGRAM_SIZE: usize = 1350; // MAX: 65527
|
||||||
|
pub const DEFAULT_ENABLE_PACING: bool = true;
|
||||||
|
pub const DEFAULT_USE_CC_BBR: bool = false;
|
||||||
|
|
|
@ -49,6 +49,7 @@ pub fn main() -> anyhow::Result<()> {
|
||||||
max_number_of_connections: args.max_number_of_connections,
|
max_number_of_connections: args.max_number_of_connections,
|
||||||
max_ack_delay: args.max_ack_delay,
|
max_ack_delay: args.max_ack_delay,
|
||||||
ack_exponent: args.ack_exponent,
|
ack_exponent: args.ack_exponent,
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
compression_parameters: CompressionParameters {
|
compression_parameters: CompressionParameters {
|
||||||
compression_type: quic_geyser_common::compression::CompressionType::Lz4Fast(
|
compression_type: quic_geyser_common::compression::CompressionType::Lz4Fast(
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::HashMap, net::SocketAddr};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub fn validate_token<'a>(
|
pub fn validate_token<'a>(
|
||||||
src: &std::net::SocketAddr,
|
src: &std::net::SocketAddr,
|
||||||
|
@ -89,20 +89,3 @@ pub struct PartialResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type PartialResponses = HashMap<u64, PartialResponse>;
|
pub type PartialResponses = HashMap<u64, PartialResponse>;
|
||||||
|
|
||||||
// returns true if the socket will block the writing of socket
|
|
||||||
// return false otherwise
|
|
||||||
pub fn write_to_socket(socket: &mio::net::UdpSocket, buf: &[u8], to: SocketAddr) -> bool {
|
|
||||||
match socket.send_to(buf, to) {
|
|
||||||
Ok(_len) => false,
|
|
||||||
Err(e) => {
|
|
||||||
if e.kind() == std::io::ErrorKind::WouldBlock {
|
|
||||||
log::warn!("writing would block");
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
log::error!("send() failed: {:?}", e);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -22,6 +22,9 @@ boring = { workspace = true }
|
||||||
mio = { workspace = true }
|
mio = { workspace = true }
|
||||||
mio_channel = { workspace = true }
|
mio_channel = { workspace = true }
|
||||||
|
|
||||||
|
libc = "0.2"
|
||||||
|
nix = { version = "0.27", features = ["net", "socket", "uio"] }
|
||||||
|
|
||||||
quic-geyser-common = { workspace = true }
|
quic-geyser-common = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -12,6 +12,8 @@ pub fn configure_server(quic_parameter: QuicParameters) -> anyhow::Result<quiche
|
||||||
let max_number_of_connections = quic_parameter.max_number_of_connections;
|
let max_number_of_connections = quic_parameter.max_number_of_connections;
|
||||||
let maximum_ack_delay = quic_parameter.max_ack_delay;
|
let maximum_ack_delay = quic_parameter.max_ack_delay;
|
||||||
let ack_exponent = quic_parameter.ack_exponent;
|
let ack_exponent = quic_parameter.ack_exponent;
|
||||||
|
let enable_pacing = quic_parameter.enable_pacing;
|
||||||
|
let use_bbr = quic_parameter.use_cc_bbr;
|
||||||
|
|
||||||
let cert = rcgen::generate_simple_self_signed(vec!["quic_geyser".into()]).unwrap();
|
let cert = rcgen::generate_simple_self_signed(vec!["quic_geyser".into()]).unwrap();
|
||||||
|
|
||||||
|
@ -43,12 +45,18 @@ pub fn configure_server(quic_parameter: QuicParameters) -> anyhow::Result<quiche
|
||||||
config.set_disable_active_migration(true);
|
config.set_disable_active_migration(true);
|
||||||
config.set_max_connection_window(128 * 1024 * 1024); // 128 Mbs
|
config.set_max_connection_window(128 * 1024 * 1024); // 128 Mbs
|
||||||
config.enable_early_data();
|
config.enable_early_data();
|
||||||
config.set_cc_algorithm(quiche::CongestionControlAlgorithm::BBR2);
|
|
||||||
|
if use_bbr {
|
||||||
|
config.set_cc_algorithm(quiche::CongestionControlAlgorithm::BBR2);
|
||||||
|
} else {
|
||||||
|
config.set_cc_algorithm(quiche::CongestionControlAlgorithm::CUBIC);
|
||||||
|
}
|
||||||
|
|
||||||
config.set_active_connection_id_limit(max_number_of_connections);
|
config.set_active_connection_id_limit(max_number_of_connections);
|
||||||
config.set_max_ack_delay(maximum_ack_delay);
|
config.set_max_ack_delay(maximum_ack_delay);
|
||||||
config.set_ack_delay_exponent(ack_exponent);
|
config.set_ack_delay_exponent(ack_exponent);
|
||||||
config.set_initial_congestion_window_packets(1024);
|
config.set_initial_congestion_window_packets(1024);
|
||||||
config.set_max_stream_window(256 * 1024 * 1024);
|
config.set_max_stream_window(256 * 1024 * 1024);
|
||||||
config.enable_pacing(false);
|
config.enable_pacing(enable_pacing);
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
use std::{fmt::Debug, sync::mpsc};
|
|
||||||
|
|
||||||
use crate::configure_server::configure_server;
|
|
||||||
use quic_geyser_common::{
|
use quic_geyser_common::{
|
||||||
channel_message::ChannelMessage, config::ConfigQuicPlugin, plugin_error::QuicGeyserError,
|
channel_message::ChannelMessage, config::ConfigQuicPlugin, plugin_error::QuicGeyserError,
|
||||||
};
|
};
|
||||||
|
use std::{fmt::Debug, sync::mpsc};
|
||||||
|
|
||||||
use super::quiche_server_loop::server_loop;
|
use super::quiche_server_loop::server_loop;
|
||||||
pub struct QuicServer {
|
pub struct QuicServer {
|
||||||
|
@ -19,15 +17,15 @@ impl Debug for QuicServer {
|
||||||
|
|
||||||
impl QuicServer {
|
impl QuicServer {
|
||||||
pub fn new(config: ConfigQuicPlugin) -> anyhow::Result<Self> {
|
pub fn new(config: ConfigQuicPlugin) -> anyhow::Result<Self> {
|
||||||
let server_config = configure_server(config.quic_parameters)?;
|
|
||||||
let socket = config.address;
|
let socket = config.address;
|
||||||
let compression_type = config.compression_parameters.compression_type;
|
let compression_type = config.compression_parameters.compression_type;
|
||||||
|
let quic_parameters = config.quic_parameters;
|
||||||
|
|
||||||
let (data_channel_sender, data_channel_tx) = mpsc::channel();
|
let (data_channel_sender, data_channel_tx) = mpsc::channel();
|
||||||
|
|
||||||
let _server_loop_jh = std::thread::spawn(move || {
|
let _server_loop_jh = std::thread::spawn(move || {
|
||||||
if let Err(e) = server_loop(
|
if let Err(e) = server_loop(
|
||||||
server_config,
|
quic_parameters,
|
||||||
socket,
|
socket,
|
||||||
data_channel_tx,
|
data_channel_tx,
|
||||||
compression_type,
|
compression_type,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashMap},
|
collections::HashMap,
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, AtomicU64, AtomicUsize},
|
atomic::{AtomicBool, AtomicU64, AtomicUsize},
|
||||||
|
@ -17,6 +17,7 @@ use ring::rand::SystemRandom;
|
||||||
use quic_geyser_common::{
|
use quic_geyser_common::{
|
||||||
channel_message::ChannelMessage,
|
channel_message::ChannelMessage,
|
||||||
compression::CompressionType,
|
compression::CompressionType,
|
||||||
|
config::QuicParameters,
|
||||||
defaults::{MAX_ALLOWED_PARTIAL_RESPONSES, MAX_DATAGRAM_SIZE},
|
defaults::{MAX_ALLOWED_PARTIAL_RESPONSES, MAX_DATAGRAM_SIZE},
|
||||||
filters::Filter,
|
filters::Filter,
|
||||||
message::Message,
|
message::Message,
|
||||||
|
@ -26,9 +27,11 @@ use quic_geyser_common::{
|
||||||
use quic_geyser_quiche_utils::{
|
use quic_geyser_quiche_utils::{
|
||||||
quiche_reciever::{recv_message, ReadStreams},
|
quiche_reciever::{recv_message, ReadStreams},
|
||||||
quiche_sender::{handle_writable, send_message},
|
quiche_sender::{handle_writable, send_message},
|
||||||
quiche_utils::{get_next_unidi, mint_token, validate_token, write_to_socket, PartialResponses},
|
quiche_utils::{get_next_unidi, mint_token, validate_token, PartialResponses},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::configure_server::configure_server;
|
||||||
|
|
||||||
struct DispatchingData {
|
struct DispatchingData {
|
||||||
pub sender: Sender<(Vec<u8>, u8)>,
|
pub sender: Sender<(Vec<u8>, u8)>,
|
||||||
pub filters: Arc<RwLock<Vec<Filter>>>,
|
pub filters: Arc<RwLock<Vec<Filter>>>,
|
||||||
|
@ -37,21 +40,15 @@ struct DispatchingData {
|
||||||
|
|
||||||
type DispachingConnections = Arc<Mutex<HashMap<ConnectionId<'static>, DispatchingData>>>;
|
type DispachingConnections = Arc<Mutex<HashMap<ConnectionId<'static>, DispatchingData>>>;
|
||||||
|
|
||||||
const ACCEPTABLE_PACING_DELAY: Duration = Duration::from_millis(100);
|
|
||||||
|
|
||||||
struct Packet {
|
|
||||||
pub buffer: Vec<u8>,
|
|
||||||
pub to: SocketAddr,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn server_loop(
|
pub fn server_loop(
|
||||||
mut config: quiche::Config,
|
quic_params: QuicParameters,
|
||||||
socket_addr: SocketAddr,
|
socket_addr: SocketAddr,
|
||||||
message_send_queue: mpsc::Receiver<ChannelMessage>,
|
message_send_queue: mpsc::Receiver<ChannelMessage>,
|
||||||
compression_type: CompressionType,
|
compression_type: CompressionType,
|
||||||
stop_laggy_client: bool,
|
stop_laggy_client: bool,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let maximum_concurrent_streams_id = u64::MAX;
|
let maximum_concurrent_streams_id = u64::MAX;
|
||||||
|
let mut config = configure_server(quic_params)?;
|
||||||
|
|
||||||
let mut socket = mio::net::UdpSocket::bind(socket_addr)?;
|
let mut socket = mio::net::UdpSocket::bind(socket_addr)?;
|
||||||
let mut poll = mio::Poll::new()?;
|
let mut poll = mio::Poll::new()?;
|
||||||
|
@ -82,7 +79,11 @@ pub fn server_loop(
|
||||||
// mio::Interest::READABLE,
|
// mio::Interest::READABLE,
|
||||||
// )?;
|
// )?;
|
||||||
|
|
||||||
let mut packets_to_send: BTreeMap<Instant, Packet> = BTreeMap::new();
|
let enable_pacing = if quic_params.enable_pacing {
|
||||||
|
set_txtime_sockopt(&socket).is_ok()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
let dispatching_connections: DispachingConnections = Arc::new(Mutex::new(HashMap::<
|
let dispatching_connections: DispachingConnections = Arc::new(Mutex::new(HashMap::<
|
||||||
ConnectionId<'static>,
|
ConnectionId<'static>,
|
||||||
|
@ -169,8 +170,8 @@ pub fn server_loop(
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if write_to_socket(&socket, &out[..len], from) {
|
if let Err(e) = socket.send_to(&out[..len], from) {
|
||||||
break;
|
log::error!("Error sending retry messages : {e:?}");
|
||||||
}
|
}
|
||||||
continue 'read;
|
continue 'read;
|
||||||
}
|
}
|
||||||
|
@ -253,38 +254,19 @@ pub fn server_loop(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let instant = Instant::now();
|
|
||||||
// send remaining packets
|
|
||||||
// log::debug!("packets to send : {}", packets_to_send.len());
|
|
||||||
while let Some((to_send, packet)) = packets_to_send.first_key_value() {
|
|
||||||
if instant + ACCEPTABLE_PACING_DELAY <= *to_send {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if write_to_socket(&socket, &packet.buffer, packet.to) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
packets_to_send.pop_first();
|
|
||||||
}
|
|
||||||
|
|
||||||
let instant = Instant::now();
|
|
||||||
while let Ok((send_info, buffer)) = write_reciver.try_recv() {
|
while let Ok((send_info, buffer)) = write_reciver.try_recv() {
|
||||||
if send_info.at > instant + ACCEPTABLE_PACING_DELAY {
|
let send_result = if enable_pacing {
|
||||||
packets_to_send.insert(
|
send_with_pacing(&socket, &buffer, &send_info)
|
||||||
send_info.at,
|
} else {
|
||||||
Packet {
|
socket.send_to(&buffer, send_info.to)
|
||||||
buffer,
|
};
|
||||||
to: send_info.to,
|
match send_result {
|
||||||
},
|
Ok(written) => {
|
||||||
);
|
log::debug!("written {written:?} to {:?}", send_info.to);
|
||||||
} else if write_to_socket(&socket, &buffer, send_info.to) {
|
}
|
||||||
packets_to_send.insert(
|
Err(e) => {
|
||||||
send_info.at,
|
log::error!("sending failed with error : {e:?}");
|
||||||
Packet {
|
}
|
||||||
buffer,
|
|
||||||
to: send_info.to,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -598,3 +580,58 @@ fn create_dispatching_thread(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_txtime_sockopt(sock: &mio::net::UdpSocket) -> std::io::Result<()> {
|
||||||
|
use nix::sys::socket::setsockopt;
|
||||||
|
use nix::sys::socket::sockopt::TxTime;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
|
||||||
|
let config = nix::libc::sock_txtime {
|
||||||
|
clockid: libc::CLOCK_MONOTONIC,
|
||||||
|
flags: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// mio::net::UdpSocket doesn't implement AsFd (yet?).
|
||||||
|
let fd = unsafe { std::os::fd::BorrowedFd::borrow_raw(sock.as_raw_fd()) };
|
||||||
|
|
||||||
|
setsockopt(&fd, TxTime, &config)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const NANOS_PER_SEC: u64 = 1_000_000_000;
|
||||||
|
|
||||||
|
const INSTANT_ZERO: std::time::Instant = unsafe { std::mem::transmute(std::time::UNIX_EPOCH) };
|
||||||
|
|
||||||
|
fn std_time_to_u64(time: &std::time::Instant) -> u64 {
|
||||||
|
let raw_time = time.duration_since(INSTANT_ZERO);
|
||||||
|
let sec = raw_time.as_secs();
|
||||||
|
let nsec = raw_time.subsec_nanos();
|
||||||
|
sec * NANOS_PER_SEC + nsec as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_with_pacing(
|
||||||
|
socket: &mio::net::UdpSocket,
|
||||||
|
buf: &[u8],
|
||||||
|
send_info: &quiche::SendInfo,
|
||||||
|
) -> std::io::Result<usize> {
|
||||||
|
use nix::sys::socket::sendmsg;
|
||||||
|
use nix::sys::socket::ControlMessage;
|
||||||
|
use nix::sys::socket::MsgFlags;
|
||||||
|
use nix::sys::socket::SockaddrStorage;
|
||||||
|
use std::io::IoSlice;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
|
||||||
|
let iov = [IoSlice::new(buf)];
|
||||||
|
let dst = SockaddrStorage::from(send_info.to);
|
||||||
|
let sockfd = socket.as_raw_fd();
|
||||||
|
|
||||||
|
// Pacing option.
|
||||||
|
let send_time = std_time_to_u64(&send_info.at);
|
||||||
|
let cmsg_txtime = ControlMessage::TxTime(&send_time);
|
||||||
|
|
||||||
|
match sendmsg(sockfd, &iov, &[cmsg_txtime], MsgFlags::empty(), Some(&dst)) {
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue