Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for most DCCP socket options #359

Merged
merged 1 commit into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
}