Skip to content

Commit 750f836

Browse files
tamirdThomasdezeeuw
authored andcommitted
Implement {join,leave}_multicast_v4_n
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.
1 parent 91ae811 commit 750f836

File tree

6 files changed

+167
-1
lines changed

6 files changed

+167
-1
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ rustdoc-args = ["--cfg", "docsrs"]
3333
features = ["all"]
3434

3535
[target."cfg(unix)".dependencies]
36-
libc = "0.2.107"
36+
libc = "0.2.113"
3737

3838
[target."cfg(windows)".dependencies]
3939
winapi = { version = "0.3.9", features = ["handleapi", "ws2ipdef", "ws2tcpip"] }

src/lib.rs

+9
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,15 @@ pub use sockaddr::SockAddr;
132132
pub use socket::Socket;
133133
pub use sockref::SockRef;
134134

135+
#[cfg(not(any(
136+
target_os = "haiku",
137+
target_os = "illumos",
138+
target_os = "netbsd",
139+
target_os = "redox",
140+
target_os = "solaris",
141+
)))]
142+
pub use socket::InterfaceIndexOrAddress;
143+
135144
/// Specification of the communication domain for a socket.
136145
///
137146
/// This is a newtype wrapper around an integer which provides a nicer API in

src/socket.rs

+78
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,25 @@ fn set_common_flags(socket: Socket) -> io::Result<Socket> {
713713
Ok(socket)
714714
}
715715

716+
/// A local interface specified by its index or an address assigned to it.
717+
///
718+
/// `Index(0)` and `Address(Ipv4Addr::UNSPECIFIED)` are equivalent and indicate
719+
/// that an appropriate interface should be selected by the system.
720+
#[cfg(not(any(
721+
target_os = "haiku",
722+
target_os = "illumos",
723+
target_os = "netbsd",
724+
target_os = "redox",
725+
target_os = "solaris",
726+
)))]
727+
#[derive(Debug)]
728+
pub enum InterfaceIndexOrAddress {
729+
/// An interface index.
730+
Index(u32),
731+
/// An address assigned to an interface.
732+
Address(Ipv4Addr),
733+
}
734+
716735
/// Socket options get/set using `SOL_SOCKET`.
717736
///
718737
/// Additional documentation can be found in documentation of the OS.
@@ -1106,6 +1125,65 @@ impl Socket {
11061125
}
11071126
}
11081127

1128+
/// Join a multicast group using `IP_ADD_MEMBERSHIP` option on this socket.
1129+
///
1130+
/// This function specifies a new multicast group for this socket to join.
1131+
/// The address must be a valid multicast address, and `interface` specifies
1132+
/// the local interface with which the system should join the multicast
1133+
/// group. See [`InterfaceIndexOrAddress`].
1134+
///
1135+
/// [`InterfaceIndexOrAddress`]: Socket::InterfaceIndexOrAddress
1136+
#[cfg(not(any(
1137+
target_os = "haiku",
1138+
target_os = "illumos",
1139+
target_os = "netbsd",
1140+
target_os = "redox",
1141+
target_os = "solaris",
1142+
)))]
1143+
pub fn join_multicast_v4_n(
1144+
&self,
1145+
multiaddr: &Ipv4Addr,
1146+
interface: &InterfaceIndexOrAddress,
1147+
) -> io::Result<()> {
1148+
let mreqn = sys::to_mreqn(multiaddr, interface);
1149+
unsafe {
1150+
setsockopt(
1151+
self.as_raw(),
1152+
sys::IPPROTO_IP,
1153+
sys::IP_ADD_MEMBERSHIP,
1154+
mreqn,
1155+
)
1156+
}
1157+
}
1158+
1159+
/// Leave a multicast group using `IP_DROP_MEMBERSHIP` option on this socket.
1160+
///
1161+
/// For more information about this option, see [`join_multicast_v4_n`].
1162+
///
1163+
/// [`join_multicast_v4_n`]: Socket::join_multicast_v4_n
1164+
#[cfg(not(any(
1165+
target_os = "haiku",
1166+
target_os = "illumos",
1167+
target_os = "netbsd",
1168+
target_os = "redox",
1169+
target_os = "solaris",
1170+
)))]
1171+
pub fn leave_multicast_v4_n(
1172+
&self,
1173+
multiaddr: &Ipv4Addr,
1174+
interface: &InterfaceIndexOrAddress,
1175+
) -> io::Result<()> {
1176+
let mreqn = sys::to_mreqn(multiaddr, interface);
1177+
unsafe {
1178+
setsockopt(
1179+
self.as_raw(),
1180+
sys::IPPROTO_IP,
1181+
sys::IP_DROP_MEMBERSHIP,
1182+
mreqn,
1183+
)
1184+
}
1185+
}
1186+
11091187
/// Get the value of the `IP_MULTICAST_IF` option for this socket.
11101188
///
11111189
/// For more information about this option, see [`set_multicast_if_v4`].

