Skip to content
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ rustdoc-args = ["--cfg", "docsrs"]
features = ["all"]

[target."cfg(unix)".dependencies]
libc = "0.2.107"
libc = "0.2.113"

[target."cfg(windows)".dependencies]
winapi = { version = "0.3.9", features = ["handleapi", "ws2ipdef", "ws2tcpip"] }
Expand Down
16 changes: 11 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,8 @@ mod sockaddr;
mod socket;
mod sockref;

#[cfg(unix)]
#[path = "sys/unix.rs"]
mod sys;
#[cfg(windows)]
#[path = "sys/windows.rs"]
#[cfg_attr(unix, path = "sys/unix.rs")]
#[cfg_attr(windows, path = "sys/windows.rs")]
mod sys;

#[cfg(not(any(windows, unix)))]
Expand All @@ -135,6 +132,15 @@ pub use sockaddr::SockAddr;
pub use socket::Socket;
pub use sockref::SockRef;

#[cfg(not(any(
target_os = "haiku",
target_os = "illumos",
target_os = "netbsd",
target_os = "redox",
target_os = "solaris",
)))]
pub use socket::InterfaceIndexOrAddress;

/// Specification of the communication domain for a socket.
///
/// This is a newtype wrapper around an integer which provides a nicer API in
Expand Down
78 changes: 78 additions & 0 deletions src/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,25 @@ fn set_common_flags(socket: Socket) -> io::Result<Socket> {
Ok(socket)
}

/// A local interface specified by its index or an address assigned to it.
///
/// `Index(0)` and `Address(Ipv4Addr::UNSPECIFIED)` are equivalent and indicate
/// that an appropriate interface should be selected by the system.
#[cfg(not(any(
target_os = "haiku",
target_os = "illumos",
target_os = "netbsd",
target_os = "redox",
target_os = "solaris",
)))]
#[derive(Debug)]
pub enum InterfaceIndexOrAddress {
/// An interface index.
Index(u32),
/// An address assigned to an interface.
Address(Ipv4Addr),
}

/// Socket options get/set using `SOL_SOCKET`.
///
/// Additional documentation can be found in documentation of the OS.
Expand Down Expand Up @@ -1106,6 +1125,65 @@ impl Socket {
}
}

/// Join a multicast group using `IP_ADD_MEMBERSHIP` option on this socket.
///
/// This function specifies a new multicast group for this socket to join.
/// The address must be a valid multicast address, and `interface` specifies
/// the local interface with which the system should join the multicast
/// group. See [`InterfaceIndexOrAddress`].
///
/// [`InterfaceIndexOrAddress`]: Socket::InterfaceIndexOrAddress
#[cfg(not(any(
target_os = "haiku",
target_os = "illumos",
target_os = "netbsd",
target_os = "redox",
target_os = "solaris",
)))]
pub fn join_multicast_v4_n(
&self,
multiaddr: &Ipv4Addr,
interface: &InterfaceIndexOrAddress,
) -> io::Result<()> {
let mreqn = sys::to_mreqn(multiaddr, interface);
unsafe {
setsockopt(
self.as_raw(),
sys::IPPROTO_IP,
sys::IP_ADD_MEMBERSHIP,
mreqn,
)
}
}

/// Leave a multicast group using `IP_DROP_MEMBERSHIP` option on this socket.
///
/// For more information about this option, see [`join_multicast_v4_n`].
///
/// [`join_multicast_v4_n`]: Socket::join_multicast_v4_n
#[cfg(not(any(
target_os = "haiku",
target_os = "illumos",
target_os = "netbsd",
target_os = "redox",
target_os = "solaris",
)))]
pub fn leave_multicast_v4_n(
&self,
multiaddr: &Ipv4Addr,
interface: &InterfaceIndexOrAddress,
) -> io::Result<()> {
let mreqn = sys::to_mreqn(multiaddr, interface);
unsafe {
setsockopt(
self.as_raw(),
sys::IPPROTO_IP,
sys::IP_DROP_MEMBERSHIP,
mreqn,
)
}
}

