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<Box<dyn InternalServiceFactory>>, 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<ServerCommand>, @@ -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<str>, { - 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<S: ToSocketAddrs>( addr: S, backlog: u32, + mptcp: &MPTCP, ) -> io::Result<Vec<MioTcpListener>> { 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<MioTcpListener> { 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"));