Add keepidle tcp option

This commit is contained in:
jiangjianfeng 2024-12-16 09:23:41 +00:00 committed by Tate, Hongliang Tian
parent 547c92d2ff
commit 72cb160539
9 changed files with 138 additions and 66 deletions

View File

@ -10,7 +10,7 @@ use connected::ConnectedStream;
use connecting::{ConnResult, ConnectingStream}; use connecting::{ConnResult, ConnectingStream};
use init::InitStream; use init::InitStream;
use listen::ListenStream; use listen::ListenStream;
use options::{Congestion, MaxSegment, NoDelay, WindowClamp, KEEPALIVE_INTERVAL}; use options::{Congestion, KeepIdle, MaxSegment, NoDelay, WindowClamp, KEEPALIVE_INTERVAL};
use ostd::sync::{PreemptDisabled, RwLockReadGuard, RwLockWriteGuard}; use ostd::sync::{PreemptDisabled, RwLockReadGuard, RwLockWriteGuard};
use takeable::Takeable; use takeable::Takeable;
use util::TcpOptionSet; use util::TcpOptionSet;
@ -670,18 +670,22 @@ impl Socket for StreamSocket {
let no_delay = options.tcp.no_delay(); let no_delay = options.tcp.no_delay();
tcp_no_delay.set(no_delay); tcp_no_delay.set(no_delay);
}, },
tcp_congestion: Congestion => {
let congestion = options.tcp.congestion();
tcp_congestion.set(congestion);
},
tcp_maxseg: MaxSegment => { tcp_maxseg: MaxSegment => {
let maxseg = options.tcp.maxseg(); let maxseg = options.tcp.maxseg();
tcp_maxseg.set(maxseg); tcp_maxseg.set(maxseg);
}, },
tcp_keep_idle: KeepIdle => {
let keep_idle = options.tcp.keep_idle();
tcp_keep_idle.set(keep_idle);
},
tcp_window_clamp: WindowClamp => { tcp_window_clamp: WindowClamp => {
let window_clamp = options.tcp.window_clamp(); let window_clamp = options.tcp.window_clamp();
tcp_window_clamp.set(window_clamp); tcp_window_clamp.set(window_clamp);
}, },
tcp_congestion: Congestion => {
let congestion = options.tcp.congestion();
tcp_congestion.set(congestion);
},
_ => return_errno_with_message!(Errno::ENOPROTOOPT, "the socket option to get is unknown") _ => return_errno_with_message!(Errno::ENOPROTOOPT, "the socket option to get is unknown")
}); });
@ -691,10 +695,14 @@ impl Socket for StreamSocket {
fn set_option(&self, option: &dyn SocketOption) -> Result<()> { fn set_option(&self, option: &dyn SocketOption) -> Result<()> {
let (mut options, mut state) = self.update_connecting(); let (mut options, mut state) = self.update_connecting();
match options.socket.set_option(option, state.as_mut()) { let need_iface_poll = match options.socket.set_option(option, state.as_mut()) {
Err(err) if err.error() == Errno::ENOPROTOOPT => (), Err(err) if err.error() == Errno::ENOPROTOOPT => {
do_tcp_setsockopt(option, &mut options, state.as_mut())?
}
Err(err) => return Err(err), Err(err) => return Err(err),
Ok(need_iface_poll) => { Ok(need_iface_poll) => need_iface_poll,
};
let iface_to_poll = need_iface_poll.then(|| state.iface().cloned()).flatten(); let iface_to_poll = need_iface_poll.then(|| state.iface().cloned()).flatten();
drop(state); drop(state);
@ -704,22 +712,21 @@ impl Socket for StreamSocket {
iface.poll(); iface.poll();
} }
return Ok(()); Ok(())
} }
} }
// FIXME: Here we have only set the value of the option, without actually fn do_tcp_setsockopt(
// making any real modifications. option: &dyn SocketOption,
options: &mut OptionSet,
state: &mut State,
) -> Result<NeedIfacePoll> {
match_sock_option_ref!(option, { match_sock_option_ref!(option, {
tcp_no_delay: NoDelay => { tcp_no_delay: NoDelay => {
let no_delay = tcp_no_delay.get().unwrap(); let no_delay = tcp_no_delay.get().unwrap();
options.tcp.set_no_delay(*no_delay); options.tcp.set_no_delay(*no_delay);
state.set_raw_option(|raw_socket: &dyn RawTcpSetOption| raw_socket.set_nagle_enabled(!no_delay)); state.set_raw_option(|raw_socket: &dyn RawTcpSetOption| raw_socket.set_nagle_enabled(!no_delay));
}, },
tcp_congestion: Congestion => {
let congestion = tcp_congestion.get().unwrap();
options.tcp.set_congestion(*congestion);
},
tcp_maxseg: MaxSegment => { tcp_maxseg: MaxSegment => {
const MIN_MAXSEG: u32 = 536; const MIN_MAXSEG: u32 = 536;
const MAX_MAXSEG: u32 = 65535; const MAX_MAXSEG: u32 = 65535;
@ -730,6 +737,18 @@ impl Socket for StreamSocket {
} }
options.tcp.set_maxseg(*maxseg); options.tcp.set_maxseg(*maxseg);
}, },
tcp_keep_idle: KeepIdle => {
const MIN_KEEP_IDLE: u32 = 1;
const MAX_KEEP_IDLE: u32 = 32767;
let keepidle = tcp_keep_idle.get().unwrap();
if *keepidle < MIN_KEEP_IDLE || *keepidle > MAX_KEEP_IDLE {
return_errno_with_message!(Errno::EINVAL, "the keep idle time is out of bounds");
}
options.tcp.set_keep_idle(*keepidle);
// TODO: Track when the socket becomes idle to actually support keep idle.
},
tcp_window_clamp: WindowClamp => { tcp_window_clamp: WindowClamp => {
let window_clamp = tcp_window_clamp.get().unwrap(); let window_clamp = tcp_window_clamp.get().unwrap();
let half_recv_buf = options.socket.recv_buf() / 2; let half_recv_buf = options.socket.recv_buf() / 2;
@ -739,11 +758,14 @@ impl Socket for StreamSocket {
options.tcp.set_window_clamp(*window_clamp); options.tcp.set_window_clamp(*window_clamp);
} }
}, },
tcp_congestion: Congestion => {
let congestion = tcp_congestion.get().unwrap();
options.tcp.set_congestion(*congestion);
},
_ => return_errno_with_message!(Errno::ENOPROTOOPT, "the socket option to be set is unknown") _ => return_errno_with_message!(Errno::ENOPROTOOPT, "the socket option to be set is unknown")
}); });
Ok(()) Ok(NeedIfacePoll::FALSE)
}
} }
impl State { impl State {

View File

@ -5,9 +5,10 @@ use crate::impl_socket_options;
impl_socket_options!( impl_socket_options!(
pub struct NoDelay(bool); pub struct NoDelay(bool);
pub struct Congestion(CongestionControl);
pub struct MaxSegment(u32); pub struct MaxSegment(u32);
pub struct KeepIdle(u32);
pub struct WindowClamp(u32); pub struct WindowClamp(u32);
pub struct Congestion(CongestionControl);
); );
/// The keepalive interval. /// The keepalive interval.

View File

@ -7,21 +7,24 @@ use crate::prelude::*;
#[set = "pub"] #[set = "pub"]
pub struct TcpOptionSet { pub struct TcpOptionSet {
no_delay: bool, no_delay: bool,
congestion: CongestionControl,
maxseg: u32, maxseg: u32,
keep_idle: u32,
window_clamp: u32, window_clamp: u32,
congestion: CongestionControl,
} }
pub const DEFAULT_MAXSEG: u32 = 536; pub const DEFAULT_MAXSEG: u32 = 536;
pub const DEFAULT_KEEP_IDLE: u32 = 7200;
pub const DEFAULT_WINDOW_CLAMP: u32 = 0x8000_0000; pub const DEFAULT_WINDOW_CLAMP: u32 = 0x8000_0000;
impl TcpOptionSet { impl TcpOptionSet {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
no_delay: false, no_delay: false,
congestion: CongestionControl::Reno,
maxseg: DEFAULT_MAXSEG, maxseg: DEFAULT_MAXSEG,
keep_idle: DEFAULT_KEEP_IDLE,
window_clamp: DEFAULT_WINDOW_CLAMP, window_clamp: DEFAULT_WINDOW_CLAMP,
congestion: CongestionControl::Reno,
} }
} }
} }

