From 9d4dcc2b21d0cab5a20b620032173d8e894e8dae Mon Sep 17 00:00:00 2001 From: ClawSeven Date: Tue, 30 Apr 2024 14:22:17 +0800 Subject: [PATCH] [libos] Implement inner-defined sockopt with ioctl api --- src/libos/src/net/socket/host/mod.rs | 2 +- src/libos/src/net/socket/host/socket_file.rs | 7 +- src/libos/src/net/socket/sockopt/get.rs | 64 +++++++++++ .../src/net/socket/sockopt/get_acceptconn.rs | 3 + .../src/net/socket/sockopt/get_domain.rs | 3 + src/libos/src/net/socket/sockopt/get_error.rs | 3 + .../src/net/socket/sockopt/get_output.rs | 105 ++++++++++++++++++ .../src/net/socket/sockopt/get_peername.rs | 15 +++ .../src/net/socket/sockopt/get_sockbuf.rs | 7 ++ src/libos/src/net/socket/sockopt/get_type.rs | 3 + src/libos/src/net/socket/sockopt/mod.rs | 105 ++++++++++++++++++ src/libos/src/net/socket/sockopt/set.rs | 38 +++++++ .../src/net/socket/sockopt/set_sockbuf.rs | 62 +++++++++++ src/libos/src/net/socket/sockopt/timeout.rs | 65 +++++++++++ 14 files changed, 478 insertions(+), 4 deletions(-) create mode 100644 src/libos/src/net/socket/sockopt/get.rs create mode 100644 src/libos/src/net/socket/sockopt/get_acceptconn.rs create mode 100644 src/libos/src/net/socket/sockopt/get_domain.rs create mode 100644 src/libos/src/net/socket/sockopt/get_error.rs create mode 100644 src/libos/src/net/socket/sockopt/get_output.rs create mode 100644 src/libos/src/net/socket/sockopt/get_peername.rs create mode 100644 src/libos/src/net/socket/sockopt/get_sockbuf.rs create mode 100644 src/libos/src/net/socket/sockopt/get_type.rs create mode 100644 src/libos/src/net/socket/sockopt/mod.rs create mode 100644 src/libos/src/net/socket/sockopt/set.rs create mode 100644 src/libos/src/net/socket/sockopt/set_sockbuf.rs create mode 100644 src/libos/src/net/socket/sockopt/timeout.rs diff --git a/src/libos/src/net/socket/host/mod.rs b/src/libos/src/net/socket/host/mod.rs index 24a3e277..d5230ee3 100644 --- a/src/libos/src/net/socket/host/mod.rs +++ b/src/libos/src/net/socket/host/mod.rs @@ -1,7 +1,7 @@ +use atomic::Atomic; use std::any::Any; use std::io::{Read, Seek, SeekFrom, Write}; use std::mem; -use atomic::Atomic; use super::*; use crate::fs::{ diff --git a/src/libos/src/net/socket/host/socket_file.rs b/src/libos/src/net/socket/host/socket_file.rs index f259b5cd..3fe9598b 100644 --- a/src/libos/src/net/socket/host/socket_file.rs +++ b/src/libos/src/net/socket/host/socket_file.rs @@ -1,14 +1,15 @@ use std::any::Any; use std::io::{Read, Seek, SeekFrom, Write}; -use atomic::{Atomic, Ordering}; - use super::*; -use crate::fs::{AccessMode, File, HostFd, IoEvents, StatusFlags, STATUS_FLAGS_MASK}; +use crate::fs::{ + AccessMode, AtomicIoEvents, File, HostFd, IoEvents, StatusFlags, STATUS_FLAGS_MASK, +}; use crate::fs::{ GetIfConf, GetIfReqWithRawCmd, GetReadBufLen, IoctlCmd, NonBuiltinIoctlCmd, SetNonBlocking, }; use crate::net::socket::sockopt::{GetSockOptRawCmd, SetSockOptRawCmd}; +use atomic::{Atomic, Ordering}; //TODO: refactor write syscall to allow zero length with non-zero buffer impl File for HostSocket { diff --git a/src/libos/src/net/socket/sockopt/get.rs b/src/libos/src/net/socket/sockopt/get.rs new file mode 100644 index 00000000..8dbd4ed3 --- /dev/null +++ b/src/libos/src/net/socket/sockopt/get.rs @@ -0,0 +1,64 @@ +use crate::{fs::IoctlCmd, prelude::*}; +use libc::ocall::getsockopt as do_getsockopt; + +#[derive(Debug)] +pub struct GetSockOptRawCmd { + level: i32, + optname: i32, + optval: Box<[u8]>, + optlen: Option, +} + +impl GetSockOptRawCmd { + pub fn new(level: i32, optname: i32, max_optlen: u32) -> Self { + // Using uninit slice is safe, since the buffer in rust SDK ocall is [out] type. + let optval = unsafe { Box::new_uninit_slice(max_optlen as usize).assume_init() }; + Self { + level, + optname, + optval, + optlen: None, + } + } + + pub fn execute(&mut self, fd: FileDesc) -> Result<()> { + if self.optlen.is_some() { + return_errno!(EINVAL, "can not execute twice"); + } + self.optlen = Some(getsockopt_by_host( + fd, + self.level, + self.optname, + &mut self.optval, + )?); + Ok(()) + } + + pub fn output(&self) -> Option<&[u8]> { + self.optlen.map(|_| self.optval.as_ref()) + } +} + +impl IoctlCmd for GetSockOptRawCmd {} + +pub fn getsockopt_by_host( + fd: FileDesc, + level: i32, + optname: i32, + optval: &mut [u8], +) -> Result { + let max_optlen = optval.len() as u32; + let mut optlen = max_optlen; + try_libc!(do_getsockopt( + fd as _, + level as _, + optname as _, + optval.as_mut_ptr() as _, + &mut optlen as *mut u32 + )); + // Defence Iago attack + if optlen > max_optlen { + return_errno!(EINVAL, "host returns a invalid optlen"); + } + Ok(optlen) +} diff --git a/src/libos/src/net/socket/sockopt/get_acceptconn.rs b/src/libos/src/net/socket/sockopt/get_acceptconn.rs new file mode 100644 index 00000000..16a1e1fe --- /dev/null +++ b/src/libos/src/net/socket/sockopt/get_acceptconn.rs @@ -0,0 +1,3 @@ +crate::impl_ioctl_cmd! { + pub struct GetAcceptConnCmd {} +} diff --git a/src/libos/src/net/socket/sockopt/get_domain.rs b/src/libos/src/net/socket/sockopt/get_domain.rs new file mode 100644 index 00000000..d9bd8294 --- /dev/null +++ b/src/libos/src/net/socket/sockopt/get_domain.rs @@ -0,0 +1,3 @@ +crate::impl_ioctl_cmd! { + pub struct GetDomainCmd {} +} diff --git a/src/libos/src/net/socket/sockopt/get_error.rs b/src/libos/src/net/socket/sockopt/get_error.rs new file mode 100644 index 00000000..d3e831f9 --- /dev/null +++ b/src/libos/src/net/socket/sockopt/get_error.rs @@ -0,0 +1,3 @@ +crate::impl_ioctl_cmd! { + pub struct GetErrorCmd {} +} diff --git a/src/libos/src/net/socket/sockopt/get_output.rs b/src/libos/src/net/socket/sockopt/get_output.rs new file mode 100644 index 00000000..dab0bb53 --- /dev/null +++ b/src/libos/src/net/socket/sockopt/get_output.rs @@ -0,0 +1,105 @@ +use super::{GetRecvTimeoutCmd, GetSendTimeoutCmd}; + +use super::{ + GetAcceptConnCmd, GetDomainCmd, GetErrorCmd, GetPeerNameCmd, GetRcvBufSizeCmd, + GetSndBufSizeCmd, GetSockOptRawCmd, GetTypeCmd, +}; + +use libc::timeval; +use std::time::Duration; + +use crate::prelude::*; + +pub trait GetOutputAsBytes { + fn get_output_as_bytes(&self) -> Option<&[u8]>; +} + +impl GetOutputAsBytes for GetSockOptRawCmd { + fn get_output_as_bytes(&self) -> Option<&[u8]> { + self.output() + } +} + +impl GetOutputAsBytes for GetDomainCmd { + fn get_output_as_bytes(&self) -> Option<&[u8]> { + self.output().map(|val_ref| unsafe { + std::slice::from_raw_parts(val_ref as *const _ as *const u8, std::mem::size_of::()) + }) + } +} + +impl GetOutputAsBytes for GetErrorCmd { + fn get_output_as_bytes(&self) -> Option<&[u8]> { + self.output().map(|val_ref| unsafe { + std::slice::from_raw_parts(val_ref as *const _ as *const u8, std::mem::size_of::()) + }) + } +} + +impl GetOutputAsBytes for GetAcceptConnCmd { + fn get_output_as_bytes(&self) -> Option<&[u8]> { + self.output().map(|val_ref| unsafe { + std::slice::from_raw_parts(val_ref as *const _ as *const u8, std::mem::size_of::()) + }) + } +} + +impl GetOutputAsBytes for GetPeerNameCmd { + fn get_output_as_bytes(&self) -> Option<&[u8]> { + self.output().map(|val_ref| unsafe { + std::slice::from_raw_parts(&(val_ref.0).0 as *const _ as *const u8, (val_ref.0).1) + }) + } +} + +impl GetOutputAsBytes for GetTypeCmd { + fn get_output_as_bytes(&self) -> Option<&[u8]> { + self.output().map(|val_ref| unsafe { + std::slice::from_raw_parts(val_ref as *const _ as *const u8, std::mem::size_of::()) + }) + } +} + +impl GetOutputAsBytes for GetSndBufSizeCmd { + fn get_output_as_bytes(&self) -> Option<&[u8]> { + self.output().map(|val_ref| unsafe { + std::slice::from_raw_parts( + val_ref as *const _ as *const u8, + std::mem::size_of::(), + ) + }) + } +} + +impl GetOutputAsBytes for GetRcvBufSizeCmd { + fn get_output_as_bytes(&self) -> Option<&[u8]> { + self.output().map(|val_ref| unsafe { + std::slice::from_raw_parts( + val_ref as *const _ as *const u8, + std::mem::size_of::(), + ) + }) + } +} + +impl GetOutputAsBytes for GetRecvTimeoutCmd { + fn get_output_as_bytes(&self) -> Option<&[u8]> { + self.output().map(|val_ref| unsafe { + std::slice::from_raw_parts( + val_ref as *const _ as *const u8, + std::mem::size_of::(), + ) + }) + } +} + +impl GetOutputAsBytes for GetSendTimeoutCmd { + fn get_output_as_bytes(&self) -> Option<&[u8]> { + self.output().map(|val_ref| unsafe { + std::slice::from_raw_parts( + val_ref as *const _ as *const u8, + std::mem::size_of::(), + ) + }) + } +} diff --git a/src/libos/src/net/socket/sockopt/get_peername.rs b/src/libos/src/net/socket/sockopt/get_peername.rs new file mode 100644 index 00000000..e71e2f4b --- /dev/null +++ b/src/libos/src/net/socket/sockopt/get_peername.rs @@ -0,0 +1,15 @@ +use sgx_trts::libc; + +pub struct AddrStorage(pub (libc::sockaddr_storage, usize)); +impl std::fmt::Debug for AddrStorage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("AddrStorage") + .field(&"sockaddr_storage") + .field(&(self.0).1) + .finish() + } +} + +crate::impl_ioctl_cmd! { + pub struct GetPeerNameCmd {} +} diff --git a/src/libos/src/net/socket/sockopt/get_sockbuf.rs b/src/libos/src/net/socket/sockopt/get_sockbuf.rs new file mode 100644 index 00000000..951c6ba1 --- /dev/null +++ b/src/libos/src/net/socket/sockopt/get_sockbuf.rs @@ -0,0 +1,7 @@ +crate::impl_ioctl_cmd! { + pub struct GetSndBufSizeCmd {} +} + +crate::impl_ioctl_cmd! { + pub struct GetRcvBufSizeCmd {} +} diff --git a/src/libos/src/net/socket/sockopt/get_type.rs b/src/libos/src/net/socket/sockopt/get_type.rs new file mode 100644 index 00000000..3fba11f4 --- /dev/null +++ b/src/libos/src/net/socket/sockopt/get_type.rs @@ -0,0 +1,3 @@ +crate::impl_ioctl_cmd! { + pub struct GetTypeCmd {} +} diff --git a/src/libos/src/net/socket/sockopt/mod.rs b/src/libos/src/net/socket/sockopt/mod.rs new file mode 100644 index 00000000..da663807 --- /dev/null +++ b/src/libos/src/net/socket/sockopt/mod.rs @@ -0,0 +1,105 @@ +mod get; +mod get_acceptconn; +mod get_domain; +mod get_error; +mod get_output; +mod get_peername; +mod get_sockbuf; +mod get_type; +mod set; +mod set_sockbuf; +mod timeout; + +pub use get::{getsockopt_by_host, GetSockOptRawCmd}; +pub use get_acceptconn::GetAcceptConnCmd; +pub use get_domain::GetDomainCmd; +pub use get_error::GetErrorCmd; +pub use get_output::*; +pub use get_peername::{AddrStorage, GetPeerNameCmd}; +pub use get_sockbuf::{GetRcvBufSizeCmd, GetSndBufSizeCmd}; +pub use get_type::GetTypeCmd; +pub use set::{setsockopt_by_host, SetSockOptRawCmd}; +pub use set_sockbuf::{SetRcvBufSizeCmd, SetSndBufSizeCmd}; +pub use timeout::{ + timeout_to_timeval, GetRecvTimeoutCmd, GetSendTimeoutCmd, SetRecvTimeoutCmd, SetSendTimeoutCmd, +}; + +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] +#[repr(i32)] +#[allow(non_camel_case_types)] +pub enum SockOptName { + SO_DEBUG = 1, // i32, bool, 0 or 1 + SO_REUSEADDR = 2, // i32, bool, 0 or 1 + SO_TYPE = 3, // [builtin] read-only, i32 + SO_ERROR = 4, // read-only, i32 + SO_DONTROUTE = 5, // i32, bool, 0 or 1, Equal to: send with MSG_DONTROUTE flag. + SO_BROADCAST = 6, // i32, bool, 0 or 1 + SO_SNDBUF = 7, // i32 + SO_RCVBUF = 8, // i32 + SO_KEEPALIVE = 9, // i32, bool, 0 or 1 + SO_OOBINLINE = 10, // i32, bool, 0 or 1, Might equal to: recv with MSG_OOB flag. + SO_NO_CHECK = 11, // i32, bool, 0 or 1 + SO_PRIORITY = 12, // i32, >= 0, <= 6 + SO_LINGER = 13, // linger structure + SO_BSDCOMPAT = 14, // removed in linux 2.2 + SO_REUSEPORT = 15, // i32, bool, 0 or 1 + SO_PASSCRED = 16, // i32, bool, 0 or 1 + SO_PEERCRED = 17, // read-only, ucred structure + // TODO: there may be a bug. + // select(2), poll(2), and epoll(7) indicate a socket as readable + // only if at least SO_RCVLOWAT bytes are available. + SO_RCVLOWAT = 18, // i32 + SO_SNDLOWAT = 19, // read-only, i32 + SO_RCVTIMEO_OLD = 20, // struct timeval + SO_SNDTIMEO_OLD = 21, // struct timeval + SO_SECURITY_AUTHENTICATION = 22, // no doc / code + SO_SECURITY_ENCRYPTION_TRANSPORT = 23, // no doc / code + SO_SECURITY_ENCRYPTION_NETWORK = 24, // no doc / code + SO_BINDTODEVICE = 25, // array + SO_ATTACH_FILTER = 26, // SO_GET_FILTER, BPF-related + SO_DETACH_FILTER = 27, // SO_DETACH_BPF, BPF-related + SO_PEERNAME = 28, // [builtin] read-only, peer name + SO_TIMESTAMP_OLD = 29, // i32, bool, 0 or 1, cmsg-related + SO_ACCEPTCONN = 30, // [builtin] read-only, i32, bool, 0 or 1 + SO_PEERSEC = 31, // read-only, array + SO_SNDBUFFORCE = 32, // i32 + SO_RCVBUFFORCE = 33, // i32 + SO_PASSSEC = 34, // i32, bool, 0 or 1, cmsg-related + SO_TIMESTAMPNS_OLD = 35, // i32, bool, 0 or 1, cmsg-related + SO_MARK = 36, // i32 + SO_TIMESTAMPING_OLD = 37, // i32, bool, 0 or 1, cmsg-related + SO_PROTOCOL = 38, // read-only, i32 + SO_DOMAIN = 39, // [builtin] read-only, i32 + SO_RXQ_OVFL = 40, // i32, bool, 0 or 1, cmsg-related + SO_WIFI_STATUS = 41, // i32, bool, 0 or 1 + // TODO: there may be a bug when specify SO_PEEK_OFF and MSG_PEEK + SO_PEEK_OFF = 42, // i32 + SO_NOFCS = 43, // i32, bool, 0 or 1 + SO_LOCK_FILTER = 44, // i32, bool, 0 or 1 + SO_SELECT_ERR_QUEUE = 45, // i32, bool, 0 or 1 + SO_BUSY_POLL = 46, // i32 + SO_MAX_PACING_RATE = 47, // u64 + SO_BPF_EXTENSIONS = 48, // BPF-related + SO_INCOMING_CPU = 49, // i32 + SO_ATTACH_BPF = 50, // BPF-related + SO_ATTACH_REUSEPORT_CBPF = 51, // BPF-related + SO_ATTACH_REUSEPORT_EBPF = 52, // BPF-related + SO_CNX_ADVICE = 53, // write-only, i32 + SCM_TIMESTAMPING_OPT_STATS = 54, // no doc / code + SO_MEMINFO = 55, // read-only, array + SO_INCOMING_NAPI_ID = 56, // read-only, i32 + SO_COOKIE = 57, // read-only, u64 + SCM_TIMESTAMPING_PKTINFO = 58, // no doc / code + SO_PEERGROUPS = 59, // read-only, array + SO_ZEROCOPY = 60, // i32, bool, 0 or 1 + SO_TXTIME = 61, // SCM_TXTIME, struct sock_txtime + SO_BINDTOIFINDEX = 62, // i32 + SO_TIMESTAMP_NEW = 63, // i32, bool, 0 or 1, cmsg-related + SO_TIMESTAMPNS_NEW = 64, // i32, bool, 0 or 1, cmsg-related + SO_TIMESTAMPING_NEW = 65, // i32, bool, 0 or 1, cmsg-related + SO_RCVTIMEO_NEW = 66, // struct timeval + SO_SNDTIMEO_NEW = 67, // struct timeval + SO_DETACH_REUSEPORT_BPF = 68, // BPF-related +} diff --git a/src/libos/src/net/socket/sockopt/set.rs b/src/libos/src/net/socket/sockopt/set.rs new file mode 100644 index 00000000..e63ea96e --- /dev/null +++ b/src/libos/src/net/socket/sockopt/set.rs @@ -0,0 +1,38 @@ +use crate::{fs::IoctlCmd, prelude::*}; +use libc::ocall::setsockopt as do_setsockopt; + +#[derive(Debug)] +pub struct SetSockOptRawCmd { + level: i32, + optname: i32, + optval: Box<[u8]>, +} + +impl SetSockOptRawCmd { + pub fn new(level: i32, optname: i32, optval: &[u8]) -> Self { + let optval = Box::from(optval); + Self { + level, + optname, + optval, + } + } + + pub fn execute(&mut self, fd: FileDesc) -> Result<()> { + setsockopt_by_host(fd, self.level, self.optname, &self.optval)?; + Ok(()) + } +} + +impl IoctlCmd for SetSockOptRawCmd {} + +pub fn setsockopt_by_host(fd: FileDesc, level: i32, optname: i32, optval: &[u8]) -> Result<()> { + try_libc!(do_setsockopt( + fd as _, + level as _, + optname as _, + optval.as_ptr() as _, + optval.len() as _ + )); + Ok(()) +} diff --git a/src/libos/src/net/socket/sockopt/set_sockbuf.rs b/src/libos/src/net/socket/sockopt/set_sockbuf.rs new file mode 100644 index 00000000..eddae439 --- /dev/null +++ b/src/libos/src/net/socket/sockopt/set_sockbuf.rs @@ -0,0 +1,62 @@ +use super::set::setsockopt_by_host; +use crate::{fs::IoctlCmd, prelude::*}; + +#[derive(Debug)] +pub struct SetSndBufSizeCmd { + buf_size: usize, +} + +impl SetSndBufSizeCmd { + pub fn new(buf_size: usize) -> Self { + Self { buf_size } + } + + pub fn buf_size(&self) -> usize { + self.buf_size + } + + pub fn update_host(&self, fd: FileDesc) -> Result<()> { + // The buf size for host call should be divided by 2 because the value will be doubled by host kernel. + let host_call_buf_size = (self.buf_size / 2).to_ne_bytes(); + + // Setting SO_SNDBUF for host socket needs to respect /proc/sys/net/core/wmem_max. Thus, the value might be different on host, but it is fine. + setsockopt_by_host( + fd, + libc::SOL_SOCKET, + super::SockOptName::SO_SNDBUF.into(), + &host_call_buf_size, + ) + } +} + +impl IoctlCmd for SetSndBufSizeCmd {} + +#[derive(Debug)] +pub struct SetRcvBufSizeCmd { + buf_size: usize, +} + +impl SetRcvBufSizeCmd { + pub fn new(buf_size: usize) -> Self { + Self { buf_size } + } + + pub fn buf_size(&self) -> usize { + self.buf_size + } + + pub fn update_host(&self, fd: FileDesc) -> Result<()> { + // The buf size for host call should be divided by 2 because the value will be doubled by host kernel. + let host_call_buf_size = (self.buf_size / 2).to_ne_bytes(); + + // Setting SO_RCVBUF for host socket needs to respect /proc/sys/net/core/rmem_max. Thus, the value might be different on host, but it is fine. + setsockopt_by_host( + fd, + libc::SOL_SOCKET, + super::SockOptName::SO_RCVBUF.into(), + &host_call_buf_size, + ) + } +} + +impl IoctlCmd for SetRcvBufSizeCmd {} diff --git a/src/libos/src/net/socket/sockopt/timeout.rs b/src/libos/src/net/socket/sockopt/timeout.rs new file mode 100644 index 00000000..954a5001 --- /dev/null +++ b/src/libos/src/net/socket/sockopt/timeout.rs @@ -0,0 +1,65 @@ +use crate::fs::IoctlCmd; +use crate::prelude::*; +use libc::{suseconds_t, time_t}; +use std::time::Duration; + +#[derive(Debug)] +pub struct SetSendTimeoutCmd(Duration); + +impl IoctlCmd for SetSendTimeoutCmd {} + +impl SetSendTimeoutCmd { + pub fn new(timeout: Duration) -> Self { + Self(timeout) + } + + pub fn timeout(&self) -> &Duration { + &self.0 + } +} + +#[derive(Debug)] +pub struct SetRecvTimeoutCmd(Duration); + +impl IoctlCmd for SetRecvTimeoutCmd {} + +impl SetRecvTimeoutCmd { + pub fn new(timeout: Duration) -> Self { + Self(timeout) + } + + pub fn timeout(&self) -> &Duration { + &self.0 + } +} + +crate::impl_ioctl_cmd! { + pub struct GetSendTimeoutCmd {} +} + +crate::impl_ioctl_cmd! { + pub struct GetRecvTimeoutCmd {} +} + +pub fn timeout_to_timeval(timeout: Option) -> timeval { + match timeout { + Some(duration) => { + let sec = duration.as_secs(); + let usec = duration.subsec_micros(); + timeval { + sec: sec as time_t, + usec: usec as suseconds_t, + } + } + None => timeval { sec: 0, usec: 0 }, + } +} + +// Same as libc::timeval +#[repr(C)] +#[derive(Debug, Copy, Clone)] +#[allow(non_camel_case_types)] +pub struct timeval { + sec: time_t, + usec: suseconds_t, +}