Skip to content

Commit

Permalink
Add support for most DCCP socket options
Browse files Browse the repository at this point in the history
Not all socket options are added. Most notably DCCP_SOCKOPT_QPOLICY_ID
could not be added since some constants needed for it aren't available
in the libc crate. Namely  DCCPQ_POLICY_SIMPLE, DCCPQ_POLICY_PRIO and
DCCP_SCM_PRIORITY.

Co-authored-by: Thomas de Zeeuw <thomasdezeeuw@gmail.com>
  • Loading branch information
onestay and Thomasdezeeuw committed Jan 17, 2023
1 parent 017455c commit 8cafc27
Show file tree
Hide file tree
Showing 3 changed files with 305 additions and 0 deletions.
12 changes: 12 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")))]
Expand Down Expand Up @@ -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);
Expand Down
260 changes: 260 additions & 0 deletions src/sys/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")))]
Expand Down Expand Up @@ -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")))]
Expand Down Expand Up @@ -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,
);
Expand Down Expand Up @@ -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<u32> {
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<u32> {
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<u32> {
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<bool> {
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<u32> {
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<u32> {
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<u32> {
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<const N: usize>(&self) -> io::Result<CcidEndpoints<N>> {
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<u32> {
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<const N: usize> {
endpoints: [u8; N],
length: u32,
}

#[cfg(all(feature = "all", target_os = "linux"))]
#[cfg_attr(docsrs, doc(cfg(feature = "all", target_os = "linux")))]
impl<const N: usize> std::ops::Deref for CcidEndpoints<N> {
type Target = [u8];

fn deref(&self) -> &[u8] {
&self.endpoints[0..self.length as usize]
}
}

#[cfg_attr(docsrs, doc(cfg(unix)))]
Expand Down
33 changes: 33 additions & 0 deletions tests/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -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::<SocketAddr>().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());
}

0 comments on commit 8cafc27

Please sign in to comment.