View File

@ -15,7 +15,7 @@ pub fn sys_getsockopt(
optlen_addr: Vaddr, optlen_addr: Vaddr,
ctx: &Context, ctx: &Context,
) -> Result<SyscallReturn> { ) -> Result<SyscallReturn> {
let level = CSocketOptionLevel::try_from(level)?; let level = CSocketOptionLevel::try_from(level).map_err(|_| Errno::EOPNOTSUPP)?;
if optval == 0 || optlen_addr == 0 { if optval == 0 || optlen_addr == 0 {
return_errno_with_message!(Errno::EINVAL, "optval or optlen_addr is null pointer"); return_errno_with_message!(Errno::EINVAL, "optval or optlen_addr is null pointer");
} }

View File

@ -15,7 +15,7 @@ pub fn sys_setsockopt(
optlen: u32, optlen: u32,
_ctx: &Context, _ctx: &Context,
) -> Result<SyscallReturn> { ) -> Result<SyscallReturn> {
let level = CSocketOptionLevel::try_from(level)?; let level = CSocketOptionLevel::try_from(level).map_err(|_| Errno::EOPNOTSUPP)?;
if optval == 0 { if optval == 0 {
return_errno_with_message!(Errno::EINVAL, "optval is null pointer"); return_errno_with_message!(Errno::EINVAL, "optval is null pointer");
} }

View File

@ -134,7 +134,7 @@ pub fn new_raw_socket_option(
match level { match level {
CSocketOptionLevel::SOL_SOCKET => new_socket_option(name), CSocketOptionLevel::SOL_SOCKET => new_socket_option(name),
CSocketOptionLevel::SOL_TCP => new_tcp_option(name), CSocketOptionLevel::SOL_TCP => new_tcp_option(name),
_ => todo!(), _ => return_errno_with_message!(Errno::EOPNOTSUPP, "unsupported option level"),
} }
} }

View File

@ -39,7 +39,7 @@ enum CSocketOptionName {
} }
pub fn new_socket_option(name: i32) -> Result<Box<dyn RawSocketOption>> { pub fn new_socket_option(name: i32) -> Result<Box<dyn RawSocketOption>> {
let name = CSocketOptionName::try_from(name)?; let name = CSocketOptionName::try_from(name).map_err(|_| Errno::ENOPROTOOPT)?;
match name { match name {
CSocketOptionName::SNDBUF => Ok(Box::new(SendBuf::new())), CSocketOptionName::SNDBUF => Ok(Box::new(SendBuf::new())),
CSocketOptionName::RCVBUF => Ok(Box::new(RecvBuf::new())), CSocketOptionName::RCVBUF => Ok(Box::new(RecvBuf::new())),
@ -48,7 +48,7 @@ pub fn new_socket_option(name: i32) -> Result<Box<dyn RawSocketOption>> {
CSocketOptionName::REUSEPORT => Ok(Box::new(ReusePort::new())), CSocketOptionName::REUSEPORT => Ok(Box::new(ReusePort::new())),
CSocketOptionName::LINGER => Ok(Box::new(Linger::new())), CSocketOptionName::LINGER => Ok(Box::new(Linger::new())),
CSocketOptionName::KEEPALIVE => Ok(Box::new(KeepAlive::new())), CSocketOptionName::KEEPALIVE => Ok(Box::new(KeepAlive::new())),
_ => todo!(), _ => return_errno_with_message!(Errno::ENOPROTOOPT, "unsupported socket-level option"),
} }
} }

View File

@ -3,7 +3,7 @@
use super::RawSocketOption; use super::RawSocketOption;
use crate::{ use crate::{
impl_raw_socket_option, impl_raw_socket_option,
net::socket::ip::stream::options::{Congestion, MaxSegment, NoDelay, WindowClamp}, net::socket::ip::stream::options::{Congestion, KeepIdle, MaxSegment, NoDelay, WindowClamp},
prelude::*, prelude::*,
util::net::options::SocketOption, util::net::options::SocketOption,
}; };
@ -26,17 +26,19 @@ pub enum CTcpOptionName {
} }
pub fn new_tcp_option(name: i32) -> Result<Box<dyn RawSocketOption>> { pub fn new_tcp_option(name: i32) -> Result<Box<dyn RawSocketOption>> {
let name = CTcpOptionName::try_from(name)?; let name = CTcpOptionName::try_from(name).map_err(|_| Errno::ENOPROTOOPT)?;
match name { match name {
CTcpOptionName::NODELAY => Ok(Box::new(NoDelay::new())), CTcpOptionName::NODELAY => Ok(Box::new(NoDelay::new())),
CTcpOptionName::CONGESTION => Ok(Box::new(Congestion::new())),
CTcpOptionName::MAXSEG => Ok(Box::new(MaxSegment::new())), CTcpOptionName::MAXSEG => Ok(Box::new(MaxSegment::new())),
CTcpOptionName::KEEPIDLE => Ok(Box::new(KeepIdle::new())),
CTcpOptionName::WINDOW_CLAMP => Ok(Box::new(WindowClamp::new())), CTcpOptionName::WINDOW_CLAMP => Ok(Box::new(WindowClamp::new())),
_ => todo!(), CTcpOptionName::CONGESTION => Ok(Box::new(Congestion::new())),
_ => return_errno_with_message!(Errno::ENOPROTOOPT, "unsupported tcp-level option"),
} }
} }
impl_raw_socket_option!(NoDelay); impl_raw_socket_option!(NoDelay);
impl_raw_socket_option!(Congestion);
impl_raw_socket_option!(MaxSegment); impl_raw_socket_option!(MaxSegment);
impl_raw_socket_option!(KeepIdle);
impl_raw_socket_option!(WindowClamp); impl_raw_socket_option!(WindowClamp);
impl_raw_socket_option!(Congestion);

