From f099409b221545568f791eb0b85ceed3e9ab0966 Mon Sep 17 00:00:00 2001 From: Jianfeng Jiang Date: Mon, 16 Oct 2023 11:10:40 +0800 Subject: [PATCH] Implement sock options --- Cargo.lock | 13 ++ services/libs/aster-std/Cargo.toml | 1 + services/libs/aster-std/src/error.rs | 4 +- services/libs/aster-std/src/lib.rs | 2 + .../aster-std/src/net/iface/any_socket.rs | 4 +- services/libs/aster-std/src/net/iface/mod.rs | 1 + .../libs/aster-std/src/net/socket/ip/mod.rs | 1 + .../aster-std/src/net/socket/ip/stream/mod.rs | 179 ++++++++++++++++-- .../src/net/socket/ip/stream/options.rs | 67 +++++++ services/libs/aster-std/src/net/socket/mod.rs | 12 +- .../aster-std/src/net/socket/options/mod.rs | 113 +++++++++++ .../src/net/socket/options/socket.rs | 86 +++++++++ .../libs/aster-std/src/net/socket/util/mod.rs | 1 - .../src/net/socket/util/sock_options.rs | 41 ---- 14 files changed, 454 insertions(+), 71 deletions(-) create mode 100644 services/libs/aster-std/src/net/socket/ip/stream/options.rs create mode 100644 services/libs/aster-std/src/net/socket/options/mod.rs create mode 100644 services/libs/aster-std/src/net/socket/options/socket.rs delete mode 100644 services/libs/aster-std/src/net/socket/util/sock_options.rs diff --git a/Cargo.lock b/Cargo.lock index 007247e96..54c72b82d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -271,6 +271,7 @@ dependencies = [ "core2", "cpio-decoder", "getrandom", + "getset", "inherit-methods-macro", "int-to-c-enum", "intrusive-collections", @@ -730,6 +731,18 @@ dependencies = [ "wasi", ] +[[package]] +name = "getset" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "ghost" version = "0.1.14" diff --git a/services/libs/aster-std/Cargo.toml b/services/libs/aster-std/Cargo.toml index 8b36f9f5f..070977a97 100644 --- a/services/libs/aster-std/Cargo.toml +++ b/services/libs/aster-std/Cargo.toml @@ -67,6 +67,7 @@ getrandom = { version = "0.2.10", default-features = false, features = [ bitvec = { version = "1.0", default-features = false, features = ["alloc"] } static_assertions = "1.1.0" inherit-methods-macro = { git = "https://github.com/asterinas/inherit-methods-macro", rev = "98f7e3e" } +getset = "0.1.2" [dependencies.lazy_static] version = "1.0" diff --git a/services/libs/aster-std/src/error.rs b/services/libs/aster-std/src/error.rs index 7394149b6..e23d819cd 100644 --- a/services/libs/aster-std/src/error.rs +++ b/services/libs/aster-std/src/error.rs @@ -149,7 +149,7 @@ pub enum Errno { } /// error used in this crate -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct Error { errno: Errno, msg: Option<&'static str>, @@ -167,7 +167,7 @@ impl Error { } } - pub fn error(&self) -> Errno { + pub const fn error(&self) -> Errno { self.errno } } diff --git a/services/libs/aster-std/src/lib.rs b/services/libs/aster-std/src/lib.rs index 3fc1b917e..a58704a86 100644 --- a/services/libs/aster-std/src/lib.rs +++ b/services/libs/aster-std/src/lib.rs @@ -41,6 +41,8 @@ extern crate lru; extern crate controlled; #[macro_use] extern crate ktest; +#[macro_use] +extern crate getset; pub mod console; pub mod device; diff --git a/services/libs/aster-std/src/net/iface/any_socket.rs b/services/libs/aster-std/src/net/iface/any_socket.rs index 3fa4fcc57..fbd5e9eb0 100644 --- a/services/libs/aster-std/src/net/iface/any_socket.rs +++ b/services/libs/aster-std/src/net/iface/any_socket.rs @@ -166,8 +166,8 @@ impl Drop for AnyBoundSocket { } // For TCP -const RECV_BUF_LEN: usize = 65536; -const SEND_BUF_LEN: usize = 65536; +pub const RECV_BUF_LEN: usize = 65536; +pub const SEND_BUF_LEN: usize = 65536; // For UDP const UDP_METADATA_LEN: usize = 256; diff --git a/services/libs/aster-std/src/net/iface/mod.rs b/services/libs/aster-std/src/net/iface/mod.rs index 56c2ff55f..92a7013cd 100644 --- a/services/libs/aster-std/src/net/iface/mod.rs +++ b/services/libs/aster-std/src/net/iface/mod.rs @@ -10,6 +10,7 @@ mod util; mod virtio; pub use any_socket::{AnyBoundSocket, AnyUnboundSocket, RawTcpSocket, RawUdpSocket}; +pub use any_socket::{RECV_BUF_LEN, SEND_BUF_LEN}; pub use loopback::IfaceLoopback; pub use smoltcp::wire::{EthernetAddress, IpAddress, IpEndpoint, IpListenEndpoint, Ipv4Address}; pub use util::{spawn_background_poll_thread, BindPortConfig}; diff --git a/services/libs/aster-std/src/net/socket/ip/mod.rs b/services/libs/aster-std/src/net/socket/ip/mod.rs index 53bc139ac..cff0a289a 100644 --- a/services/libs/aster-std/src/net/socket/ip/mod.rs +++ b/services/libs/aster-std/src/net/socket/ip/mod.rs @@ -4,4 +4,5 @@ mod datagram; mod stream; pub use datagram::DatagramSocket; +pub use stream::options as tcp_options; pub use stream::StreamSocket; diff --git a/services/libs/aster-std/src/net/socket/ip/stream/mod.rs b/services/libs/aster-std/src/net/socket/ip/stream/mod.rs index d96ce2156..ef03d53d2 100644 --- a/services/libs/aster-std/src/net/socket/ip/stream/mod.rs +++ b/services/libs/aster-std/src/net/socket/ip/stream/mod.rs @@ -1,27 +1,36 @@ use crate::events::IoEvents; -use crate::fs::{file_handle::FileLike, utils::StatusFlags}; -use crate::net::iface::IpEndpoint; -use crate::net::socket::{ - util::{ - send_recv_flags::SendRecvFlags, shutdown_cmd::SockShutdownCmd, - sock_options::SockOptionName, sockaddr::SocketAddr, - }, - Socket, +use crate::fs::file_handle::FileLike; +use crate::fs::utils::StatusFlags; +use crate::net::socket::ip::tcp_options::{ + TcpCongestion, TcpMaxseg, TcpWindowClamp, DEFAULT_MAXSEG, }; +use crate::net::socket::options::{ + SockOption, SocketError, SocketLinger, SocketOptions, SocketRecvBuf, SocketReuseAddr, + SocketReusePort, SocketSendBuf, MIN_RECVBUF, MIN_SENDBUF, +}; +use crate::net::socket::util::{ + send_recv_flags::SendRecvFlags, shutdown_cmd::SockShutdownCmd, sockaddr::SocketAddr, +}; +use crate::net::socket::Socket; use crate::prelude::*; use crate::process::signal::Poller; +use crate::{match_sock_option_mut, match_sock_option_ref}; -use self::{ - connected::ConnectedStream, connecting::ConnectingStream, init::InitStream, - listen::ListenStream, -}; +use connected::ConnectedStream; +use connecting::ConnectingStream; +use init::InitStream; +use listen::ListenStream; +use options::{TcpNoDelay, TcpOptions}; +use smoltcp::wire::IpEndpoint; mod connected; mod connecting; mod init; mod listen; +pub mod options; pub struct StreamSocket { + options: RwLock, state: RwLock, } @@ -36,10 +45,26 @@ enum State { Listen(Arc), } +#[derive(Debug, Clone)] +struct Options { + socket: SocketOptions, + tcp: TcpOptions, +} + +impl Options { + fn new() -> Self { + let socket = SocketOptions::new_tcp(); + let tcp = TcpOptions::new(); + Options { socket, tcp } + } +} + impl StreamSocket { pub fn new(nonblocking: bool) -> Self { + let options = Options::new(); let state = State::Init(InitStream::new(nonblocking)); Self { + options: RwLock::new(options), state: RwLock::new(state), } } @@ -180,7 +205,10 @@ impl Socket for StreamSocket { let accepted_socket = { let state = RwLock::new(State::Connected(connected_stream)); - Arc::new(StreamSocket { state }) + Arc::new(StreamSocket { + options: RwLock::new(Options::new()), + state, + }) }; let socket_addr = remote_endpoint.try_into()?; @@ -222,10 +250,6 @@ impl Socket for StreamSocket { remote_endpoint.try_into() } - fn sock_option(&self, optname: &SockOptionName) -> Result<&[u8]> { - return_errno_with_message!(Errno::EINVAL, "getsockopt not implemented"); - } - fn recvfrom(&self, buf: &mut [u8], flags: SendRecvFlags) -> Result<(usize, SocketAddr)> { let connected_stream = match &*self.state.read() { State::Connected(connected_stream) => connected_stream.clone(), @@ -254,4 +278,125 @@ impl Socket for StreamSocket { }; connected_stream.sendto(buf, flags) } + + fn option(&self, option: &mut dyn SockOption) -> Result<()> { + let options = self.options.read(); + match_sock_option_mut!(option, { + // Socket Options + socket_errors: SocketError => { + let sock_errors = options.socket.sock_errors(); + socket_errors.set_output(sock_errors); + }, + socket_reuse_addr: SocketReuseAddr => { + let reuse_addr = options.socket.reuse_addr(); + socket_reuse_addr.set_output(reuse_addr); + }, + socket_send_buf: SocketSendBuf => { + let send_buf = options.socket.send_buf(); + socket_send_buf.set_output(send_buf); + }, + socket_recv_buf: SocketRecvBuf => { + let recv_buf = options.socket.recv_buf(); + socket_recv_buf.set_output(recv_buf); + }, + socket_reuse_port: SocketReusePort => { + let reuse_port = options.socket.reuse_port(); + socket_reuse_port.set_output(reuse_port); + }, + // Tcp Options + tcp_no_delay: TcpNoDelay => { + let no_delay = options.tcp.no_delay(); + tcp_no_delay.set_output(no_delay); + }, + tcp_congestion: TcpCongestion => { + let congestion = options.tcp.congestion(); + tcp_congestion.set_output(congestion); + }, + tcp_maxseg: TcpMaxseg => { + // It will always return the default MSS value defined above for an unconnected socket + // and always return the actual current MSS for a connected one. + + // FIXME: how to get the current MSS? + let maxseg = match &*self.state.read() { + State::Init(_) | State::Listen(_) | State::Connecting(_) => DEFAULT_MAXSEG, + State::Connected(_) => options.tcp.maxseg(), + }; + tcp_maxseg.set_output(maxseg); + }, + tcp_window_clamp: TcpWindowClamp => { + let window_clamp = options.tcp.window_clamp(); + tcp_window_clamp.set_output(window_clamp); + }, + _ => return_errno_with_message!(Errno::ENOPROTOOPT, "get unknown option") + }); + Ok(()) + } + + fn set_option(&self, option: &dyn SockOption) -> Result<()> { + let mut options = self.options.write(); + // FIXME: here we have only set the value of the option, without actually + // making any real modifications. + match_sock_option_ref!(option, { + // Socket options + socket_recv_buf: SocketRecvBuf => { + let recv_buf = socket_recv_buf.input().unwrap(); + if *recv_buf <= MIN_RECVBUF { + options.socket.set_recv_buf(MIN_RECVBUF); + } else{ + options.socket.set_recv_buf(*recv_buf); + } + }, + socket_send_buf: SocketSendBuf => { + let send_buf = socket_send_buf.input().unwrap(); + if *send_buf <= MIN_SENDBUF { + options.socket.set_send_buf(MIN_SENDBUF); + } else { + options.socket.set_send_buf(*send_buf); + } + }, + socket_reuse_addr: SocketReuseAddr => { + let reuse_addr = socket_reuse_addr.input().unwrap(); + options.socket.set_reuse_addr(*reuse_addr); + }, + socket_reuse_port: SocketReusePort => { + let reuse_port = socket_reuse_port.input().unwrap(); + options.socket.set_reuse_port(*reuse_port); + }, + socket_linger: SocketLinger => { + let linger = socket_linger.input().unwrap(); + options.socket.set_linger(*linger); + }, + // Tcp options + tcp_no_delay: TcpNoDelay => { + let no_delay = tcp_no_delay.input().unwrap(); + options.tcp.set_no_delay(*no_delay); + }, + tcp_congestion: TcpCongestion => { + let congestion = tcp_congestion.input().unwrap(); + options.tcp.set_congestion(*congestion); + }, + tcp_maxseg: TcpMaxseg => { + const MIN_MAXSEG: u32 = 536; + const MAX_MAXSEG: u32 = 65535; + let maxseg = tcp_maxseg.input().unwrap(); + + if *maxseg < MIN_MAXSEG || *maxseg > MAX_MAXSEG { + return_errno_with_message!(Errno::EINVAL, "New maxseg should be in allowed range."); + } + + options.tcp.set_maxseg(*maxseg); + }, + tcp_window_clamp: TcpWindowClamp => { + let window_clamp = tcp_window_clamp.input().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, "set unknown option") + }); + Ok(()) + } } diff --git a/services/libs/aster-std/src/net/socket/ip/stream/options.rs b/services/libs/aster-std/src/net/socket/ip/stream/options.rs new file mode 100644 index 000000000..7b98c69a4 --- /dev/null +++ b/services/libs/aster-std/src/net/socket/ip/stream/options.rs @@ -0,0 +1,67 @@ +use crate::impl_sock_options; +use crate::prelude::*; + +#[derive(Debug, Clone, Copy, CopyGetters, Setters)] +#[get_copy = "pub"] +#[set = "pub"] +pub struct TcpOptions { + no_delay: bool, + congestion: Congestion, + maxseg: u32, + window_clamp: u32, +} + +pub const DEFAULT_MAXSEG: u32 = 536; +pub const DEFAULT_WINDOW_CLAMP: u32 = 0x8000_0000; + +impl TcpOptions { + pub fn new() -> Self { + Self { + no_delay: false, + congestion: Congestion::Reno, + maxseg: DEFAULT_MAXSEG, + window_clamp: DEFAULT_WINDOW_CLAMP, + } + } +} + +impl Default for TcpOptions { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Congestion { + Reno, + Cubic, +} + +impl Congestion { + const RENO: &'static str = "reno"; + const CUBIC: &'static str = "cubic"; + + pub fn new(name: &str) -> Result { + let congestion = match name { + Self::RENO => Self::Reno, + Self::CUBIC => Self::Cubic, + _ => return_errno_with_message!(Errno::EINVAL, "unsupported congestion name"), + }; + + Ok(congestion) + } + + pub fn name(&self) -> &'static str { + match self { + Self::Reno => Self::RENO, + Self::Cubic => Self::CUBIC, + } + } +} + +impl_sock_options!( + pub struct TcpNoDelay {} + pub struct TcpCongestion {} + pub struct TcpMaxseg {} + pub struct TcpWindowClamp {} +); diff --git a/services/libs/aster-std/src/net/socket/mod.rs b/services/libs/aster-std/src/net/socket/mod.rs index aa992d090..c9f796824 100644 --- a/services/libs/aster-std/src/net/socket/mod.rs +++ b/services/libs/aster-std/src/net/socket/mod.rs @@ -1,11 +1,12 @@ use crate::{fs::file_handle::FileLike, prelude::*}; +use self::options::SockOption; pub use self::util::send_recv_flags::SendRecvFlags; pub use self::util::shutdown_cmd::SockShutdownCmd; -pub use self::util::sock_options::{SockOptionLevel, SockOptionName}; pub use self::util::sockaddr::SocketAddr; pub mod ip; +pub mod options; pub mod unix; mod util; @@ -47,17 +48,12 @@ pub trait Socket: FileLike + Send + Sync { } /// Get options on the socket - fn sock_option(&self, optname: &SockOptionName) -> Result<&[u8]> { + fn option(&self, option: &mut dyn SockOption) -> Result<()> { return_errno_with_message!(Errno::EINVAL, "getsockopt not implemented"); } /// Set options on the socket - fn set_sock_option( - &self, - opt_level: SockOptionLevel, - optname: SockOptionName, - option_val: &[u8], - ) -> Result<()> { + fn set_option(&self, option: &dyn SockOption) -> Result<()> { return_errno_with_message!(Errno::EINVAL, "setsockopt not implemented"); } diff --git a/services/libs/aster-std/src/net/socket/options/mod.rs b/services/libs/aster-std/src/net/socket/options/mod.rs new file mode 100644 index 000000000..8019a0203 --- /dev/null +++ b/services/libs/aster-std/src/net/socket/options/mod.rs @@ -0,0 +1,113 @@ +use crate::prelude::*; + +mod socket; + +pub use socket::{ + LingerOption, SockErrors, SocketError, SocketLinger, SocketOptions, SocketRecvBuf, + SocketReuseAddr, SocketReusePort, SocketSendBuf, MIN_RECVBUF, MIN_SENDBUF, +}; + +pub trait SockOption: Any + Send + Sync + Debug { + fn as_any(&self) -> &dyn Any; + fn as_any_mut(&mut self) -> &mut dyn Any; +} + +// The following macros are mainly from occlum/ngo. + +#[macro_export] +macro_rules! impl_sock_options { + ($( + $(#[$outer:meta])* + pub struct $name: ident {} + )*) => { + $( + $(#[$outer])* + #[derive(Debug)] + pub struct $name { + input: Option<$input>, + output: Option<$output>, + } + + impl $name { + pub fn new() -> Self { + Self { + input: None, + output: None, + } + } + + pub fn input(&self) -> Option<&$input> { + self.input.as_ref() + } + + pub fn set_input(&mut self, input: $input) { + self.input = Some(input); + } + + pub fn output(&self) -> Option<&$output> { + self.output.as_ref() + } + + pub fn set_output(&mut self, output: $output) { + self.output = Some(output); + } + } + + impl $crate::net::socket::SockOption for $name { + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + } + + impl Default for $name { + fn default() -> Self { + Self::new() + } + } + )* + }; +} + +#[macro_export] +macro_rules! match_sock_option_ref { + ( + $option:expr, { + $( $bind: ident : $ty:ty => $arm:expr ),*, + _ => $default:expr + } + ) => {{ + let __option : &dyn SockOption = $option; + $( + if let Some($bind) = __option.as_any().downcast_ref::<$ty>() { + $arm + } else + )* + { + $default + } + }}; +} + +#[macro_export] +macro_rules! match_sock_option_mut { + ( + $option:expr, { + $( $bind: ident : $ty:ty => $arm:expr ),*, + _ => $default:expr + } + ) => {{ + let __option : &mut dyn SockOption = $option; + $( + if let Some($bind) = __option.as_any_mut().downcast_mut::<$ty>() { + $arm + } else + )* + { + $default + } + }}; +} diff --git a/services/libs/aster-std/src/net/socket/options/socket.rs b/services/libs/aster-std/src/net/socket/options/socket.rs new file mode 100644 index 000000000..d7a65533b --- /dev/null +++ b/services/libs/aster-std/src/net/socket/options/socket.rs @@ -0,0 +1,86 @@ +use core::time::Duration; + +use crate::impl_sock_options; +use crate::net::iface::{RECV_BUF_LEN, SEND_BUF_LEN}; +use crate::prelude::*; + +#[derive(Debug, Clone, CopyGetters, Setters)] +#[get_copy = "pub"] +#[set = "pub"] +pub struct SocketOptions { + sock_errors: SockErrors, + reuse_addr: bool, + reuse_port: bool, + send_buf: u32, + recv_buf: u32, + linger: LingerOption, +} + +impl SocketOptions { + pub fn new_tcp() -> Self { + Self { + sock_errors: SockErrors::no_error(), + reuse_addr: false, + reuse_port: false, + send_buf: SEND_BUF_LEN as u32, + recv_buf: RECV_BUF_LEN as u32, + linger: LingerOption::default(), + } + } +} + +pub const MIN_SENDBUF: u32 = 2304; +pub const MIN_RECVBUF: u32 = 2304; + +impl_sock_options!( + pub struct SocketReuseAddr {} + pub struct SocketReusePort {} + pub struct SocketSendBuf {} + pub struct SocketRecvBuf {} + pub struct SocketError {} + pub struct SocketLinger {} +); + +#[derive(Debug, Clone, Copy)] +pub struct SockErrors(Option); + +impl SockErrors { + pub const fn no_error() -> Self { + Self(None) + } + + pub const fn with_error(error: Error) -> Self { + Self(Some(error)) + } + + pub const fn error(&self) -> Option<&Error> { + self.0.as_ref() + } + + pub const fn as_i32(&self) -> i32 { + match &self.0 { + None => 0, + Some(err) => err.error() as i32, + } + } +} + +#[derive(Debug, Default, Clone, Copy)] +pub struct LingerOption { + is_on: bool, + timeout: Duration, +} + +impl LingerOption { + pub fn new(is_on: bool, timeout: Duration) -> Self { + Self { is_on, timeout } + } + + pub fn is_on(&self) -> bool { + self.is_on + } + + pub fn timeout(&self) -> Duration { + self.timeout + } +} diff --git a/services/libs/aster-std/src/net/socket/util/mod.rs b/services/libs/aster-std/src/net/socket/util/mod.rs index a372591fa..d187727f3 100644 --- a/services/libs/aster-std/src/net/socket/util/mod.rs +++ b/services/libs/aster-std/src/net/socket/util/mod.rs @@ -1,4 +1,3 @@ pub mod send_recv_flags; pub mod shutdown_cmd; -pub mod sock_options; pub mod sockaddr; diff --git a/services/libs/aster-std/src/net/socket/util/sock_options.rs b/services/libs/aster-std/src/net/socket/util/sock_options.rs deleted file mode 100644 index 4e46083c4..000000000 --- a/services/libs/aster-std/src/net/socket/util/sock_options.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::prelude::*; - -/// The definition is from https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/socket.h. -/// We do not include all options here -#[repr(i32)] -#[derive(Debug, Clone, Copy, TryFromInt, PartialEq, Eq, PartialOrd, Ord)] -#[allow(non_camel_case_types)] -pub enum SockOptionName { - SO_DEBUG = 1, - SO_REUSEADDR = 2, - SO_TYPE = 3, - SO_ERROR = 4, - SO_DONTROUTE = 5, - SO_BROADCAST = 6, - SO_SNDBUF = 7, - SO_RCVBUF = 8, - SO_SNDBUFFORCE = 32, - SO_RCVBUFFORCE = 33, - SO_KEEPALIVE = 9, - SO_OOBINLINE = 10, - SO_NO_CHECK = 11, - SO_PRIORITY = 12, - SO_LINGER = 13, - SO_BSDCOMPAT = 14, - SO_REUSEPORT = 15, - SO_RCVTIMEO_NEW = 66, - SO_SNDTIMEO_NEW = 67, -} - -/// 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, -}