[libos] Implement async network framework based on IO_Uring
This commit is contained in:
parent
9d4dcc2b21
commit
f8be7e7454
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -24,3 +24,6 @@
|
||||
[submodule "deps/resolv-conf"]
|
||||
path = deps/resolv-conf
|
||||
url = https://github.com/tailhook/resolv-conf.git
|
||||
[submodule "deps/io-uring"]
|
||||
path = deps/io-uring
|
||||
url = https://github.com/occlum/io-uring.git
|
||||
|
2
Makefile
2
Makefile
@ -42,6 +42,7 @@ submodule: githooks init-submodule
|
||||
@cp deps/sefs/sefs-cli/lib/libsefs-cli_sim.so build/lib
|
||||
@cp deps/sefs/sefs-cli/lib/libsefs-cli.signed.so build/lib
|
||||
@cp deps/sefs/sefs-cli/enclave/Enclave.config.xml build/sefs-cli.Enclave.xml
|
||||
@cd deps/io-uring/ocalls && cargo clean && cargo build --release
|
||||
else
|
||||
submodule: githooks init-submodule
|
||||
@rm -rf build
|
||||
@ -60,6 +61,7 @@ submodule: githooks init-submodule
|
||||
@cp deps/sefs/sefs-cli/lib/libsefs-cli_sim.so build/lib
|
||||
@cp deps/sefs/sefs-cli/lib/libsefs-cli.signed.so build/lib
|
||||
@cp deps/sefs/sefs-cli/enclave/Enclave.config.xml build/sefs-cli.Enclave.xml
|
||||
@cd deps/io-uring/ocalls && cargo clean && cargo build --release
|
||||
endif
|
||||
|
||||
init-submodule:
|
||||
|
1
deps/io-uring
vendored
Submodule
1
deps/io-uring
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit c654c4925bb0b013d3eec736015f8ac4888722be
|
@ -7,6 +7,8 @@ enclave {
|
||||
from "sgx_net.edl" import *;
|
||||
from "sgx_occlum_utils.edl" import *;
|
||||
from "sgx_vdso_time_ocalls.edl" import *;
|
||||
from "sgx_thread.edl" import *;
|
||||
from "sgx_io_uring_ocalls.edl" import *;
|
||||
|
||||
include "sgx_quote.h"
|
||||
include "occlum_edl_types.h"
|
||||
|
186
src/libos/Cargo.lock
generated
186
src/libos/Cargo.lock
generated
@ -10,16 +10,21 @@ dependencies = [
|
||||
"atomic",
|
||||
"bitflags",
|
||||
"bitvec 1.0.1",
|
||||
"byteorder",
|
||||
"ctor",
|
||||
"derive_builder",
|
||||
"downcast-rs",
|
||||
"errno",
|
||||
"goblin",
|
||||
"intrusive-collections",
|
||||
"io-uring-callback",
|
||||
"itertools",
|
||||
"keyable-arc",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"memoffset 0.6.5",
|
||||
"modular-bitfield",
|
||||
"num_enum",
|
||||
"rcore-fs",
|
||||
"rcore-fs-devfs",
|
||||
"rcore-fs-mountfs",
|
||||
@ -32,6 +37,7 @@ dependencies = [
|
||||
"scroll",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sgx-untrusted-alloc",
|
||||
"sgx_cov",
|
||||
"sgx_tcrypto",
|
||||
"sgx_trts",
|
||||
@ -112,6 +118,12 @@ dependencies = [
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
@ -203,6 +215,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.0"
|
||||
@ -237,6 +255,67 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "goblin"
|
||||
version = "0.5.4"
|
||||
@ -267,6 +346,36 @@ dependencies = [
|
||||
"memoffset 0.5.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.5.9"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"sgx_libc",
|
||||
"sgx_trts",
|
||||
"sgx_tstd",
|
||||
"sgx_types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring-callback"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"atomic",
|
||||
"cfg-if",
|
||||
"futures",
|
||||
"io-uring",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"lock_api",
|
||||
"log",
|
||||
"sgx_libc",
|
||||
"sgx_tstd",
|
||||
"slab",
|
||||
"spin 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.3"
|
||||
@ -283,6 +392,10 @@ dependencies = [
|
||||
"sgx_tstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyable-arc"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
@ -298,6 +411,15 @@ version = "0.2.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
@ -346,6 +468,38 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "plain"
|
||||
version = "0.2.3"
|
||||
@ -601,6 +755,12 @@ version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "scroll"
|
||||
version = "0.11.0"
|
||||
@ -648,6 +808,23 @@ dependencies = [
|
||||
"sgx_tstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sgx-untrusted-alloc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"errno",
|
||||
"intrusive-collections",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"sgx_libc",
|
||||
"sgx_trts",
|
||||
"sgx_tstd",
|
||||
"sgx_types",
|
||||
"spin 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sgx_alloc"
|
||||
version = "1.1.6"
|
||||
@ -753,6 +930,15 @@ dependencies = [
|
||||
"sgx_build_helper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
|
@ -7,6 +7,7 @@ use super::*;
|
||||
use crate::exception::*;
|
||||
use crate::fs::HostStdioFds;
|
||||
use crate::interrupt;
|
||||
use crate::io_uring::ENABLE_URING;
|
||||
use crate::process::idle_reap_zombie_children;
|
||||
use crate::process::{ProcessFilter, SpawnAttr};
|
||||
use crate::signal::SigNum;
|
||||
@ -101,11 +102,14 @@ pub extern "C" fn occlum_ecall_init(
|
||||
|
||||
vm::init_user_space();
|
||||
|
||||
if ENABLE_URING.load(Ordering::Relaxed) {
|
||||
crate::io_uring::MULTITON.poll_completions();
|
||||
}
|
||||
|
||||
// Register exception handlers (support cpuid & rdtsc for now)
|
||||
register_exception_handlers();
|
||||
|
||||
HAS_INIT.store(true, Ordering::Release);
|
||||
|
||||
// Enable global backtrace
|
||||
unsafe { backtrace::enable_backtrace(&ENCLAVE_PATH, PrintFormat::Short) };
|
||||
|
||||
|
169
src/libos/src/io_uring.rs
Normal file
169
src/libos/src/io_uring.rs
Normal file
@ -0,0 +1,169 @@
|
||||
use core::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize};
|
||||
use std::{collections::HashMap, thread::current};
|
||||
|
||||
use crate::util::sync::Mutex;
|
||||
use alloc::{sync::Arc, vec::Vec};
|
||||
use atomic::Ordering;
|
||||
use io_uring_callback::{Builder, IoUring};
|
||||
use keyable_arc::KeyableArc;
|
||||
|
||||
use crate::config::LIBOS_CONFIG;
|
||||
|
||||
// The number of sockets to reach the network bandwidth threshold of one io_uring instance
|
||||
const SOCKET_THRESHOLD_PER_URING: u32 = 1;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref MULTITON: UringSet = {
|
||||
let uring_set = UringSet::new();
|
||||
uring_set
|
||||
};
|
||||
|
||||
pub static ref ENABLE_URING: AtomicBool = AtomicBool::new(LIBOS_CONFIG.feature.io_uring > 0);
|
||||
|
||||
// Four uring instances are sufficient to reach the network bandwidth threshold of host kernel.
|
||||
pub static ref URING_LIMIT: AtomicUsize = {
|
||||
let uring_limit = LIBOS_CONFIG.feature.io_uring;
|
||||
assert!(uring_limit <= 16, "io_uring limit must not exceed 16");
|
||||
AtomicUsize::new(uring_limit as usize)
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
struct UringState {
|
||||
registered_num: u32,
|
||||
is_enable_poll: bool, // CQE polling thread
|
||||
}
|
||||
|
||||
impl UringState {
|
||||
fn register_one_socket(&mut self) {
|
||||
self.registered_num += 1;
|
||||
}
|
||||
|
||||
fn unregister_one_socket(&mut self) {
|
||||
self.registered_num -= 1;
|
||||
}
|
||||
|
||||
fn enable_poll(&mut self, uring: Arc<IoUring>) {
|
||||
if !self.is_enable_poll {
|
||||
self.is_enable_poll = true;
|
||||
std::thread::spawn(move || loop {
|
||||
let min_complete = 1;
|
||||
let polling_retries = 10000;
|
||||
uring.poll_completions(min_complete, polling_retries);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UringSet {
|
||||
urings: Mutex<HashMap<KeyableArc<IoUring>, UringState>>,
|
||||
running_uring_num: AtomicU32,
|
||||
}
|
||||
|
||||
impl UringSet {
|
||||
pub fn new() -> Self {
|
||||
let urings = Mutex::new(HashMap::new());
|
||||
let running_uring_num = AtomicU32::new(0);
|
||||
Self {
|
||||
urings,
|
||||
running_uring_num,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_completions(&self) {
|
||||
let mut guard = self.urings.lock();
|
||||
let uring_limit = URING_LIMIT.load(Ordering::Relaxed) as u32;
|
||||
|
||||
for _ in 0..uring_limit {
|
||||
let uring: KeyableArc<IoUring> = Arc::new(
|
||||
Builder::new()
|
||||
.setup_sqpoll(500 /* ms */)
|
||||
.build(256)
|
||||
.unwrap(),
|
||||
)
|
||||
.into();
|
||||
let mut state = UringState::default();
|
||||
state.enable_poll(uring.clone().into());
|
||||
|
||||
guard.insert(uring.clone(), state);
|
||||
self.running_uring_num.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_uring(&self) -> Arc<IoUring> {
|
||||
let mut map = self.urings.lock();
|
||||
let running_uring_num = self.running_uring_num.load(Ordering::Relaxed);
|
||||
let uring_limit = URING_LIMIT.load(Ordering::Relaxed) as u32;
|
||||
assert!(running_uring_num <= uring_limit);
|
||||
|
||||
let init_stage = running_uring_num < uring_limit;
|
||||
|
||||
// Construct an io_uring instance and initiate a polling thread
|
||||
if init_stage {
|
||||
let should_build_uring = {
|
||||
// Sum registered socket
|
||||
let total_socket_num = map
|
||||
.values()
|
||||
.fold(0, |acc, state| acc + state.registered_num)
|
||||
+ 1;
|
||||
// Determine the number of available io_uring
|
||||
let uring_num = (total_socket_num / SOCKET_THRESHOLD_PER_URING) + 1;
|
||||
let existed_uring_num = self.running_uring_num.load(Ordering::Relaxed);
|
||||
assert!(existed_uring_num <= uring_num);
|
||||
existed_uring_num < uring_num
|
||||
};
|
||||
|
||||
if should_build_uring {
|
||||
let uring: KeyableArc<IoUring> = Arc::new(
|
||||
Builder::new()
|
||||
.setup_sqpoll(500 /* ms */)
|
||||
.build(256)
|
||||
.unwrap(),
|
||||
)
|
||||
.into();
|
||||
let mut state = UringState::default();
|
||||
state.register_one_socket();
|
||||
state.enable_poll(uring.clone().into());
|
||||
|
||||
map.insert(uring.clone(), state);
|
||||
self.running_uring_num.fetch_add(1, Ordering::Relaxed);
|
||||
return uring.into();
|
||||
}
|
||||
}
|
||||
|
||||
// Link the file to the io_uring instance with the least load.
|
||||
let (mut uring, mut state) = map
|
||||
.iter_mut()
|
||||
.min_by_key(|(_, &mut state)| state.registered_num)
|
||||
.unwrap();
|
||||
|
||||
// Re-select io_uring instance with least task load
|
||||
if !init_stage {
|
||||
let min_registered_num = state.registered_num;
|
||||
(uring, state) = map
|
||||
.iter_mut()
|
||||
.filter(|(_, state)| state.registered_num == min_registered_num)
|
||||
.min_by_key(|(uring, _)| uring.task_load())
|
||||
.unwrap();
|
||||
} else {
|
||||
// At the initial stage, without constructing additional io_uring instances,
|
||||
// there exists a singular io_uring which has the minimum number of registered sockets.
|
||||
}
|
||||
|
||||
// Update io_uring instance states
|
||||
state.register_one_socket();
|
||||
assert!(state.is_enable_poll);
|
||||
|
||||
uring.clone().into()
|
||||
}
|
||||
|
||||
pub fn disattach_uring(&self, fd: usize, uring: Arc<IoUring>) {
|
||||
let uring: KeyableArc<IoUring> = uring.into();
|
||||
let mut map = self.urings.lock();
|
||||
let mut state = map.get_mut(&uring).unwrap();
|
||||
state.unregister_one_socket();
|
||||
drop(map);
|
||||
|
||||
uring.disattach_fd(fd);
|
||||
}
|
||||
}
|
@ -28,6 +28,8 @@
|
||||
#![feature(is_some_and)]
|
||||
// for edmm_api macro
|
||||
#![feature(linkage)]
|
||||
#![feature(new_uninit)]
|
||||
#![feature(raw_ref_op)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate alloc;
|
||||
@ -66,7 +68,6 @@ extern crate intrusive_collections;
|
||||
extern crate itertools;
|
||||
extern crate modular_bitfield;
|
||||
extern crate resolv_conf;
|
||||
extern crate vdso_time;
|
||||
|
||||
use sgx_trts::libc;
|
||||
use sgx_types::*;
|
||||
@ -82,15 +83,18 @@ mod prelude;
|
||||
#[macro_use]
|
||||
mod error;
|
||||
|
||||
#[macro_use]
|
||||
mod net;
|
||||
|
||||
mod config;
|
||||
mod entry;
|
||||
mod events;
|
||||
mod exception;
|
||||
mod fs;
|
||||
mod interrupt;
|
||||
mod io_uring;
|
||||
mod ipc;
|
||||
mod misc;
|
||||
mod net;
|
||||
mod process;
|
||||
mod sched;
|
||||
mod signal;
|
||||
|
@ -7,12 +7,13 @@ pub use self::io_multiplexing::{
|
||||
PollEventFlags, PollFd, THREAD_NOTIFIERS,
|
||||
};
|
||||
pub use self::socket::{
|
||||
mmsghdr, msghdr, msghdr_mut, socketpair, unix_socket, AddressFamily, AsUnixSocket, FileFlags,
|
||||
HostSocket, HostSocketType, HowToShut, Iovs, IovsMut, MsgHdr, MsgHdrFlags, MsgHdrMut,
|
||||
RecvFlags, SendFlags, SliceAsLibcIovec, SockAddr, SocketType, UnixAddr,
|
||||
socketpair, unix_socket, AsUnixSocket, Domain, HostSocket, HostSocketType, Iovs, IovsMut,
|
||||
RawAddr, SliceAsLibcIovec, UnixAddr,
|
||||
};
|
||||
pub use self::syscalls::*;
|
||||
|
||||
mod io_multiplexing;
|
||||
mod socket;
|
||||
pub(crate) mod socket;
|
||||
mod syscalls;
|
||||
|
||||
pub use self::syscalls::*;
|
||||
|
@ -1,21 +1,15 @@
|
||||
use super::*;
|
||||
|
||||
mod address_family;
|
||||
mod flags;
|
||||
mod host;
|
||||
mod iovs;
|
||||
mod msg;
|
||||
mod shutdown;
|
||||
mod socket_address;
|
||||
mod socket_type;
|
||||
pub(crate) mod sockopt;
|
||||
mod unix;
|
||||
pub(crate) mod uring;
|
||||
pub(crate) mod util;
|
||||
|
||||
pub use self::address_family::AddressFamily;
|
||||
pub use self::flags::{FileFlags, MsgHdrFlags, RecvFlags, SendFlags};
|
||||
pub use self::host::{HostSocket, HostSocketType};
|
||||
pub use self::iovs::{Iovs, IovsMut, SliceAsLibcIovec};
|
||||
pub use self::msg::{mmsghdr, msghdr, msghdr_mut, CMessages, CmsgData, MsgHdr, MsgHdrMut};
|
||||
pub use self::shutdown::HowToShut;
|
||||
pub use self::socket_address::SockAddr;
|
||||
pub use self::socket_type::SocketType;
|
||||
pub use self::unix::{socketpair, unix_socket, AsUnixSocket, UnixAddr};
|
||||
pub use self::unix::{socketpair, unix_socket, AsUnixSocket};
|
||||
pub use self::util::{
|
||||
Addr, AnyAddr, CMessages, CSockAddr, CmsgData, Domain, Iovs, IovsMut, Ipv4Addr, Ipv4SocketAddr,
|
||||
Ipv6SocketAddr, MsgFlags, RawAddr, RecvFlags, SendFlags, Shutdown, SliceAsLibcIovec,
|
||||
SocketProtocol, Type, UnixAddr,
|
||||
};
|
||||
|
241
src/libos/src/net/socket/uring/common/common.rs
Normal file
241
src/libos/src/net/socket/uring/common/common.rs
Normal file
@ -0,0 +1,241 @@
|
||||
use core::time::Duration;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use super::Timeout;
|
||||
use io_uring_callback::IoUring;
|
||||
|
||||
use libc::ocall::getsockname as do_getsockname;
|
||||
use libc::ocall::shutdown as do_shutdown;
|
||||
use libc::ocall::socket as do_socket;
|
||||
use libc::ocall::socketpair as do_socketpair;
|
||||
|
||||
use crate::events::Pollee;
|
||||
use crate::fs::{IoEvents, IoNotifier};
|
||||
use crate::net::socket::uring::runtime::Runtime;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// The common parts of all stream sockets.
|
||||
pub struct Common<A: Addr + 'static, R: Runtime> {
|
||||
host_fd: FileDesc,
|
||||
type_: Type,
|
||||
nonblocking: AtomicBool,
|
||||
is_closed: AtomicBool,
|
||||
pollee: Pollee,
|
||||
inner: Mutex<Inner<A>>,
|
||||
timeout: Mutex<Timeout>,
|
||||
errno: Mutex<Option<Errno>>,
|
||||
io_uring: Arc<IoUring>,
|
||||
phantom_data: PhantomData<(A, R)>,
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> Common<A, R> {
|
||||
pub fn new(type_: Type, nonblocking: bool, protocol: Option<i32>) -> Result<Self> {
|
||||
let domain_c = A::domain() as libc::c_int;
|
||||
let type_c = type_ as libc::c_int;
|
||||
let protocol = protocol.unwrap_or(0) as libc::c_int;
|
||||
let host_fd = try_libc!(do_socket(domain_c, type_c, protocol)) as FileDesc;
|
||||
let nonblocking = AtomicBool::new(nonblocking);
|
||||
let is_closed = AtomicBool::new(false);
|
||||
let pollee = Pollee::new(IoEvents::empty());
|
||||
let inner = Mutex::new(Inner::new());
|
||||
let timeout = Mutex::new(Timeout::new());
|
||||
let io_uring = R::io_uring();
|
||||
let errno = Mutex::new(None);
|
||||
Ok(Self {
|
||||
host_fd,
|
||||
type_,
|
||||
nonblocking,
|
||||
is_closed,
|
||||
pollee,
|
||||
inner,
|
||||
timeout,
|
||||
errno,
|
||||
io_uring,
|
||||
phantom_data: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_pair(sock_type: Type, nonblocking: bool) -> Result<(Self, Self)> {
|
||||
return_errno!(EINVAL, "Unix is unsupported");
|
||||
}
|
||||
|
||||
pub fn with_host_fd(host_fd: FileDesc, type_: Type, nonblocking: bool) -> Self {
|
||||
let nonblocking = AtomicBool::new(nonblocking);
|
||||
let is_closed = AtomicBool::new(false);
|
||||
let pollee = Pollee::new(IoEvents::empty());
|
||||
let inner = Mutex::new(Inner::new());
|
||||
let timeout = Mutex::new(Timeout::new());
|
||||
let io_uring = R::io_uring();
|
||||
let errno = Mutex::new(None);
|
||||
Self {
|
||||
host_fd,
|
||||
type_,
|
||||
nonblocking,
|
||||
is_closed,
|
||||
pollee,
|
||||
inner,
|
||||
timeout,
|
||||
errno,
|
||||
io_uring,
|
||||
phantom_data: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn io_uring(&self) -> Arc<IoUring> {
|
||||
self.io_uring.clone()
|
||||
}
|
||||
|
||||
pub fn host_fd(&self) -> FileDesc {
|
||||
self.host_fd
|
||||
}
|
||||
|
||||
pub fn type_(&self) -> Type {
|
||||
self.type_
|
||||
}
|
||||
|
||||
pub fn nonblocking(&self) -> bool {
|
||||
self.nonblocking.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn set_nonblocking(&self, is_nonblocking: bool) {
|
||||
self.nonblocking.store(is_nonblocking, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn notifier(&self) -> &IoNotifier {
|
||||
self.pollee.notifier()
|
||||
}
|
||||
|
||||
pub fn send_timeout(&self) -> Option<Duration> {
|
||||
self.timeout.lock().sender_timeout()
|
||||
}
|
||||
|
||||
pub fn recv_timeout(&self) -> Option<Duration> {
|
||||
self.timeout.lock().receiver_timeout()
|
||||
}
|
||||
|
||||
pub fn set_send_timeout(&self, timeout: Duration) {
|
||||
self.timeout.lock().set_sender(timeout)
|
||||
}
|
||||
|
||||
pub fn set_recv_timeout(&self, timeout: Duration) {
|
||||
self.timeout.lock().set_receiver(timeout)
|
||||
}
|
||||
|
||||
pub fn is_closed(&self) -> bool {
|
||||
self.is_closed.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn set_closed(&self) {
|
||||
self.is_closed.store(true, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn reset_closed(&self) {
|
||||
self.is_closed.store(false, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn pollee(&self) -> &Pollee {
|
||||
&self.pollee
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn addr(&self) -> Option<A> {
|
||||
let inner = self.inner.lock();
|
||||
inner.addr.clone()
|
||||
}
|
||||
|
||||
pub fn set_addr(&self, addr: &A) {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.addr = Some(addr.clone())
|
||||
}
|
||||
|
||||
pub fn get_addr_from_host(&self) -> Result<A> {
|
||||
let mut c_addr: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
|
||||
let mut c_addr_len = std::mem::size_of::<libc::sockaddr_storage>() as u32;
|
||||
try_libc!(do_getsockname(
|
||||
self.host_fd as _,
|
||||
&mut c_addr as *mut libc::sockaddr_storage as *mut _,
|
||||
&mut c_addr_len as *mut _,
|
||||
));
|
||||
A::from_c_storage(&c_addr, c_addr_len as _)
|
||||
}
|
||||
|
||||
pub fn peer_addr(&self) -> Option<A> {
|
||||
let inner = self.inner.lock();
|
||||
inner.peer_addr.clone()
|
||||
}
|
||||
|
||||
pub fn set_peer_addr(&self, peer_addr: &A) {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.peer_addr = Some(peer_addr.clone());
|
||||
}
|
||||
|
||||
pub fn reset_peer_addr(&self) {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.peer_addr = None;
|
||||
}
|
||||
|
||||
// For getsockopt SO_ERROR command
|
||||
pub fn errno(&self) -> Option<Errno> {
|
||||
let mut errno_option = self.errno.lock();
|
||||
errno_option.take()
|
||||
}
|
||||
|
||||
pub fn set_errno(&self, errno: Errno) {
|
||||
let mut errno_option = self.errno.lock();
|
||||
*errno_option = Some(errno);
|
||||
}
|
||||
|
||||
pub fn host_shutdown(&self, how: Shutdown) -> Result<()> {
|
||||
trace!("host shutdown: {:?}", how);
|
||||
match how {
|
||||
Shutdown::Write => {
|
||||
try_libc!(do_shutdown(self.host_fd as _, libc::SHUT_WR));
|
||||
}
|
||||
Shutdown::Read => {
|
||||
try_libc!(do_shutdown(self.host_fd as _, libc::SHUT_RD));
|
||||
}
|
||||
Shutdown::Both => {
|
||||
try_libc!(do_shutdown(self.host_fd as _, libc::SHUT_RDWR));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> std::fmt::Debug for Common<A, R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Common")
|
||||
.field("host_fd", &self.host_fd)
|
||||
.field("type", &self.type_)
|
||||
.field("nonblocking", &self.nonblocking)
|
||||
.field("pollee", &self.pollee)
|
||||
.field("inner", &self.inner.lock())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> Drop for Common<A, R> {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = super::do_close(self.host_fd) {
|
||||
log::error!("do_close failed, host_fd: {}, err: {:?}", self.host_fd, e);
|
||||
}
|
||||
|
||||
R::disattach_io_uring(self.host_fd as usize, self.io_uring())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner<A: Addr + 'static> {
|
||||
addr: Option<A>,
|
||||
peer_addr: Option<A>,
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static> Inner<A> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
addr: None,
|
||||
peer_addr: None,
|
||||
}
|
||||
}
|
||||
}
|
7
src/libos/src/net/socket/uring/common/mod.rs
Normal file
7
src/libos/src/net/socket/uring/common/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
mod common;
|
||||
mod operation;
|
||||
mod timeout;
|
||||
|
||||
pub use self::common::Common;
|
||||
pub use self::operation::{do_bind, do_close, do_connect, do_unlink};
|
||||
pub use self::timeout::Timeout;
|
44
src/libos/src/net/socket/uring/common/operation.rs
Normal file
44
src/libos/src/net/socket/uring/common/operation.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use std::ffi::CString;
|
||||
use std::mem::{self, MaybeUninit};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn do_bind<A: Addr>(host_fd: FileDesc, addr: &A) -> Result<()> {
|
||||
let fd = host_fd as i32;
|
||||
let (c_addr_storage, c_addr_len) = addr.to_c_storage();
|
||||
let c_addr_ptr = &c_addr_storage as *const _ as _;
|
||||
let c_addr_len = c_addr_len as u32;
|
||||
try_libc!(libc::ocall::bind(fd, c_addr_ptr, c_addr_len));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn do_close(host_fd: FileDesc) -> Result<()> {
|
||||
let fd = host_fd as i32;
|
||||
try_libc!(libc::ocall::close(fd));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn do_unlink(path: &String) -> Result<()> {
|
||||
let c_string =
|
||||
CString::new(path.as_bytes()).map_err(|_| errno!(EINVAL, "cstring new failure"))?;
|
||||
let c_path = c_string.as_c_str().as_ptr();
|
||||
try_libc!(libc::ocall::unlink(c_path));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn do_connect<A: Addr>(host_fd: FileDesc, addr: Option<&A>) -> Result<()> {
|
||||
let fd = host_fd as i32;
|
||||
let (c_addr_storage, c_addr_len) = match addr {
|
||||
Some(addr_inner) => addr_inner.to_c_storage(),
|
||||
None => {
|
||||
let mut sockaddr_storage =
|
||||
unsafe { MaybeUninit::<libc::sockaddr_storage>::uninit().assume_init() };
|
||||
sockaddr_storage.ss_family = libc::AF_UNSPEC as _;
|
||||
(sockaddr_storage, mem::size_of::<libc::sa_family_t>())
|
||||
}
|
||||
};
|
||||
let c_addr_ptr = &c_addr_storage as *const _ as _;
|
||||
let c_addr_len = c_addr_len as u32;
|
||||
try_libc!(libc::ocall::connect(fd, c_addr_ptr, c_addr_len));
|
||||
Ok(())
|
||||
}
|
32
src/libos/src/net/socket/uring/common/timeout.rs
Normal file
32
src/libos/src/net/socket/uring/common/timeout.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Timeout {
|
||||
sender: Option<Duration>,
|
||||
receiver: Option<Duration>,
|
||||
}
|
||||
|
||||
impl Timeout {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
sender: None,
|
||||
receiver: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sender_timeout(&self) -> Option<Duration> {
|
||||
self.sender
|
||||
}
|
||||
|
||||
pub fn receiver_timeout(&self) -> Option<Duration> {
|
||||
self.receiver
|
||||
}
|
||||
|
||||
pub fn set_sender(&mut self, timeout: Duration) {
|
||||
self.sender = Some(timeout);
|
||||
}
|
||||
|
||||
pub fn set_receiver(&mut self, timeout: Duration) {
|
||||
self.receiver = Some(timeout);
|
||||
}
|
||||
}
|
494
src/libos/src/net/socket/uring/datagram/generic.rs
Normal file
494
src/libos/src/net/socket/uring/datagram/generic.rs
Normal file
@ -0,0 +1,494 @@
|
||||
use core::time::Duration;
|
||||
|
||||
use crate::{
|
||||
events::{Observer, Poller},
|
||||
fs::{IoNotifier, StatusFlags},
|
||||
match_ioctl_cmd_mut,
|
||||
net::socket::MsgFlags,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use crate::fs::IoEvents as Events;
|
||||
use crate::fs::{GetIfConf, GetIfReqWithRawCmd, GetReadBufLen, IoctlCmd};
|
||||
|
||||
pub struct DatagramSocket<A: Addr + 'static, R: Runtime> {
|
||||
common: Arc<Common<A, R>>,
|
||||
state: RwLock<State>,
|
||||
sender: Arc<Sender<A, R>>,
|
||||
receiver: Arc<Receiver<A, R>>,
|
||||
}
|
||||
|
||||
impl<A: Addr, R: Runtime> DatagramSocket<A, R> {
|
||||
pub fn new(nonblocking: bool) -> Result<Self> {
|
||||
let common = Arc::new(Common::new(Type::DGRAM, nonblocking, None)?);
|
||||
let state = RwLock::new(State::new());
|
||||
let sender = Sender::new(common.clone());
|
||||
let receiver = Receiver::new(common.clone());
|
||||
Ok(Self {
|
||||
common,
|
||||
state,
|
||||
sender,
|
||||
receiver,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_pair(nonblocking: bool) -> Result<(Self, Self)> {
|
||||
let (common1, common2) = Common::new_pair(Type::DGRAM, nonblocking)?;
|
||||
let socket1 = Self::new_connected(common1);
|
||||
let socket2 = Self::new_connected(common2);
|
||||
Ok((socket1, socket2))
|
||||
}
|
||||
|
||||
fn new_connected(common: Common<A, R>) -> Self {
|
||||
let common = Arc::new(common);
|
||||
let state = RwLock::new(State::new_connected());
|
||||
let sender = Sender::new(common.clone());
|
||||
let receiver = Receiver::new(common.clone());
|
||||
receiver.initiate_async_recv();
|
||||
Self {
|
||||
common,
|
||||
state,
|
||||
sender,
|
||||
receiver,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn domain(&self) -> Domain {
|
||||
A::domain()
|
||||
}
|
||||
|
||||
pub fn host_fd(&self) -> FileDesc {
|
||||
self.common.host_fd()
|
||||
}
|
||||
|
||||
pub fn status_flags(&self) -> StatusFlags {
|
||||
// Only support O_NONBLOCK
|
||||
if self.common.nonblocking() {
|
||||
StatusFlags::O_NONBLOCK
|
||||
} else {
|
||||
StatusFlags::empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_status_flags(&self, new_flags: StatusFlags) -> Result<()> {
|
||||
// Only support O_NONBLOCK
|
||||
let nonblocking = new_flags.is_nonblocking();
|
||||
self.common.set_nonblocking(nonblocking);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// When creating a datagram socket, you can use `bind` to bind the socket
|
||||
/// to a address, hence another socket can send data to this address.
|
||||
///
|
||||
/// Binding is divided into explicit and implicit. Invoking `bind` is
|
||||
/// explicit binding, while invoking `sendto` / `sendmsg` / `connect`
|
||||
/// will trigger implicit binding.
|
||||
///
|
||||
/// Datagram sockets can only bind once. You should use explicit binding or
|
||||
/// just implicit binding. The explicit binding will failed if it happens after
|
||||
/// a implicit binding.
|
||||
pub fn bind(&self, addr: &A) -> Result<()> {
|
||||
let mut state = self.state.write().unwrap();
|
||||
if state.is_bound() {
|
||||
return_errno!(EINVAL, "The socket is already bound to an address");
|
||||
}
|
||||
|
||||
do_bind(self.host_fd(), addr)?;
|
||||
|
||||
self.common.set_addr(addr);
|
||||
state.mark_explicit_bind();
|
||||
// Start async recv after explicit binding or implicit binding
|
||||
self.receiver.initiate_async_recv();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Datagram sockets provide only connectionless interactions, But datagram sockets
|
||||
/// can also use connect to associate a socket with a specific address.
|
||||
/// After connection, any data sent on the socket is automatically addressed to the
|
||||
/// connected peer, and only data received from that peer is delivered to the user.
|
||||
///
|
||||
/// Unlike stream sockets, datagram sockets can connect multiple times. But the socket
|
||||
/// can only connect to one peer in the same time; a second connect will change the
|
||||
/// peer address, and a connect to a address with family AF_UNSPEC will dissolve the
|
||||
/// association ("disconnect" or "unconnect").
|
||||
///
|
||||
/// Before connection you can only use `sendto` / `sendmsg` / `recvfrom` / `recvmsg`.
|
||||
/// Only after connection, you can use `read` / `recv` / `write` / `send`.
|
||||
/// And you can ignore the address in `sendto` / `sendmsg` if you just want to
|
||||
/// send data to the connected peer.
|
||||
///
|
||||
/// Ref 1: http://osr507doc.xinuos.com/en/netguide/disockD.connecting_datagrams.html
|
||||
/// Ref 2: https://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch08lev1sec11.html
|
||||
pub fn connect(&self, peer_addr: Option<&A>) -> Result<()> {
|
||||
let mut state = self.state.write().unwrap();
|
||||
|
||||
// if previous peer.is_default() and peer_addr.is_none()
|
||||
// is unspec, so the situation exists that both
|
||||
// !state.is_connected() and peer_addr.is_none() are true.
|
||||
|
||||
if let Some(peer) = peer_addr {
|
||||
do_connect(self.host_fd(), Some(peer))?;
|
||||
|
||||
self.receiver.reset_shutdown();
|
||||
self.sender.reset_shutdown();
|
||||
self.common.set_peer_addr(peer);
|
||||
|
||||
if peer.is_default() {
|
||||
state.mark_disconnected();
|
||||
} else {
|
||||
state.mark_connected();
|
||||
}
|
||||
if !state.is_bound() {
|
||||
state.mark_implicit_bind();
|
||||
// Start async recv after explicit binding or implicit binding
|
||||
self.receiver.initiate_async_recv();
|
||||
}
|
||||
|
||||
// TODO: update binding address in some cases
|
||||
// For a ipv4 socket bound to 0.0.0.0 (INADDR_ANY), if you do connection
|
||||
// to 127.0.0.1 (Local IP address), the IP address of the socket will
|
||||
// change to 127.0.0.1 too. And if connect to non-local IP address, linux
|
||||
// will assign a address to the socket.
|
||||
// In both cases, we should update the binding address that we stored.
|
||||
} else {
|
||||
do_connect::<A>(self.host_fd(), None)?;
|
||||
|
||||
self.common.reset_peer_addr();
|
||||
state.mark_disconnected();
|
||||
|
||||
// TODO: clear binding in some cases.
|
||||
// Disconnect will effect the binding address. In Linux, for socket that
|
||||
// explicit bound to local IP address, disconnect will clear the binding address,
|
||||
// but leave the port intact. For socket with implicit bound, disconnect will
|
||||
// clear both the address and port.
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Close the datagram socket, cancel pending iouring requests
|
||||
pub fn close(&self) -> Result<()> {
|
||||
self.sender.shutdown();
|
||||
self.receiver.shutdown();
|
||||
self.common.set_closed();
|
||||
self.cancel_requests();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Shutdown the udp socket. This syscall is very TCP-oriented, but it is also useful for udp socket.
|
||||
/// Not like tcp, shutdown does nothing on the wire, it only changes shutdown states.
|
||||
/// The shutdown states block the io-uring request of receiving or sending message.
|
||||
pub fn shutdown(&self, how: Shutdown) -> Result<()> {
|
||||
let state = self.state.read().unwrap();
|
||||
if !state.is_connected() {
|
||||
return_errno!(ENOTCONN, "The udp socket is not connected");
|
||||
}
|
||||
drop(state);
|
||||
match how {
|
||||
Shutdown::Read => {
|
||||
self.common.host_shutdown(how)?;
|
||||
self.receiver.shutdown();
|
||||
self.common.pollee().add_events(Events::IN);
|
||||
}
|
||||
Shutdown::Write => {
|
||||
if self.sender.is_empty() {
|
||||
self.common.host_shutdown(how)?;
|
||||
}
|
||||
self.sender.shutdown();
|
||||
self.common.pollee().add_events(Events::OUT);
|
||||
}
|
||||
Shutdown::Both => {
|
||||
self.common.host_shutdown(Shutdown::Read)?;
|
||||
if self.sender.is_empty() {
|
||||
self.common.host_shutdown(Shutdown::Write)?;
|
||||
}
|
||||
self.receiver.shutdown();
|
||||
self.sender.shutdown();
|
||||
self.common
|
||||
.pollee()
|
||||
.add_events(Events::IN | Events::OUT | Events::HUP);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read(&self, buf: &mut [u8]) -> Result<usize> {
|
||||
self.readv(&mut [buf])
|
||||
}
|
||||
|
||||
pub fn readv(&self, bufs: &mut [&mut [u8]]) -> Result<usize> {
|
||||
let state = self.state.read().unwrap();
|
||||
drop(state);
|
||||
|
||||
self.recvmsg(bufs, RecvFlags::empty(), None)
|
||||
.map(|(ret, ..)| ret)
|
||||
}
|
||||
|
||||
/// You can not invoke `recvfrom` directly after creating a datagram socket.
|
||||
/// That is because `recvfrom` doesn't privide a implicit binding. If you
|
||||
/// don't do a explicit or implicit binding, the sender doesn't know where
|
||||
/// to send the data.
|
||||
pub fn recvmsg(
|
||||
&self,
|
||||
bufs: &mut [&mut [u8]],
|
||||
flags: RecvFlags,
|
||||
control: Option<&mut [u8]>,
|
||||
) -> Result<(usize, Option<A>, MsgFlags, usize)> {
|
||||
self.receiver.recvmsg(bufs, flags, control)
|
||||
}
|
||||
|
||||
pub fn write(&self, buf: &[u8]) -> Result<usize> {
|
||||
self.writev(&[buf])
|
||||
}
|
||||
|
||||
pub fn writev(&self, bufs: &[&[u8]]) -> Result<usize> {
|
||||
self.sendmsg(bufs, None, SendFlags::empty(), None)
|
||||
}
|
||||
|
||||
pub fn sendmsg(
|
||||
&self,
|
||||
bufs: &[&[u8]],
|
||||
addr: Option<&A>,
|
||||
flags: SendFlags,
|
||||
control: Option<&[u8]>,
|
||||
) -> Result<usize> {
|
||||
let state = self.state.read().unwrap();
|
||||
if addr.is_none() && !state.is_connected() {
|
||||
return_errno!(EDESTADDRREQ, "Destination address required");
|
||||
}
|
||||
|
||||
drop(state);
|
||||
let res = if let Some(addr) = addr {
|
||||
self.sender.sendmsg(bufs, addr, flags, control)
|
||||
} else {
|
||||
let peer = self.common.peer_addr();
|
||||
if let Some(peer) = peer.as_ref() {
|
||||
self.sender.sendmsg(bufs, peer, flags, control)
|
||||
} else {
|
||||
return_errno!(EDESTADDRREQ, "Destination address required");
|
||||
}
|
||||
};
|
||||
|
||||
let mut state = self.state.write().unwrap();
|
||||
if !state.is_bound() {
|
||||
state.mark_implicit_bind();
|
||||
// Start async recv after explicit binding or implicit binding
|
||||
self.receiver.initiate_async_recv();
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn poll(&self, mask: Events, poller: Option<&Poller>) -> Events {
|
||||
let pollee = self.common.pollee();
|
||||
pollee.poll(mask, poller)
|
||||
}
|
||||
|
||||
pub fn addr(&self) -> Result<A> {
|
||||
let common = &self.common;
|
||||
|
||||
// Always get addr from host.
|
||||
// Because for IP socket, users can specify "0" as port and the kernel should select a usable port for him.
|
||||
// Thus, when calling getsockname, this should be updated.
|
||||
let addr = common.get_addr_from_host()?;
|
||||
common.set_addr(&addr);
|
||||
Ok(addr)
|
||||
}
|
||||
|
||||
pub fn notifier(&self) -> &IoNotifier {
|
||||
let notifier = self.common.notifier();
|
||||
notifier
|
||||
}
|
||||
|
||||
pub fn peer_addr(&self) -> Result<A> {
|
||||
let state = self.state.read().unwrap();
|
||||
if !state.is_connected() {
|
||||
return_errno!(ENOTCONN, "the socket is not connected");
|
||||
}
|
||||
Ok(self.common.peer_addr().unwrap())
|
||||
}
|
||||
|
||||
pub fn errno(&self) -> Option<Errno> {
|
||||
self.common.errno()
|
||||
}
|
||||
|
||||
pub fn ioctl(&self, cmd: &mut dyn IoctlCmd) -> Result<()> {
|
||||
match_ioctl_cmd_mut!(&mut *cmd, {
|
||||
cmd: GetSockOptRawCmd => {
|
||||
cmd.execute(self.host_fd())?;
|
||||
},
|
||||
cmd: SetSockOptRawCmd => {
|
||||
cmd.execute(self.host_fd())?;
|
||||
},
|
||||
cmd: SetRecvTimeoutCmd => {
|
||||
self.set_recv_timeout(*cmd.timeout());
|
||||
},
|
||||
cmd: SetSendTimeoutCmd => {
|
||||
self.set_send_timeout(*cmd.timeout());
|
||||
},
|
||||
cmd: GetRecvTimeoutCmd => {
|
||||
let timeval = timeout_to_timeval(self.recv_timeout());
|
||||
cmd.set_output(timeval);
|
||||
},
|
||||
cmd: GetSendTimeoutCmd => {
|
||||
let timeval = timeout_to_timeval(self.send_timeout());
|
||||
cmd.set_output(timeval);
|
||||
},
|
||||
cmd: GetAcceptConnCmd => {
|
||||
// Datagram doesn't support listen
|
||||
cmd.set_output(0);
|
||||
},
|
||||
cmd: GetDomainCmd => {
|
||||
cmd.set_output(self.domain() as _);
|
||||
},
|
||||
cmd: GetErrorCmd => {
|
||||
let error: i32 = self.errno().map(|err| err as i32).unwrap_or(0);
|
||||
cmd.set_output(error);
|
||||
},
|
||||
cmd: GetPeerNameCmd => {
|
||||
let peer = self.peer_addr()?;
|
||||
cmd.set_output(AddrStorage(peer.to_c_storage()));
|
||||
},
|
||||
cmd: GetTypeCmd => {
|
||||
cmd.set_output(self.common.type_() as _);
|
||||
},
|
||||
cmd: GetIfReqWithRawCmd => {
|
||||
cmd.execute(self.host_fd())?;
|
||||
},
|
||||
cmd: GetIfConf => {
|
||||
cmd.execute(self.host_fd())?;
|
||||
},
|
||||
cmd: GetReadBufLen => {
|
||||
let read_buf_len = self.receiver.ready_len();
|
||||
cmd.set_output(read_buf_len as _);
|
||||
},
|
||||
_ => {
|
||||
return_errno!(EINVAL, "Not supported yet");
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_timeout(&self) -> Option<Duration> {
|
||||
self.common.send_timeout()
|
||||
}
|
||||
|
||||
fn recv_timeout(&self) -> Option<Duration> {
|
||||
self.common.recv_timeout()
|
||||
}
|
||||
|
||||
fn set_send_timeout(&self, timeout: Duration) {
|
||||
self.common.set_send_timeout(timeout);
|
||||
}
|
||||
|
||||
fn set_recv_timeout(&self, timeout: Duration) {
|
||||
self.common.set_recv_timeout(timeout);
|
||||
}
|
||||
|
||||
fn cancel_requests(&self) {
|
||||
self.receiver.cancel_recv_requests();
|
||||
self.sender.try_clear_msg_queue_when_close();
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> Drop for DatagramSocket<A, R> {
|
||||
fn drop(&mut self) {
|
||||
self.common.set_closed();
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> std::fmt::Debug for DatagramSocket<A, R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("DatagramSocket")
|
||||
.field("common", &self.common)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
bind_state: BindState,
|
||||
is_connected: bool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
bind_state: BindState::Unbound,
|
||||
is_connected: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_connected() -> Self {
|
||||
Self {
|
||||
bind_state: BindState::Unbound,
|
||||
is_connected: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_bound(&self) -> bool {
|
||||
self.bind_state.is_bound()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_explicit_bound(&self) -> bool {
|
||||
self.bind_state.is_explicit_bound()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_implicit_bound(&self) -> bool {
|
||||
self.bind_state.is_implicit_bound()
|
||||
}
|
||||
|
||||
pub fn is_connected(&self) -> bool {
|
||||
self.is_connected
|
||||
}
|
||||
|
||||
pub fn mark_explicit_bind(&mut self) {
|
||||
self.bind_state = BindState::ExplicitBound;
|
||||
}
|
||||
|
||||
pub fn mark_implicit_bind(&mut self) {
|
||||
self.bind_state = BindState::ImplicitBound;
|
||||
}
|
||||
|
||||
pub fn mark_connected(&mut self) {
|
||||
self.is_connected = true;
|
||||
}
|
||||
|
||||
pub fn mark_disconnected(&mut self) {
|
||||
self.is_connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum BindState {
|
||||
Unbound,
|
||||
ExplicitBound,
|
||||
ImplicitBound,
|
||||
}
|
||||
|
||||
impl BindState {
|
||||
pub fn is_bound(&self) -> bool {
|
||||
match self {
|
||||
Self::Unbound => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_explicit_bound(&self) -> bool {
|
||||
match self {
|
||||
Self::ExplicitBound => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_implicit_bound(&self) -> bool {
|
||||
match self {
|
||||
Self::ImplicitBound => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
20
src/libos/src/net/socket/uring/datagram/mod.rs
Normal file
20
src/libos/src/net/socket/uring/datagram/mod.rs
Normal file
@ -0,0 +1,20 @@
|
||||
//! Datagram sockets.
|
||||
mod generic;
|
||||
mod receiver;
|
||||
mod sender;
|
||||
|
||||
use self::receiver::Receiver;
|
||||
use self::sender::Sender;
|
||||
use crate::net::socket::sockopt::*;
|
||||
use crate::net::socket::uring::common::{do_bind, do_connect, Common};
|
||||
use crate::net::socket::uring::runtime::Runtime;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub use generic::DatagramSocket;
|
||||
|
||||
use crate::net::socket::sockopt::{
|
||||
timeout_to_timeval, GetRecvTimeoutCmd, GetSendTimeoutCmd, SetRecvTimeoutCmd, SetSendTimeoutCmd,
|
||||
};
|
||||
|
||||
const MAX_BUF_SIZE: usize = 64 * 1024;
|
||||
const OPTMEM_MAX: usize = 64 * 1024;
|
382
src/libos/src/net/socket/uring/datagram/receiver.rs
Normal file
382
src/libos/src/net/socket/uring/datagram/receiver.rs
Normal file
@ -0,0 +1,382 @@
|
||||
use core::time::Duration;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
use crate::events::Poller;
|
||||
use crate::net::socket::MsgFlags;
|
||||
use io_uring_callback::{Fd, IoHandle};
|
||||
use sgx_untrusted_alloc::{MaybeUntrusted, UntrustedBox};
|
||||
|
||||
use crate::fs::IoEvents as Events;
|
||||
use crate::net::socket::uring::common::Common;
|
||||
use crate::net::socket::uring::runtime::Runtime;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Receiver<A: Addr + 'static, R: Runtime> {
|
||||
common: Arc<Common<A, R>>,
|
||||
inner: Mutex<Inner>,
|
||||
}
|
||||
|
||||
impl<A: Addr, R: Runtime> Receiver<A, R> {
|
||||
pub fn new(common: Arc<Common<A, R>>) -> Arc<Self> {
|
||||
let inner = Mutex::new(Inner::new());
|
||||
Arc::new(Self { common, inner })
|
||||
}
|
||||
|
||||
pub fn recvmsg(
|
||||
self: &Arc<Self>,
|
||||
bufs: &mut [&mut [u8]],
|
||||
flags: RecvFlags,
|
||||
mut control: Option<&mut [u8]>,
|
||||
) -> Result<(usize, Option<A>, MsgFlags, usize)> {
|
||||
let mask = Events::IN;
|
||||
// Initialize the poller only when needed
|
||||
let mut poller = None;
|
||||
let mut timeout = self.common.recv_timeout();
|
||||
loop {
|
||||
// Attempt to recv
|
||||
let res = self.try_recvmsg(bufs, flags, &mut control);
|
||||
if !res.has_errno(EAGAIN) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// Need more handles for flags not MSG_DONTWAIT
|
||||
// recv*(MSG_ERRQUEUE) never blocks, even without MSG_DONTWAIT
|
||||
if self.common.nonblocking()
|
||||
|| flags.contains(RecvFlags::MSG_DONTWAIT)
|
||||
|| flags.contains(RecvFlags::MSG_ERRQUEUE)
|
||||
{
|
||||
return_errno!(EAGAIN, "no data are present to be received");
|
||||
}
|
||||
|
||||
// Wait for interesting events by polling
|
||||
if poller.is_none() {
|
||||
let new_poller = Poller::new();
|
||||
self.common.pollee().connect_poller(mask, &new_poller);
|
||||
poller = Some(new_poller);
|
||||
}
|
||||
let events = self.common.pollee().poll(mask, None);
|
||||
if events.is_empty() {
|
||||
let ret = poller.as_ref().unwrap().wait_timeout(timeout.as_mut());
|
||||
if let Err(e) = ret {
|
||||
warn!("recv wait errno = {:?}", e.errno());
|
||||
match e.errno() {
|
||||
ETIMEDOUT => {
|
||||
return_errno!(EAGAIN, "timeout reached")
|
||||
}
|
||||
_ => {
|
||||
return_errno!(e.errno(), "wait error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_recvmsg(
|
||||
self: &Arc<Self>,
|
||||
bufs: &mut [&mut [u8]],
|
||||
flags: RecvFlags,
|
||||
control: &mut Option<&mut [u8]>,
|
||||
) -> Result<(usize, Option<A>, MsgFlags, usize)> {
|
||||
let mut inner = self.inner.lock();
|
||||
|
||||
if !flags.is_empty() && flags.contains(RecvFlags::MSG_OOB | RecvFlags::MSG_CMSG_CLOEXEC) {
|
||||
// todo!("Support other flags");
|
||||
return_errno!(EINVAL, "the socket flags is not supported");
|
||||
}
|
||||
|
||||
// Mark the socket as non-readable since Datagram uses single packet
|
||||
self.common.pollee().del_events(Events::IN);
|
||||
|
||||
let mut recv_bytes = 0;
|
||||
let mut msg_flags = MsgFlags::empty();
|
||||
let recv_addr = inner.get_packet_addr();
|
||||
let msg_controllen = inner.control_len.unwrap_or(0);
|
||||
let user_controllen = control.as_ref().map_or(0, |buf| buf.len());
|
||||
|
||||
// Copy ancillary data from control buffer
|
||||
if user_controllen > super::OPTMEM_MAX {
|
||||
return_errno!(EINVAL, "invalid msg control length");
|
||||
}
|
||||
|
||||
if user_controllen < msg_controllen {
|
||||
msg_flags = msg_flags | MsgFlags::MSG_CTRUNC
|
||||
}
|
||||
|
||||
if msg_controllen > 0 {
|
||||
let copied_bytes = msg_controllen.min(user_controllen);
|
||||
control
|
||||
.as_mut()
|
||||
.map(|buf| buf[..copied_bytes].copy_from_slice(&inner.msg_control[..copied_bytes]));
|
||||
}
|
||||
|
||||
// Copy data from the recv buffer to the bufs
|
||||
let copied_bytes = inner.try_copy_buf(bufs);
|
||||
if let Some(copied_bytes) = copied_bytes {
|
||||
let bufs_len: usize = bufs.iter().map(|buf| buf.len()).sum();
|
||||
|
||||
// If user provided buffer length is smaller than kernel received datagram length,
|
||||
// discard the datagram and set MsgFlags::MSG_TRUNC in returned msg_flags.
|
||||
if bufs_len < inner.recv_len().unwrap() {
|
||||
// update msg.msg_flags to MSG_TRUNC
|
||||
msg_flags = msg_flags | MsgFlags::MSG_TRUNC
|
||||
};
|
||||
|
||||
// If user provided flags contain MSG_TRUNC, the return received length should be
|
||||
// kernel receiver buffer length, vice versa should return truly copied bytes length.
|
||||
recv_bytes = if flags.contains(RecvFlags::MSG_TRUNC) {
|
||||
inner.recv_len().unwrap()
|
||||
} else {
|
||||
copied_bytes
|
||||
};
|
||||
|
||||
// When flags contain MSG_PEEK and there is data in socket recv buffer,
|
||||
// it is unnecessary to send blocking recv request (do_recv) to fetch data
|
||||
// from iouring buffer, which may flush the data in recv buffer.
|
||||
// When flags don't contain MSG_PEEK or there is no available data,
|
||||
// it is time to send blocking request to iouring for notifying events.
|
||||
if !flags.contains(RecvFlags::MSG_PEEK) {
|
||||
self.do_recv(&mut inner);
|
||||
}
|
||||
return Ok((recv_bytes, recv_addr, msg_flags, msg_controllen));
|
||||
}
|
||||
|
||||
// In some situantions of MSG_ERRQUEUE, users only require control buffer but setting iovec length to zero.
|
||||
if msg_controllen > 0 {
|
||||
return Ok((recv_bytes, recv_addr, msg_flags, msg_controllen));
|
||||
}
|
||||
|
||||
// Handle iouring message error
|
||||
if let Some(errno) = inner.error {
|
||||
// Reset error
|
||||
inner.error = None;
|
||||
self.common.pollee().del_events(Events::ERR);
|
||||
return_errno!(errno, "recv failed");
|
||||
}
|
||||
|
||||
if inner.is_shutdown {
|
||||
if self.common.nonblocking()
|
||||
|| flags.contains(RecvFlags::MSG_DONTWAIT)
|
||||
|| flags.contains(RecvFlags::MSG_ERRQUEUE)
|
||||
{
|
||||
return_errno!(Errno::EWOULDBLOCK, "the socket recv has been shutdown");
|
||||
} else {
|
||||
return Ok((0, None, msg_flags, 0));
|
||||
}
|
||||
}
|
||||
|
||||
self.do_recv(&mut inner);
|
||||
return_errno!(EAGAIN, "try recv again");
|
||||
}
|
||||
|
||||
fn do_recv(self: &Arc<Self>, inner: &mut MutexGuard<Inner>) {
|
||||
if inner.io_handle.is_some() || self.common.is_closed() {
|
||||
return;
|
||||
}
|
||||
// Clear recv_len and error
|
||||
inner.recv_len.take();
|
||||
inner.control_len.take();
|
||||
inner.error.take();
|
||||
|
||||
if inner.is_shutdown {
|
||||
info!("do_recv early return, the socket recv has been shutdown");
|
||||
return;
|
||||
}
|
||||
|
||||
let receiver = self.clone();
|
||||
// Init the callback invoked upon the completion of the async recv
|
||||
let complete_fn = move |retval: i32| {
|
||||
let mut inner = receiver.inner.lock();
|
||||
|
||||
// Release the handle to the async recv
|
||||
inner.io_handle.take();
|
||||
|
||||
// Handle error
|
||||
if retval < 0 {
|
||||
// TODO: Should we filter the error case? Do we have the ability to filter?
|
||||
// We only filter the normal case now. According to the man page of recvmsg,
|
||||
// these errors should not happen, since our fd and arguments should always
|
||||
// be valid unless being attacked.
|
||||
|
||||
// TODO: guard against Iago attack through errno
|
||||
let errno = Errno::from(-retval as u32);
|
||||
inner.error = Some(errno);
|
||||
receiver.common.set_errno(errno);
|
||||
// TODO: add PRI event if set SO_SELECT_ERR_QUEUE
|
||||
receiver.common.pollee().add_events(Events::ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the normal case of a successful read
|
||||
inner.recv_len = Some(retval as usize);
|
||||
|
||||
let control_len = inner.req.msg.msg_controllen;
|
||||
inner.control_len = Some(control_len);
|
||||
|
||||
receiver.common.pollee().add_events(Events::IN);
|
||||
|
||||
// We don't do_recv() here, since do_recv() will clear the recv message.
|
||||
};
|
||||
|
||||
// Generate the async recv request
|
||||
let msghdr_ptr = inner.new_recv_req();
|
||||
|
||||
// Submit the async recv to io_uring
|
||||
let io_uring = self.common.io_uring();
|
||||
let host_fd = Fd(self.common.host_fd() as _);
|
||||
let handle = unsafe { io_uring.recvmsg(host_fd, msghdr_ptr, 0, complete_fn) };
|
||||
inner.io_handle.replace(handle);
|
||||
}
|
||||
|
||||
pub fn initiate_async_recv(self: &Arc<Self>) {
|
||||
let mut inner = self.inner.lock();
|
||||
self.do_recv(&mut inner);
|
||||
}
|
||||
|
||||
pub fn cancel_recv_requests(&self) {
|
||||
{
|
||||
let inner = self.inner.lock();
|
||||
if let Some(io_handle) = &inner.io_handle {
|
||||
let io_uring = self.common.io_uring();
|
||||
unsafe { io_uring.cancel(io_handle) };
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// wait for the cancel to complete
|
||||
let poller = Poller::new();
|
||||
let mask = Events::ERR | Events::IN;
|
||||
self.common.pollee().connect_poller(mask, &poller);
|
||||
|
||||
loop {
|
||||
let pending_request_exist = {
|
||||
let inner = self.inner.lock();
|
||||
inner.io_handle.is_some()
|
||||
};
|
||||
|
||||
if pending_request_exist {
|
||||
let mut timeout = Some(Duration::from_secs(10));
|
||||
let ret = poller.wait_timeout(timeout.as_mut());
|
||||
if let Err(e) = ret {
|
||||
warn!("wait cancel recv request error = {:?}", e.errno());
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shutdown udp receiver.
|
||||
pub fn shutdown(&self) {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.is_shutdown = true;
|
||||
}
|
||||
|
||||
/// Reset udp receiver shutdown state.
|
||||
pub fn reset_shutdown(&self) {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.is_shutdown = false;
|
||||
}
|
||||
|
||||
pub fn ready_len(&self) -> usize {
|
||||
let inner = self.inner.lock();
|
||||
inner.recv_len().unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
recv_buf: UntrustedBox<[u8]>,
|
||||
// Datagram sockets in various domains permit zero-length datagrams.
|
||||
// Hence the recv_len might be 0.
|
||||
recv_len: Option<usize>,
|
||||
// When the recv_buf content length is greater than user buffer,
|
||||
// store the offset for the recv_buf for read loop
|
||||
recv_buf_offset: usize,
|
||||
msg_control: UntrustedBox<[u8]>,
|
||||
control_len: Option<usize>,
|
||||
req: UntrustedBox<RecvReq>,
|
||||
io_handle: Option<IoHandle>,
|
||||
error: Option<Errno>,
|
||||
is_shutdown: bool,
|
||||
}
|
||||
|
||||
unsafe impl Send for Inner {}
|
||||
|
||||
impl Inner {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
recv_buf: UntrustedBox::new_uninit_slice(super::MAX_BUF_SIZE),
|
||||
recv_len: None,
|
||||
recv_buf_offset: 0,
|
||||
msg_control: UntrustedBox::new_uninit_slice(super::OPTMEM_MAX),
|
||||
control_len: None,
|
||||
req: UntrustedBox::new_uninit(),
|
||||
io_handle: None,
|
||||
error: None,
|
||||
is_shutdown: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_recv_req(&mut self) -> *mut libc::msghdr {
|
||||
let iovec = libc::iovec {
|
||||
iov_base: self.recv_buf.as_mut_ptr() as _,
|
||||
iov_len: self.recv_buf.len(),
|
||||
};
|
||||
|
||||
let msghdr_ptr = &raw mut self.req.msg;
|
||||
|
||||
let mut msg: libc::msghdr = unsafe { MaybeUninit::zeroed().assume_init() };
|
||||
msg.msg_iov = &raw mut self.req.iovec as _;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_name = &raw mut self.req.addr as _;
|
||||
msg.msg_namelen = std::mem::size_of::<libc::sockaddr_storage>() as _;
|
||||
|
||||
msg.msg_control = self.msg_control.as_mut_ptr() as _;
|
||||
msg.msg_controllen = self.msg_control.len() as _;
|
||||
|
||||
self.req.msg = msg;
|
||||
self.req.iovec = iovec;
|
||||
|
||||
msghdr_ptr
|
||||
}
|
||||
|
||||
pub fn try_copy_buf(&self, bufs: &mut [&mut [u8]]) -> Option<usize> {
|
||||
self.recv_len.map(|recv_len| {
|
||||
let mut copy_len = 0;
|
||||
for buf in bufs {
|
||||
let recv_buf = &self.recv_buf[copy_len..recv_len];
|
||||
if buf.len() <= recv_buf.len() {
|
||||
buf.copy_from_slice(&recv_buf[..buf.len()]);
|
||||
copy_len += buf.len();
|
||||
} else {
|
||||
buf[..recv_buf.len()].copy_from_slice(&recv_buf[..]);
|
||||
copy_len += recv_buf.len();
|
||||
break;
|
||||
}
|
||||
}
|
||||
copy_len
|
||||
})
|
||||
}
|
||||
|
||||
pub fn recv_len(&self) -> Option<usize> {
|
||||
self.recv_len
|
||||
}
|
||||
|
||||
/// Return the addr of the received packet if udp socket is not connected.
|
||||
/// Return None if udp socket is connected.
|
||||
pub fn get_packet_addr<A: Addr>(&self) -> Option<A> {
|
||||
let recv_addr_len = self.req.msg.msg_namelen as usize;
|
||||
A::from_c_storage(&self.req.addr, recv_addr_len).ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct RecvReq {
|
||||
msg: libc::msghdr,
|
||||
iovec: libc::iovec,
|
||||
addr: libc::sockaddr_storage,
|
||||
}
|
||||
|
||||
unsafe impl MaybeUntrusted for RecvReq {}
|
406
src/libos/src/net/socket/uring/datagram/sender.rs
Normal file
406
src/libos/src/net/socket/uring/datagram/sender.rs
Normal file
@ -0,0 +1,406 @@
|
||||
use core::time::Duration;
|
||||
use std::ptr::{self};
|
||||
|
||||
use io_uring_callback::{Fd, IoHandle};
|
||||
use libc::c_void;
|
||||
use sgx_untrusted_alloc::{MaybeUntrusted, UntrustedBox};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::events::Poller;
|
||||
use crate::fs::IoEvents as Events;
|
||||
use crate::net::socket::uring::common::Common;
|
||||
use crate::net::socket::uring::runtime::Runtime;
|
||||
use crate::prelude::*;
|
||||
use crate::util::sync::MutexGuard;
|
||||
|
||||
const SENDMSG_QUEUE_LEN: usize = 16;
|
||||
|
||||
pub struct Sender<A: Addr + 'static, R: Runtime> {
|
||||
common: Arc<Common<A, R>>,
|
||||
inner: Mutex<Inner>,
|
||||
}
|
||||
|
||||
impl<A: Addr, R: Runtime> Sender<A, R> {
|
||||
pub fn new(common: Arc<Common<A, R>>) -> Arc<Self> {
|
||||
common.pollee().add_events(Events::OUT);
|
||||
let inner = Mutex::new(Inner::new());
|
||||
Arc::new(Self { common, inner })
|
||||
}
|
||||
|
||||
/// Shutdown udp sender.
|
||||
pub fn shutdown(&self) {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.is_shutdown = ShutdownStatus::PreShutdown;
|
||||
}
|
||||
|
||||
/// Reset udp sender shutdown state.
|
||||
pub fn reset_shutdown(&self) {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.is_shutdown = ShutdownStatus::Running;
|
||||
}
|
||||
|
||||
/// Whether no buffer in sender.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
let inner = self.inner.lock();
|
||||
inner.msg_queue.is_empty()
|
||||
}
|
||||
|
||||
// Normally, We will always try to send as long as the kernel send buf is not empty.
|
||||
// However, if the user calls close, we will wait LINGER time
|
||||
// and then cancel on-going or new-issued send requests.
|
||||
pub fn try_clear_msg_queue_when_close(&self) {
|
||||
let inner = self.inner.lock();
|
||||
debug_assert!(inner.is_shutdown());
|
||||
if inner.msg_queue.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for linger time to empty the kernel buffer or cancel subsequent requests.
|
||||
drop(inner);
|
||||
const DEFUALT_LINGER_TIME: usize = 10;
|
||||
let poller = Poller::new();
|
||||
let mask = Events::ERR | Events::OUT;
|
||||
self.common.pollee().connect_poller(mask, &poller);
|
||||
|
||||
loop {
|
||||
let pending_request_exist = {
|
||||
let inner = self.inner.lock();
|
||||
inner.io_handle.is_some()
|
||||
};
|
||||
|
||||
if pending_request_exist {
|
||||
let mut timeout = Some(Duration::from_secs(DEFUALT_LINGER_TIME as u64));
|
||||
let ret = poller.wait_timeout(timeout.as_mut());
|
||||
trace!("wait empty send buffer ret = {:?}", ret);
|
||||
if let Err(_) = ret {
|
||||
// No complete request to wake. Just cancel the send requests.
|
||||
let io_uring = self.common.io_uring();
|
||||
let inner = self.inner.lock();
|
||||
if let Some(io_handle) = &inner.io_handle {
|
||||
unsafe { io_uring.cancel(io_handle) };
|
||||
// Loop again to wait the cancel request to complete
|
||||
continue;
|
||||
} else {
|
||||
// No pending request, just break
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// There is no pending requests
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sendmsg(
|
||||
self: &Arc<Self>,
|
||||
bufs: &[&[u8]],
|
||||
addr: &A,
|
||||
flags: SendFlags,
|
||||
control: Option<&[u8]>,
|
||||
) -> Result<usize> {
|
||||
if !flags.is_empty()
|
||||
&& flags.intersects(!(SendFlags::MSG_DONTWAIT | SendFlags::MSG_NOSIGNAL))
|
||||
{
|
||||
error!("Not supported flags: {:?}", flags);
|
||||
return_errno!(EINVAL, "not supported flags");
|
||||
}
|
||||
let mask = Events::OUT;
|
||||
// Initialize the poller only when needed
|
||||
let mut poller = None;
|
||||
let mut timeout = self.common.send_timeout();
|
||||
loop {
|
||||
// Attempt to write
|
||||
let res = self.try_sendmsg(bufs, addr, control);
|
||||
if !res.has_errno(EAGAIN) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// Still some buffer contents pending
|
||||
if self.common.nonblocking() || flags.contains(SendFlags::MSG_DONTWAIT) {
|
||||
return_errno!(EAGAIN, "try write again");
|
||||
}
|
||||
|
||||
// Wait for interesting events by polling
|
||||
if poller.is_none() {
|
||||
let new_poller = Poller::new();
|
||||
self.common.pollee().connect_poller(mask, &new_poller);
|
||||
poller = Some(new_poller);
|
||||
}
|
||||
|
||||
let events = self.common.pollee().poll(mask, None);
|
||||
if events.is_empty() {
|
||||
let ret = poller.as_ref().unwrap().wait_timeout(timeout.as_mut());
|
||||
if let Err(e) = ret {
|
||||
warn!("send wait errno = {:?}", e.errno());
|
||||
match e.errno() {
|
||||
ETIMEDOUT => {
|
||||
return_errno!(EAGAIN, "timeout reached")
|
||||
}
|
||||
_ => {
|
||||
return_errno!(e.errno(), "wait error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_sendmsg(
|
||||
self: &Arc<Self>,
|
||||
bufs: &[&[u8]],
|
||||
addr: &A,
|
||||
control: Option<&[u8]>,
|
||||
) -> Result<usize> {
|
||||
let mut inner = self.inner.lock();
|
||||
if inner.is_shutdown() {
|
||||
return_errno!(EPIPE, "the write has been shutdown")
|
||||
}
|
||||
|
||||
if let Some(errno) = inner.error {
|
||||
// Reset error
|
||||
inner.error = None;
|
||||
self.common.pollee().del_events(Events::ERR);
|
||||
return_errno!(errno, "write failed");
|
||||
}
|
||||
|
||||
let buf_len: usize = bufs.iter().map(|buf| buf.len()).sum();
|
||||
let mut msg = DataMsg::new(buf_len);
|
||||
let total_copied = msg.copy_buf(bufs)?;
|
||||
msg.copy_control(control)?;
|
||||
|
||||
let msghdr_ptr = new_send_req(&mut msg, addr);
|
||||
|
||||
if !inner.msg_queue.push_msg(msg) {
|
||||
// Msg queue can not push this msg, mark the socket as non-writable
|
||||
self.common.pollee().del_events(Events::OUT);
|
||||
return_errno!(EAGAIN, "try write again");
|
||||
}
|
||||
|
||||
// Since the send buffer is not empty, try to flush the buffer
|
||||
if inner.io_handle.is_none() {
|
||||
self.do_send(&mut inner, msghdr_ptr);
|
||||
}
|
||||
Ok(total_copied)
|
||||
}
|
||||
|
||||
fn do_send(self: &Arc<Self>, inner: &mut MutexGuard<Inner>, msghdr_ptr: *const libc::msghdr) {
|
||||
debug_assert!(!inner.msg_queue.is_empty());
|
||||
debug_assert!(inner.io_handle.is_none());
|
||||
let sender = self.clone();
|
||||
// Submit the async send to io_uring
|
||||
let complete_fn = move |retval: i32| {
|
||||
let mut inner = sender.inner.lock();
|
||||
trace!("send request complete with retval: {}", retval);
|
||||
|
||||
// Release the handle to the async recv
|
||||
inner.io_handle.take();
|
||||
|
||||
if retval < 0 {
|
||||
// TODO: add PRI event if set SO_SELECT_ERR_QUEUE
|
||||
let errno = Errno::from(-retval as u32);
|
||||
|
||||
inner.error = Some(errno);
|
||||
sender.common.set_errno(errno);
|
||||
sender.common.pollee().add_events(Events::ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to handle normal case
|
||||
inner.msg_queue.pop_msg();
|
||||
sender.common.pollee().add_events(Events::OUT);
|
||||
if !inner.msg_queue.is_empty() {
|
||||
let msghdr_ptr = inner.msg_queue.first_msg_ptr();
|
||||
debug_assert!(msghdr_ptr.is_some());
|
||||
sender.do_send(&mut inner, msghdr_ptr.unwrap());
|
||||
} else if inner.is_shutdown == ShutdownStatus::PreShutdown {
|
||||
// The buffer is empty and the write side is shutdown by the user.
|
||||
// We can safely shutdown host file here.
|
||||
let _ = sender.common.host_shutdown(Shutdown::Write);
|
||||
inner.is_shutdown = ShutdownStatus::PostShutdown
|
||||
}
|
||||
};
|
||||
|
||||
// Generate the async recv request
|
||||
let io_uring = self.common.io_uring();
|
||||
let host_fd = Fd(self.common.host_fd() as _);
|
||||
let handle = unsafe { io_uring.sendmsg(host_fd, msghdr_ptr, 0, complete_fn) };
|
||||
inner.io_handle.replace(handle);
|
||||
}
|
||||
}
|
||||
|
||||
fn new_send_req<A: Addr>(dmsg: &mut DataMsg, addr: &A) -> *const libc::msghdr {
|
||||
let iovec = libc::iovec {
|
||||
iov_base: dmsg.send_buf.as_ptr() as _,
|
||||
iov_len: dmsg.send_buf.len(),
|
||||
};
|
||||
|
||||
let (control, controllen) = match &dmsg.control {
|
||||
Some(control) => (control.as_mut_ptr() as *mut c_void, control.len()),
|
||||
None => (ptr::null_mut(), 0),
|
||||
};
|
||||
|
||||
dmsg.req.iovec = iovec;
|
||||
|
||||
dmsg.req.msg.msg_iov = &raw mut dmsg.req.iovec as _;
|
||||
dmsg.req.msg.msg_iovlen = 1;
|
||||
|
||||
let (c_addr_storage, c_addr_len) = addr.to_c_storage();
|
||||
|
||||
dmsg.req.addr = c_addr_storage;
|
||||
dmsg.req.msg.msg_name = &raw mut dmsg.req.addr as _;
|
||||
dmsg.req.msg.msg_namelen = c_addr_len as _;
|
||||
dmsg.req.msg.msg_control = control;
|
||||
dmsg.req.msg.msg_controllen = controllen;
|
||||
|
||||
&mut dmsg.req.msg
|
||||
}
|
||||
|
||||
pub struct Inner {
|
||||
io_handle: Option<IoHandle>,
|
||||
error: Option<Errno>,
|
||||
is_shutdown: ShutdownStatus,
|
||||
msg_queue: MsgQueue,
|
||||
}
|
||||
|
||||
unsafe impl Send for Inner {}
|
||||
|
||||
impl Inner {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
io_handle: None,
|
||||
error: None,
|
||||
is_shutdown: ShutdownStatus::Running,
|
||||
msg_queue: MsgQueue::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain udp sender shutdown state.
|
||||
#[inline(always)]
|
||||
pub fn is_shutdown(&self) -> bool {
|
||||
self.is_shutdown == ShutdownStatus::PreShutdown
|
||||
|| self.is_shutdown == ShutdownStatus::PostShutdown
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct SendReq {
|
||||
msg: libc::msghdr,
|
||||
iovec: libc::iovec,
|
||||
addr: libc::sockaddr_storage,
|
||||
}
|
||||
|
||||
unsafe impl MaybeUntrusted for SendReq {}
|
||||
|
||||
struct MsgQueue {
|
||||
queue: VecDeque<DataMsg>,
|
||||
curr_size: usize,
|
||||
}
|
||||
|
||||
impl MsgQueue {
|
||||
#[inline(always)]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
queue: VecDeque::with_capacity(SENDMSG_QUEUE_LEN),
|
||||
curr_size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn size(&self) -> usize {
|
||||
self.curr_size
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
self.queue.is_empty()
|
||||
}
|
||||
|
||||
// Push datagram msg, return true if succeed,
|
||||
// return false if buffer is full.
|
||||
#[inline(always)]
|
||||
fn push_msg(&mut self, msg: DataMsg) -> bool {
|
||||
let total_len = msg.len() + self.size();
|
||||
if total_len <= super::MAX_BUF_SIZE {
|
||||
self.curr_size = total_len;
|
||||
self.queue.push_back(msg);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pop_msg(&mut self) {
|
||||
if let Some(msg) = self.queue.pop_front() {
|
||||
self.curr_size = self.size() - msg.len();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn first_msg_ptr(&self) -> Option<*const libc::msghdr> {
|
||||
self.queue
|
||||
.front()
|
||||
.map(|data_msg| &data_msg.req.msg as *const libc::msghdr)
|
||||
}
|
||||
}
|
||||
|
||||
// Datagram msg contents in untrusted region
|
||||
struct DataMsg {
|
||||
req: UntrustedBox<SendReq>,
|
||||
send_buf: UntrustedBox<[u8]>,
|
||||
control: Option<UntrustedBox<[u8]>>,
|
||||
}
|
||||
|
||||
impl DataMsg {
|
||||
#[inline(always)]
|
||||
fn new(buf_len: usize) -> Self {
|
||||
Self {
|
||||
req: UntrustedBox::<SendReq>::new_uninit(),
|
||||
send_buf: UntrustedBox::new_uninit_slice(buf_len),
|
||||
control: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn copy_buf(&mut self, bufs: &[&[u8]]) -> Result<usize> {
|
||||
let total_len = self.send_buf.len();
|
||||
if total_len > super::MAX_BUF_SIZE {
|
||||
return_errno!(EMSGSIZE, "the message is too large")
|
||||
}
|
||||
// Copy data from the bufs to the send buffer
|
||||
let mut total_copied = 0;
|
||||
for buf in bufs {
|
||||
self.send_buf[total_copied..(total_copied + buf.len())].copy_from_slice(buf);
|
||||
total_copied += buf.len();
|
||||
}
|
||||
Ok(total_copied)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn copy_control(&mut self, control: Option<&[u8]>) -> Result<usize> {
|
||||
if let Some(msg_control) = control {
|
||||
let send_controllen = msg_control.len();
|
||||
if send_controllen > super::OPTMEM_MAX {
|
||||
return_errno!(EINVAL, "invalid msg control length");
|
||||
}
|
||||
let mut send_control_buf = UntrustedBox::new_uninit_slice(send_controllen);
|
||||
send_control_buf.copy_from_slice(&msg_control[..send_controllen]);
|
||||
|
||||
self.control = Some(send_control_buf);
|
||||
return Ok(send_controllen);
|
||||
};
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
self.send_buf.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum ShutdownStatus {
|
||||
Running, // not shutdown
|
||||
PreShutdown, // start the shutdown process, set by calling shutdown syscall
|
||||
PostShutdown, // shutdown process is done, set when the buffer is empty
|
||||
}
|
79
src/libos/src/net/socket/uring/file_impl.rs
Normal file
79
src/libos/src/net/socket/uring/file_impl.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use super::socket_file::SocketFile;
|
||||
use crate::fs::{
|
||||
AccessMode, FileDesc, HostFd, IoEvents, IoNotifier, IoctlCmd, IoctlRawCmd, StatusFlags,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use std::{io::SeekFrom, os::unix::raw::off_t};
|
||||
|
||||
impl File for SocketFile {
|
||||
fn read(&self, buf: &mut [u8]) -> Result<usize> {
|
||||
self.read(buf)
|
||||
}
|
||||
|
||||
fn readv(&self, bufs: &mut [&mut [u8]]) -> Result<usize> {
|
||||
self.readv(bufs)
|
||||
}
|
||||
|
||||
fn write(&self, buf: &[u8]) -> Result<usize> {
|
||||
self.write(buf)
|
||||
}
|
||||
|
||||
fn writev(&self, bufs: &[&[u8]]) -> Result<usize> {
|
||||
self.writev(bufs)
|
||||
}
|
||||
|
||||
fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize> {
|
||||
if offset != 0 {
|
||||
return_errno!(ESPIPE, "a nonzero position is not supported");
|
||||
}
|
||||
self.read(buf)
|
||||
}
|
||||
|
||||
fn write_at(&self, offset: usize, buf: &[u8]) -> Result<usize> {
|
||||
if offset != 0 {
|
||||
return_errno!(ESPIPE, "a nonzero position is not supported");
|
||||
}
|
||||
self.write(buf)
|
||||
}
|
||||
|
||||
fn seek(&self, pos: SeekFrom) -> Result<off_t> {
|
||||
return_errno!(ESPIPE, "Socket does not support seek")
|
||||
}
|
||||
|
||||
fn ioctl(&self, cmd: &mut dyn IoctlCmd) -> Result<()> {
|
||||
self.ioctl(cmd)
|
||||
}
|
||||
|
||||
fn notifier(&self) -> Option<&IoNotifier> {
|
||||
Some(self.notifier())
|
||||
}
|
||||
|
||||
fn access_mode(&self) -> Result<AccessMode> {
|
||||
Ok(AccessMode::O_RDWR)
|
||||
}
|
||||
|
||||
fn status_flags(&self) -> Result<StatusFlags> {
|
||||
Ok(self.status_flags())
|
||||
}
|
||||
|
||||
fn set_status_flags(&self, new_status_flags: StatusFlags) -> Result<()> {
|
||||
self.set_status_flags(new_status_flags)
|
||||
}
|
||||
|
||||
fn poll_new(&self) -> IoEvents {
|
||||
let mask = IoEvents::all();
|
||||
self.poll(mask, None)
|
||||
}
|
||||
|
||||
fn host_fd(&self) -> Option<&HostFd> {
|
||||
None
|
||||
}
|
||||
|
||||
fn update_host_events(&self, ready: &IoEvents, mask: &IoEvents, trigger_notifier: bool) {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
12
src/libos/src/net/socket/uring/mod.rs
Normal file
12
src/libos/src/net/socket/uring/mod.rs
Normal file
@ -0,0 +1,12 @@
|
||||
#![feature(stmt_expr_attributes)]
|
||||
#![feature(new_uninit)]
|
||||
#![feature(raw_ref_op)]
|
||||
|
||||
pub mod common;
|
||||
pub mod datagram;
|
||||
pub mod file_impl;
|
||||
pub mod runtime;
|
||||
pub mod socket_file;
|
||||
pub mod stream;
|
||||
|
||||
pub use self::socket_file::UringSocketType;
|
12
src/libos/src/net/socket/uring/runtime.rs
Normal file
12
src/libos/src/net/socket/uring/runtime.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use alloc::sync::Arc;
|
||||
use io_uring_callback::IoUring;
|
||||
|
||||
/// The runtime support for HostSocket.
|
||||
///
|
||||
/// This trait provides a common interface for user-implemented runtimes
|
||||
/// that support HostSocket. Currently, the only dependency is a singleton
|
||||
/// of IoUring instance.
|
||||
pub trait Runtime: Send + Sync + 'static {
|
||||
fn io_uring() -> Arc<IoUring>;
|
||||
fn disattach_io_uring(fd: usize, uring: Arc<IoUring>);
|
||||
}
|
446
src/libos/src/net/socket/uring/socket_file.rs
Normal file
446
src/libos/src/net/socket/uring/socket_file.rs
Normal file
@ -0,0 +1,446 @@
|
||||
use self::impls::{Ipv4Datagram, Ipv6Datagram};
|
||||
use crate::events::{Observer, Poller};
|
||||
use crate::net::socket::{MsgFlags, SocketProtocol};
|
||||
|
||||
use self::impls::{Ipv4Stream, Ipv6Stream};
|
||||
use crate::fs::{AccessMode, IoEvents, IoNotifier, IoctlCmd, StatusFlags};
|
||||
use crate::net::socket::{AnyAddr, Ipv4SocketAddr, Ipv6SocketAddr};
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SocketFile {
|
||||
socket: AnySocket,
|
||||
}
|
||||
|
||||
// Apply a function to all variants of AnySocket enum.
|
||||
macro_rules! apply_fn_on_any_socket {
|
||||
($any_socket:expr, |$socket:ident| { $($fn_body:tt)* }) => {{
|
||||
let any_socket: &AnySocket = $any_socket;
|
||||
match any_socket {
|
||||
AnySocket::Ipv4Stream($socket) => {
|
||||
$($fn_body)*
|
||||
}
|
||||
AnySocket::Ipv6Stream($socket) => {
|
||||
$($fn_body)*
|
||||
}
|
||||
AnySocket::Ipv4Datagram($socket) => {
|
||||
$($fn_body)*
|
||||
}
|
||||
AnySocket::Ipv6Datagram($socket) => {
|
||||
$($fn_body)*
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
pub trait UringSocketType {
|
||||
fn as_uring_socket(&self) -> Result<&SocketFile>;
|
||||
}
|
||||
|
||||
impl UringSocketType for FileRef {
|
||||
fn as_uring_socket(&self) -> Result<&SocketFile> {
|
||||
self.as_any()
|
||||
.downcast_ref::<SocketFile>()
|
||||
.ok_or_else(|| errno!(ENOTSOCK, "not a uring socket"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum AnySocket {
|
||||
Ipv4Stream(Ipv4Stream),
|
||||
Ipv6Stream(Ipv6Stream),
|
||||
Ipv4Datagram(Ipv4Datagram),
|
||||
Ipv6Datagram(Ipv6Datagram),
|
||||
}
|
||||
|
||||
// Implement the common methods required by FileHandle
|
||||
impl SocketFile {
|
||||
pub fn read(&self, buf: &mut [u8]) -> Result<usize> {
|
||||
apply_fn_on_any_socket!(&self.socket, |socket| { socket.read(buf) })
|
||||
}
|
||||
|
||||
pub fn readv(&self, bufs: &mut [&mut [u8]]) -> Result<usize> {
|
||||
apply_fn_on_any_socket!(&self.socket, |socket| { socket.readv(bufs) })
|
||||
}
|
||||
|
||||
pub fn write(&self, buf: &[u8]) -> Result<usize> {
|
||||
apply_fn_on_any_socket!(&self.socket, |socket| { socket.write(buf) })
|
||||
}
|
||||
|
||||
pub fn writev(&self, bufs: &[&[u8]]) -> Result<usize> {
|
||||
apply_fn_on_any_socket!(&self.socket, |socket| { socket.writev(bufs) })
|
||||
}
|
||||
|
||||
pub fn access_mode(&self) -> AccessMode {
|
||||
// We consider all sockets both readable and writable
|
||||
AccessMode::O_RDWR
|
||||
}
|
||||
|
||||
pub fn status_flags(&self) -> StatusFlags {
|
||||
apply_fn_on_any_socket!(&self.socket, |socket| { socket.status_flags() })
|
||||
}
|
||||
|
||||
pub fn host_fd_inner(&self) -> FileDesc {
|
||||
apply_fn_on_any_socket!(&self.socket, |socket| { socket.host_fd() })
|
||||
}
|
||||
|
||||
pub fn set_status_flags(&self, new_flags: StatusFlags) -> Result<()> {
|
||||
apply_fn_on_any_socket!(&self.socket, |socket| {
|
||||
socket.set_status_flags(new_flags)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn notifier(&self) -> &IoNotifier {
|
||||
apply_fn_on_any_socket!(&self.socket, |socket| { socket.notifier() })
|
||||
}
|
||||
|
||||
pub fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents {
|
||||
apply_fn_on_any_socket!(&self.socket, |socket| { socket.poll(mask, poller) })
|
||||
}
|
||||
|
||||
pub fn ioctl(&self, cmd: &mut dyn IoctlCmd) -> Result<()> {
|
||||
apply_fn_on_any_socket!(&self.socket, |socket| { socket.ioctl(cmd) })
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> Type {
|
||||
match self.socket {
|
||||
AnySocket::Ipv4Stream(_) | AnySocket::Ipv6Stream(_) => Type::STREAM,
|
||||
AnySocket::Ipv4Datagram(_) | AnySocket::Ipv6Datagram(_) => Type::DGRAM,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implement socket-specific methods
|
||||
impl SocketFile {
|
||||
pub fn new(
|
||||
domain: Domain,
|
||||
protocol: SocketProtocol,
|
||||
socket_type: Type,
|
||||
nonblocking: bool,
|
||||
) -> Result<Self> {
|
||||
match socket_type {
|
||||
Type::STREAM => {
|
||||
if protocol != SocketProtocol::IPPROTO_IP && protocol != SocketProtocol::IPPROTO_TCP
|
||||
{
|
||||
return_errno!(EPROTONOSUPPORT, "Protocol not supported");
|
||||
}
|
||||
let any_socket = match domain {
|
||||
Domain::INET => {
|
||||
let ipv4_stream = Ipv4Stream::new(nonblocking)?;
|
||||
AnySocket::Ipv4Stream(ipv4_stream)
|
||||
}
|
||||
Domain::INET6 => {
|
||||
let ipv6_stream = Ipv6Stream::new(nonblocking)?;
|
||||
AnySocket::Ipv6Stream(ipv6_stream)
|
||||
}
|
||||
_ => {
|
||||
panic!()
|
||||
}
|
||||
};
|
||||
let new_self = Self { socket: any_socket };
|
||||
Ok(new_self)
|
||||
}
|
||||
Type::DGRAM => {
|
||||
if protocol != SocketProtocol::IPPROTO_IP && protocol != SocketProtocol::IPPROTO_UDP
|
||||
{
|
||||
return_errno!(EPROTONOSUPPORT, "Protocol not supported");
|
||||
}
|
||||
let any_socket = match domain {
|
||||
Domain::INET => {
|
||||
let ipv4_datagram = Ipv4Datagram::new(nonblocking)?;
|
||||
AnySocket::Ipv4Datagram(ipv4_datagram)
|
||||
}
|
||||
Domain::INET6 => {
|
||||
let ipv6_datagram = Ipv6Datagram::new(nonblocking)?;
|
||||
AnySocket::Ipv6Datagram(ipv6_datagram)
|
||||
}
|
||||
_ => {
|
||||
return_errno!(EINVAL, "not support yet");
|
||||
}
|
||||
};
|
||||
let new_self = Self { socket: any_socket };
|
||||
Ok(new_self)
|
||||
}
|
||||
Type::RAW => {
|
||||
return_errno!(EINVAL, "RAW socket not supported");
|
||||
}
|
||||
_ => {
|
||||
return_errno!(ESOCKTNOSUPPORT, "socket type not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn domain(&self) -> Domain {
|
||||
apply_fn_on_any_socket!(&self.socket, |socket| { socket.domain() })
|
||||
}
|
||||
|
||||
pub fn is_stream(&self) -> bool {
|
||||
matches!(&self.socket, AnySocket::Ipv4Stream(_))
|
||||
}
|
||||
|
||||
pub fn connect(&self, addr: &AnyAddr) -> Result<()> {
|
||||
match &self.socket {
|
||||
AnySocket::Ipv4Stream(ipv4_stream) => {
|
||||
let ip_addr = addr.to_ipv4()?;
|
||||
ipv4_stream.connect(ip_addr)
|
||||
}
|
||||
AnySocket::Ipv6Stream(ipv6_stream) => {
|
||||
let ip_addr = addr.to_ipv6()?;
|
||||
ipv6_stream.connect(ip_addr)
|
||||
}
|
||||
AnySocket::Ipv4Datagram(ipv4_datagram) => {
|
||||
let mut ip_addr = None;
|
||||
if !addr.is_unspec() {
|
||||
let ipv4_addr = addr.to_ipv4()?;
|
||||
ip_addr = Some(ipv4_addr);
|
||||
}
|
||||
ipv4_datagram.connect(ip_addr)
|
||||
}
|
||||
AnySocket::Ipv6Datagram(ipv6_datagram) => {
|
||||
let mut ip_addr = None;
|
||||
if !addr.is_unspec() {
|
||||
let ipv6_addr = addr.to_ipv6()?;
|
||||
ip_addr = Some(ipv6_addr);
|
||||
}
|
||||
ipv6_datagram.connect(ip_addr)
|
||||
}
|
||||
_ => {
|
||||
return_errno!(EINVAL, "connect is not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind(&self, addr: &mut AnyAddr) -> Result<()> {
|
||||
match &self.socket {
|
||||
AnySocket::Ipv4Stream(ipv4_stream) => {
|
||||
let ip_addr = addr.to_ipv4()?;
|
||||
ipv4_stream.bind(ip_addr)
|
||||
}
|
||||
AnySocket::Ipv6Stream(ipv6_stream) => {
|
||||
let ip_addr = addr.to_ipv6()?;
|
||||
ipv6_stream.bind(ip_addr)
|
||||
}
|
||||
AnySocket::Ipv4Datagram(ipv4_datagram) => {
|
||||
let ip_addr = addr.to_ipv4()?;
|
||||
ipv4_datagram.bind(ip_addr)
|
||||
}
|
||||
|
||||
AnySocket::Ipv6Datagram(ipv6_datagram) => {
|
||||
let ip_addr = addr.to_ipv6()?;
|
||||
ipv6_datagram.bind(ip_addr)
|
||||
}
|
||||
|
||||
_ => {
|
||||
return_errno!(EINVAL, "bind is not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listen(&self, backlog: u32) -> Result<()> {
|
||||
match &self.socket {
|
||||
AnySocket::Ipv4Stream(ip_stream) => ip_stream.listen(backlog),
|
||||
AnySocket::Ipv6Stream(ip_stream) => ip_stream.listen(backlog),
|
||||
_ => {
|
||||
return_errno!(EOPNOTSUPP, "The socket is not of a listen supported type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accept(&self, nonblocking: bool) -> Result<Self> {
|
||||
let accepted_any_socket = match &self.socket {
|
||||
AnySocket::Ipv4Stream(ipv4_stream) => {
|
||||
let accepted_ipv4_stream = ipv4_stream.accept(nonblocking)?;
|
||||
AnySocket::Ipv4Stream(accepted_ipv4_stream)
|
||||
}
|
||||
AnySocket::Ipv6Stream(ipv6_stream) => {
|
||||
let accepted_ipv6_stream = ipv6_stream.accept(nonblocking)?;
|
||||
AnySocket::Ipv6Stream(accepted_ipv6_stream)
|
||||
}
|
||||
_ => {
|
||||
return_errno!(EOPNOTSUPP, "The socket is not of a accept supported type");
|
||||
}
|
||||
};
|
||||
let accepted_socket_file = SocketFile {
|
||||
socket: accepted_any_socket,
|
||||
};
|
||||
Ok(accepted_socket_file)
|
||||
}
|
||||
|
||||
pub fn recvfrom(&self, buf: &mut [u8], flags: RecvFlags) -> Result<(usize, Option<AnyAddr>)> {
|
||||
let (bytes_recv, addr_recv, _, _) = self.recvmsg(&mut [buf], flags, None)?;
|
||||
Ok((bytes_recv, addr_recv))
|
||||
}
|
||||
|
||||
pub fn recvmsg(
|
||||
&self,
|
||||
bufs: &mut [&mut [u8]],
|
||||
flags: RecvFlags,
|
||||
control: Option<&mut [u8]>,
|
||||
) -> Result<(usize, Option<AnyAddr>, MsgFlags, usize)> {
|
||||
// TODO: support msg_flags and msg_control
|
||||
Ok(match &self.socket {
|
||||
AnySocket::Ipv4Stream(ipv4_stream) => {
|
||||
let (bytes_recv, addr_recv, msg_flags) = ipv4_stream.recvmsg(bufs, flags)?;
|
||||
(
|
||||
bytes_recv,
|
||||
addr_recv.map(|addr| AnyAddr::Ipv4(addr)),
|
||||
msg_flags,
|
||||
0,
|
||||
)
|
||||
}
|
||||
AnySocket::Ipv6Stream(ipv6_stream) => {
|
||||
let (bytes_recv, addr_recv, msg_flags) = ipv6_stream.recvmsg(bufs, flags)?;
|
||||
(
|
||||
bytes_recv,
|
||||
addr_recv.map(|addr| AnyAddr::Ipv6(addr)),
|
||||
msg_flags,
|
||||
0,
|
||||
)
|
||||
}
|
||||
AnySocket::Ipv4Datagram(ipv4_datagram) => {
|
||||
let (bytes_recv, addr_recv, msg_flags, msg_controllen) =
|
||||
ipv4_datagram.recvmsg(bufs, flags, control)?;
|
||||
(
|
||||
bytes_recv,
|
||||
addr_recv.map(|addr| AnyAddr::Ipv4(addr)),
|
||||
msg_flags,
|
||||
msg_controllen,
|
||||
)
|
||||
}
|
||||
AnySocket::Ipv6Datagram(ipv6_datagram) => {
|
||||
let (bytes_recv, addr_recv, msg_flags, msg_controllen) =
|
||||
ipv6_datagram.recvmsg(bufs, flags, control)?;
|
||||
(
|
||||
bytes_recv,
|
||||
addr_recv.map(|addr| AnyAddr::Ipv6(addr)),
|
||||
msg_flags,
|
||||
msg_controllen,
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
return_errno!(EINVAL, "recvfrom is not supported");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn sendto(&self, buf: &[u8], addr: Option<AnyAddr>, flags: SendFlags) -> Result<usize> {
|
||||
self.sendmsg(&[buf], addr, flags, None)
|
||||
}
|
||||
|
||||
pub fn sendmsg(
|
||||
&self,
|
||||
bufs: &[&[u8]],
|
||||
addr: Option<AnyAddr>,
|
||||
flags: SendFlags,
|
||||
control: Option<&[u8]>,
|
||||
) -> Result<usize> {
|
||||
let res = match &self.socket {
|
||||
AnySocket::Ipv4Stream(ipv4_stream) => ipv4_stream.sendmsg(bufs, flags),
|
||||
AnySocket::Ipv6Stream(ipv6_stream) => ipv6_stream.sendmsg(bufs, flags),
|
||||
AnySocket::Ipv4Datagram(ipv4_datagram) => {
|
||||
let ip_addr = if let Some(addr) = addr.as_ref() {
|
||||
Some(addr.to_ipv4()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
ipv4_datagram.sendmsg(bufs, ip_addr, flags, control)
|
||||
}
|
||||
AnySocket::Ipv6Datagram(ipv6_datagram) => {
|
||||
let ip_addr = if let Some(addr) = addr.as_ref() {
|
||||
Some(addr.to_ipv6()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
ipv6_datagram.sendmsg(bufs, ip_addr, flags, control)
|
||||
}
|
||||
_ => {
|
||||
return_errno!(EINVAL, "sendmsg is not supported");
|
||||
}
|
||||
};
|
||||
if res.has_errno(EPIPE) && !flags.contains(SendFlags::MSG_NOSIGNAL) {
|
||||
crate::signal::do_tkill(current!().tid(), crate::signal::SIGPIPE.as_u8() as i32);
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn addr(&self) -> Result<AnyAddr> {
|
||||
Ok(match &self.socket {
|
||||
AnySocket::Ipv4Stream(ipv4_stream) => AnyAddr::Ipv4(ipv4_stream.addr()?),
|
||||
AnySocket::Ipv6Stream(ipv6_stream) => AnyAddr::Ipv6(ipv6_stream.addr()?),
|
||||
AnySocket::Ipv4Datagram(ipv4_datagram) => AnyAddr::Ipv4(ipv4_datagram.addr()?),
|
||||
AnySocket::Ipv6Datagram(ipv6_datagram) => AnyAddr::Ipv6(ipv6_datagram.addr()?),
|
||||
_ => {
|
||||
return_errno!(EINVAL, "addr is not supported");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn peer_addr(&self) -> Result<AnyAddr> {
|
||||
Ok(match &self.socket {
|
||||
AnySocket::Ipv4Stream(ipv4_stream) => AnyAddr::Ipv4(ipv4_stream.peer_addr()?),
|
||||
AnySocket::Ipv6Stream(ipv6_stream) => AnyAddr::Ipv6(ipv6_stream.peer_addr()?),
|
||||
AnySocket::Ipv4Datagram(ipv4_datagram) => AnyAddr::Ipv4(ipv4_datagram.peer_addr()?),
|
||||
AnySocket::Ipv6Datagram(ipv6_datagram) => AnyAddr::Ipv6(ipv6_datagram.peer_addr()?),
|
||||
_ => {
|
||||
return_errno!(EINVAL, "peer_addr is not supported");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn shutdown(&self, how: Shutdown) -> Result<()> {
|
||||
match &self.socket {
|
||||
AnySocket::Ipv4Stream(ipv4_stream) => ipv4_stream.shutdown(how),
|
||||
AnySocket::Ipv6Stream(ipv6_stream) => ipv6_stream.shutdown(how),
|
||||
AnySocket::Ipv4Datagram(ipv4_datagram) => ipv4_datagram.shutdown(how),
|
||||
AnySocket::Ipv6Datagram(ipv6_datagram) => ipv6_datagram.shutdown(how),
|
||||
_ => {
|
||||
return_errno!(EINVAL, "shutdown is not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(&self) -> Result<()> {
|
||||
match &self.socket {
|
||||
AnySocket::Ipv4Stream(ipv4_stream) => ipv4_stream.close(),
|
||||
AnySocket::Ipv6Stream(ipv6_stream) => ipv6_stream.close(),
|
||||
AnySocket::Ipv4Datagram(ipv4_datagram) => ipv4_datagram.close(),
|
||||
AnySocket::Ipv6Datagram(ipv6_datagram) => ipv6_datagram.close(),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SocketFile {
|
||||
fn drop(&mut self) {
|
||||
self.close();
|
||||
}
|
||||
}
|
||||
|
||||
mod impls {
|
||||
use super::*;
|
||||
use io_uring_callback::IoUring;
|
||||
|
||||
pub type Ipv4Stream =
|
||||
crate::net::socket::uring::stream::StreamSocket<Ipv4SocketAddr, SocketRuntime>;
|
||||
pub type Ipv6Stream =
|
||||
crate::net::socket::uring::stream::StreamSocket<Ipv6SocketAddr, SocketRuntime>;
|
||||
|
||||
pub type Ipv4Datagram =
|
||||
crate::net::socket::uring::datagram::DatagramSocket<Ipv4SocketAddr, SocketRuntime>;
|
||||
pub type Ipv6Datagram =
|
||||
crate::net::socket::uring::datagram::DatagramSocket<Ipv6SocketAddr, SocketRuntime>;
|
||||
|
||||
pub struct SocketRuntime;
|
||||
impl crate::net::socket::uring::runtime::Runtime for SocketRuntime {
|
||||
// Assign an IO-Uring instance for newly created socket
|
||||
fn io_uring() -> Arc<IoUring> {
|
||||
crate::io_uring::MULTITON.get_uring()
|
||||
}
|
||||
|
||||
// Disattach IO-Uring instance with closed socket
|
||||
fn disattach_io_uring(fd: usize, uring: Arc<IoUring>) {
|
||||
crate::io_uring::MULTITON.disattach_uring(fd, uring);
|
||||
}
|
||||
}
|
||||
}
|
610
src/libos/src/net/socket/uring/stream/mod.rs
Normal file
610
src/libos/src/net/socket/uring/stream/mod.rs
Normal file
@ -0,0 +1,610 @@
|
||||
mod states;
|
||||
|
||||
use core::hint;
|
||||
use core::sync::atomic::AtomicUsize;
|
||||
use core::time::Duration;
|
||||
|
||||
use atomic::Ordering;
|
||||
|
||||
use self::states::{ConnectedStream, ConnectingStream, InitStream, ListenerStream};
|
||||
use crate::events::Observer;
|
||||
use crate::fs::{
|
||||
GetIfConf, GetIfReqWithRawCmd, GetReadBufLen, IoEvents, IoNotifier, IoctlCmd, SetNonBlocking,
|
||||
StatusFlags,
|
||||
};
|
||||
|
||||
use crate::net::socket::uring::common::Common;
|
||||
use crate::net::socket::uring::runtime::Runtime;
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::events::Poller;
|
||||
use crate::net::socket::{sockopt::*, MsgFlags};
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SEND_BUF_SIZE: AtomicUsize = AtomicUsize::new(2565 * 1024 + 1); // Default Linux send buffer size is 2.5MB.
|
||||
pub static ref RECV_BUF_SIZE: AtomicUsize = AtomicUsize::new(256 * 1024 + 1);
|
||||
}
|
||||
|
||||
pub struct StreamSocket<A: Addr + 'static, R: Runtime> {
|
||||
state: RwLock<State<A, R>>,
|
||||
common: Arc<Common<A, R>>,
|
||||
}
|
||||
|
||||
enum State<A: Addr + 'static, R: Runtime> {
|
||||
// Start state
|
||||
Init(Arc<InitStream<A, R>>),
|
||||
// Intermediate state
|
||||
Connect(Arc<ConnectingStream<A, R>>),
|
||||
// Final state 1
|
||||
Connected(Arc<ConnectedStream<A, R>>),
|
||||
// Final state 2
|
||||
Listen(Arc<ListenerStream<A, R>>),
|
||||
}
|
||||
|
||||
impl<A: Addr, R: Runtime> StreamSocket<A, R> {
|
||||
pub fn new(nonblocking: bool) -> Result<Self> {
|
||||
let init_stream = InitStream::new(nonblocking)?;
|
||||
let common = init_stream.common().clone();
|
||||
|
||||
let fd = common.host_fd();
|
||||
debug!("host fd: {}", fd);
|
||||
|
||||
let init_state = State::Init(init_stream);
|
||||
Ok(Self {
|
||||
state: RwLock::new(init_state),
|
||||
common,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_pair(nonblocking: bool) -> Result<(Self, Self)> {
|
||||
let (common1, common2) = Common::new_pair(Type::STREAM, nonblocking)?;
|
||||
let connected1 = ConnectedStream::new(Arc::new(common1));
|
||||
let connected2 = ConnectedStream::new(Arc::new(common2));
|
||||
let socket1 = Self::new_connected(connected1);
|
||||
let socket2 = Self::new_connected(connected2);
|
||||
Ok((socket1, socket2))
|
||||
}
|
||||
|
||||
fn new_connected(connected_stream: Arc<ConnectedStream<A, R>>) -> Self {
|
||||
let common = connected_stream.common().clone();
|
||||
let state = RwLock::new(State::Connected(connected_stream));
|
||||
Self { state, common }
|
||||
}
|
||||
|
||||
fn try_switch_to_connected_state(
|
||||
connecting_stream: &Arc<ConnectingStream<A, R>>,
|
||||
) -> Option<Arc<ConnectedStream<A, R>>> {
|
||||
// Previously, I thought connecting state only exists for non-blocking socket. However, some applications can set non-blocking for
|
||||
// connect syscall and after the connect returns, set the socket to blocking socket. Thus, this function shouldn't assert the connecting
|
||||
// stream is non-blocking socket.
|
||||
if connecting_stream.check_connection() {
|
||||
let common = connecting_stream.common().clone();
|
||||
common.set_peer_addr(connecting_stream.peer_addr());
|
||||
Some(ConnectedStream::new(common))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn domain(&self) -> Domain {
|
||||
A::domain()
|
||||
}
|
||||
|
||||
pub fn errno(&self) -> Option<Errno> {
|
||||
self.common.errno()
|
||||
}
|
||||
|
||||
pub fn host_fd(&self) -> FileDesc {
|
||||
let state = self.state.read().unwrap();
|
||||
state.common().host_fd()
|
||||
}
|
||||
|
||||
pub fn status_flags(&self) -> StatusFlags {
|
||||
// Only support O_NONBLOCK
|
||||
let state = self.state.read().unwrap();
|
||||
if state.common().nonblocking() {
|
||||
StatusFlags::O_NONBLOCK
|
||||
} else {
|
||||
StatusFlags::empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_status_flags(&self, new_flags: StatusFlags) -> Result<()> {
|
||||
// Only support O_NONBLOCK
|
||||
let state = self.state.read().unwrap();
|
||||
let nonblocking = new_flags.is_nonblocking();
|
||||
state.common().set_nonblocking(nonblocking);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bind(&self, addr: &A) -> Result<()> {
|
||||
let state = self.state.read().unwrap();
|
||||
match &*state {
|
||||
State::Init(init_stream) => init_stream.bind(addr),
|
||||
_ => {
|
||||
return_errno!(EINVAL, "cannot bind");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listen(&self, backlog: u32) -> Result<()> {
|
||||
let mut state = self.state.write().unwrap();
|
||||
match &*state {
|
||||
State::Init(init_stream) => {
|
||||
let common = init_stream.common().clone();
|
||||
let listener = ListenerStream::new(backlog, common)?;
|
||||
*state = State::Listen(listener);
|
||||
Ok(())
|
||||
}
|
||||
_ => {
|
||||
return_errno!(EINVAL, "cannot listen");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect(&self, peer_addr: &A) -> Result<()> {
|
||||
// Create the new intermediate state of connecting and save the
|
||||
// old state of init in case of failure to connect.
|
||||
let (init_stream, connecting_stream) = {
|
||||
let mut state = self.state.write().unwrap();
|
||||
match &*state {
|
||||
State::Init(init_stream) => {
|
||||
let connecting_stream = {
|
||||
let common = init_stream.common().clone();
|
||||
ConnectingStream::new(peer_addr, common)?
|
||||
};
|
||||
let init_stream = init_stream.clone();
|
||||
*state = State::Connect(connecting_stream.clone());
|
||||
(init_stream, connecting_stream)
|
||||
}
|
||||
State::Connect(connecting_stream) => {
|
||||
if let Some(connected_stream) =
|
||||
Self::try_switch_to_connected_state(connecting_stream)
|
||||
{
|
||||
*state = State::Connected(connected_stream);
|
||||
return_errno!(EISCONN, "the socket is already connected");
|
||||
} else {
|
||||
// Not connected, keep the connecting state and try connect
|
||||
let init_stream =
|
||||
InitStream::new_with_common(connecting_stream.common().clone())?;
|
||||
(init_stream, connecting_stream.clone())
|
||||
}
|
||||
}
|
||||
State::Connected(_) => {
|
||||
return_errno!(EISCONN, "the socket is already connected");
|
||||
}
|
||||
State::Listen(_) => {
|
||||
return_errno!(EINVAL, "the socket is listening");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let res = connecting_stream.connect();
|
||||
|
||||
// If success, then the state is switched to connected; otherwise, for blocking socket
|
||||
// the state is restored to the init state, and for non-blocking socket, the state
|
||||
// keeps in connecting state.
|
||||
match &res {
|
||||
Ok(()) => {
|
||||
let connected_stream = {
|
||||
let common = init_stream.common().clone();
|
||||
common.set_peer_addr(peer_addr);
|
||||
ConnectedStream::new(common)
|
||||
};
|
||||
|
||||
let mut state = self.state.write().unwrap();
|
||||
*state = State::Connected(connected_stream);
|
||||
}
|
||||
Err(_) => {
|
||||
if !connecting_stream.common().nonblocking() {
|
||||
let mut state = self.state.write().unwrap();
|
||||
*state = State::Init(init_stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub fn accept(&self, nonblocking: bool) -> Result<Self> {
|
||||
let listener_stream = {
|
||||
let state = self.state.read().unwrap();
|
||||
match &*state {
|
||||
State::Listen(listener_stream) => listener_stream.clone(),
|
||||
_ => {
|
||||
return_errno!(EINVAL, "the socket is not listening");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let connected_stream = listener_stream.accept(nonblocking)?;
|
||||
|
||||
let new_self = Self::new_connected(connected_stream);
|
||||
Ok(new_self)
|
||||
}
|
||||
|
||||
pub fn read(&self, buf: &mut [u8]) -> Result<usize> {
|
||||
self.readv(&mut [buf])
|
||||
}
|
||||
|
||||
pub fn readv(&self, bufs: &mut [&mut [u8]]) -> Result<usize> {
|
||||
let ret = self.recvmsg(bufs, RecvFlags::empty())?;
|
||||
Ok(ret.0)
|
||||
}
|
||||
|
||||
/// Receive messages from connected socket
|
||||
///
|
||||
/// Linux behavior:
|
||||
/// Unlike datagram socket, `recvfrom` / `recvmsg` of stream socket will
|
||||
/// ignore the address even if user specified it.
|
||||
pub fn recvmsg(
|
||||
&self,
|
||||
buf: &mut [&mut [u8]],
|
||||
flags: RecvFlags,
|
||||
) -> Result<(usize, Option<A>, MsgFlags)> {
|
||||
let connected_stream = {
|
||||
let mut state = self.state.write().unwrap();
|
||||
match &*state {
|
||||
State::Connected(connected_stream) => connected_stream.clone(),
|
||||
State::Connect(connecting_stream) => {
|
||||
if let Some(connected_stream) =
|
||||
Self::try_switch_to_connected_state(connecting_stream)
|
||||
{
|
||||
*state = State::Connected(connected_stream.clone());
|
||||
connected_stream
|
||||
} else {
|
||||
return_errno!(ENOTCONN, "the socket is not connected");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return_errno!(ENOTCONN, "the socket is not connected");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let recv_len = connected_stream.recvmsg(buf, flags)?;
|
||||
Ok((recv_len, None, MsgFlags::empty()))
|
||||
}
|
||||
|
||||
pub fn write(&self, buf: &[u8]) -> Result<usize> {
|
||||
self.writev(&[buf])
|
||||
}
|
||||
|
||||
pub fn writev(&self, bufs: &[&[u8]]) -> Result<usize> {
|
||||
self.sendmsg(bufs, SendFlags::empty())
|
||||
}
|
||||
|
||||
pub fn sendmsg(&self, bufs: &[&[u8]], flags: SendFlags) -> Result<usize> {
|
||||
let connected_stream = {
|
||||
let mut state = self.state.write().unwrap();
|
||||
match &*state {
|
||||
State::Connected(connected_stream) => connected_stream.clone(),
|
||||
State::Connect(connecting_stream) => {
|
||||
if let Some(connected_stream) =
|
||||
Self::try_switch_to_connected_state(connecting_stream)
|
||||
{
|
||||
*state = State::Connected(connected_stream.clone());
|
||||
connected_stream
|
||||
} else {
|
||||
return_errno!(ENOTCONN, "the socket is not connected");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return_errno!(EPIPE, "the socket is not connected");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
connected_stream.sendmsg(bufs, flags)
|
||||
}
|
||||
|
||||
pub fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents {
|
||||
let state = self.state.read().unwrap();
|
||||
let pollee = state.common().pollee();
|
||||
pollee.poll(mask, poller)
|
||||
}
|
||||
|
||||
pub fn addr(&self) -> Result<A> {
|
||||
let state = self.state.read().unwrap();
|
||||
let common = state.common();
|
||||
|
||||
// Always get addr from host.
|
||||
// Because for IP socket, users can specify "0" as port and the kernel should select a usable port for him.
|
||||
// Thus, when calling getsockname, this should be updated.
|
||||
let addr = common.get_addr_from_host()?;
|
||||
common.set_addr(&addr);
|
||||
Ok(addr)
|
||||
}
|
||||
|
||||
pub fn notifier(&self) -> &IoNotifier {
|
||||
{
|
||||
let mut state = self.state.write().unwrap();
|
||||
// Try switch to connected state to receive endpoint status
|
||||
if let State::Connect(connecting_stream) = &*state {
|
||||
if let Some(connected_stream) =
|
||||
Self::try_switch_to_connected_state(connecting_stream)
|
||||
{
|
||||
*state = State::Connected(connected_stream.clone());
|
||||
}
|
||||
}
|
||||
// `state` goes out of scope here and the lock is implicitly released.
|
||||
}
|
||||
|
||||
self.common.notifier()
|
||||
}
|
||||
|
||||
pub fn peer_addr(&self) -> Result<A> {
|
||||
let mut state = self.state.write().unwrap();
|
||||
match &*state {
|
||||
State::Connected(connected_stream) => {
|
||||
Ok(connected_stream.common().peer_addr().unwrap())
|
||||
}
|
||||
State::Connect(connecting_stream) => {
|
||||
if let Some(connected_stream) =
|
||||
Self::try_switch_to_connected_state(connecting_stream)
|
||||
{
|
||||
*state = State::Connected(connected_stream.clone());
|
||||
Ok(connected_stream.common().peer_addr().unwrap())
|
||||
} else {
|
||||
return_errno!(ENOTCONN, "the socket is not connected");
|
||||
}
|
||||
}
|
||||
_ => return_errno!(ENOTCONN, "the socket is not connected"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ioctl(&self, cmd: &mut dyn IoctlCmd) -> Result<()> {
|
||||
let mut state = self.state.write().unwrap();
|
||||
match &*state {
|
||||
State::Connect(connecting_stream) => {
|
||||
if let Some(connected_stream) =
|
||||
Self::try_switch_to_connected_state(connecting_stream)
|
||||
{
|
||||
*state = State::Connected(connected_stream.clone());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
drop(state);
|
||||
crate::match_ioctl_cmd_mut!(&mut *cmd, {
|
||||
cmd: GetSockOptRawCmd => {
|
||||
cmd.execute(self.host_fd())?;
|
||||
},
|
||||
cmd: SetSockOptRawCmd => {
|
||||
cmd.execute(self.host_fd())?;
|
||||
},
|
||||
cmd: SetRecvTimeoutCmd => {
|
||||
self.set_recv_timeout(*cmd.timeout());
|
||||
},
|
||||
cmd: SetSendTimeoutCmd => {
|
||||
self.set_send_timeout(*cmd.timeout());
|
||||
},
|
||||
cmd: GetRecvTimeoutCmd => {
|
||||
let timeval = timeout_to_timeval(self.recv_timeout());
|
||||
cmd.set_output(timeval);
|
||||
},
|
||||
cmd: GetSendTimeoutCmd => {
|
||||
let timeval = timeout_to_timeval(self.send_timeout());
|
||||
cmd.set_output(timeval);
|
||||
},
|
||||
cmd: SetSndBufSizeCmd => {
|
||||
cmd.update_host(self.host_fd())?;
|
||||
let buf_size = cmd.buf_size();
|
||||
self.set_kernel_send_buf_size(buf_size);
|
||||
},
|
||||
cmd: SetRcvBufSizeCmd => {
|
||||
cmd.update_host(self.host_fd())?;
|
||||
let buf_size = cmd.buf_size();
|
||||
self.set_kernel_recv_buf_size(buf_size);
|
||||
},
|
||||
cmd: GetSndBufSizeCmd => {
|
||||
let buf_size = SEND_BUF_SIZE.load(Ordering::Relaxed);
|
||||
cmd.set_output(buf_size);
|
||||
},
|
||||
cmd: GetRcvBufSizeCmd => {
|
||||
let buf_size = RECV_BUF_SIZE.load(Ordering::Relaxed);
|
||||
cmd.set_output(buf_size);
|
||||
},
|
||||
cmd: GetAcceptConnCmd => {
|
||||
let mut is_listen = false;
|
||||
let state = self.state.read().unwrap();
|
||||
if let State::Listen(_listener_stream) = &*state {
|
||||
is_listen = true;
|
||||
}
|
||||
cmd.set_output(is_listen as _);
|
||||
},
|
||||
cmd: GetDomainCmd => {
|
||||
cmd.set_output(self.domain() as _);
|
||||
},
|
||||
cmd: GetPeerNameCmd => {
|
||||
let peer = self.peer_addr()?;
|
||||
cmd.set_output(AddrStorage(peer.to_c_storage()));
|
||||
},
|
||||
cmd: GetErrorCmd => {
|
||||
let error: i32 = self.errno().map(|err| err as i32).unwrap_or(0);
|
||||
cmd.set_output(error);
|
||||
},
|
||||
cmd: GetTypeCmd => {
|
||||
let state = self.state.read().unwrap();
|
||||
cmd.set_output(state.common().type_() as _);
|
||||
},
|
||||
cmd: SetNonBlocking => {
|
||||
let state = self.state.read().unwrap();
|
||||
state.common().set_nonblocking(*cmd.input() != 0);
|
||||
},
|
||||
cmd: GetReadBufLen => {
|
||||
let state = self.state.read().unwrap();
|
||||
if let State::Connected(connected_stream) = &*state {
|
||||
let read_buf_len = connected_stream.bytes_to_consume();
|
||||
cmd.set_output(read_buf_len as _);
|
||||
} else {
|
||||
return_errno!(ENOTCONN, "unconnected socket");
|
||||
}
|
||||
},
|
||||
cmd: GetIfReqWithRawCmd => {
|
||||
cmd.execute(self.host_fd())?;
|
||||
},
|
||||
cmd: GetIfConf => {
|
||||
cmd.execute(self.host_fd())?;
|
||||
},
|
||||
_ => {
|
||||
return_errno!(EINVAL, "Not supported yet");
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_kernel_send_buf_size(&self, buf_size: usize) {
|
||||
let state = self.state.read().unwrap();
|
||||
match &*state {
|
||||
State::Init(_) | State::Listen(_) | State::Connect(_) => {
|
||||
// The kernel buffer is only created when the socket is connected. Just update the static variable.
|
||||
SEND_BUF_SIZE.store(buf_size, Ordering::Relaxed);
|
||||
}
|
||||
State::Connected(connected_stream) => {
|
||||
connected_stream.try_update_send_buf_size(buf_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_kernel_recv_buf_size(&self, buf_size: usize) {
|
||||
let state = self.state.read().unwrap();
|
||||
match &*state {
|
||||
State::Init(_) | State::Listen(_) | State::Connect(_) => {
|
||||
// The kernel buffer is only created when the socket is connected. Just update the static variable.
|
||||
RECV_BUF_SIZE.store(buf_size, Ordering::Relaxed);
|
||||
}
|
||||
State::Connected(connected_stream) => {
|
||||
connected_stream.try_update_recv_buf_size(buf_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shutdown(&self, shutdown: Shutdown) -> Result<()> {
|
||||
let mut state = self.state.write().unwrap();
|
||||
match &*state {
|
||||
State::Listen(listener_stream) => {
|
||||
// listening socket can be shutdown and then re-use by calling listen again.
|
||||
listener_stream.shutdown(shutdown)?;
|
||||
if shutdown.should_shut_read() {
|
||||
// Cancel pending accept requests. This is necessary because the socket is reusable.
|
||||
listener_stream.cancel_accept_requests();
|
||||
// Set init state
|
||||
let init_stream =
|
||||
InitStream::new_with_common(listener_stream.common().clone())?;
|
||||
let init_state = State::Init(init_stream);
|
||||
*state = init_state;
|
||||
Ok(())
|
||||
} else {
|
||||
// shutdown the writer of the listener expect to have no effect
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
State::Connected(connected_stream) => connected_stream.shutdown(shutdown),
|
||||
State::Connect(connecting_stream) => {
|
||||
if let Some(connected_stream) =
|
||||
Self::try_switch_to_connected_state(connecting_stream)
|
||||
{
|
||||
connected_stream.shutdown(shutdown)?;
|
||||
*state = State::Connected(connected_stream);
|
||||
Ok(())
|
||||
} else {
|
||||
return_errno!(ENOTCONN, "the socket is not connected");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return_errno!(ENOTCONN, "the socket is not connected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(&self) -> Result<()> {
|
||||
let state = self.state.read().unwrap();
|
||||
match &*state {
|
||||
State::Init(_) => {}
|
||||
State::Listen(listener_stream) => {
|
||||
listener_stream.common().set_closed();
|
||||
listener_stream.cancel_accept_requests();
|
||||
}
|
||||
State::Connect(connecting_stream) => {
|
||||
connecting_stream.common().set_closed();
|
||||
let need_wait = true;
|
||||
connecting_stream.cancel_connect_request(need_wait);
|
||||
}
|
||||
State::Connected(connected_stream) => {
|
||||
connected_stream.set_closed();
|
||||
connected_stream.cancel_recv_requests();
|
||||
connected_stream.try_empty_send_buf_when_close();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_timeout(&self) -> Option<Duration> {
|
||||
let state = self.state.read().unwrap();
|
||||
state.common().send_timeout()
|
||||
}
|
||||
|
||||
fn recv_timeout(&self) -> Option<Duration> {
|
||||
let state = self.state.read().unwrap();
|
||||
state.common().recv_timeout()
|
||||
}
|
||||
|
||||
fn set_send_timeout(&self, timeout: Duration) {
|
||||
let state = self.state.read().unwrap();
|
||||
state.common().set_send_timeout(timeout);
|
||||
}
|
||||
|
||||
fn set_recv_timeout(&self, timeout: Duration) {
|
||||
let state = self.state.read().unwrap();
|
||||
state.common().set_recv_timeout(timeout);
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn poll_by(&self, mask: Events, mut poller: Option<&mut Poller>) -> Events {
|
||||
let state = self.state.read();
|
||||
match *state {
|
||||
Init(init_stream) => init_stream.poll_by(mask, poller),
|
||||
Connect(connect_stream) => connect_stream.poll_by(mask, poller),
|
||||
Connected(connected_stream) = connected_stream.poll_by(mask, poller),
|
||||
Listen(listener_stream) = listener_stream.poll_by(mask, poller),
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> Drop for StreamSocket<A, R> {
|
||||
fn drop(&mut self) {
|
||||
let state = self.state.read().unwrap();
|
||||
state.common().set_closed();
|
||||
drop(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> std::fmt::Debug for State<A, R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let inner: &dyn std::fmt::Debug = match self {
|
||||
State::Init(inner) => inner as _,
|
||||
State::Connect(inner) => inner as _,
|
||||
State::Connected(inner) => inner as _,
|
||||
State::Listen(inner) => inner as _,
|
||||
};
|
||||
f.debug_tuple("State").field(inner).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> std::fmt::Debug for StreamSocket<A, R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("StreamSocket").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> State<A, R> {
|
||||
fn common(&self) -> &Common<A, R> {
|
||||
match self {
|
||||
Self::Init(stream) => stream.common(),
|
||||
Self::Connect(stream) => stream.common(),
|
||||
Self::Connected(stream) => stream.common(),
|
||||
Self::Listen(stream) => stream.common(),
|
||||
}
|
||||
}
|
||||
}
|
227
src/libos/src/net/socket/uring/stream/states/connect.rs
Normal file
227
src/libos/src/net/socket/uring/stream/states/connect.rs
Normal file
@ -0,0 +1,227 @@
|
||||
use core::time::Duration;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use io_uring_callback::{Fd, IoHandle};
|
||||
use sgx_untrusted_alloc::UntrustedBox;
|
||||
|
||||
use crate::events::Poller;
|
||||
use crate::fs::IoEvents;
|
||||
use crate::net::socket::uring::common::Common;
|
||||
use crate::net::socket::uring::runtime::Runtime;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A stream socket that is in its connecting state.
|
||||
pub struct ConnectingStream<A: Addr + 'static, R: Runtime> {
|
||||
common: Arc<Common<A, R>>,
|
||||
peer_addr: A,
|
||||
req: Mutex<ConnectReq<A>>,
|
||||
connected: AtomicBool, // Mainly use for nonblocking socket to update status asynchronously
|
||||
}
|
||||
|
||||
struct ConnectReq<A: Addr> {
|
||||
io_handle: Option<IoHandle>,
|
||||
c_addr: UntrustedBox<libc::sockaddr_storage>,
|
||||
c_addr_len: usize,
|
||||
errno: Option<Errno>,
|
||||
phantom_data: PhantomData<A>,
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> ConnectingStream<A, R> {
|
||||
pub fn new(peer_addr: &A, common: Arc<Common<A, R>>) -> Result<Arc<Self>> {
|
||||
let req = Mutex::new(ConnectReq::new(peer_addr));
|
||||
let new_self = Self {
|
||||
common,
|
||||
peer_addr: peer_addr.clone(),
|
||||
req,
|
||||
connected: AtomicBool::new(false),
|
||||
};
|
||||
Ok(Arc::new(new_self))
|
||||
}
|
||||
|
||||
/// Connect to the peer address.
|
||||
pub fn connect(self: &Arc<Self>) -> Result<()> {
|
||||
let pollee = self.common.pollee();
|
||||
pollee.reset_events();
|
||||
|
||||
self.initiate_async_connect();
|
||||
|
||||
if self.common.nonblocking() {
|
||||
return_errno!(EINPROGRESS, "non-blocking connect request in progress");
|
||||
}
|
||||
|
||||
// Wait for the async connect to complete
|
||||
let mask = IoEvents::OUT;
|
||||
let poller = Poller::new();
|
||||
pollee.connect_poller(mask, &poller);
|
||||
let mut timeout = self.common.send_timeout();
|
||||
loop {
|
||||
let events = pollee.poll(mask, None);
|
||||
if !events.is_empty() {
|
||||
break;
|
||||
}
|
||||
let ret = poller.wait_timeout(timeout.as_mut());
|
||||
if let Err(e) = ret {
|
||||
let errno = e.errno();
|
||||
warn!("connect wait errno = {:?}", errno);
|
||||
match errno {
|
||||
ETIMEDOUT => {
|
||||
// Cancel connect request if timeout. No need to wait for cancel to complete.
|
||||
self.cancel_connect_request(false);
|
||||
// This error code is same as the connect timeout error code on Linux
|
||||
return_errno!(EINPROGRESS, "timeout reached")
|
||||
}
|
||||
_ => {
|
||||
return_errno!(e.errno(), "wait error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finish the async connect
|
||||
let req = self.req.lock();
|
||||
if let Some(e) = req.errno {
|
||||
return_errno!(e, "connect failed");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn initiate_async_connect(self: &Arc<Self>) {
|
||||
let io_uring = self.common.io_uring();
|
||||
let mut req = self.req.lock();
|
||||
// Skip if there is pending request
|
||||
if req.io_handle.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let arc_self = self.clone();
|
||||
let callback = move |retval: i32| {
|
||||
// Guard against Igao attack
|
||||
assert!(retval <= 0);
|
||||
debug!("connect request complete with retval: {}", retval);
|
||||
|
||||
let mut req = arc_self.req.lock();
|
||||
// Release the handle to the async connect
|
||||
req.io_handle.take();
|
||||
|
||||
if retval == 0 {
|
||||
arc_self.connected.store(true, Ordering::Relaxed);
|
||||
arc_self.common.pollee().add_events(IoEvents::OUT);
|
||||
} else {
|
||||
// Store the errno
|
||||
let errno = Errno::from(-retval as u32);
|
||||
req.errno = Some(errno);
|
||||
drop(req);
|
||||
arc_self.common.set_errno(errno);
|
||||
arc_self.connected.store(false, Ordering::Relaxed);
|
||||
|
||||
let events = if errno == ENOTCONN || errno == ECONNRESET || errno == ECONNREFUSED {
|
||||
IoEvents::HUP | IoEvents::IN | IoEvents::ERR
|
||||
} else {
|
||||
IoEvents::ERR
|
||||
};
|
||||
arc_self.common.pollee().add_events(events);
|
||||
}
|
||||
};
|
||||
|
||||
let host_fd = self.common.host_fd() as _;
|
||||
let c_addr_ptr = req.c_addr.as_ptr();
|
||||
let c_addr_len = req.c_addr_len;
|
||||
let io_handle = unsafe {
|
||||
io_uring.connect(
|
||||
Fd(host_fd),
|
||||
c_addr_ptr as *const libc::sockaddr,
|
||||
c_addr_len as u32,
|
||||
callback,
|
||||
)
|
||||
};
|
||||
req.io_handle = Some(io_handle);
|
||||
}
|
||||
|
||||
pub fn cancel_connect_request(&self, need_wait: bool) {
|
||||
{
|
||||
let io_uring = self.common.io_uring();
|
||||
let req = self.req.lock();
|
||||
if let Some(io_handle) = &req.io_handle {
|
||||
unsafe { io_uring.cancel(io_handle) };
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the cancel to complete if needed
|
||||
if !need_wait {
|
||||
return;
|
||||
}
|
||||
|
||||
let poller = Poller::new();
|
||||
let mask = IoEvents::ERR | IoEvents::IN;
|
||||
self.common.pollee().connect_poller(mask, &poller);
|
||||
|
||||
loop {
|
||||
let pending_request_exist = {
|
||||
let req = self.req.lock();
|
||||
req.io_handle.is_some()
|
||||
};
|
||||
|
||||
if pending_request_exist {
|
||||
let mut timeout = Some(Duration::from_secs(10));
|
||||
let ret = poller.wait_timeout(timeout.as_mut());
|
||||
if let Err(e) = ret {
|
||||
warn!("wait cancel connect request error = {:?}", e.errno());
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn peer_addr(&self) -> &A {
|
||||
&self.peer_addr
|
||||
}
|
||||
|
||||
pub fn common(&self) -> &Arc<Common<A, R>> {
|
||||
&self.common
|
||||
}
|
||||
|
||||
// This can be used in connecting state to check non-blocking connect status.
|
||||
pub fn check_connection(&self) -> bool {
|
||||
// It is fine whether the load happens before or after the store operation
|
||||
self.connected.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Addr> ConnectReq<A> {
|
||||
pub fn new(peer_addr: &A) -> Self {
|
||||
let (c_addr_storage, c_addr_len) = peer_addr.to_c_storage();
|
||||
Self {
|
||||
io_handle: None,
|
||||
c_addr: UntrustedBox::new(c_addr_storage),
|
||||
c_addr_len,
|
||||
errno: None,
|
||||
phantom_data: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Addr, R: Runtime> std::fmt::Debug for ConnectingStream<A, R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ConnectingStream")
|
||||
.field("common", &self.common)
|
||||
.field("peer_addr", &self.peer_addr)
|
||||
.field("req", &*self.req.lock())
|
||||
.field("connected", &self.connected)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Addr> std::fmt::Debug for ConnectReq<A> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ConnectReq")
|
||||
.field("io_handle", &self.io_handle)
|
||||
.field("errno", &self.errno)
|
||||
.finish()
|
||||
}
|
||||
}
|
114
src/libos/src/net/socket/uring/stream/states/connected/mod.rs
Normal file
114
src/libos/src/net/socket/uring/stream/states/connected/mod.rs
Normal file
@ -0,0 +1,114 @@
|
||||
use atomic::Ordering;
|
||||
|
||||
use self::recv::Receiver;
|
||||
use self::send::Sender;
|
||||
use crate::fs::IoEvents as Events;
|
||||
use crate::net::socket::sockopt::SockOptName;
|
||||
use crate::net::socket::uring::common::Common;
|
||||
use crate::net::socket::uring::runtime::Runtime;
|
||||
use crate::prelude::*;
|
||||
|
||||
mod recv;
|
||||
mod send;
|
||||
|
||||
pub struct ConnectedStream<A: Addr + 'static, R: Runtime> {
|
||||
common: Arc<Common<A, R>>,
|
||||
sender: Sender,
|
||||
receiver: Receiver,
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> ConnectedStream<A, R> {
|
||||
pub fn new(common: Arc<Common<A, R>>) -> Arc<Self> {
|
||||
common.pollee().reset_events();
|
||||
common.pollee().add_events(Events::OUT);
|
||||
|
||||
let fd = common.host_fd();
|
||||
|
||||
let sender = Sender::new();
|
||||
let receiver = Receiver::new();
|
||||
let new_self = Arc::new(Self {
|
||||
common,
|
||||
sender,
|
||||
receiver,
|
||||
});
|
||||
|
||||
// Start async recv requests right as early as possible to support poll and
|
||||
// improve performance. If we don't start recv requests early, the poll()
|
||||
// might block forever when user just invokes poll(Event::In) without read().
|
||||
// Once we have recv requests completed, we can have Event::In in the events.
|
||||
new_self.initiate_async_recv();
|
||||
|
||||
new_self
|
||||
}
|
||||
|
||||
pub fn common(&self) -> &Arc<Common<A, R>> {
|
||||
&self.common
|
||||
}
|
||||
|
||||
pub fn shutdown(&self, how: Shutdown) -> Result<()> {
|
||||
// Do host shutdown
|
||||
// For shutdown write, don't call host_shutdown until the content in the pending buffer is sent.
|
||||
// For shutdown read, ignore the pending buffer.
|
||||
let (shut_write, send_buf_is_empty, shut_read) = (
|
||||
how.should_shut_write(),
|
||||
self.sender.is_empty(),
|
||||
how.should_shut_read(),
|
||||
);
|
||||
match (shut_write, send_buf_is_empty, shut_read) {
|
||||
// As long as send buf is empty, just shutdown.
|
||||
(_, true, _) => self.common.host_shutdown(how)?,
|
||||
// If not shutdown write, just shutdown.
|
||||
(false, _, _) => self.common.host_shutdown(how)?,
|
||||
// If shutdown both but the send buf is not empty, only shutdown read.
|
||||
(true, false, true) => self.common.host_shutdown(Shutdown::Read)?,
|
||||
// If shutdown write but the send buf is not empty, don't do shutdown.
|
||||
(true, false, false) => {}
|
||||
}
|
||||
|
||||
// Set internal state and trigger events.
|
||||
if shut_read {
|
||||
self.receiver.shutdown();
|
||||
self.common.pollee().add_events(Events::IN);
|
||||
}
|
||||
if shut_write {
|
||||
self.sender.shutdown();
|
||||
self.common.pollee().add_events(Events::OUT);
|
||||
}
|
||||
|
||||
if shut_read && shut_write {
|
||||
self.common.pollee().add_events(Events::HUP);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_closed(&self) {
|
||||
// Mark the sender and receiver to shutdown to prevent submitting new requests.
|
||||
self.receiver.shutdown();
|
||||
self.sender.shutdown();
|
||||
|
||||
self.common.set_closed();
|
||||
}
|
||||
|
||||
// Other methods are implemented in the send and receive modules
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> std::fmt::Debug for ConnectedStream<A, R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ConnectedStream")
|
||||
.field("common", &self.common)
|
||||
.field("sender", &self.sender)
|
||||
.field("receiver", &self.receiver)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn new_msghdr(iovecs_ptr: *mut libc::iovec, iovecs_len: usize) -> libc::msghdr {
|
||||
use std::mem::MaybeUninit;
|
||||
// Safety. Setting all fields to zeros is a valid state for msghdr.
|
||||
let mut msghdr: libc::msghdr = unsafe { MaybeUninit::zeroed().assume_init() };
|
||||
msghdr.msg_iov = iovecs_ptr;
|
||||
msghdr.msg_iovlen = iovecs_len as _;
|
||||
// We do want to leave all other fields as zeros
|
||||
msghdr
|
||||
}
|
458
src/libos/src/net/socket/uring/stream/states/connected/recv.rs
Normal file
458
src/libos/src/net/socket/uring/stream/states/connected/recv.rs
Normal file
@ -0,0 +1,458 @@
|
||||
use core::hint;
|
||||
use core::sync::atomic::AtomicBool;
|
||||
use core::time::Duration;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr::{self};
|
||||
|
||||
use atomic::Ordering;
|
||||
use io_uring_callback::{Fd, IoHandle};
|
||||
use sgx_untrusted_alloc::{MaybeUntrusted, UntrustedBox};
|
||||
|
||||
use super::ConnectedStream;
|
||||
use crate::net::socket::uring::runtime::Runtime;
|
||||
use crate::net::socket::uring::stream::RECV_BUF_SIZE;
|
||||
use crate::prelude::*;
|
||||
use crate::untrusted::UntrustedCircularBuf;
|
||||
use crate::util::sync::{Mutex, MutexGuard};
|
||||
|
||||
use crate::events::Poller;
|
||||
use crate::fs::IoEvents as Events;
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> ConnectedStream<A, R> {
|
||||
pub fn recvmsg(self: &Arc<Self>, bufs: &mut [&mut [u8]], flags: RecvFlags) -> Result<usize> {
|
||||
let total_len: usize = bufs.iter().map(|buf| buf.len()).sum();
|
||||
if total_len == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let mut total_received = 0;
|
||||
let mut iov_buffer_index = 0;
|
||||
let mut iov_buffer_offset = 0;
|
||||
|
||||
let mask = Events::IN;
|
||||
// Initialize the poller only when needed
|
||||
let mut poller = None;
|
||||
let mut timeout = self.common.recv_timeout();
|
||||
loop {
|
||||
// Attempt to read
|
||||
let res = self.try_recvmsg(bufs, flags, iov_buffer_index, iov_buffer_offset);
|
||||
|
||||
match res {
|
||||
Ok((received_size, index, offset)) => {
|
||||
total_received += received_size;
|
||||
|
||||
if !flags.contains(RecvFlags::MSG_WAITALL) || total_received == total_len {
|
||||
return Ok(total_received);
|
||||
} else {
|
||||
// save the index and offset for the next round
|
||||
iov_buffer_index = index;
|
||||
iov_buffer_offset = offset;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if e.errno() != EAGAIN {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if self.common.nonblocking() || flags.contains(RecvFlags::MSG_DONTWAIT) {
|
||||
return_errno!(EAGAIN, "no data are present to be received");
|
||||
}
|
||||
|
||||
// Wait for interesting events by polling
|
||||
if poller.is_none() {
|
||||
let new_poller = Poller::new();
|
||||
self.common.pollee().connect_poller(mask, &new_poller);
|
||||
poller = Some(new_poller);
|
||||
}
|
||||
let events = self.common.pollee().poll(mask, None);
|
||||
if events.is_empty() {
|
||||
let ret = poller.as_ref().unwrap().wait_timeout(timeout.as_mut());
|
||||
if let Err(e) = ret {
|
||||
warn!("recv wait errno = {:?}", e.errno());
|
||||
// For recv with MSG_WAITALL, return total received bytes if timeout or interrupt
|
||||
if flags.contains(RecvFlags::MSG_WAITALL) && total_received > 0 {
|
||||
return Ok(total_received);
|
||||
}
|
||||
match e.errno() {
|
||||
ETIMEDOUT => {
|
||||
return_errno!(EAGAIN, "timeout reached")
|
||||
}
|
||||
_ => {
|
||||
return_errno!(e.errno(), "wait error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_recvmsg(
|
||||
self: &Arc<Self>,
|
||||
bufs: &mut [&mut [u8]],
|
||||
flags: RecvFlags,
|
||||
iov_buffer_index: usize,
|
||||
iov_buffer_offset: usize,
|
||||
) -> Result<(usize, usize, usize)> {
|
||||
let mut inner = self.receiver.inner.lock();
|
||||
|
||||
if !flags.is_empty()
|
||||
&& flags.intersects(!(RecvFlags::MSG_DONTWAIT | RecvFlags::MSG_WAITALL))
|
||||
{
|
||||
warn!("Unsupported flags: {:?}", flags);
|
||||
return_errno!(EINVAL, "flags not supported");
|
||||
}
|
||||
|
||||
let res = {
|
||||
let mut total_consumed = 0;
|
||||
let mut iov_buffer_index = iov_buffer_index;
|
||||
let mut iov_buffer_offset = iov_buffer_offset;
|
||||
|
||||
// save the received data from bufs[iov_buffer_index][iov_buffer_offset..]
|
||||
for (_, buf) in bufs.iter_mut().skip(iov_buffer_index).enumerate() {
|
||||
let this_consumed = inner.recv_buf.consume(&mut buf[iov_buffer_offset..]);
|
||||
if this_consumed == 0 {
|
||||
break;
|
||||
}
|
||||
total_consumed += this_consumed;
|
||||
|
||||
// if the buffer is not full, then the try_recvmsg will be used again
|
||||
// next time, the data will be stored from the offset
|
||||
if this_consumed < buf[iov_buffer_offset..].len() {
|
||||
iov_buffer_offset += this_consumed;
|
||||
break;
|
||||
} else {
|
||||
iov_buffer_index += 1;
|
||||
iov_buffer_offset = 0;
|
||||
}
|
||||
}
|
||||
(total_consumed, iov_buffer_index, iov_buffer_offset)
|
||||
};
|
||||
|
||||
if self.receiver.need_update() {
|
||||
// Only update the recv buf when it is empty and there is no pending recv request
|
||||
if inner.recv_buf.is_empty() && inner.io_handle.is_none() {
|
||||
self.receiver.set_need_update(false);
|
||||
inner.update_buf_size(RECV_BUF_SIZE.load(Ordering::Relaxed));
|
||||
}
|
||||
}
|
||||
|
||||
if inner.end_of_file {
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
if inner.recv_buf.is_empty() {
|
||||
// Mark the socket as non-readable
|
||||
self.common.pollee().del_events(Events::IN);
|
||||
}
|
||||
|
||||
if res.0 > 0 {
|
||||
self.do_recv(&mut inner);
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
// Only when there are no data available in the recv buffer, shall we check
|
||||
// the following error conditions.
|
||||
//
|
||||
// Case 1: If the read side of the connection has been shutdown...
|
||||
if inner.is_shutdown {
|
||||
return_errno!(EPIPE, "read side is shutdown");
|
||||
}
|
||||
// Case 2: If the connenction has been broken...
|
||||
if let Some(errno) = inner.fatal {
|
||||
// Reset error
|
||||
inner.fatal = None;
|
||||
self.common.pollee().del_events(Events::ERR);
|
||||
return_errno!(errno, "read failed");
|
||||
}
|
||||
|
||||
self.do_recv(&mut inner);
|
||||
return_errno!(EAGAIN, "try read again");
|
||||
}
|
||||
|
||||
fn do_recv(self: &Arc<Self>, inner: &mut MutexGuard<Inner>) {
|
||||
if inner.recv_buf.is_full()
|
||||
|| inner.is_shutdown
|
||||
|| inner.io_handle.is_some()
|
||||
|| inner.end_of_file
|
||||
|| self.common.is_closed()
|
||||
{
|
||||
// Delete ERR events from sender. If io_handle is some, the recv request must be
|
||||
// pending and the events can't be for the reciever. Just delete this event.
|
||||
// This can happen when send request is timeout and canceled.
|
||||
let events = self.common.pollee().poll(Events::IN, None);
|
||||
if events.contains(Events::ERR) && inner.io_handle.is_some() {
|
||||
self.common.pollee().del_events(Events::ERR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Init the callback invoked upon the completion of the async recv
|
||||
let stream = self.clone();
|
||||
let complete_fn = move |retval: i32| {
|
||||
// let mut inner = stream.receiver.inner.lock().unwrap();
|
||||
let mut inner = stream.receiver.inner.lock();
|
||||
trace!("recv request complete with retval: {:?}", retval);
|
||||
|
||||
// Release the handle to the async recv
|
||||
inner.io_handle.take();
|
||||
|
||||
// Handle error
|
||||
if retval < 0 {
|
||||
// TODO: guard against Iago attack through errno
|
||||
// We should return here, The error may be due to network reasons
|
||||
// or because the request was cancelled. We don't want to start a
|
||||
// new request after cancelled a request.
|
||||
let errno = Errno::from(-retval as u32);
|
||||
inner.fatal = Some(errno);
|
||||
stream.common.set_errno(errno);
|
||||
|
||||
let events = if errno == ENOTCONN || errno == ECONNRESET || errno == ECONNREFUSED {
|
||||
Events::HUP | Events::IN | Events::ERR
|
||||
} else {
|
||||
Events::ERR
|
||||
};
|
||||
stream.common.pollee().add_events(events);
|
||||
|
||||
return;
|
||||
}
|
||||
// Handle end of file
|
||||
else if retval == 0 {
|
||||
inner.end_of_file = true;
|
||||
stream.common.pollee().add_events(Events::IN);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the normal case of a successful read
|
||||
let nbytes = retval as usize;
|
||||
inner.recv_buf.produce_without_copy(nbytes);
|
||||
|
||||
// Now that we have produced non-zero bytes, the buf must become
|
||||
// ready to read.
|
||||
stream.common.pollee().add_events(Events::IN);
|
||||
|
||||
stream.do_recv(&mut inner);
|
||||
};
|
||||
|
||||
// Generate the async recv request
|
||||
let msghdr_ptr = inner.new_recv_req();
|
||||
|
||||
// Submit the async recv to io_uring
|
||||
let io_uring = self.common.io_uring();
|
||||
let host_fd = Fd(self.common.host_fd() as _);
|
||||
|
||||
let handle = unsafe { io_uring.recvmsg(host_fd, msghdr_ptr, 0, complete_fn) };
|
||||
inner.io_handle.replace(handle);
|
||||
}
|
||||
|
||||
pub(super) fn initiate_async_recv(self: &Arc<Self>) {
|
||||
// trace!("initiate async recv");
|
||||
let mut inner = self.receiver.inner.lock();
|
||||
self.do_recv(&mut inner);
|
||||
}
|
||||
|
||||
pub fn cancel_recv_requests(&self) {
|
||||
{
|
||||
let inner = self.receiver.inner.lock();
|
||||
if let Some(io_handle) = &inner.io_handle {
|
||||
let io_uring = self.common.io_uring();
|
||||
unsafe { io_uring.cancel(io_handle) };
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// wait for the cancel to complete
|
||||
let poller = Poller::new();
|
||||
let mask = Events::ERR | Events::IN;
|
||||
self.common.pollee().connect_poller(mask, &poller);
|
||||
|
||||
loop {
|
||||
let pending_request_exist = {
|
||||
let inner = self.receiver.inner.lock();
|
||||
inner.io_handle.is_some()
|
||||
};
|
||||
|
||||
if pending_request_exist {
|
||||
let mut timeout = Some(Duration::from_secs(10));
|
||||
let ret = poller.wait_timeout(timeout.as_mut());
|
||||
if let Err(e) = ret {
|
||||
warn!("wait cancel recv request error = {:?}", e.errno());
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes_to_consume(self: &Arc<Self>) -> usize {
|
||||
let inner = self.receiver.inner.lock();
|
||||
inner.recv_buf.consumable()
|
||||
}
|
||||
|
||||
// This function will try to update the kernel recv buf size.
|
||||
// For socket recv, there will always be a pending request in advance. Thus,we can only update the kernel
|
||||
// buffer when a recv request is done and the kernel buffer is empty. Here, we just set the update flag.
|
||||
pub fn try_update_recv_buf_size(&self, buf_size: usize) {
|
||||
let pre_buf_size = RECV_BUF_SIZE.swap(buf_size, Ordering::Relaxed);
|
||||
if buf_size == pre_buf_size {
|
||||
return;
|
||||
}
|
||||
|
||||
self.receiver.set_need_update(true);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Receiver {
|
||||
inner: Mutex<Inner>,
|
||||
need_update: AtomicBool,
|
||||
}
|
||||
|
||||
impl Receiver {
|
||||
pub fn new() -> Self {
|
||||
let inner = Mutex::new(Inner::new());
|
||||
let need_update = AtomicBool::new(false);
|
||||
|
||||
Self { inner, need_update }
|
||||
}
|
||||
|
||||
pub fn shutdown(&self) {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.is_shutdown = true;
|
||||
}
|
||||
|
||||
pub fn set_need_update(&self, need_update: bool) {
|
||||
self.need_update.store(need_update, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn need_update(&self) -> bool {
|
||||
self.need_update.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Receiver {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Receiver")
|
||||
.field("inner", &self.inner.lock())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
recv_buf: UntrustedCircularBuf,
|
||||
recv_req: UntrustedBox<RecvReq>,
|
||||
io_handle: Option<IoHandle>,
|
||||
is_shutdown: bool,
|
||||
end_of_file: bool,
|
||||
fatal: Option<Errno>,
|
||||
}
|
||||
|
||||
// Safety. `RecvReq` does not implement `Send`. But since all pointers in `RecvReq`
|
||||
// refer to `recv_buf`, we can be sure that it is ok for `RecvReq` to move between
|
||||
// threads. All other fields in `RecvReq` implement `Send` as well. So the entirety
|
||||
// of `Inner` is `Send`-safe.
|
||||
unsafe impl Send for Inner {}
|
||||
|
||||
impl Inner {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
recv_buf: UntrustedCircularBuf::with_capacity(RECV_BUF_SIZE.load(Ordering::Relaxed)),
|
||||
recv_req: UntrustedBox::new_uninit(),
|
||||
io_handle: None,
|
||||
is_shutdown: false,
|
||||
end_of_file: false,
|
||||
fatal: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_buf_size(&mut self, buf_size: usize) {
|
||||
debug_assert!(self.recv_buf.is_empty() && self.io_handle.is_none());
|
||||
let new_recv_buf = UntrustedCircularBuf::with_capacity(buf_size);
|
||||
self.recv_buf = new_recv_buf;
|
||||
}
|
||||
|
||||
/// Constructs a new recv request according to the receiver's internal state.
|
||||
///
|
||||
/// The new `RecvReq` will be put into `self.recv_req`, which is a location that is
|
||||
/// accessible by io_uring. A pointer to the C version of the resulting `RecvReq`,
|
||||
/// which is `libc::msghdr`, will be returned.
|
||||
///
|
||||
/// The buffer used in the new `RecvReq` is part of `self.recv_buf`.
|
||||
pub fn new_recv_req(&mut self) -> *mut libc::msghdr {
|
||||
let (iovecs, iovecs_len) = self.gen_iovecs_from_recv_buf();
|
||||
|
||||
let msghdr_ptr: *mut libc::msghdr = &mut self.recv_req.msg;
|
||||
let iovecs_ptr: *mut libc::iovec = &mut self.recv_req.iovecs as *mut _ as _;
|
||||
|
||||
let msg = super::new_msghdr(iovecs_ptr, iovecs_len);
|
||||
|
||||
self.recv_req.msg = msg;
|
||||
self.recv_req.iovecs = iovecs;
|
||||
|
||||
msghdr_ptr
|
||||
}
|
||||
|
||||
fn gen_iovecs_from_recv_buf(&mut self) -> ([libc::iovec; 2], usize) {
|
||||
let mut iovecs_len = 0;
|
||||
let mut iovecs = unsafe { MaybeUninit::<[libc::iovec; 2]>::uninit().assume_init() };
|
||||
self.recv_buf.with_producer_view(|part0, part1| {
|
||||
debug_assert!(part0.len() > 0);
|
||||
|
||||
iovecs[0] = libc::iovec {
|
||||
iov_base: part0.as_ptr() as _,
|
||||
iov_len: part0.len() as _,
|
||||
};
|
||||
|
||||
iovecs[1] = if part1.len() > 0 {
|
||||
iovecs_len = 2;
|
||||
libc::iovec {
|
||||
iov_base: part1.as_ptr() as _,
|
||||
iov_len: part1.len() as _,
|
||||
}
|
||||
} else {
|
||||
iovecs_len = 1;
|
||||
libc::iovec {
|
||||
iov_base: ptr::null_mut(),
|
||||
iov_len: 0,
|
||||
}
|
||||
};
|
||||
|
||||
// Only access the producer's buffer; zero bytes produced for now.
|
||||
0
|
||||
});
|
||||
debug_assert!(iovecs_len > 0);
|
||||
(iovecs, iovecs_len)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Inner {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Inner")
|
||||
.field("recv_buf", &self.recv_buf)
|
||||
.field("io_handle", &self.io_handle)
|
||||
.field("is_shutdown", &self.is_shutdown)
|
||||
.field("end_of_file", &self.end_of_file)
|
||||
.field("fatal", &self.fatal)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct RecvReq {
|
||||
msg: libc::msghdr,
|
||||
iovecs: [libc::iovec; 2],
|
||||
}
|
||||
|
||||
// Safety. RecvReq is a C-style struct.
|
||||
unsafe impl MaybeUntrusted for RecvReq {}
|
||||
|
||||
// Acquired by `IoUringCell<T: Copy>`.
|
||||
impl Copy for RecvReq {}
|
||||
|
||||
impl Clone for RecvReq {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
466
src/libos/src/net/socket/uring/stream/states/connected/send.rs
Normal file
466
src/libos/src/net/socket/uring/stream/states/connected/send.rs
Normal file
@ -0,0 +1,466 @@
|
||||
use core::hint;
|
||||
use core::sync::atomic::AtomicBool;
|
||||
use core::time::Duration;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr::{self};
|
||||
|
||||
use atomic::Ordering;
|
||||
use io_uring_callback::{Fd, IoHandle};
|
||||
use log::error;
|
||||
use sgx_untrusted_alloc::{MaybeUntrusted, UntrustedBox};
|
||||
|
||||
use super::ConnectedStream;
|
||||
use crate::net::socket::uring::runtime::Runtime;
|
||||
use crate::net::socket::uring::stream::SEND_BUF_SIZE;
|
||||
use crate::prelude::*;
|
||||
use crate::untrusted::UntrustedCircularBuf;
|
||||
|
||||
use crate::util::sync::{Mutex, MutexGuard};
|
||||
|
||||
use crate::events::Poller;
|
||||
use crate::fs::IoEvents as Events;
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> ConnectedStream<A, R> {
|
||||
// We make sure the all the buffer contents are buffered in kernel and then return.
|
||||
pub fn sendmsg(self: &Arc<Self>, bufs: &[&[u8]], flags: SendFlags) -> Result<usize> {
|
||||
let total_len: usize = bufs.iter().map(|buf| buf.len()).sum();
|
||||
if total_len == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let mut send_len = 0;
|
||||
// variables to track the position of async sendmsg.
|
||||
let mut iov_buf_id = 0; // user buffer id tracker
|
||||
let mut iov_buf_index = 0; // user buffer index tracker
|
||||
|
||||
let mask = Events::OUT;
|
||||
// Initialize the poller only when needed
|
||||
let mut poller = None;
|
||||
let mut timeout = self.common.send_timeout();
|
||||
loop {
|
||||
// Attempt to write
|
||||
let res = self.try_sendmsg(bufs, flags, &mut iov_buf_id, &mut iov_buf_index);
|
||||
if let Ok(len) = res {
|
||||
send_len += len;
|
||||
// Sent all or sent partial but it is nonblocking, return bytes sent
|
||||
if send_len == total_len
|
||||
|| self.common.nonblocking()
|
||||
|| flags.contains(SendFlags::MSG_DONTWAIT)
|
||||
{
|
||||
return Ok(send_len);
|
||||
}
|
||||
} else if !res.has_errno(EAGAIN) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// Still some buffer contents pending
|
||||
if self.common.nonblocking() || flags.contains(SendFlags::MSG_DONTWAIT) {
|
||||
return_errno!(EAGAIN, "try write again");
|
||||
}
|
||||
|
||||
// Wait for interesting events by polling
|
||||
if poller.is_none() {
|
||||
let new_poller = Poller::new();
|
||||
self.common.pollee().connect_poller(mask, &new_poller);
|
||||
poller = Some(new_poller);
|
||||
}
|
||||
|
||||
let events = self.common.pollee().poll(mask, None);
|
||||
if events.is_empty() {
|
||||
let ret = poller.as_ref().unwrap().wait_timeout(timeout.as_mut());
|
||||
if let Err(e) = ret {
|
||||
warn!("send wait errno = {:?}", e.errno());
|
||||
match e.errno() {
|
||||
ETIMEDOUT => {
|
||||
// Just cancel send requests if timeout
|
||||
self.cancel_send_requests();
|
||||
return_errno!(EAGAIN, "timeout reached")
|
||||
}
|
||||
_ => {
|
||||
return_errno!(e.errno(), "wait error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_sendmsg(
|
||||
self: &Arc<Self>,
|
||||
bufs: &[&[u8]],
|
||||
flags: SendFlags,
|
||||
iov_buf_id: &mut usize,
|
||||
iov_buf_index: &mut usize,
|
||||
) -> Result<usize> {
|
||||
let mut inner = self.sender.inner.lock();
|
||||
|
||||
if !flags.is_empty()
|
||||
&& flags.intersects(
|
||||
!(SendFlags::MSG_DONTWAIT | SendFlags::MSG_NOSIGNAL | SendFlags::MSG_MORE),
|
||||
)
|
||||
{
|
||||
error!("Not supported flags: {:?}", flags);
|
||||
return_errno!(EINVAL, "not supported flags");
|
||||
}
|
||||
|
||||
// Check for error condition before write.
|
||||
//
|
||||
// Case 1. If the write side of the connection has been shutdown...
|
||||
if inner.is_shutdown() {
|
||||
return_errno!(EPIPE, "write side is shutdown");
|
||||
}
|
||||
// Case 2. If the connenction has been broken...
|
||||
if let Some(errno) = inner.fatal {
|
||||
// Reset error
|
||||
inner.fatal = None;
|
||||
self.common.pollee().del_events(Events::ERR);
|
||||
return_errno!(errno, "write failed");
|
||||
}
|
||||
|
||||
// Copy data from the bufs to the send buffer
|
||||
// If the send buffer is full, update the user buffer tracker, return error to wait for events
|
||||
// And once there is free space, continue from the user buffer tracker
|
||||
let nbytes = {
|
||||
let mut total_produced = 0;
|
||||
let last_time_buf_id = iov_buf_id.clone();
|
||||
let mut last_time_buf_idx = iov_buf_index.clone();
|
||||
for (_i, buf) in bufs.iter().skip(last_time_buf_id).enumerate() {
|
||||
let i = _i + last_time_buf_id; // After skipping ,the index still starts from 0
|
||||
let this_produced = inner.send_buf.produce(&buf[last_time_buf_idx..]);
|
||||
total_produced += this_produced;
|
||||
if this_produced < buf[last_time_buf_idx..].len() {
|
||||
// Send buffer is full.
|
||||
*iov_buf_id = i;
|
||||
*iov_buf_index = last_time_buf_idx + this_produced;
|
||||
break;
|
||||
} else {
|
||||
// For next buffer, start from the front
|
||||
last_time_buf_idx = 0;
|
||||
}
|
||||
}
|
||||
total_produced
|
||||
};
|
||||
|
||||
if inner.send_buf.is_full() {
|
||||
// Mark the socket as non-writable
|
||||
self.common.pollee().del_events(Events::OUT);
|
||||
}
|
||||
|
||||
// Since the send buffer is not empty, we can try to flush the buffer
|
||||
if inner.io_handle.is_none() {
|
||||
self.do_send(&mut inner);
|
||||
}
|
||||
|
||||
if nbytes > 0 {
|
||||
Ok(nbytes)
|
||||
} else {
|
||||
return_errno!(EAGAIN, "try write again");
|
||||
}
|
||||
}
|
||||
|
||||
fn do_send(self: &Arc<Self>, inner: &mut MutexGuard<Inner>) {
|
||||
// This function can also be called even if the socket is set to shutdown by shutdown syscall. This is due to the
|
||||
// async behaviour that the kernel may return to user before actually issuing the request. We should
|
||||
// keep sending the request as long as the send buffer is not empty even if the socket is shutdown.
|
||||
debug_assert!(inner.is_shutdown != ShutdownStatus::PostShutdown);
|
||||
debug_assert!(!inner.send_buf.is_empty());
|
||||
debug_assert!(inner.io_handle.is_none());
|
||||
|
||||
// Init the callback invoked upon the completion of the async send
|
||||
let stream = self.clone();
|
||||
let complete_fn = move |retval: i32| {
|
||||
let mut inner = stream.sender.inner.lock();
|
||||
|
||||
trace!("send request complete with retval: {}", retval);
|
||||
// Release the handle to the async send
|
||||
inner.io_handle.take();
|
||||
|
||||
// Handle error
|
||||
if retval < 0 {
|
||||
// TODO: guard against Iago attack through errno
|
||||
// TODO: should we ignore EINTR and try again?
|
||||
let errno = Errno::from(-retval as u32);
|
||||
|
||||
inner.fatal = Some(errno);
|
||||
stream.common.set_errno(errno);
|
||||
stream.common.pollee().add_events(Events::ERR);
|
||||
return;
|
||||
}
|
||||
assert!(retval != 0);
|
||||
|
||||
// Handle the normal case of a successful write
|
||||
let nbytes = retval as usize;
|
||||
inner.send_buf.consume_without_copy(nbytes);
|
||||
|
||||
// Now that we have consume non-zero bytes, the buf must become
|
||||
// ready to write.
|
||||
stream.common.pollee().add_events(Events::OUT);
|
||||
|
||||
// Attempt to send again if there are available data in the buf.
|
||||
if !inner.send_buf.is_empty() {
|
||||
stream.do_send(&mut inner);
|
||||
} else if inner.is_shutdown == ShutdownStatus::PreShutdown {
|
||||
// The buffer is empty and the write side is shutdown by the user. We can safely shutdown host file here.
|
||||
let _ = stream.common.host_shutdown(Shutdown::Write);
|
||||
inner.is_shutdown = ShutdownStatus::PostShutdown
|
||||
} else if stream.sender.need_update() {
|
||||
// send_buf is empty. We can try to update the send_buf
|
||||
stream.sender.set_need_update(false);
|
||||
inner.update_buf_size(SEND_BUF_SIZE.load(Ordering::Relaxed));
|
||||
}
|
||||
};
|
||||
|
||||
// Generate the async send request
|
||||
let msghdr_ptr = inner.new_send_req();
|
||||
|
||||
trace!("send submit request");
|
||||
// Submit the async send to io_uring
|
||||
let io_uring = self.common.io_uring();
|
||||
let host_fd = Fd(self.common.host_fd() as _);
|
||||
let handle = unsafe { io_uring.sendmsg(host_fd, msghdr_ptr, 0, complete_fn) };
|
||||
inner.io_handle.replace(handle);
|
||||
}
|
||||
|
||||
pub fn cancel_send_requests(&self) {
|
||||
let io_uring = self.common.io_uring();
|
||||
let inner = self.sender.inner.lock();
|
||||
if let Some(io_handle) = &inner.io_handle {
|
||||
unsafe { io_uring.cancel(io_handle) };
|
||||
}
|
||||
}
|
||||
|
||||
// This function will try to update the kernel buf size.
|
||||
// If the kernel buf is currently empty, the size will be updated immediately.
|
||||
// If the kernel buf is not empty, update the flag in Sender and update the kernel buf after send.
|
||||
pub fn try_update_send_buf_size(&self, buf_size: usize) {
|
||||
let pre_buf_size = SEND_BUF_SIZE.swap(buf_size, Ordering::Relaxed);
|
||||
if pre_buf_size == buf_size {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to acquire the lock. If success, try directly update here.
|
||||
// If failure, don't wait because there is pending send request.
|
||||
if let Some(mut inner) = self.sender.inner.try_lock() {
|
||||
if inner.send_buf.is_empty() && inner.io_handle.is_none() {
|
||||
inner.update_buf_size(buf_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Can't easily aquire lock or the sendbuf is not empty. Update the flag only
|
||||
self.sender.set_need_update(true);
|
||||
}
|
||||
|
||||
// Normally, We will always try to send as long as the kernel send buf is not empty. However, if the user calls close, we will wait LINGER time
|
||||
// and then cancel on-going or new-issued send requests.
|
||||
pub fn try_empty_send_buf_when_close(&self) {
|
||||
// let inner = self.sender.inner.lock().unwrap();
|
||||
let inner = self.sender.inner.lock();
|
||||
debug_assert!(inner.is_shutdown());
|
||||
if inner.send_buf.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for linger time to empty the kernel buffer or cancel subsequent requests.
|
||||
drop(inner);
|
||||
const DEFUALT_LINGER_TIME: usize = 10;
|
||||
let poller = Poller::new();
|
||||
let mask = Events::ERR | Events::OUT;
|
||||
self.common.pollee().connect_poller(mask, &poller);
|
||||
|
||||
loop {
|
||||
let pending_request_exist = {
|
||||
// let inner = self.sender.inner.lock().unwrap();
|
||||
let inner = self.sender.inner.lock();
|
||||
inner.io_handle.is_some()
|
||||
};
|
||||
|
||||
if pending_request_exist {
|
||||
let mut timeout = Some(Duration::from_secs(DEFUALT_LINGER_TIME as u64));
|
||||
let ret = poller.wait_timeout(timeout.as_mut());
|
||||
trace!("wait empty send buffer ret = {:?}", ret);
|
||||
if let Err(_) = ret {
|
||||
// No complete request to wake. Just cancel the send requests.
|
||||
let io_uring = self.common.io_uring();
|
||||
let inner = self.sender.inner.lock();
|
||||
if let Some(io_handle) = &inner.io_handle {
|
||||
unsafe { io_uring.cancel(io_handle) };
|
||||
// Loop again to wait the cancel request to complete
|
||||
continue;
|
||||
} else {
|
||||
// No pending request, just break
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// There is no pending requests
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Sender {
|
||||
inner: Mutex<Inner>,
|
||||
need_update: AtomicBool,
|
||||
}
|
||||
|
||||
impl Sender {
|
||||
pub fn new() -> Self {
|
||||
let inner = Mutex::new(Inner::new());
|
||||
let need_update = AtomicBool::new(false);
|
||||
Self { inner, need_update }
|
||||
}
|
||||
|
||||
pub fn shutdown(&self) {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.is_shutdown = ShutdownStatus::PreShutdown;
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
let inner = self.inner.lock();
|
||||
inner.send_buf.is_empty()
|
||||
}
|
||||
|
||||
pub fn set_need_update(&self, need_update: bool) {
|
||||
self.need_update.store(need_update, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn need_update(&self) -> bool {
|
||||
self.need_update.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Sender {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Sender")
|
||||
.field("inner", &self.inner.lock())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
send_buf: UntrustedCircularBuf,
|
||||
send_req: UntrustedBox<SendReq>,
|
||||
io_handle: Option<IoHandle>,
|
||||
is_shutdown: ShutdownStatus,
|
||||
fatal: Option<Errno>,
|
||||
}
|
||||
|
||||
// Safety. `SendReq` does not implement `Send`. But since all pointers in `SengReq`
|
||||
// refer to `send_buf`, we can be sure that it is ok for `SendReq` to move between
|
||||
// threads. All other fields in `SendReq` implement `Send` as well. So the entirety
|
||||
// of `Inner` is `Send`-safe.
|
||||
unsafe impl Send for Inner {}
|
||||
|
||||
impl Inner {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
send_buf: UntrustedCircularBuf::with_capacity(SEND_BUF_SIZE.load(Ordering::Relaxed)),
|
||||
send_req: UntrustedBox::new_uninit(),
|
||||
io_handle: None,
|
||||
is_shutdown: ShutdownStatus::Running,
|
||||
fatal: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_buf_size(&mut self, buf_size: usize) {
|
||||
debug_assert!(self.send_buf.is_empty() && self.io_handle.is_none());
|
||||
let new_send_buf = UntrustedCircularBuf::with_capacity(buf_size);
|
||||
self.send_buf = new_send_buf;
|
||||
}
|
||||
|
||||
pub fn is_shutdown(&self) -> bool {
|
||||
self.is_shutdown == ShutdownStatus::PreShutdown
|
||||
|| self.is_shutdown == ShutdownStatus::PostShutdown
|
||||
}
|
||||
|
||||
/// Constructs a new send request according to the sender's internal state.
|
||||
///
|
||||
/// The new `SendReq` will be put into `self.send_req`, which is a location that is
|
||||
/// accessible by io_uring. A pointer to the C version of the resulting `SendReq`,
|
||||
/// which is `libc::msghdr`, will be returned.
|
||||
///
|
||||
/// The buffer used in the new `SendReq` is part of `self.send_buf`.
|
||||
pub fn new_send_req(&mut self) -> *mut libc::msghdr {
|
||||
let (iovecs, iovecs_len) = self.gen_iovecs_from_send_buf();
|
||||
|
||||
let msghdr_ptr: *mut libc::msghdr = &mut self.send_req.msg;
|
||||
let iovecs_ptr: *mut libc::iovec = &mut self.send_req.iovecs as *mut _ as _;
|
||||
|
||||
let msg = super::new_msghdr(iovecs_ptr, iovecs_len);
|
||||
|
||||
self.send_req.msg = msg;
|
||||
self.send_req.iovecs = iovecs;
|
||||
|
||||
msghdr_ptr
|
||||
}
|
||||
|
||||
fn gen_iovecs_from_send_buf(&mut self) -> ([libc::iovec; 2], usize) {
|
||||
let mut iovecs_len = 0;
|
||||
let mut iovecs = unsafe { MaybeUninit::<[libc::iovec; 2]>::uninit().assume_init() };
|
||||
self.send_buf.with_consumer_view(|part0, part1| {
|
||||
debug_assert!(part0.len() > 0);
|
||||
|
||||
iovecs[0] = libc::iovec {
|
||||
iov_base: part0.as_ptr() as _,
|
||||
iov_len: part0.len() as _,
|
||||
};
|
||||
|
||||
iovecs[1] = if part1.len() > 0 {
|
||||
iovecs_len = 2;
|
||||
libc::iovec {
|
||||
iov_base: part1.as_ptr() as _,
|
||||
iov_len: part1.len() as _,
|
||||
}
|
||||
} else {
|
||||
iovecs_len = 1;
|
||||
libc::iovec {
|
||||
iov_base: ptr::null_mut(),
|
||||
iov_len: 0,
|
||||
}
|
||||
};
|
||||
|
||||
// Only access the consumer's buffer; zero bytes consumed for now.
|
||||
0
|
||||
});
|
||||
debug_assert!(iovecs_len > 0);
|
||||
(iovecs, iovecs_len)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Inner {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Inner")
|
||||
.field("send_buf", &self.send_buf)
|
||||
.field("io_handle", &self.io_handle)
|
||||
.field("is_shutdown", &self.is_shutdown)
|
||||
.field("fatal", &self.fatal)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct SendReq {
|
||||
msg: libc::msghdr,
|
||||
iovecs: [libc::iovec; 2],
|
||||
}
|
||||
|
||||
// Safety. SendReq is a C-style struct.
|
||||
unsafe impl MaybeUntrusted for SendReq {}
|
||||
|
||||
// Acquired by `IoUringCell<T: Copy>`.
|
||||
impl Copy for SendReq {}
|
||||
|
||||
impl Clone for SendReq {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum ShutdownStatus {
|
||||
Running, // not shutdown
|
||||
PreShutdown, // start the shutdown process, set by calling shutdown syscall
|
||||
PostShutdown, // shutdown process is done, set when the buffer is empty
|
||||
}
|
72
src/libos/src/net/socket/uring/stream/states/init.rs
Normal file
72
src/libos/src/net/socket/uring/stream/states/init.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use crate::fs::IoEvents;
|
||||
use crate::net::socket::uring::common::Common;
|
||||
use crate::net::socket::uring::runtime::Runtime;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A stream socket that is in its initial state.
|
||||
pub struct InitStream<A: Addr + 'static, R: Runtime> {
|
||||
common: Arc<Common<A, R>>,
|
||||
inner: Mutex<Inner>,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
has_bound: bool,
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> InitStream<A, R> {
|
||||
pub fn new(nonblocking: bool) -> Result<Arc<Self>> {
|
||||
let common = Arc::new(Common::new(Type::STREAM, nonblocking, None)?);
|
||||
common.pollee().add_events(IoEvents::HUP | IoEvents::OUT);
|
||||
let inner = Mutex::new(Inner::new());
|
||||
let new_self = Self { common, inner };
|
||||
Ok(Arc::new(new_self))
|
||||
}
|
||||
|
||||
pub fn new_with_common(common: Arc<Common<A, R>>) -> Result<Arc<Self>> {
|
||||
let inner = Mutex::new(Inner {
|
||||
has_bound: common.addr().is_some(),
|
||||
});
|
||||
let new_self = Self { common, inner };
|
||||
Ok(Arc::new(new_self))
|
||||
}
|
||||
|
||||
pub fn bind(&self, addr: &A) -> Result<()> {
|
||||
let mut inner = self.inner.lock();
|
||||
if inner.has_bound {
|
||||
return_errno!(EINVAL, "the socket is already bound to an address");
|
||||
}
|
||||
|
||||
crate::net::socket::uring::common::do_bind(self.common.host_fd(), addr)?;
|
||||
|
||||
inner.has_bound = true;
|
||||
self.common.set_addr(addr);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn common(&self) -> &Arc<Common<A, R>> {
|
||||
&self.common
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> std::fmt::Debug for InitStream<A, R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("InitStream")
|
||||
.field("common", &self.common)
|
||||
.field("inner", &*self.inner.lock())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
pub fn new() -> Self {
|
||||
Self { has_bound: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Inner {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Inner")
|
||||
.field("has_bound", &self.has_bound)
|
||||
.finish()
|
||||
}
|
||||
}
|
430
src/libos/src/net/socket/uring/stream/states/listen.rs
Normal file
430
src/libos/src/net/socket/uring/stream/states/listen.rs
Normal file
@ -0,0 +1,430 @@
|
||||
use core::time::Duration;
|
||||
use std::collections::VecDeque;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::size_of;
|
||||
|
||||
use io_uring_callback::{Fd, IoHandle};
|
||||
use sgx_untrusted_alloc::{MaybeUntrusted, UntrustedBox};
|
||||
|
||||
use super::ConnectedStream;
|
||||
use crate::events::Poller;
|
||||
use crate::fs::IoEvents;
|
||||
use crate::net::socket::uring::common::{do_close, Common};
|
||||
use crate::net::socket::uring::runtime::Runtime;
|
||||
use crate::prelude::*;
|
||||
use libc::ocall::shutdown as do_shutdown;
|
||||
|
||||
// We issue the async accept request ahead of time. But with big backlog number,
|
||||
// there will be too many pending requests, which could be harmful to the system.
|
||||
const PENDING_ASYNC_ACCEPT_NUM_MAX: usize = 128;
|
||||
|
||||
/// A listener stream, ready to accept incoming connections.
|
||||
pub struct ListenerStream<A: Addr + 'static, R: Runtime> {
|
||||
common: Arc<Common<A, R>>,
|
||||
inner: Mutex<Inner<A>>,
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> ListenerStream<A, R> {
|
||||
/// Creates a new listener stream.
|
||||
pub fn new(backlog: u32, common: Arc<Common<A, R>>) -> Result<Arc<Self>> {
|
||||
// Here we use different variables for the backlog. For the libos, as we will issue async accept request
|
||||
// ahead of time, and cacel when the socket closes, we set the libos backlog to a reasonable value which
|
||||
// is no greater than the max value we set to save resources and make it more efficient. For the host,
|
||||
// we just use the backlog value for maximum connection.
|
||||
let libos_backlog = std::cmp::min(backlog, PENDING_ASYNC_ACCEPT_NUM_MAX as u32);
|
||||
let host_backlog = backlog;
|
||||
|
||||
let inner = Inner::new(libos_backlog)?;
|
||||
Self::do_listen(common.host_fd(), host_backlog)?;
|
||||
|
||||
common.pollee().reset_events();
|
||||
let new_self = Arc::new(Self {
|
||||
common,
|
||||
inner: Mutex::new(inner),
|
||||
});
|
||||
|
||||
// Start async accept requests right as early as possible to improve performance
|
||||
{
|
||||
let inner = new_self.inner.lock();
|
||||
new_self.initiate_async_accepts(inner);
|
||||
}
|
||||
|
||||
Ok(new_self)
|
||||
}
|
||||
|
||||
fn do_listen(host_fd: FileDesc, backlog: u32) -> Result<()> {
|
||||
try_libc!(libc::ocall::listen(host_fd as _, backlog as _));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn accept(self: &Arc<Self>, nonblocking: bool) -> Result<Arc<ConnectedStream<A, R>>> {
|
||||
let mask = IoEvents::IN;
|
||||
// Init the poller only when needed
|
||||
let mut poller = None;
|
||||
let mut timeout = self.common.recv_timeout();
|
||||
loop {
|
||||
// Attempt to accept
|
||||
let res = self.try_accept(nonblocking);
|
||||
if !res.has_errno(EAGAIN) {
|
||||
return res;
|
||||
}
|
||||
|
||||
if self.common.nonblocking() {
|
||||
return_errno!(EAGAIN, "no connections are present to be accepted");
|
||||
}
|
||||
|
||||
// Ensure the poller is initialized
|
||||
if poller.is_none() {
|
||||
let new_poller = Poller::new();
|
||||
self.common.pollee().connect_poller(mask, &new_poller);
|
||||
poller = Some(new_poller);
|
||||
}
|
||||
// Wait for interesting events by polling
|
||||
|
||||
let events = self.common.pollee().poll(mask, None);
|
||||
if events.is_empty() {
|
||||
let ret = poller.as_ref().unwrap().wait_timeout(timeout.as_mut());
|
||||
if let Err(e) = ret {
|
||||
warn!("accept wait errno = {:?}", e.errno());
|
||||
match e.errno() {
|
||||
ETIMEDOUT => {
|
||||
return_errno!(EAGAIN, "timeout reached")
|
||||
}
|
||||
_ => {
|
||||
return_errno!(e.errno(), "wait error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_accept(self: &Arc<Self>, nonblocking: bool) -> Result<Arc<ConnectedStream<A, R>>> {
|
||||
let mut inner = self.inner.lock();
|
||||
|
||||
if let Some(errno) = inner.fatal {
|
||||
// Reset error
|
||||
inner.fatal = None;
|
||||
self.common.pollee().del_events(IoEvents::ERR);
|
||||
return_errno!(errno, "accept failed");
|
||||
}
|
||||
|
||||
let (accepted_fd, accepted_addr) = inner.backlog.pop_completed_req().ok_or_else(|| {
|
||||
self.common.pollee().del_events(IoEvents::IN);
|
||||
errno!(EAGAIN, "try accept again")
|
||||
})?;
|
||||
|
||||
if !inner.backlog.has_completed_reqs() {
|
||||
self.common.pollee().del_events(IoEvents::IN);
|
||||
}
|
||||
|
||||
self.initiate_async_accepts(inner);
|
||||
|
||||
let common = {
|
||||
let common = Arc::new(Common::with_host_fd(accepted_fd, Type::STREAM, nonblocking));
|
||||
common.set_peer_addr(&accepted_addr);
|
||||
common
|
||||
};
|
||||
let accepted_stream = ConnectedStream::new(common);
|
||||
Ok(accepted_stream)
|
||||
}
|
||||
|
||||
fn initiate_async_accepts(self: &Arc<Self>, mut inner: MutexGuard<Inner<A>>) {
|
||||
let backlog = &mut inner.backlog;
|
||||
while backlog.has_free_entries() {
|
||||
backlog.start_new_req(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn common(&self) -> &Arc<Common<A, R>> {
|
||||
&self.common
|
||||
}
|
||||
|
||||
pub fn cancel_accept_requests(&self) {
|
||||
{
|
||||
// Set the listener stream as closed to prevent submitting new request in the callback fn
|
||||
self.common().set_closed();
|
||||
let io_uring = self.common.io_uring();
|
||||
let inner = self.inner.lock();
|
||||
for entry in inner.backlog.entries.iter() {
|
||||
if let Entry::Pending { io_handle } = entry {
|
||||
unsafe { io_uring.cancel(&io_handle) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wait for all the cancel requests to complete
|
||||
let poller = Poller::new();
|
||||
let mask = IoEvents::ERR | IoEvents::IN;
|
||||
self.common.pollee().connect_poller(mask, &poller);
|
||||
|
||||
loop {
|
||||
let pending_entry_exists = {
|
||||
let inner = self.inner.lock();
|
||||
inner
|
||||
.backlog
|
||||
.entries
|
||||
.iter()
|
||||
.find(|entry| match entry {
|
||||
Entry::Pending { .. } => true,
|
||||
_ => false,
|
||||
})
|
||||
.is_some()
|
||||
};
|
||||
|
||||
if pending_entry_exists {
|
||||
let mut timeout = Some(Duration::from_secs(20));
|
||||
let ret = poller.wait_timeout(timeout.as_mut());
|
||||
if let Err(e) = ret {
|
||||
warn!("wait cancel accept request error = {:?}", e.errno());
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Reset the stream for re-listen
|
||||
self.common().reset_closed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shutdown(&self, how: Shutdown) -> Result<()> {
|
||||
if how == Shutdown::Both {
|
||||
self.common.host_shutdown(Shutdown::Both)?;
|
||||
self.common
|
||||
.pollee()
|
||||
.add_events(IoEvents::IN | IoEvents::OUT | IoEvents::HUP);
|
||||
} else if how.should_shut_read() {
|
||||
self.common.host_shutdown(Shutdown::Read)?;
|
||||
self.common.pollee().add_events(IoEvents::IN);
|
||||
} else if how.should_shut_write() {
|
||||
self.common.host_shutdown(Shutdown::Write)?;
|
||||
self.common.pollee().add_events(IoEvents::OUT);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static, R: Runtime> std::fmt::Debug for ListenerStream<A, R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ListenerStream")
|
||||
.field("common", &self.common)
|
||||
.field("inner", &self.inner.lock())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// The mutable, internal state of a listener stream.
|
||||
struct Inner<A: Addr> {
|
||||
backlog: Backlog<A>,
|
||||
fatal: Option<Errno>,
|
||||
}
|
||||
|
||||
impl<A: Addr> Inner<A> {
|
||||
pub fn new(backlog: u32) -> Result<Self> {
|
||||
Ok(Inner {
|
||||
backlog: Backlog::with_capacity(backlog as usize)?,
|
||||
fatal: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static> std::fmt::Debug for Inner<A> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Inner")
|
||||
.field("backlog", &self.backlog)
|
||||
.field("fatal", &self.fatal)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// An entry in the backlog.
|
||||
#[derive(Debug)]
|
||||
enum Entry {
|
||||
/// The entry is free to use.
|
||||
Free,
|
||||
/// The entry is a pending accept request.
|
||||
Pending { io_handle: IoHandle },
|
||||
/// The entry is a completed accept request.
|
||||
Completed { host_fd: FileDesc },
|
||||
}
|
||||
|
||||
impl Default for Entry {
|
||||
fn default() -> Self {
|
||||
Self::Free
|
||||
}
|
||||
}
|
||||
|
||||
/// An async io_uring accept request.
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
struct AcceptReq {
|
||||
c_addr: libc::sockaddr_storage,
|
||||
c_addr_len: libc::socklen_t,
|
||||
}
|
||||
|
||||
// Safety. AcceptReq is a C-style struct with C-style fields.
|
||||
unsafe impl MaybeUntrusted for AcceptReq {}
|
||||
|
||||
/// A backlog of incoming connections of a listener stream.
|
||||
///
|
||||
/// With backlog, we can start async accept requests, keep track of the pending requests,
|
||||
/// and maintain the ones that have completed.
|
||||
struct Backlog<A: Addr> {
|
||||
// The entries in the backlog.
|
||||
entries: Box<[Entry]>,
|
||||
// Arguments of the io_uring requests submitted for the entries in the backlog.
|
||||
reqs: UntrustedBox<[AcceptReq]>,
|
||||
// The indexes of completed entries.
|
||||
completed: VecDeque<usize>,
|
||||
// The number of free entries.
|
||||
num_free: usize,
|
||||
phantom_data: PhantomData<A>,
|
||||
}
|
||||
|
||||
impl<A: Addr> Backlog<A> {
|
||||
pub fn with_capacity(capacity: usize) -> Result<Self> {
|
||||
if capacity == 0 {
|
||||
return_errno!(EINVAL, "capacity cannot be zero");
|
||||
}
|
||||
|
||||
let entries = (0..capacity)
|
||||
.map(|_| Entry::Free)
|
||||
.collect::<Vec<Entry>>()
|
||||
.into_boxed_slice();
|
||||
let reqs = UntrustedBox::new_uninit_slice(capacity);
|
||||
let completed = VecDeque::new();
|
||||
let num_free = capacity;
|
||||
let new_self = Self {
|
||||
entries,
|
||||
reqs,
|
||||
completed,
|
||||
num_free,
|
||||
phantom_data: PhantomData,
|
||||
};
|
||||
Ok(new_self)
|
||||
}
|
||||
|
||||
pub fn has_free_entries(&self) -> bool {
|
||||
self.num_free > 0
|
||||
}
|
||||
|
||||
/// Start a new async accept request, turning a free entry into a pending one.
|
||||
pub fn start_new_req<R: Runtime>(&mut self, stream: &Arc<ListenerStream<A, R>>) {
|
||||
if stream.common.is_closed() {
|
||||
return;
|
||||
}
|
||||
debug_assert!(self.has_free_entries());
|
||||
|
||||
let entry_idx = self
|
||||
.entries
|
||||
.iter()
|
||||
.position(|entry| matches!(entry, Entry::Free))
|
||||
.unwrap();
|
||||
|
||||
let (c_addr_ptr, c_addr_len_ptr) = {
|
||||
let accept_req = &mut self.reqs[entry_idx];
|
||||
accept_req.c_addr_len = size_of::<libc::sockaddr_storage>() as _;
|
||||
|
||||
let c_addr_ptr = &mut accept_req.c_addr as *mut _ as _;
|
||||
let c_addr_len_ptr = &mut accept_req.c_addr_len as _;
|
||||
(c_addr_ptr, c_addr_len_ptr)
|
||||
};
|
||||
|
||||
let callback = {
|
||||
let stream = stream.clone();
|
||||
move |retval: i32| {
|
||||
let mut inner = stream.inner.lock();
|
||||
|
||||
trace!("accept request complete with retval: {:?}", retval);
|
||||
|
||||
if retval < 0 {
|
||||
// Since most errors that may result from the accept syscall are _not fatal_,
|
||||
// we simply ignore the errno code and try again.
|
||||
//
|
||||
// According to the man page, Linux may report the network errors on an
|
||||
// newly-accepted socket through the accept system call. Thus, we should not
|
||||
// treat the listener socket as "broken" simply because an error is returned
|
||||
// from the accept syscall.
|
||||
//
|
||||
// TODO: throw fatal errors to the upper layer.
|
||||
let errno = Errno::from(-retval as u32);
|
||||
log::error!("Accept error: errno = {}", errno);
|
||||
|
||||
inner.backlog.entries[entry_idx] = Entry::Free;
|
||||
inner.backlog.num_free += 1;
|
||||
|
||||
// When canceling request, a poller might be waiting for this to return.
|
||||
inner.fatal = Some(errno);
|
||||
stream.common.set_errno(errno);
|
||||
stream.common.pollee().add_events(IoEvents::ERR);
|
||||
|
||||
// After getting the error from the accept system call, we should not start
|
||||
// the async accept requests again, because this may cause a large number of
|
||||
// io-uring requests to be retried
|
||||
return;
|
||||
}
|
||||
|
||||
let host_fd = retval as FileDesc;
|
||||
inner.backlog.entries[entry_idx] = Entry::Completed { host_fd };
|
||||
inner.backlog.completed.push_back(entry_idx);
|
||||
|
||||
stream.common.pollee().add_events(IoEvents::IN);
|
||||
|
||||
stream.initiate_async_accepts(inner);
|
||||
}
|
||||
};
|
||||
let io_uring = stream.common.io_uring();
|
||||
let fd = stream.common.host_fd() as i32;
|
||||
let flags = 0;
|
||||
let io_handle =
|
||||
unsafe { io_uring.accept(Fd(fd), c_addr_ptr, c_addr_len_ptr, flags, callback) };
|
||||
self.entries[entry_idx] = Entry::Pending { io_handle };
|
||||
self.num_free -= 1;
|
||||
}
|
||||
|
||||
pub fn has_completed_reqs(&self) -> bool {
|
||||
self.completed.len() > 0
|
||||
}
|
||||
|
||||
/// Pop a completed async accept request, turing a completed entry into a free one.
|
||||
pub fn pop_completed_req(&mut self) -> Option<(FileDesc, A)> {
|
||||
let completed_idx = self.completed.pop_front()?;
|
||||
let accepted_addr = {
|
||||
let AcceptReq { c_addr, c_addr_len } = self.reqs[completed_idx].clone();
|
||||
A::from_c_storage(&c_addr, c_addr_len as _).unwrap()
|
||||
};
|
||||
let accepted_fd = {
|
||||
let entry = &mut self.entries[completed_idx];
|
||||
let accepted_fd = match entry {
|
||||
Entry::Completed { host_fd } => *host_fd,
|
||||
_ => unreachable!("the entry should have been completed"),
|
||||
};
|
||||
self.num_free += 1;
|
||||
*entry = Entry::Free;
|
||||
accepted_fd
|
||||
};
|
||||
Some((accepted_fd, accepted_addr))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Addr + 'static> std::fmt::Debug for Backlog<A> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Backlog")
|
||||
.field("entries", &self.entries)
|
||||
.field("completed", &self.completed)
|
||||
.field("num_free", &self.num_free)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Addr> Drop for Backlog<A> {
|
||||
fn drop(&mut self) {
|
||||
for entry in self.entries.iter() {
|
||||
if let Entry::Completed { host_fd } = entry {
|
||||
if let Err(e) = do_close(*host_fd) {
|
||||
log::error!("close fd failed, host_fd: {}, err: {}", host_fd, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
src/libos/src/net/socket/uring/stream/states/mod.rs
Normal file
9
src/libos/src/net/socket/uring/stream/states/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
mod connect;
|
||||
mod connected;
|
||||
mod init;
|
||||
mod listen;
|
||||
|
||||
pub use self::connect::ConnectingStream;
|
||||
pub use self::connected::ConnectedStream;
|
||||
pub use self::init::InitStream;
|
||||
pub use self::listen::ListenerStream;
|
@ -17,8 +17,11 @@ pub use std::sync::{
|
||||
pub use crate::error::Result;
|
||||
pub use crate::error::*;
|
||||
pub use crate::fs::{File, FileDesc, FileRef};
|
||||
pub use crate::net::socket::util::Addr;
|
||||
pub use crate::net::socket::{Domain, RecvFlags, SendFlags, Shutdown, Type};
|
||||
pub use crate::process::{pid_t, uid_t};
|
||||
pub use crate::util::sync::RwLock;
|
||||
pub use crate::util::sync::{Mutex, MutexGuard};
|
||||
|
||||
macro_rules! debug_trace {
|
||||
() => {
|
||||
|
@ -44,8 +44,7 @@ use crate::net::{
|
||||
do_accept, do_accept4, do_bind, do_connect, do_epoll_create, do_epoll_create1, do_epoll_ctl,
|
||||
do_epoll_pwait, do_epoll_wait, do_getpeername, do_getsockname, do_getsockopt, do_listen,
|
||||
do_poll, do_ppoll, do_pselect6, do_recvfrom, do_recvmsg, do_select, do_sendmmsg, do_sendmsg,
|
||||
do_sendto, do_setsockopt, do_shutdown, do_socket, do_socketpair, mmsghdr, msghdr, msghdr_mut,
|
||||
sigset_argpack,
|
||||
do_sendto, do_setsockopt, do_shutdown, do_socket, do_socketpair, mmsghdr, sigset_argpack,
|
||||
};
|
||||
use crate::process::{
|
||||
do_arch_prctl, do_clone, do_execve, do_exit, do_exit_group, do_futex, do_get_robust_list,
|
||||
@ -143,8 +142,8 @@ macro_rules! process_syscall_table_with_callback {
|
||||
(Accept = 43) => do_accept(fd: c_int, addr: *mut libc::sockaddr, addr_len: *mut libc::socklen_t),
|
||||
(Sendto = 44) => do_sendto(fd: c_int, base: *const c_void, len: size_t, flags: c_int, addr: *const libc::sockaddr, addr_len: libc::socklen_t),
|
||||
(Recvfrom = 45) => do_recvfrom(fd: c_int, base: *mut c_void, len: size_t, flags: c_int, addr: *mut libc::sockaddr, addr_len: *mut libc::socklen_t),
|
||||
(Sendmsg = 46) => do_sendmsg(fd: c_int, msg_ptr: *const msghdr, flags_c: c_int),
|
||||
(Recvmsg = 47) => do_recvmsg(fd: c_int, msg_mut_ptr: *mut msghdr_mut, flags_c: c_int),
|
||||
(Sendmsg = 46) => do_sendmsg(fd: c_int, msg_ptr: *const libc::msghdr, flags_c: c_int),
|
||||
(Recvmsg = 47) => do_recvmsg(fd: c_int, msg_mut_ptr: *mut libc::msghdr, flags_c: c_int),
|
||||
(Shutdown = 48) => do_shutdown(fd: c_int, how: c_int),
|
||||
(Bind = 49) => do_bind(fd: c_int, addr: *const libc::sockaddr, addr_len: libc::socklen_t),
|
||||
(Listen = 50) => do_listen(fd: c_int, backlog: c_int),
|
||||
|
@ -54,6 +54,7 @@ LINK_FLAGS += -lsgx_quote_ex_sim
|
||||
endif
|
||||
endif
|
||||
|
||||
LINK_FLAGS += -L$(PROJECT_DIR)/deps/io-uring/ocalls/target/release/ -lsgx_io_uring_ocalls
|
||||
ALL_BUILD_SUBDIRS := $(sort $(patsubst %/,%,$(dir $(LIBOCCLUM_PAL_SO_REAL) $(EDL_C_OBJS) $(C_OBJS) $(CXX_OBJS) $(VDSO_OBJS))))
|
||||
|
||||
.PHONY: all format format-check clean
|
||||
@ -79,7 +80,8 @@ $(OBJ_DIR)/pal/$(SRC_OBJ)/Enclave_u.c: $(SGX_EDGER8R) ../Enclave.edl
|
||||
$(SGX_EDGER8R) $(SGX_EDGER8R_MODE) --untrusted $(CUR_DIR)/../Enclave.edl \
|
||||
--search-path $(SGX_SDK)/include \
|
||||
--search-path $(RUST_SGX_SDK_DIR)/edl/ \
|
||||
--search-path $(CRATES_DIR)/vdso-time/ocalls
|
||||
--search-path $(CRATES_DIR)/vdso-time/ocalls \
|
||||
--search-path $(PROJECT_DIR)/deps/io-uring/ocalls
|
||||
@echo "GEN <= $@"
|
||||
|
||||
$(OBJ_DIR)/pal/$(SRC_OBJ)/%.o: src/%.c
|
||||
|
Loading…
Reference in New Issue
Block a user