View File

@ -38,6 +38,26 @@ FN_SETUP(general)
} }
END_SETUP() END_SETUP()
FN_TEST(invalid_socket_option)
{
int res;
socklen_t res_len = sizeof(res);
#define INVALID_LEVEL 99999
TEST_ERRNO(getsockopt(sk_connected, INVALID_LEVEL, SO_SNDBUF, &res,
&res_len),
EOPNOTSUPP);
#define INVALID_SOCKET_OPTION 99999
TEST_ERRNO(getsockopt(sk_connected, SOL_SOCKET, INVALID_SOCKET_OPTION,
&res, &res_len),
ENOPROTOOPT);
#define INVALID_TCP_OPTION 99999
TEST_ERRNO(getsockopt(sk_connected, IPPROTO_TCP, INVALID_TCP_OPTION,
&res, &res_len),
ENOPROTOOPT);
}
END_TEST()
int refresh_connection() int refresh_connection()
{ {
close(sk_connected); close(sk_connected);
@ -230,3 +250,27 @@ FN_TEST(keepalive)
keepalive == 0); keepalive == 0);
} }
END_TEST() END_TEST()
FN_TEST(keepidle)
{
int keepidle;
socklen_t keepidle_len = sizeof(keepidle);
// 1. Check default values
refresh_connection();
TEST_RES(getsockopt(sk_connected, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle,
&keepidle_len),
keepidle == 7200);
TEST_RES(getsockopt(sk_accepted, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle,
&keepidle_len),
keepidle == 7200);
// 2. Set and Get value
int seconds = 200;
CHECK(setsockopt(sk_connected, IPPROTO_TCP, TCP_KEEPIDLE, &seconds,
sizeof(seconds)));
TEST_RES(getsockopt(sk_connected, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle,
&keepidle_len),
keepidle == 200);
}
END_TEST()