diff --git a/src/lib.rs b/src/lib.rs index a3963267..7e21cb56 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 5f99bf0b..f7b41e42 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -57,12 +57,17 @@ 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")))] @@ -392,6 +397,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, ); @@ -2001,6 +2008,221 @@ impl crate::Socket { pub fn detach_filter(&self) -> io::Result<()> { unsafe { setsockopt(self.as_raw(), libc::SOL_SOCKET, libc::SO_DETACH_FILTER, 0) } } + + /// Set value for the `DCCP_SOCKOPT_SERVICE` option on this socket. + /// + /// Sets the 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. + /// + /// Different TX and RX CCIDs, while in practice allowed, is rarely done and not supported by this library. + // todo: allow passing of multiple CCIDs + #[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. + /// + /// The underlying sockopt used here is the DCCP_SOCKOPT_TX_CCID which means that theoretically only the CCID for TX is returned. + /// + /// 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_ccid(&self) -> io::Result { + unsafe { getsockopt(self.as_raw(), libc::SOL_DCCP, libc::DCCP_SOCKOPT_TX_CCID) } + } + + /// Set value for the `DCCP_SOCKOPT_SERVER_TIMEWAIT` option on this socket. + /// + /// Enables the server (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 always 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, qlen: u32) -> io::Result<()> { + unsafe { + setsockopt( + self.as_raw(), + libc::SOL_DCCP, + libc::DCCP_SOCKOPT_QPOLICY_TXQLEN, + qlen, + ) + } + } + + /// 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. + /// + /// This option is read-only and returns the list of CCIDs supported by the endpoint. + #[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> { + // Creating the buffer with a value of 5 is ~probably~ fine. + // https://www.kernel.org/doc/html/latest/networking/dccp.html says the minimum is 4, however currently there are only 3 CCIDs implemented in the kernel. + // So 5 should cover us for some time. Even if 3 more CCIDs are implemented it would be very unlikely, that all 6 are available. + let mut buf: [MaybeUninit; 5] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut len = buf.len() as libc::socklen_t; + syscall!(getsockopt( + self.as_raw(), + libc::SOL_DCCP, + libc::DCCP_SOCKOPT_AVAILABLE_CCIDS, + buf.as_mut_ptr().cast(), + &mut len, + ))?; + let buf = &buf[..len as usize - 1]; + Ok(unsafe { &*(buf as *const [_] as *const [u8]) }.into()) + } + + /// 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, + ) + } + } } #[cfg_attr(docsrs, doc(cfg(unix)))] diff --git a/tests/socket.rs b/tests/socket.rs index b5f6dfa1..97b0b3e3 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"), @@ -1297,3 +1299,34 @@ fn header_included() { let got = socket.header_included().expect("failed to get value"); assert_eq!(got, true, "set and get values differ"); } + +#[test] +#[ignore = "DCCP support is not enabled in all kernels of majors Linux distros"] +#[cfg(all(feature = "all", target_os = "linux"))] +fn dccp() { + let socket_s = Socket::new(Domain::IPV4, Type::DCCP, Some(Protocol::DCCP)).unwrap(); + let addr = "127.0.0.1:8686".parse::().unwrap().into(); + socket_s.set_dccp_service(45).unwrap(); + assert!(socket_s.dccp_service().unwrap() == 45); + assert!(socket_s.dccp_cur_mps().unwrap() > 0); + assert!(socket_s.dccp_available_ccids().is_ok()); + assert!( + socket_s.dccp_send_cscov().unwrap() == 0, + "sender cscov should be zero by default" + ); + socket_s.set_dccp_ccid(2).unwrap(); + socket_s.set_dccp_qpolicy_txqlen(6).unwrap(); + assert!(socket_s.dccp_qpolicy_txqlen().unwrap() == 6); + socket_s.bind(&addr).unwrap(); + socket_s.listen(10).unwrap(); + + let mut socket_c = Socket::new(Domain::IPV4, Type::DCCP, Some(Protocol::DCCP)).unwrap(); + socket_c.set_dccp_service(45).unwrap(); + socket_c.connect(&addr).unwrap(); + + let (mut socket_s_c, _) = socket_s.accept().unwrap(); + let msg = "Hello World!"; + assert!(socket_c.write(msg.as_bytes()).unwrap() == msg.len()); + let mut recv_buf = [0_u8; 64]; + assert!(socket_s_c.read(&mut recv_buf).unwrap() == msg.len()); +}