diff --git a/src/source.rs b/src/source.rs index f701751..a0c4691 100644 --- a/src/source.rs +++ b/src/source.rs @@ -443,12 +443,12 @@ impl SacnSource { self.internal.lock().unwrap().set_is_sending_discovery(val); } - /// Returns the multicast time to live of the socket. + /// Returns the multicast time to live of the socket (the multicast hop limit for an IPv6 source). pub fn multicast_ttl(&self) -> Result { unlock_internal(&self.internal)?.multicast_ttl() } - /// Sets the multicast time to live. + /// Sets the multicast time to live (the multicast hop limit for an IPv6 source). /// /// # Arguments /// `multicast_ttl`: The new time to live value for network packets sent using multicast. @@ -462,7 +462,7 @@ impl SacnSource { unlock_internal_mut(&mut self.internal)?.set_multicast_ttl(multicast_ttl) } - /// Returns the current Time To Live for unicast packets send by this source. + /// Returns the current Time To Live for unicast packets send by this source (the unicast hop limit for an IPv6 source). /// /// # Errors /// Io: Returned if the TTL cannot be retrieved from the underlying socket. @@ -473,7 +473,7 @@ impl SacnSource { unlock_internal(&self.internal)?.ttl() } - /// Sets the Time To Live for packets sent by this source. + /// Sets the Time To Live for packets sent by this source (the unicast hop limit for an IPv6 source). /// /// # Arguments /// ttl: The new time to live value for new packets. @@ -487,7 +487,7 @@ impl SacnSource { unlock_internal_mut(&mut self.internal)?.set_ttl(ttl) } - /// Sets if multicast loop is enabled. + /// Sets if multicast loop is enabled for an IPv4 source. Returns an error on an IPv6 source; use `set_multicast_loop` instead. /// /// # Arguments: /// `multicast_loop`: If true then multicast loop is enabled, if false it is not. @@ -501,6 +501,22 @@ impl SacnSource { unlock_internal_mut(&mut self.internal)?.set_multicast_loop_v4(multicast_loop) } + /// Sets if multicast loop is enabled, for either an IPv4 or an IPv6 source. + /// + /// Unlike `set_multicast_loop_v4`, this works on IPv6 sources too. + /// + /// # Arguments: + /// `multicast_loop`: If true then multicast loop is enabled, if false it is not. + /// + /// # Errors + /// Io: Returned if the multicast loop option fails to be set on the socket. + /// + /// `SourceCorrupt`: Returned if the Mutex used to control access to the internal sender is poisoned by a thread encountering + /// a panic while accessing causing the source to be left in a potentially inconsistent state. + pub fn set_multicast_loop(&mut self, multicast_loop: bool) -> Result<()> { + unlock_internal_mut(&mut self.internal)?.set_multicast_loop(multicast_loop) + } + /// Returns true if multicast loop is enabled, false if not. /// /// # Errors @@ -1177,7 +1193,11 @@ impl SacnSourceInternal { /// # Errors /// Io: Returned if the multicast TTL fails to be set on the underlying socket. fn set_multicast_ttl(&self, multicast_ttl: u32) -> Result<()> { - Ok(self.socket.set_multicast_ttl_v4(multicast_ttl)?) + if self.addr.is_ipv6() { + Ok(self.socket.set_multicast_hops_v6(multicast_ttl)?) + } else { + Ok(self.socket.set_multicast_ttl_v4(multicast_ttl)?) + } } /// Returns the current Time To Live for unicast packets send by this source. @@ -1185,7 +1205,11 @@ impl SacnSourceInternal { /// # Errors /// Io: Returned if the TTL cannot be retrieved from the underlying socket. fn ttl(&self) -> Result { - Ok(self.socket.ttl_v4()?) + if self.addr.is_ipv6() { + Ok(self.socket.unicast_hops_v6()?) + } else { + Ok(self.socket.ttl_v4()?) + } } /// Sets the Time To Live for unicast packets sent by this source. @@ -1196,12 +1220,20 @@ impl SacnSourceInternal { /// # Errors /// Io: Returned if the TTL fails to be set on the underlying socket. fn set_ttl(&mut self, ttl: u32) -> Result<()> { - Ok(self.socket.set_ttl_v4(ttl)?) + if self.addr.is_ipv6() { + Ok(self.socket.set_unicast_hops_v6(ttl)?) + } else { + Ok(self.socket.set_ttl_v4(ttl)?) + } } /// Returns the multicast time to live of the socket. fn multicast_ttl(&self) -> Result { - Ok(self.socket.multicast_ttl_v4()?) + if self.addr.is_ipv6() { + Ok(self.socket.multicast_hops_v6()?) + } else { + Ok(self.socket.multicast_ttl_v4()?) + } } /// Sets if multicast loop is enabled. @@ -1215,9 +1247,22 @@ impl SacnSourceInternal { Ok(self.socket.set_multicast_loop_v4(multicast_loop)?) } + /// Sets if multicast loop is enabled, using the option for this source's IP version. + fn set_multicast_loop(&self, multicast_loop: bool) -> Result<()> { + if self.addr.is_ipv6() { + Ok(self.socket.set_multicast_loop_v6(multicast_loop)?) + } else { + Ok(self.socket.set_multicast_loop_v4(multicast_loop)?) + } + } + /// Returns true if multicast loop is enabled, false if not. fn multicast_loop(&self) -> Result { - Ok(self.socket.multicast_loop_v4()?) + if self.addr.is_ipv6() { + Ok(self.socket.multicast_loop_v6()?) + } else { + Ok(self.socket.multicast_loop_v4()?) + } } /// Returns the universes currently registered on this source. diff --git a/tests/src_tests.rs b/tests/src_tests.rs index ee9605f..be2d820 100644 --- a/tests/src_tests.rs +++ b/tests/src_tests.rs @@ -702,3 +702,36 @@ const TEST_DATA_MULTIPLE_UNIVERSE: [u8; 712] = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, ]; + +/// Checks that the TTL / hop limit and multicast loop options can be set and read back for the given source. +fn check_ttl_and_loop(mut src: SacnSource) { + src.set_ttl(7).unwrap(); + assert_eq!(src.ttl().unwrap(), 7); + src.set_multicast_ttl(3).unwrap(); + assert_eq!(src.multicast_ttl().unwrap(), 3); + src.set_multicast_loop(false).unwrap(); + assert!(!src.multicast_loop().unwrap()); + src.set_multicast_loop(true).unwrap(); + assert!(src.multicast_loop().unwrap()); +} + +#[test] +#[serial] +fn test_ttl_and_multicast_loop_ipv4() { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0); + check_ttl_and_loop(SacnSource::with_ip("IPv4 source", addr).unwrap()); +} + +/// The TTL and loop setters used IPv4-only socket options, so they always failed on an IPv6 source. +#[test] +#[serial] +fn test_ttl_and_multicast_loop_ipv6() { + use std::net::Ipv6Addr; + let addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 0); + // Some CI and container environments have no IPv6 stack at all. + if let Err(e) = std::net::UdpSocket::bind(addr) { + println!("Skipping, IPv6 unavailable: {e}"); + return; + } + check_ttl_and_loop(SacnSource::with_ip("IPv6 source", addr).unwrap()); +}