From f63de8b36bd629f75ea3fb59f9f67df2d10bcafc Mon Sep 17 00:00:00 2001 From: Martichou Date: Sun, 11 Sep 2022 00:11:48 +0200 Subject: [PATCH] add MPTCP socket protocol (optional) Add the possibility to use the MPTCP protocol at the socket level for users of ServerBuilder. MPTCP is now more widely available since Linux Kernel version >= 5.6. But it still need to be enabled manually using: `sysctl net.mptcp.enabled=1`. (of course, MPTCP is only available on Linux) The new MPTCP struct give the user the option to determine how we'll handle the case where MPTCP is not available on the host, either we crash, or we fallback to regular TCP. Signed-off-by: Martin Andre --- actix-server/CHANGES.md | 1 + actix-server/Cargo.toml | 2 +- actix-server/src/builder.rs | 33 +++++++++++++++++++++++++++++++-- actix-server/src/lib.rs | 1 + actix-server/src/socket.rs | 25 +++++++++++++++++++++++-- 5 files changed, 57 insertions(+), 5 deletions(-) diff --git a/actix-server/CHANGES.md b/actix-server/CHANGES.md index 1147dcbd..6985fcaa 100644 --- a/actix-server/CHANGES.md +++ b/actix-server/CHANGES.md @@ -2,6 +2,7 @@ ## Unreleased - 2023-xx-xx +- ServerBuilder: add support for MPTCP (optional). - Minimum supported Rust version (MSRV) is now 1.60. ## 2.2.0 - 2022-12-21 diff --git a/actix-server/Cargo.toml b/actix-server/Cargo.toml index ade711f4..58f2bba6 100755 --- a/actix-server/Cargo.toml +++ b/actix-server/Cargo.toml @@ -28,7 +28,7 @@ futures-core = { version = "0.3.17", default-features = false, features = ["allo futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] } mio = { version = "0.8", features = ["os-poll", "net"] } num_cpus = "1.13" -socket2 = "0.4.2" # TODO(MSRV 1.64) update to 0.5 +socket2 = "0.5" tokio = { version = "1.23.1", features = ["sync"] } tracing = { version = "0.1.30", default-features = false, features = ["log"] } diff --git a/actix-server/src/builder.rs b/actix-server/src/builder.rs index b6646081..6e80f64f 100644 --- a/actix-server/src/builder.rs +++ b/actix-server/src/builder.rs @@ -14,6 +14,13 @@ use crate::{ Server, }; +#[derive(PartialEq, Eq, Copy, Clone)] +pub enum MPTCP { + Disabled, + TcpFallback, + NoFallback, +} + /// [Server] builder. pub struct ServerBuilder { pub(crate) threads: usize, @@ -21,6 +28,7 @@ pub struct ServerBuilder { pub(crate) backlog: u32, pub(crate) factories: Vec>, pub(crate) sockets: Vec<(usize, String, MioListener)>, + pub(crate) mptcp: MPTCP, pub(crate) exit: bool, pub(crate) listen_os_signals: bool, pub(crate) cmd_tx: UnboundedSender, @@ -45,6 +53,7 @@ impl ServerBuilder { factories: Vec::new(), sockets: Vec::new(), backlog: 2048, + mptcp: MPTCP::Disabled, exit: false, listen_os_signals: true, cmd_tx, @@ -98,6 +107,25 @@ impl ServerBuilder { self } + /// Enable the MPTCP protocol at the socket level. + /// + /// This enable the Multiple Path TCP protocol at the socket level. This means it's managed + /// by the kernel and the application (userspace) doesn't have to deal with path management. + /// + /// Only available in Linux Kernel >= 5.6. If you try to set it on a Windows machine or on + /// an older Linux machine, this will fail with a panic. + /// + /// In Addition to a recent Linux Kernel, you also need to enable the sysctl (if it's not on + /// by default): + /// `sysctl sysctl net.mptcp.enabled=1` + /// + /// This method will have no effect if called after a `bind()`. + #[cfg(target_os = "linux")] + pub fn mptcp(mut self, mptcp_enabled: MPTCP) -> Self { + self.mptcp = mptcp_enabled; + self + } + /// Sets the maximum per-worker number of concurrent connections. /// /// All socket listeners will stop accepting connections when this limit is reached for @@ -146,7 +174,7 @@ impl ServerBuilder { U: ToSocketAddrs, N: AsRef, { - let sockets = bind_addr(addr, self.backlog)?; + let sockets = bind_addr(addr, self.backlog, &self.mptcp)?; trace!("binding server to: {:?}", &sockets); @@ -263,13 +291,14 @@ impl ServerBuilder { pub(super) fn bind_addr( addr: S, backlog: u32, + mptcp: &MPTCP, ) -> io::Result> { let mut opt_err = None; let mut success = false; let mut sockets = Vec::new(); for addr in addr.to_socket_addrs()? { - match create_mio_tcp_listener(addr, backlog) { + match create_mio_tcp_listener(addr, backlog, mptcp) { Ok(lst) => { success = true; sockets.push(lst); diff --git a/actix-server/src/lib.rs b/actix-server/src/lib.rs index 532313b6..bec561e1 100644 --- a/actix-server/src/lib.rs +++ b/actix-server/src/lib.rs @@ -19,6 +19,7 @@ mod waker_queue; mod worker; pub use self::builder::ServerBuilder; +pub use self::builder::MPTCP; pub use self::handle::ServerHandle; pub use self::server::Server; pub use self::service::ServerServiceFactory; diff --git a/actix-server/src/socket.rs b/actix-server/src/socket.rs index 8d2ffe8f..2ef97050 100644 --- a/actix-server/src/socket.rs +++ b/actix-server/src/socket.rs @@ -12,6 +12,8 @@ pub(crate) use { std::os::unix::net::UnixListener as StdUnixListener, }; +use crate::builder::MPTCP; + pub(crate) enum MioListener { Tcp(MioTcpListener), #[cfg(unix)] @@ -224,10 +226,29 @@ mod unix_impl { pub(crate) fn create_mio_tcp_listener( addr: StdSocketAddr, backlog: u32, + mptcp: &MPTCP, ) -> io::Result { use socket2::{Domain, Protocol, Socket, Type}; - let socket = Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP))?; + #[cfg(not(target_os = "linux"))] + let protocol = Protocol::TCP; + #[cfg(target_os = "linux")] + let protocol = if mptcp == &MPTCP::Disabled { + Protocol::TCP + } else { + Protocol::MPTCP + }; + + let socket = match Socket::new(Domain::for_address(addr), Type::STREAM, Some(protocol)) { + Ok(sock) => sock, + Err(err) => { + if mptcp == &MPTCP::TcpFallback { + Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP))? + } else { + return Err(err); + } + } + }; socket.set_reuse_address(true)?; socket.set_nonblocking(true)?; @@ -248,7 +269,7 @@ mod tests { assert_eq!(format!("{}", addr), "127.0.0.1:8080"); let addr: StdSocketAddr = "127.0.0.1:0".parse().unwrap(); - let lst = create_mio_tcp_listener(addr, 128).unwrap(); + let lst = create_mio_tcp_listener(addr, 128, &MPTCP::Disabled).unwrap(); let lst = MioListener::Tcp(lst); assert!(format!("{:?}", lst).contains("TcpListener")); assert!(format!("{}", lst).contains("127.0.0.1"));