diff --git a/Cargo.toml b/Cargo.toml index 6ca34227..f99228ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ rustdoc-args = ["--cfg", "docsrs"] features = ["all"] [target."cfg(unix)".dependencies] -libc = "0.2.114" +libc = "0.2.124" [target."cfg(windows)".dependencies] winapi = { version = "0.3.9", features = ["handleapi", "ws2ipdef", "ws2tcpip"] } diff --git a/src/socket.rs b/src/socket.rs index ee190c31..0491e109 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -1184,6 +1184,72 @@ impl Socket { } } + /// Join a multicast SSM channel using `IP_ADD_SOURCE_MEMBERSHIP` option on this socket. + /// + /// This function specifies a new multicast channel for this socket to join. + /// The group must be a valid SSM group address, the source must be the address of the sender + /// and `interface` is the address of the local interface with which the system should join the + /// multicast group. If it's [`Ipv4Addr::UNSPECIFIED`] (`INADDR_ANY`) then + /// an appropriate interface is chosen by the system. + #[cfg(not(any( + target_os = "haiku", + target_os = "netbsd", + target_os = "redox", + target_os = "fuchsia", + )))] + pub fn join_ssm_v4( + &self, + source: &Ipv4Addr, + group: &Ipv4Addr, + interface: &Ipv4Addr, + ) -> io::Result<()> { + let mreqs = sys::IpMreqSource { + imr_multiaddr: sys::to_in_addr(group), + imr_interface: sys::to_in_addr(interface), + imr_sourceaddr: sys::to_in_addr(source), + }; + unsafe { + setsockopt( + self.as_raw(), + sys::IPPROTO_IP, + sys::IP_ADD_SOURCE_MEMBERSHIP, + mreqs, + ) + } + } + + /// Leave a multicast group using `IP_DROP_SOURCE_MEMBERSHIP` option on this socket. + /// + /// For more information about this option, see [`join_ssm_v4`]. + /// + /// [`join_ssm_v4`]: Socket::join_ssm_v4 + #[cfg(not(any( + target_os = "haiku", + target_os = "netbsd", + target_os = "redox", + target_os = "fuchsia", + )))] + pub fn leave_ssm_v4( + &self, + source: &Ipv4Addr, + group: &Ipv4Addr, + interface: &Ipv4Addr, + ) -> io::Result<()> { + let mreqs = sys::IpMreqSource { + imr_multiaddr: sys::to_in_addr(group), + imr_interface: sys::to_in_addr(interface), + imr_sourceaddr: sys::to_in_addr(source), + }; + unsafe { + setsockopt( + self.as_raw(), + sys::IPPROTO_IP, + sys::IP_DROP_SOURCE_MEMBERSHIP, + mreqs, + ) + } + } + /// Get the value of the `IP_MULTICAST_IF` option for this socket. /// /// For more information about this option, see [`set_multicast_if_v4`]. diff --git a/src/sys/unix.rs b/src/sys/unix.rs index a0c32eb0..58b48ecd 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -95,6 +95,15 @@ pub(crate) use libc::{ IP_TTL, MSG_OOB, MSG_PEEK, SOL_SOCKET, SO_BROADCAST, SO_ERROR, SO_KEEPALIVE, SO_RCVBUF, SO_RCVTIMEO, SO_REUSEADDR, SO_SNDBUF, SO_SNDTIMEO, SO_TYPE, TCP_NODELAY, }; +#[cfg(not(any( + target_os = "haiku", + target_os = "netbsd", + target_os = "redox", + target_os = "fuchsia", +)))] +pub(crate) use libc::{ + ip_mreq_source as IpMreqSource, IP_ADD_SOURCE_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP, +}; #[cfg(not(any( target_os = "dragonfly", target_os = "freebsd", diff --git a/src/sys/windows.rs b/src/sys/windows.rs index ab598399..fc4ca464 100644 --- a/src/sys/windows.rs +++ b/src/sys/windows.rs @@ -71,8 +71,9 @@ pub(crate) use winapi::shared::ws2ipdef::IP_HDRINCL; pub(crate) use winapi::shared::ws2ipdef::{ IPV6_ADD_MEMBERSHIP, IPV6_DROP_MEMBERSHIP, IPV6_MREQ as Ipv6Mreq, IPV6_MULTICAST_HOPS, IPV6_MULTICAST_IF, IPV6_MULTICAST_LOOP, IPV6_UNICAST_HOPS, IPV6_V6ONLY, IP_ADD_MEMBERSHIP, - IP_DROP_MEMBERSHIP, IP_MREQ as IpMreq, IP_MULTICAST_IF, IP_MULTICAST_LOOP, IP_MULTICAST_TTL, - IP_TOS, IP_TTL, + IP_ADD_SOURCE_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP, IP_MREQ as IpMreq, + IP_MREQ_SOURCE as IpMreqSource, IP_MULTICAST_IF, IP_MULTICAST_LOOP, IP_MULTICAST_TTL, IP_TOS, + IP_TTL, }; pub(crate) use winapi::um::winsock2::{linger, MSG_OOB, MSG_PEEK}; pub(crate) const IPPROTO_IPV6: c_int = winapi::shared::ws2def::IPPROTO_IPV6 as c_int; diff --git a/tests/socket.rs b/tests/socket.rs index d0ee0e3b..c6e3ad33 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -1219,6 +1219,22 @@ fn join_leave_multicast_v4_n() { .expect("leave multicast group"); } +#[test] +#[cfg(not(any( + target_os = "haiku", + target_os = "netbsd", + target_os = "redox", + target_os = "fuchsia", +)))] +fn join_leave_ssm_v4() { + let socket = Socket::new(Domain::IPV4, Type::DGRAM, None).unwrap(); + let g = Ipv4Addr::new(232, 123, 52, 36); + let s = Ipv4Addr::new(62, 40, 109, 31); + let interface = Ipv4Addr::new(0, 0, 0, 0); + let () = socket.join_ssm_v4(&s, &g, &interface).expect("Joined SSM"); + let () = socket.leave_ssm_v4(&s, &g, &interface).expect("Left SSM"); +} + #[test] #[cfg(all(feature = "all", not(target_os = "redox")))] fn header_included() {