mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-16 08:46:48 +00:00
Implement raw sock options
This commit is contained in:
parent
f099409b22
commit
782cd05ade
@ -1,13 +1,10 @@
|
||||
use crate::{
|
||||
fs::file_table::FileDescripter,
|
||||
log_syscall_entry,
|
||||
net::socket::{SockOptionLevel, SockOptionName},
|
||||
prelude::*,
|
||||
syscall::SYS_SETSOCKOPT,
|
||||
util::{read_val_from_user, write_val_to_user},
|
||||
};
|
||||
use crate::fs::file_table::FileDescripter;
|
||||
use crate::prelude::*;
|
||||
use crate::util::net::{new_raw_socket_option, SockOptionLevel};
|
||||
use crate::util::{read_val_from_user, write_val_to_user};
|
||||
use crate::{get_socket_without_holding_filetable_lock, log_syscall_entry};
|
||||
|
||||
use super::SyscallReturn;
|
||||
use super::{SyscallReturn, SYS_SETSOCKOPT};
|
||||
|
||||
pub fn sys_getsockopt(
|
||||
sockfd: FileDescripter,
|
||||
@ -18,29 +15,26 @@ pub fn sys_getsockopt(
|
||||
) -> Result<SyscallReturn> {
|
||||
log_syscall_entry!(SYS_SETSOCKOPT);
|
||||
let level = SockOptionLevel::try_from(level)?;
|
||||
let sock_option_name = SockOptionName::try_from(optname)?;
|
||||
if optval == 0 || optlen_addr == 0 {
|
||||
return_errno_with_message!(Errno::EINVAL, "optval or optlen_addr is null pointer");
|
||||
}
|
||||
let optlen: u32 = read_val_from_user(optlen_addr)?;
|
||||
debug!(
|
||||
"level = {level:?}, sockfd = {sockfd}, optname = {sock_option_name:?}, optlen = {optlen}"
|
||||
);
|
||||
debug!("level = {level:?}, sockfd = {sockfd}, optname = {optname:?}, optlen = {optlen}");
|
||||
let current = current!();
|
||||
let file_table = current.file_table().lock();
|
||||
let socket = file_table
|
||||
.get_file(sockfd)?
|
||||
.as_socket()
|
||||
.ok_or_else(|| Error::with_message(Errno::ENOTSOCK, "the file is not socket"))?;
|
||||
get_socket_without_holding_filetable_lock!(socket, current, sockfd);
|
||||
|
||||
// FIXME: This is only a workaround. Writing zero means the socket does not have error.
|
||||
// The linux manual says that writing a non-zero value if there are errors. But what value is it?
|
||||
if sock_option_name == SockOptionName::SO_ERROR {
|
||||
assert!(optlen == 4);
|
||||
write_val_to_user(optval, &0i32)?;
|
||||
}
|
||||
let mut raw_option = new_raw_socket_option(level, optname)?;
|
||||
|
||||
// TODO: do real getsockopt
|
||||
debug!("raw option: {:?}", raw_option);
|
||||
|
||||
socket.option(raw_option.as_sock_option_mut())?;
|
||||
|
||||
let write_len = {
|
||||
let vmar = current.root_vmar();
|
||||
raw_option.write_output(vmar, optval, optlen)?
|
||||
};
|
||||
|
||||
write_val_to_user(optlen_addr, &(write_len as u32))?;
|
||||
|
||||
Ok(SyscallReturn::Return(0))
|
||||
}
|
||||
|
@ -1,34 +1,42 @@
|
||||
use crate::log_syscall_entry;
|
||||
use crate::net::socket::{SockOptionLevel, SockOptionName};
|
||||
use crate::util::read_bytes_from_user;
|
||||
use crate::util::net::{new_raw_socket_option, SockOptionLevel};
|
||||
use crate::{fs::file_table::FileDescripter, prelude::*};
|
||||
use crate::{get_socket_without_holding_filetable_lock, log_syscall_entry};
|
||||
|
||||
use super::SyscallReturn;
|
||||
use super::SYS_SETSOCKOPT;
|
||||
use super::{SyscallReturn, SYS_SETSOCKOPT};
|
||||
|
||||
pub fn sys_setsockopt(
|
||||
sockfd: FileDescripter,
|
||||
level: i32,
|
||||
optname: i32,
|
||||
optval: Vaddr,
|
||||
optlen: usize,
|
||||
optlen: u32,
|
||||
) -> Result<SyscallReturn> {
|
||||
log_syscall_entry!(SYS_SETSOCKOPT);
|
||||
let level = SockOptionLevel::try_from(level)?;
|
||||
let sock_option_name = SockOptionName::try_from(optname)?;
|
||||
if optval == 0 {
|
||||
return_errno_with_message!(Errno::EINVAL, "optval is null pointer");
|
||||
}
|
||||
let mut sock_opt_val = vec![0u8; optlen];
|
||||
read_bytes_from_user(optval, &mut sock_opt_val)?;
|
||||
|
||||
debug!("level = {level:?}, sockfd = {sockfd}, optname = {sock_option_name:?}, optval = {sock_opt_val:?}");
|
||||
debug!(
|
||||
"level = {:?}, sockfd = {}, optname = {}, optval = {}",
|
||||
level, sockfd, optname, optlen
|
||||
);
|
||||
|
||||
let current = current!();
|
||||
let file_table = current.file_table().lock();
|
||||
let socket = file_table
|
||||
.get_file(sockfd)?
|
||||
.as_socket()
|
||||
.ok_or_else(|| Error::with_message(Errno::ENOTSOCK, "the file is not socket"))?;
|
||||
// TODO: do setsockopt
|
||||
get_socket_without_holding_filetable_lock!(socket, current, sockfd);
|
||||
|
||||
let raw_option = {
|
||||
let mut option = new_raw_socket_option(level, optname)?;
|
||||
|
||||
let vmar = current.root_vmar();
|
||||
option.read_input(vmar, optval, optlen)?;
|
||||
|
||||
option
|
||||
};
|
||||
|
||||
debug!("raw option: {:?}", raw_option);
|
||||
|
||||
socket.set_option(raw_option.as_sock_option())?;
|
||||
|
||||
Ok(SyscallReturn::Return(0))
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
mod addr;
|
||||
mod options;
|
||||
mod socket;
|
||||
|
||||
pub use addr::{read_socket_addr_from_user, write_socket_addr_to_user, SaFamily};
|
||||
pub use addr::{
|
||||
read_socket_addr_from_user, write_socket_addr_to_user, InetAddr, SaFamily, SockAddr,
|
||||
SockAddrInet, SockAddrInet6, SockAddrUnix,
|
||||
};
|
||||
pub use options::{new_raw_socket_option, RawSockOption, SockOptionLevel};
|
||||
pub use socket::{Protocol, SockFlags, SockType, SOCK_TYPE_MASK};
|
||||
|
||||
#[macro_export]
|
||||
|
175
services/libs/aster-std/src/util/net/options/mod.rs
Normal file
175
services/libs/aster-std/src/util/net/options/mod.rs
Normal file
@ -0,0 +1,175 @@
|
||||
use aster_rights::Full;
|
||||
|
||||
use crate::net::socket::options::SockOption;
|
||||
use crate::prelude::*;
|
||||
use crate::vm::vmar::Vmar;
|
||||
|
||||
mod socket;
|
||||
mod tcp;
|
||||
mod utils;
|
||||
|
||||
use self::socket::new_socket_option;
|
||||
use self::tcp::new_tcp_option;
|
||||
|
||||
pub trait RawSockOption: SockOption {
|
||||
fn read_input(&mut self, vmar: &Vmar<Full>, addr: Vaddr, max_len: u32) -> Result<()>;
|
||||
|
||||
fn write_output(&self, vmar: &Vmar<Full>, addr: Vaddr, max_len: u32) -> Result<usize>;
|
||||
|
||||
fn as_sock_option_mut(&mut self) -> &mut dyn SockOption;
|
||||
|
||||
fn as_sock_option(&self) -> &dyn SockOption;
|
||||
}
|
||||
|
||||
/// Impl `RawSockOption` for a struct which implements `SockOption`.
|
||||
#[macro_export]
|
||||
macro_rules! impl_raw_sock_option {
|
||||
($option:ty) => {
|
||||
impl RawSockOption for $option {
|
||||
fn read_input(&mut self, vmar: &Vmar<Full>, addr: Vaddr, max_len: u32) -> Result<()> {
|
||||
use aster_frame::vm::VmIo;
|
||||
|
||||
let input = vmar.read_val(addr)?;
|
||||
|
||||
if (max_len as usize) < core::mem::size_of_val(&input) {
|
||||
return_errno_with_message!(Errno::EINVAL, "max_len is too small");
|
||||
}
|
||||
|
||||
self.set_input(input);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_output(&self, vmar: &Vmar<Full>, addr: Vaddr, max_len: u32) -> Result<usize> {
|
||||
use aster_frame::vm::VmIo;
|
||||
|
||||
let output = self.output().unwrap();
|
||||
|
||||
let write_len = core::mem::size_of_val(output);
|
||||
|
||||
if (max_len as usize) < write_len {
|
||||
return_errno_with_message!(Errno::EINVAL, "max_len is too small");
|
||||
}
|
||||
|
||||
vmar.write_val(addr, output)?;
|
||||
Ok(write_len)
|
||||
}
|
||||
|
||||
fn as_sock_option_mut(&mut self) -> &mut dyn SockOption {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_sock_option(&self) -> &dyn SockOption {
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
($option: ty, $reader: ident, $writer: ident) => {
|
||||
impl RawSockOption for $option {
|
||||
fn read_input(&mut self, vmar: &Vmar<Full>, addr: Vaddr, max_len: u32) -> Result<()> {
|
||||
let input = $reader(vmar, addr, max_len)?;
|
||||
self.set_input(input);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_output(&self, vmar: &Vmar<Full>, addr: Vaddr, max_len: u32) -> Result<usize> {
|
||||
let output = self.output().unwrap();
|
||||
$writer(output, vmar, addr, max_len)
|
||||
}
|
||||
|
||||
fn as_sock_option_mut(&mut self) -> &mut dyn SockOption {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_sock_option(&self) -> &dyn SockOption {
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Impl `RawSockOption` for a struct which is for only `getsockopt` and implements `SockOption`.
|
||||
#[macro_export]
|
||||
macro_rules! impl_raw_sock_option_get_only {
|
||||
($option:ty) => {
|
||||
impl RawSockOption for $option {
|
||||
fn read_input(
|
||||
&mut self,
|
||||
_vmar: &Vmar<Full>,
|
||||
_addr: Vaddr,
|
||||
_max_len: u32,
|
||||
) -> Result<()> {
|
||||
return_errno_with_message!(Errno::ENOPROTOOPT, "the option is getter-only");
|
||||
}
|
||||
|
||||
fn write_output(&self, vmar: &Vmar<Full>, addr: Vaddr, max_len: u32) -> Result<usize> {
|
||||
use jinux_frame::vm::VmIo;
|
||||
|
||||
let output = self.output().unwrap();
|
||||
|
||||
let write_len = core::mem::size_of_val(output);
|
||||
|
||||
if (max_len as usize) < write_len {
|
||||
return_errno_with_message!(Errno::EINVAL, "max_len is too small");
|
||||
}
|
||||
|
||||
vmar.write_val(addr, output)?;
|
||||
Ok(write_len)
|
||||
}
|
||||
|
||||
fn as_sock_option_mut(&mut self) -> &mut dyn SockOption {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_sock_option(&self) -> &dyn SockOption {
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
($option: ty, $writer: ident) => {
|
||||
impl RawSockOption for $option {
|
||||
fn read_input(
|
||||
&mut self,
|
||||
_vmar: &Vmar<Full>,
|
||||
_addr: Vaddr,
|
||||
_max_len: u32,
|
||||
) -> Result<()> {
|
||||
return_errno_with_message!(Errno::ENOPROTOOPT, "the option is getter-only");
|
||||
}
|
||||
|
||||
fn write_output(&self, vmar: &Vmar<Full>, addr: Vaddr, max_len: u32) -> Result<usize> {
|
||||
let output = self.output().unwrap();
|
||||
$writer(output, vmar, addr, max_len)
|
||||
}
|
||||
|
||||
fn as_sock_option_mut(&mut self) -> &mut dyn SockOption {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_sock_option(&self) -> &dyn SockOption {
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn new_raw_socket_option(level: SockOptionLevel, name: i32) -> Result<Box<dyn RawSockOption>> {
|
||||
match level {
|
||||
SockOptionLevel::SOL_SOCKET => new_socket_option(name),
|
||||
SockOptionLevel::SOL_TCP => new_tcp_option(name),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sock Opt level. The definition is from https://elixir.bootlin.com/linux/v6.0.9/source/include/linux/socket.h#L343
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Clone, Copy, TryFromInt, PartialEq, Eq)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum SockOptionLevel {
|
||||
SOL_IP = 0,
|
||||
SOL_SOCKET = 1,
|
||||
SOL_TCP = 6,
|
||||
SOL_UDP = 17,
|
||||
SOL_IPV6 = 41,
|
||||
SOL_RAW = 255,
|
||||
}
|
60
services/libs/aster-std/src/util/net/options/socket.rs
Normal file
60
services/libs/aster-std/src/util/net/options/socket.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use crate::net::socket::options::{
|
||||
SockOption, SocketError, SocketLinger, SocketRecvBuf, SocketReuseAddr, SocketReusePort,
|
||||
SocketSendBuf,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::vm::vmar::Vmar;
|
||||
use crate::{impl_raw_sock_option, impl_raw_sock_option_get_only};
|
||||
use aster_rights::Full;
|
||||
|
||||
use super::utils::{read_bool, read_linger, write_bool, write_errors, write_linger};
|
||||
use super::RawSockOption;
|
||||
|
||||
/// Socket level options.
|
||||
///
|
||||
/// The definition is from https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/socket.h.
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Clone, Copy, TryFromInt, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
enum SocketOptionName {
|
||||
DEBUG = 1,
|
||||
REUSEADDR = 2,
|
||||
TYPE = 3,
|
||||
ERROR = 4,
|
||||
DONTROUTE = 5,
|
||||
BROADCAST = 6,
|
||||
SNDBUF = 7,
|
||||
RCVBUF = 8,
|
||||
SNDBUFFORCE = 32,
|
||||
RCVBUFFORCE = 33,
|
||||
KEEPALIVE = 9,
|
||||
OOBINLINE = 10,
|
||||
NO_CHECK = 11,
|
||||
PRIORITY = 12,
|
||||
LINGER = 13,
|
||||
BSDCOMPAT = 14,
|
||||
REUSEPORT = 15,
|
||||
RCVTIMEO_NEW = 66,
|
||||
SNDTIMEO_NEW = 67,
|
||||
}
|
||||
|
||||
pub fn new_socket_option(name: i32) -> Result<Box<dyn RawSockOption>> {
|
||||
let name = SocketOptionName::try_from(name)?;
|
||||
match name {
|
||||
SocketOptionName::SNDBUF => Ok(Box::new(SocketSendBuf::new())),
|
||||
SocketOptionName::RCVBUF => Ok(Box::new(SocketRecvBuf::new())),
|
||||
SocketOptionName::REUSEADDR => Ok(Box::new(SocketReuseAddr::new())),
|
||||
SocketOptionName::ERROR => Ok(Box::new(SocketError::new())),
|
||||
SocketOptionName::REUSEPORT => Ok(Box::new(SocketReusePort::new())),
|
||||
SocketOptionName::LINGER => Ok(Box::new(SocketLinger::new())),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
impl_raw_sock_option!(SocketSendBuf);
|
||||
impl_raw_sock_option!(SocketRecvBuf);
|
||||
impl_raw_sock_option!(SocketReuseAddr, read_bool, write_bool);
|
||||
impl_raw_sock_option_get_only!(SocketError, write_errors);
|
||||
impl_raw_sock_option!(SocketReusePort, read_bool, write_bool);
|
||||
impl_raw_sock_option!(SocketLinger, read_linger, write_linger);
|
42
services/libs/aster-std/src/util/net/options/tcp.rs
Normal file
42
services/libs/aster-std/src/util/net/options/tcp.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use crate::impl_raw_sock_option;
|
||||
use crate::net::socket::ip::tcp_options::{TcpCongestion, TcpMaxseg, TcpNoDelay, TcpWindowClamp};
|
||||
use crate::prelude::*;
|
||||
use crate::util::net::options::SockOption;
|
||||
use crate::vm::vmar::Vmar;
|
||||
use aster_rights::Full;
|
||||
|
||||
use super::utils::{read_bool, read_congestion, write_bool, write_congestion};
|
||||
use super::RawSockOption;
|
||||
|
||||
/// Sock options for tcp socket.
|
||||
///
|
||||
/// The raw definition is from https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/linux/tcp.h#L92
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Clone, Copy, TryFromInt)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub enum TcpOptionName {
|
||||
NODELAY = 1, /* Turn off Nagle's algorithm. */
|
||||
MAXSEG = 2, /* Limit MSS */
|
||||
CORK = 3, /* Never send partially complete segments */
|
||||
KEEPIDLE = 4, /* Start keeplives after this period */
|
||||
KEEPALIVE = 5, /* Interval between keepalives */
|
||||
WINDOW_CLAMP = 10, /* Bound advertised window */
|
||||
CONGESTION = 13, /* Congestion control algorithm */
|
||||
}
|
||||
|
||||
pub fn new_tcp_option(name: i32) -> Result<Box<dyn RawSockOption>> {
|
||||
let name = TcpOptionName::try_from(name)?;
|
||||
match name {
|
||||
TcpOptionName::NODELAY => Ok(Box::new(TcpNoDelay::new())),
|
||||
TcpOptionName::CONGESTION => Ok(Box::new(TcpCongestion::new())),
|
||||
TcpOptionName::MAXSEG => Ok(Box::new(TcpMaxseg::new())),
|
||||
TcpOptionName::WINDOW_CLAMP => Ok(Box::new(TcpWindowClamp::new())),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
impl_raw_sock_option!(TcpNoDelay, read_bool, write_bool);
|
||||
impl_raw_sock_option!(TcpCongestion, read_congestion, write_congestion);
|
||||
impl_raw_sock_option!(TcpMaxseg);
|
||||
impl_raw_sock_option!(TcpWindowClamp);
|
123
services/libs/aster-std/src/util/net/options/utils.rs
Normal file
123
services/libs/aster-std/src/util/net/options/utils.rs
Normal file
@ -0,0 +1,123 @@
|
||||
use crate::net::socket::ip::tcp_options::Congestion;
|
||||
use crate::net::socket::options::{LingerOption, SockErrors};
|
||||
use crate::prelude::*;
|
||||
use crate::vm::vmar::Vmar;
|
||||
use aster_frame::vm::VmIo;
|
||||
use aster_rights::Full;
|
||||
use core::time::Duration;
|
||||
|
||||
pub fn read_bool(vmar: &Vmar<Full>, addr: Vaddr, max_len: u32) -> Result<bool> {
|
||||
if (max_len as usize) < core::mem::size_of::<i32>() {
|
||||
return_errno_with_message!(Errno::EINVAL, "max_len is too short");
|
||||
}
|
||||
|
||||
let val = vmar.read_val::<i32>(addr)?;
|
||||
|
||||
Ok(val != 0)
|
||||
}
|
||||
|
||||
pub fn write_bool(val: &bool, vmar: &Vmar<Full>, addr: Vaddr, max_len: u32) -> Result<usize> {
|
||||
let write_len = core::mem::size_of::<i32>();
|
||||
|
||||
if (max_len as usize) < write_len {
|
||||
return_errno_with_message!(Errno::EINVAL, "max_len is too short");
|
||||
}
|
||||
|
||||
let val = if *val { 1i32 } else { 0i32 };
|
||||
vmar.write_val(addr, &val)?;
|
||||
Ok(write_len)
|
||||
}
|
||||
|
||||
pub fn write_errors(
|
||||
errors: &SockErrors,
|
||||
vmar: &Vmar<Full>,
|
||||
addr: Vaddr,
|
||||
max_len: u32,
|
||||
) -> Result<usize> {
|
||||
let write_len = core::mem::size_of::<i32>();
|
||||
|
||||
if (max_len as usize) < write_len {
|
||||
return_errno_with_message!(Errno::EINVAL, "max_len is too short");
|
||||
}
|
||||
|
||||
let val = errors.as_i32();
|
||||
vmar.write_val(addr, &val)?;
|
||||
Ok(write_len)
|
||||
}
|
||||
|
||||
pub fn read_linger(vmar: &Vmar<Full>, addr: Vaddr, max_len: u32) -> Result<LingerOption> {
|
||||
if (max_len as usize) < core::mem::size_of::<Linger>() {
|
||||
return_errno_with_message!(Errno::EINVAL, "max_len is too short");
|
||||
}
|
||||
|
||||
let linger = vmar.read_val::<Linger>(addr)?;
|
||||
|
||||
Ok(LingerOption::from(linger))
|
||||
}
|
||||
|
||||
pub fn write_linger(
|
||||
linger_option: &LingerOption,
|
||||
vmar: &Vmar<Full>,
|
||||
addr: Vaddr,
|
||||
max_len: u32,
|
||||
) -> Result<usize> {
|
||||
let write_len = core::mem::size_of::<Linger>();
|
||||
|
||||
if (max_len as usize) < write_len {
|
||||
return_errno_with_message!(Errno::EINVAL, "max_len is too short");
|
||||
}
|
||||
|
||||
let linger = Linger::from(*linger_option);
|
||||
vmar.write_val(addr, &linger)?;
|
||||
Ok(write_len)
|
||||
}
|
||||
|
||||
pub fn read_congestion(vmar: &Vmar<Full>, addr: Vaddr, max_len: u32) -> Result<Congestion> {
|
||||
let mut bytes = vec![0; max_len as usize];
|
||||
vmar.read_bytes(addr, &mut bytes)?;
|
||||
let name = String::from_utf8(bytes).unwrap();
|
||||
Congestion::new(&name)
|
||||
}
|
||||
|
||||
pub fn write_congestion(
|
||||
congestion: &Congestion,
|
||||
vmar: &Vmar<Full>,
|
||||
addr: Vaddr,
|
||||
max_len: u32,
|
||||
) -> Result<usize> {
|
||||
let name = congestion.name().as_bytes();
|
||||
|
||||
let write_len = name.len();
|
||||
if write_len > max_len as usize {
|
||||
return_errno_with_message!(Errno::EINVAL, "max_len is too short");
|
||||
}
|
||||
|
||||
vmar.write_bytes(addr, name)?;
|
||||
|
||||
Ok(write_len)
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Pod)]
|
||||
struct Linger {
|
||||
l_onoff: i32, // linger active
|
||||
l_linger: i32, // how many seconds to linger for
|
||||
}
|
||||
|
||||
impl From<LingerOption> for Linger {
|
||||
fn from(value: LingerOption) -> Self {
|
||||
let l_onoff = if value.is_on() { 1 } else { 0 };
|
||||
|
||||
let l_linger = value.timeout().as_secs() as i32;
|
||||
|
||||
Self { l_onoff, l_linger }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Linger> for LingerOption {
|
||||
fn from(value: Linger) -> Self {
|
||||
let is_on = value.l_onoff != 0;
|
||||
let timeout = Duration::new(value.l_linger as _, 0);
|
||||
LingerOption::new(is_on, timeout)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user