From 72cb160539b84292489f93816bc2c4e29e55725c Mon Sep 17 00:00:00 2001 From: jiangjianfeng Date: Mon, 16 Dec 2024 09:23:41 +0000 Subject: [PATCH] Add keepidle tcp option --- kernel/src/net/socket/ip/stream/mod.rs | 128 ++++++++++++--------- kernel/src/net/socket/ip/stream/options.rs | 3 +- kernel/src/net/socket/ip/stream/util.rs | 7 +- kernel/src/syscall/getsockopt.rs | 2 +- kernel/src/syscall/setsockopt.rs | 2 +- kernel/src/util/net/options/mod.rs | 2 +- kernel/src/util/net/options/socket.rs | 4 +- kernel/src/util/net/options/tcp.rs | 12 +- test/apps/network/sockoption.c | 44 +++++++ 9 files changed, 138 insertions(+), 66 deletions(-) diff --git a/kernel/src/net/socket/ip/stream/mod.rs b/kernel/src/net/socket/ip/stream/mod.rs index d05eb6ee..05af5988 100644 --- a/kernel/src/net/socket/ip/stream/mod.rs +++ b/kernel/src/net/socket/ip/stream/mod.rs @@ -10,7 +10,7 @@ use connected::ConnectedStream; use connecting::{ConnResult, ConnectingStream}; use init::InitStream; 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 takeable::Takeable; use util::TcpOptionSet; @@ -670,18 +670,22 @@ impl Socket for StreamSocket { let no_delay = options.tcp.no_delay(); tcp_no_delay.set(no_delay); }, - tcp_congestion: Congestion => { - let congestion = options.tcp.congestion(); - tcp_congestion.set(congestion); - }, tcp_maxseg: MaxSegment => { let maxseg = options.tcp.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 => { let window_clamp = options.tcp.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") }); @@ -691,61 +695,79 @@ impl Socket for StreamSocket { fn set_option(&self, option: &dyn SocketOption) -> Result<()> { let (mut options, mut state) = self.update_connecting(); - match options.socket.set_option(option, state.as_mut()) { - Err(err) if err.error() == Errno::ENOPROTOOPT => (), - Err(err) => return Err(err), - Ok(need_iface_poll) => { - let iface_to_poll = need_iface_poll.then(|| state.iface().cloned()).flatten(); - - drop(state); - drop(options); - - if let Some(iface) = iface_to_poll { - iface.poll(); - } - - return Ok(()); + let need_iface_poll = match options.socket.set_option(option, state.as_mut()) { + Err(err) if err.error() == Errno::ENOPROTOOPT => { + do_tcp_setsockopt(option, &mut options, state.as_mut())? } + Err(err) => return Err(err), + Ok(need_iface_poll) => need_iface_poll, + }; + + let iface_to_poll = need_iface_poll.then(|| state.iface().cloned()).flatten(); + + drop(state); + drop(options); + + if let Some(iface) = iface_to_poll { + iface.poll(); } - // FIXME: Here we have only set the value of the option, without actually - // making any real modifications. - match_sock_option_ref!(option, { - tcp_no_delay: NoDelay => { - let no_delay = tcp_no_delay.get().unwrap(); - options.tcp.set_no_delay(*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 => { - const MIN_MAXSEG: u32 = 536; - const MAX_MAXSEG: u32 = 65535; - - let maxseg = tcp_maxseg.get().unwrap(); - if *maxseg < MIN_MAXSEG || *maxseg > MAX_MAXSEG { - return_errno_with_message!(Errno::EINVAL, "the maximum segment size is out of bounds"); - } - options.tcp.set_maxseg(*maxseg); - }, - tcp_window_clamp: WindowClamp => { - let window_clamp = tcp_window_clamp.get().unwrap(); - let half_recv_buf = options.socket.recv_buf() / 2; - if *window_clamp <= half_recv_buf { - options.tcp.set_window_clamp(half_recv_buf); - } else { - options.tcp.set_window_clamp(*window_clamp); - } - }, - _ => return_errno_with_message!(Errno::ENOPROTOOPT, "the socket option to be set is unknown") - }); - Ok(()) } } +fn do_tcp_setsockopt( + option: &dyn SocketOption, + options: &mut OptionSet, + state: &mut State, +) -> Result { + match_sock_option_ref!(option, { + tcp_no_delay: NoDelay => { + let no_delay = tcp_no_delay.get().unwrap(); + options.tcp.set_no_delay(*no_delay); + state.set_raw_option(|raw_socket: &dyn RawTcpSetOption| raw_socket.set_nagle_enabled(!no_delay)); + }, + tcp_maxseg: MaxSegment => { + const MIN_MAXSEG: u32 = 536; + const MAX_MAXSEG: u32 = 65535; + + let maxseg = tcp_maxseg.get().unwrap(); + if *maxseg < MIN_MAXSEG || *maxseg > MAX_MAXSEG { + return_errno_with_message!(Errno::EINVAL, "the maximum segment size is out of bounds"); + } + 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 => { + let window_clamp = tcp_window_clamp.get().unwrap(); + let half_recv_buf = options.socket.recv_buf() / 2; + if *window_clamp <= half_recv_buf { + options.tcp.set_window_clamp(half_recv_buf); + } else { + 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") + }); + + Ok(NeedIfacePoll::FALSE) +} + impl State { /// Calls `f` to set raw socket option. /// diff --git a/kernel/src/net/socket/ip/stream/options.rs b/kernel/src/net/socket/ip/stream/options.rs index fbb1c903..630df9fe 100644 --- a/kernel/src/net/socket/ip/stream/options.rs +++ b/kernel/src/net/socket/ip/stream/options.rs @@ -5,9 +5,10 @@ use crate::impl_socket_options; impl_socket_options!( pub struct NoDelay(bool); - pub struct Congestion(CongestionControl); pub struct MaxSegment(u32); + pub struct KeepIdle(u32); pub struct WindowClamp(u32); + pub struct Congestion(CongestionControl); ); /// The keepalive interval. diff --git a/kernel/src/net/socket/ip/stream/util.rs b/kernel/src/net/socket/ip/stream/util.rs index 1f22ba9a..06acc6aa 100644 --- a/kernel/src/net/socket/ip/stream/util.rs +++ b/kernel/src/net/socket/ip/stream/util.rs @@ -7,21 +7,24 @@ use crate::prelude::*; #[set = "pub"] pub struct TcpOptionSet { no_delay: bool, - congestion: CongestionControl, maxseg: u32, + keep_idle: u32, window_clamp: u32, + congestion: CongestionControl, } pub const DEFAULT_MAXSEG: u32 = 536; +pub const DEFAULT_KEEP_IDLE: u32 = 7200; pub const DEFAULT_WINDOW_CLAMP: u32 = 0x8000_0000; impl TcpOptionSet { pub fn new() -> Self { Self { no_delay: false, - congestion: CongestionControl::Reno, maxseg: DEFAULT_MAXSEG, + keep_idle: DEFAULT_KEEP_IDLE, window_clamp: DEFAULT_WINDOW_CLAMP, + congestion: CongestionControl::Reno, } } } diff --git a/kernel/src/syscall/getsockopt.rs b/kernel/src/syscall/getsockopt.rs index 101c8a5d..83ac5fc6 100644 --- a/kernel/src/syscall/getsockopt.rs +++ b/kernel/src/syscall/getsockopt.rs @@ -15,7 +15,7 @@ pub fn sys_getsockopt( optlen_addr: Vaddr, ctx: &Context, ) -> Result { - let level = CSocketOptionLevel::try_from(level)?; + let level = CSocketOptionLevel::try_from(level).map_err(|_| Errno::EOPNOTSUPP)?; if optval == 0 || optlen_addr == 0 { return_errno_with_message!(Errno::EINVAL, "optval or optlen_addr is null pointer"); } diff --git a/kernel/src/syscall/setsockopt.rs b/kernel/src/syscall/setsockopt.rs index 75d9cbfb..68f53980 100644 --- a/kernel/src/syscall/setsockopt.rs +++ b/kernel/src/syscall/setsockopt.rs @@ -15,7 +15,7 @@ pub fn sys_setsockopt( optlen: u32, _ctx: &Context, ) -> Result { - let level = CSocketOptionLevel::try_from(level)?; + let level = CSocketOptionLevel::try_from(level).map_err(|_| Errno::EOPNOTSUPP)?; if optval == 0 { return_errno_with_message!(Errno::EINVAL, "optval is null pointer"); } diff --git a/kernel/src/util/net/options/mod.rs b/kernel/src/util/net/options/mod.rs index f5741f00..75a6e360 100644 --- a/kernel/src/util/net/options/mod.rs +++ b/kernel/src/util/net/options/mod.rs @@ -134,7 +134,7 @@ pub fn new_raw_socket_option( match level { CSocketOptionLevel::SOL_SOCKET => new_socket_option(name), CSocketOptionLevel::SOL_TCP => new_tcp_option(name), - _ => todo!(), + _ => return_errno_with_message!(Errno::EOPNOTSUPP, "unsupported option level"), } } diff --git a/kernel/src/util/net/options/socket.rs b/kernel/src/util/net/options/socket.rs index 06084a83..be486a3a 100644 --- a/kernel/src/util/net/options/socket.rs +++ b/kernel/src/util/net/options/socket.rs @@ -39,7 +39,7 @@ enum CSocketOptionName { } pub fn new_socket_option(name: i32) -> Result> { - let name = CSocketOptionName::try_from(name)?; + let name = CSocketOptionName::try_from(name).map_err(|_| Errno::ENOPROTOOPT)?; match name { CSocketOptionName::SNDBUF => Ok(Box::new(SendBuf::new())), CSocketOptionName::RCVBUF => Ok(Box::new(RecvBuf::new())), @@ -48,7 +48,7 @@ pub fn new_socket_option(name: i32) -> Result> { CSocketOptionName::REUSEPORT => Ok(Box::new(ReusePort::new())), CSocketOptionName::LINGER => Ok(Box::new(Linger::new())), CSocketOptionName::KEEPALIVE => Ok(Box::new(KeepAlive::new())), - _ => todo!(), + _ => return_errno_with_message!(Errno::ENOPROTOOPT, "unsupported socket-level option"), } } diff --git a/kernel/src/util/net/options/tcp.rs b/kernel/src/util/net/options/tcp.rs index 2ab0c989..283b615e 100644 --- a/kernel/src/util/net/options/tcp.rs +++ b/kernel/src/util/net/options/tcp.rs @@ -3,7 +3,7 @@ use super::RawSocketOption; use crate::{ impl_raw_socket_option, - net::socket::ip::stream::options::{Congestion, MaxSegment, NoDelay, WindowClamp}, + net::socket::ip::stream::options::{Congestion, KeepIdle, MaxSegment, NoDelay, WindowClamp}, prelude::*, util::net::options::SocketOption, }; @@ -26,17 +26,19 @@ pub enum CTcpOptionName { } pub fn new_tcp_option(name: i32) -> Result> { - let name = CTcpOptionName::try_from(name)?; + let name = CTcpOptionName::try_from(name).map_err(|_| Errno::ENOPROTOOPT)?; match name { CTcpOptionName::NODELAY => Ok(Box::new(NoDelay::new())), - CTcpOptionName::CONGESTION => Ok(Box::new(Congestion::new())), CTcpOptionName::MAXSEG => Ok(Box::new(MaxSegment::new())), + CTcpOptionName::KEEPIDLE => Ok(Box::new(KeepIdle::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!(Congestion); impl_raw_socket_option!(MaxSegment); +impl_raw_socket_option!(KeepIdle); impl_raw_socket_option!(WindowClamp); +impl_raw_socket_option!(Congestion); diff --git a/test/apps/network/sockoption.c b/test/apps/network/sockoption.c index 0fcd3308..73b8a349 100644 --- a/test/apps/network/sockoption.c +++ b/test/apps/network/sockoption.c @@ -38,6 +38,26 @@ FN_SETUP(general) } 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() { close(sk_connected); @@ -230,3 +250,27 @@ FN_TEST(keepalive) keepalive == 0); } 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() \ No newline at end of file