Skip to content

Commit

Permalink
Implement {join,leave}_multicast_v4_n
Browse files Browse the repository at this point in the history
These methods allow the local interface to be specified by its index or
an address assigned to it. This is supported in Linux since 2.2, in
FreeBSD since 13.0.0, and in macOS since 10.7.

DragonFlyBSD: DragonFlyBSD/DragonFlyBSD@1926f58.
OpenBSD: openbsd/src@c0ba2d2.

Haiku, illumos, netbsd, redox, and solaris (and perhaps others) do not
support ip_mreqn, so these functions are not available on those systems.

Requires libc with rust-lang/libc@8cba30b,
released in https://crates.io/crates/libc/0.2.113; the libc dependency
is bumped to this version.

Fixes #283.
  • Loading branch information
tamird authored and Thomasdezeeuw committed Jan 20, 2022
1 parent 91ae811 commit 750f836
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 1 deletion.
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
9 changes: 9 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,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

0 comments on commit 750f836

Please sign in to comment.