src/sys/unix.rs

+25
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,31 @@ pub(crate) fn from_in6_addr(addr: in6_addr) -> Ipv6Addr {
10001000
Ipv6Addr::from(addr.s6_addr)
10011001
}
10021002

1003+
#[cfg(not(any(
1004+
target_os = "haiku",
1005+
target_os = "illumos",
1006+
target_os = "netbsd",
1007+
target_os = "redox",
1008+
target_os = "solaris",
1009+
)))]
1010+
pub(crate) fn to_mreqn(
1011+
multiaddr: &Ipv4Addr,
1012+
interface: &crate::socket::InterfaceIndexOrAddress,
1013+
) -> libc::ip_mreqn {
1014+
match interface {
1015+
crate::socket::InterfaceIndexOrAddress::Index(interface) => libc::ip_mreqn {
1016+
imr_multiaddr: to_in_addr(multiaddr),
1017+
imr_address: to_in_addr(&Ipv4Addr::UNSPECIFIED),
1018+
imr_ifindex: *interface as _,
1019+
},
1020+
crate::socket::InterfaceIndexOrAddress::Address(interface) => libc::ip_mreqn {
1021+
imr_multiaddr: to_in_addr(multiaddr),
1022+
imr_address: to_in_addr(interface),
1023+
imr_ifindex: 0,
1024+
},
1025+
}
1026+
}
1027+
10031028
/// Unix only API.
10041029
impl crate::Socket {
10051030
/// Accept a new incoming connection from this listener.

src/sys/windows.rs

+26
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,32 @@ pub(crate) fn from_in6_addr(addr: in6_addr) -> Ipv6Addr {
741741
Ipv6Addr::from(*unsafe { addr.u.Byte() })
742742
}
743743

744+
pub(crate) fn to_mreqn(
745+
multiaddr: &Ipv4Addr,
746+
interface: &crate::socket::InterfaceIndexOrAddress,
747+
) -> IpMreq {
748+
IpMreq {
749+
imr_multiaddr: to_in_addr(multiaddr),
750+
// Per https://docs.microsoft.com/en-us/windows/win32/api/ws2ipdef/ns-ws2ipdef-ip_mreq#members:
751+
//
752+
// imr_interface
753+
//
754+
// The local IPv4 address of the interface or the interface index on
755+
// which the multicast group should be joined or dropped. This value is
756+
// in network byte order. If this member specifies an IPv4 address of
757+
// 0.0.0.0, the default IPv4 multicast interface is used.
758+
//
759+
// To use an interface index of 1 would be the same as an IP address of
760+
// 0.0.0.1.
761+
imr_interface: match interface {
762+
crate::socket::InterfaceIndexOrAddress::Index(interface) => {
763+
to_in_addr(&(*interface).into())
764+
}
765+
crate::socket::InterfaceIndexOrAddress::Address(interface) => to_in_addr(interface),
766+
},
767+
}
768+
}
769+
744770
/// Windows only API.
745771
impl crate::Socket {
746772
/// Sets `HANDLE_FLAG_INHERIT` using `SetHandleInformation`.

tests/socket.rs

+28
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,34 @@ test!(
11911191
set_tcp_user_timeout(Some(Duration::from_secs(10)))
11921192
);
11931193

1194+
#[test]
1195+
#[cfg(not(any(
1196+
target_os = "haiku",
1197+
target_os = "illumos",
1198+
target_os = "netbsd",
1199+
target_os = "redox",
1200+
target_os = "solaris",
1201+
)))]
1202+
fn join_leave_multicast_v4_n() {
1203+
let socket = Socket::new(Domain::IPV4, Type::DGRAM, None).unwrap();
1204+
let multiaddr = Ipv4Addr::new(224, 0, 1, 1);
1205+
let interface = socket2::InterfaceIndexOrAddress::Index(0);
1206+
match socket.leave_multicast_v4_n(&multiaddr, &interface) {
1207+
Ok(()) => panic!("leaving an unjoined group should fail"),
1208+
Err(err) => {
1209+
assert_eq!(err.kind(), io::ErrorKind::AddrNotAvailable);
1210+
#[cfg(unix)]
1211+
assert_eq!(err.raw_os_error(), Some(libc::EADDRNOTAVAIL));
1212+
}
1213+
};
1214+
let () = socket
1215+
.join_multicast_v4_n(&multiaddr, &interface)
1216+
.expect("join multicast group");
1217+
let () = socket
1218+
.leave_multicast_v4_n(&multiaddr, &interface)
1219+
.expect("leave multicast group");
1220+
}
1221+
11941222
#[test]
11951223
#[cfg(all(feature = "all", not(target_os = "redox")))]
11961224
fn header_included() {

0 commit comments

Comments
 (0)