#[cfg(feature = "socket-opts")]
use std::net::SocketAddr;
#[derive(Clone, Debug, Default)]
pub(crate) struct SocketOptions {
#[cfg(feature = "socket-opts")]
pub v6only: Option<bool>,
#[cfg(feature = "socket-opts")]
pub dscp: Option<u8>,
}
#[cfg(feature = "socket-opts")]
impl SocketOptions {
pub(crate) fn bind_udp(&self, bind_addr: SocketAddr) -> std::io::Result<std::net::UdpSocket> {
use socket2::{Domain, Protocol, Socket, Type};
let domain = if bind_addr.is_ipv4() {
Domain::IPV4
} else {
Domain::IPV6
};
let socket = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP))?;
if let Some(v6only) = self.v6only
&& bind_addr.is_ipv6()
{
socket.set_only_v6(v6only)?;
}
if let Some(dscp) = self.dscp {
let tos = u32::from(dscp & 0x3F) << 2;
if bind_addr.is_ipv4() {
socket.set_tos_v4(tos)?;
} else {
#[cfg(not(target_os = "windows"))]
socket.set_tclass_v6(tos)?;
}
}
socket.set_nonblocking(true)?;
socket.bind(&bind_addr.into())?;
Ok(socket.into())
}
}
#[cfg(test)]
#[cfg(feature = "socket-opts")]
mod tests {
use super::*;
#[test]
fn test_default_socket_options() {
let opts = SocketOptions::default();
assert_eq!(opts.v6only, None);
assert_eq!(opts.dscp, None);
}
#[test]
fn test_bind_udp_v4() {
let opts = SocketOptions::default();
let addr: SocketAddr = "0.0.0.0:0".parse().unwrap();
let sock = opts.bind_udp(addr).unwrap();
assert!(sock.local_addr().unwrap().is_ipv4());
}
#[test]
fn test_bind_udp_v6() {
let opts = SocketOptions {
v6only: Some(true),
dscp: None,
};
let addr: SocketAddr = "[::]:0".parse().unwrap();
let sock = opts.bind_udp(addr).unwrap();
assert!(sock.local_addr().unwrap().is_ipv6());
}
#[test]
fn test_bind_udp_with_dscp() {
let opts = SocketOptions {
v6only: None,
dscp: Some(46), };
let addr: SocketAddr = "0.0.0.0:0".parse().unwrap();
let sock = opts.bind_udp(addr).unwrap();
assert!(sock.local_addr().is_ok());
}
}