/// Get the value of the `IP_MULTICAST_IF` option for this socket.
///
/// For more information about this option, see [`set_multicast_if_v4`].
Expand Down
25 changes: 25 additions & 0 deletions src/sys/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,31 @@ pub(crate) fn from_in6_addr(addr: in6_addr) -> Ipv6Addr {
Ipv6Addr::from(addr.s6_addr)
}

#[cfg(not(any(
target_os = "haiku",
target_os = "illumos",
target_os = "netbsd",
target_os = "redox",
target_os = "solaris",
)))]
pub(crate) fn to_mreqn(
multiaddr: &Ipv4Addr,
interface: &crate::socket::InterfaceIndexOrAddress,
) -> libc::ip_mreqn {
match interface {
crate::socket::InterfaceIndexOrAddress::Index(interface) => libc::ip_mreqn {
imr_multiaddr: to_in_addr(multiaddr),
imr_address: to_in_addr(&Ipv4Addr::UNSPECIFIED),
imr_ifindex: *interface as _,
},
crate::socket::InterfaceIndexOrAddress::Address(interface) => libc::ip_mreqn {
imr_multiaddr: to_in_addr(multiaddr),
imr_address: to_in_addr(interface),
imr_ifindex: 0,
},
}
}

/// Unix only API.
impl crate::Socket {
/// Accept a new incoming connection from this listener.
Expand Down
26 changes: 26 additions & 0 deletions src/sys/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,32 @@ pub(crate) fn from_in6_addr(addr: in6_addr) -> Ipv6Addr {
Ipv6Addr::from(*unsafe { addr.u.Byte() })
}

pub(crate) fn to_mreqn(
multiaddr: &Ipv4Addr,
interface: &crate::socket::InterfaceIndexOrAddress,
) -> IpMreq {
IpMreq {
imr_multiaddr: to_in_addr(multiaddr),
// Per https://docs.microsoft.com/en-us/windows/win32/api/ws2ipdef/ns-ws2ipdef-ip_mreq#members:
//
// imr_interface
//
// The local IPv4 address of the interface or the interface index on
// which the multicast group should be joined or dropped. This value is
// in network byte order. If this member specifies an IPv4 address of
// 0.0.0.0, the default IPv4 multicast interface is used.
//
// To use an interface index of 1 would be the same as an IP address of
// 0.0.0.1.
imr_interface: match interface {
crate::socket::InterfaceIndexOrAddress::Index(interface) => {
to_in_addr(&(*interface).into())
}
crate::socket::InterfaceIndexOrAddress::Address(interface) => to_in_addr(interface),
},
}
}

/// Windows only API.
impl crate::Socket {
/// Sets `HANDLE_FLAG_INHERIT` using `SetHandleInformation`.
Expand Down
28 changes: 28 additions & 0 deletions tests/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,34 @@ test!(
set_tcp_user_timeout(Some(Duration::from_secs(10)))
);

#[test]
#[cfg(not(any(
target_os = "haiku",
target_os = "illumos",
target_os = "netbsd",
target_os = "redox",
target_os = "solaris",
)))]
fn join_leave_multicast_v4_n() {
let socket = Socket::new(Domain::IPV4, Type::DGRAM, None).unwrap();
let multiaddr = Ipv4Addr::new(224, 0, 1, 1);
let interface = socket2::InterfaceIndexOrAddress::Index(0);
match socket.leave_multicast_v4_n(&multiaddr, &interface) {
Ok(()) => panic!("leaving an unjoined group should fail"),
Err(err) => {
assert_eq!(err.kind(), io::ErrorKind::AddrNotAvailable);
#[cfg(unix)]
assert_eq!(err.raw_os_error(), Some(libc::EADDRNOTAVAIL));
}
};
let () = socket
.join_multicast_v4_n(&multiaddr, &interface)
.expect("join multicast group");
let () = socket
.leave_multicast_v4_n(&multiaddr, &interface)
.expect("leave multicast group");
}

#[test]
#[cfg(all(feature = "all", not(target_os = "redox")))]
fn header_included() {
Expand Down