diff --git a/src/lib.rs b/src/lib.rs index 0b08a0f8..7b134de7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -257,6 +257,13 @@ impl Type { /// Used for protocols such as UDP. pub const DGRAM: Type = Type(sys::SOCK_DGRAM); + /// Type corresponding to `SOCK_DCCP`. + /// + /// Used for the DCCP protocol. + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub const DCCP: Type = Type(sys::SOCK_DCCP); + /// Type corresponding to `SOCK_SEQPACKET`. #[cfg(feature = "all")] #[cfg_attr(docsrs, doc(cfg(feature = "all")))] @@ -307,6 +314,11 @@ impl Protocol { /// Protocol corresponding to `MPTCP`. pub const MPTCP: Protocol = Protocol(sys::IPPROTO_MPTCP); + /// Protocol corresponding to `DCCP`. + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub const DCCP: Protocol = Protocol(sys::IPPROTO_DCCP); + #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))] /// Protocol corresponding to `SCTP`. pub const SCTP: Protocol = Protocol(sys::IPPROTO_SCTP); diff --git a/src/sys/unix.rs b/src/sys/unix.rs index d8403a0f..088ab94a 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -57,12 +57,16 @@ pub(crate) use libc::c_int; // Used in `Domain`. pub(crate) use libc::{AF_INET, AF_INET6, AF_UNIX}; // Used in `Type`. +#[cfg(all(feature = "all", target_os = "linux"))] +pub(crate) use libc::SOCK_DCCP; #[cfg(all(feature = "all", not(target_os = "redox")))] pub(crate) use libc::SOCK_RAW; #[cfg(feature = "all")] pub(crate) use libc::SOCK_SEQPACKET; pub(crate) use libc::{SOCK_DGRAM, SOCK_STREAM}; // Used in `Protocol`. +#[cfg(all(feature = "all", target_os = "linux"))] +pub(crate) use libc::IPPROTO_DCCP; #[cfg(target_os = "linux")] pub(crate) use libc::IPPROTO_MPTCP; #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))] @@ -382,6 +386,8 @@ impl_debug!( Type, libc::SOCK_STREAM, libc::SOCK_DGRAM, + #[cfg(all(feature = "all", target_os = "linux"))] + libc::SOCK_DCCP, #[cfg(not(target_os = "redox"))] libc::SOCK_RAW, #[cfg(not(any(target_os = "redox", target_os = "haiku")))] @@ -419,6 +425,8 @@ impl_debug!( libc::IPPROTO_UDP, #[cfg(target_os = "linux")] libc::IPPROTO_MPTCP, + #[cfg(all(feature = "all", target_os = "linux"))] + libc::IPPROTO_DCCP, #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))] libc::IPPROTO_SCTP, ); @@ -2213,6 +2221,258 @@ impl crate::Socket { )) .map(|_| ()) } + + /// Set value for the `DCCP_SOCKOPT_SERVICE` option on this socket. + /// + /// Sets the DCCP service. The specification mandates use of service codes. + /// If this socket option is not set, the socket will fall back to 0 (which + /// means that no meaningful service code is present). On active sockets + /// this is set before [`connect`]. On passive sockets up to 32 service + /// codes can be set before calling [`bind`] + /// + /// [`connect`]: crate::Socket::connect + /// [`bind`]: crate::Socket::bind + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub fn set_dccp_service(&self, code: u32) -> io::Result<()> { + unsafe { + setsockopt( + self.as_raw(), + libc::SOL_DCCP, + libc::DCCP_SOCKOPT_SERVICE, + code, + ) + } + } + + /// Get the value of the `DCCP_SOCKOPT_SERVICE` option on this socket. + /// + /// For more information about this option see [`set_dccp_service`] + /// + /// [`set_dccp_service`]: crate::Socket::set_dccp_service + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub fn dccp_service(&self) -> io::Result { + unsafe { getsockopt(self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_SERVICE) } + } + + /// Set value for the `DCCP_SOCKOPT_CCID` option on this socket. + /// + /// This option sets both the TX and RX CCIDs at the same time. + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub fn set_dccp_ccid(&self, ccid: u8) -> io::Result<()> { + unsafe { setsockopt(self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_CCID, ccid) } + } + + /// Get the value of the `DCCP_SOCKOPT_TX_CCID` option on this socket. + /// + /// For more information about this option see [`set_dccp_ccid`]. + /// + /// [`set_dccp_ccid`]: crate::Socket::set_dccp_ccid + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub fn dccp_tx_ccid(&self) -> io::Result { + unsafe { getsockopt(self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_TX_CCID) } + } + + /// Get the value of the `DCCP_SOCKOPT_RX_CCID` option on this socket. + /// + /// For more information about this option see [`set_dccp_ccid`]. + /// + /// [`set_dccp_ccid`]: crate::Socket::set_dccp_ccid + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub fn dccp_xx_ccid(&self) -> io::Result { + unsafe { getsockopt(self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_RX_CCID) } + } + + /// Set value for the `DCCP_SOCKOPT_SERVER_TIMEWAIT` option on this socket. + /// + /// Enables a listening socket to hold timewait state when closing the + /// connection. This option must be set after `accept` returns. + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub fn set_dccp_server_timewait(&self, hold_timewait: bool) -> io::Result<()> { + unsafe { + setsockopt( + self.as_raw(), + libc::SOL_DCCP, + libc::DCCP_SOCKOPT_SERVER_TIMEWAIT, + hold_timewait as c_int, + ) + } + } + + /// Get the value of the `DCCP_SOCKOPT_SERVER_TIMEWAIT` option on this socket. + /// + /// For more information see [`set_dccp_server_timewait`] + /// + /// [`set_dccp_server_timewait`]: crate::Socket::set_dccp_server_timewait + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub fn dccp_server_timewait(&self) -> io::Result { + unsafe { + getsockopt( + self.as_raw(), + libc::SOL_DCCP, + libc::DCCP_SOCKOPT_SERVER_TIMEWAIT, + ) + } + } + + /// Set value for the `DCCP_SOCKOPT_SEND_CSCOV` option on this socket. + /// + /// Both this option and `DCCP_SOCKOPT_RECV_CSCOV` are used for setting the + /// partial checksum coverage. The default is that checksums always cover + /// the entire packet and that only fully covered application data is + /// accepted by the receiver. Hence, when using this feature on the sender, + /// it must be enabled at the receiver too, with suitable choice of CsCov. + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub fn set_dccp_send_cscov(&self, level: u32) -> io::Result<()> { + unsafe { + setsockopt( + self.as_raw(), + libc::SOL_DCCP, + libc::DCCP_SOCKOPT_SEND_CSCOV, + level, + ) + } + } + + /// Get the value of the `DCCP_SOCKOPT_SEND_CSCOV` option on this socket. + /// + /// For more information on this option see [`set_dccp_send_cscov`]. + /// + /// [`set_dccp_send_cscov`]: crate::Socket::set_dccp_send_cscov + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub fn dccp_send_cscov(&self) -> io::Result { + unsafe { getsockopt(self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_SEND_CSCOV) } + } + + /// Set the value of the `DCCP_SOCKOPT_RECV_CSCOV` option on this socket. + /// + /// This option is only useful when combined with [`set_dccp_send_cscov`]. + /// + /// [`set_dccp_send_cscov`]: crate::Socket::set_dccp_send_cscov + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub fn set_dccp_recv_cscov(&self, level: u32) -> io::Result<()> { + unsafe { + setsockopt( + self.as_raw(), + libc::SOL_DCCP, + libc::DCCP_SOCKOPT_RECV_CSCOV, + level, + ) + } + } + + /// Get the value of the `DCCP_SOCKOPT_RECV_CSCOV` option on this socket. + /// + /// For more information on this option see [`set_dccp_recv_cscov`]. + /// + /// [`set_dccp_recv_cscov`]: crate::Socket::set_dccp_recv_cscov + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub fn dccp_recv_cscov(&self) -> io::Result { + unsafe { getsockopt(self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_RECV_CSCOV) } + } + + /// Set value for the `DCCP_SOCKOPT_QPOLICY_TXQLEN` option on this socket. + /// + /// This option sets the maximum length of the output queue. A zero value is + /// interpreted as unbounded queue length. + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub fn set_dccp_qpolicy_txqlen(&self, length: u32) -> io::Result<()> { + unsafe { + setsockopt( + self.as_raw(), + libc::SOL_DCCP, + libc::DCCP_SOCKOPT_QPOLICY_TXQLEN, + length, + ) + } + } + + /// Get the value of the `DCCP_SOCKOPT_QPOLICY_TXQLEN` on this socket. + /// + /// For more information on this option see [`set_dccp_qpolicy_txqlen`]. + /// + /// [`set_dccp_qpolicy_txqlen`]: crate::Socket::set_dccp_qpolicy_txqlen + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub fn dccp_qpolicy_txqlen(&self) -> io::Result { + unsafe { + getsockopt( + self.as_raw(), + libc::SOL_DCCP, + libc::DCCP_SOCKOPT_QPOLICY_TXQLEN, + ) + } + } + + /// Get the value of the `DCCP_SOCKOPT_AVAILABLE_CCIDS` option on this socket. + /// + /// Returns the list of CCIDs supported by the endpoint. + /// + /// The parameter `N` is used to get the maximum number of supported + /// endpoints. The [documentation] recommends a minimum of four at the time + /// of writing. + /// + /// [documentation]: https://www.kernel.org/doc/html/latest/networking/dccp.html + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub fn dccp_available_ccids(&self) -> io::Result> { + let mut endpoints = [0; N]; + let mut length = endpoints.len() as libc::socklen_t; + syscall!(getsockopt( + self.as_raw(), + libc::SOL_DCCP, + libc::DCCP_SOCKOPT_AVAILABLE_CCIDS, + endpoints.as_mut_ptr().cast(), + &mut length, + ))?; + Ok(CcidEndpoints { endpoints, length }) + } + + /// Get the value of the `DCCP_SOCKOPT_GET_CUR_MPS` option on this socket. + /// + /// This option retrieves the current maximum packet size (application + /// payload size) in bytes. + #[cfg(all(feature = "all", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] + pub fn dccp_cur_mps(&self) -> io::Result { + unsafe { + getsockopt( + self.as_raw(), + libc::SOL_DCCP, + libc::DCCP_SOCKOPT_GET_CUR_MPS, + ) + } + } +} + +/// See [`Socket::dccp_available_ccids`]. +#[cfg(all(feature = "all", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] +#[derive(Debug)] +pub struct CcidEndpoints { + endpoints: [u8; N], + length: u32, +} + +#[cfg(all(feature = "all", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))] +impl std::ops::Deref for CcidEndpoints { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + &self.endpoints[0..self.length as usize] + } } #[cfg_attr(docsrs, doc(cfg(unix)))] diff --git a/tests/socket.rs b/tests/socket.rs index c3872bd1..78d914a5 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -108,6 +108,8 @@ fn protocol_fmt_debug() { (Protocol::UDP, "IPPROTO_UDP"), #[cfg(target_os = "linux")] (Protocol::MPTCP, "IPPROTO_MPTCP"), + #[cfg(all(feature = "all", target_os = "linux"))] + (Protocol::DCCP, "IPPROTO_DCCP"), #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))] (Protocol::SCTP, "IPPROTO_SCTP"), (500.into(), "500"), @@ -1387,3 +1389,34 @@ fn tcp_congestion() { new_tcp_ca, ); } + +#[test] +#[ignore = "DCCP support is not enabled in all kernels of majors Linux distros"] +#[cfg(all(feature = "all", target_os = "linux"))] +fn dccp() { + let listener = Socket::new(Domain::IPV4, Type::DCCP, Some(Protocol::DCCP)).unwrap(); + let addr = "127.0.0.1:0".parse::().unwrap().into(); + listener.set_dccp_service(45).unwrap(); + assert!(listener.dccp_service().unwrap() == 45); + assert!(listener.dccp_cur_mps().unwrap() > 0); + assert!(listener.dccp_available_ccids::<4>().unwrap().len() >= 3); + assert!( + listener.dccp_send_cscov().unwrap() == 0, + "sender cscov should be zero by default" + ); + listener.set_dccp_ccid(2).unwrap(); + listener.set_dccp_qpolicy_txqlen(6).unwrap(); + assert!(listener.dccp_qpolicy_txqlen().unwrap() == 6); + listener.bind(&addr).unwrap(); + listener.listen(10).unwrap(); + + let mut client = Socket::new(Domain::IPV4, Type::DCCP, Some(Protocol::DCCP)).unwrap(); + client.set_dccp_service(45).unwrap(); + client.connect(&addr).unwrap(); + + let (mut accepted, _) = listener.accept().unwrap(); + let msg = "Hello World!"; + assert!(client.write(msg.as_bytes()).unwrap() == msg.len()); + let mut recv_buf = [0_u8; 64]; + assert!(accepted.read(&mut recv_buf).unwrap() == msg.len()); +}