diff --git a/examples/onoff_light/src/main.rs b/examples/onoff_light/src/main.rs index 069f1dd..7115a28 100644 --- a/examples/onoff_light/src/main.rs +++ b/examples/onoff_light/src/main.rs @@ -77,13 +77,14 @@ fn run() -> Result<(), Error> { device_name: "OnOff Light", }; - let (ipv4_addr, ipv6_addr) = initialize_network()?; + let (ipv4_addr, ipv6_addr, interface) = initialize_network()?; let mdns = DefaultMdns::new( 0, "matter-demo", ipv4_addr.octets(), Some(ipv6_addr.octets()), + interface, &dev_det, matter::MATTER_PORT, ); @@ -231,7 +232,7 @@ fn initialize_logger() { #[cfg(not(target_os = "espidf"))] #[inline(never)] -fn initialize_network() -> Result<(Ipv4Addr, Ipv6Addr), Error> { +fn initialize_network() -> Result<(Ipv4Addr, Ipv6Addr, u32), Error> { use log::error; use matter::error::ErrorCode; use nix::{net::if_::InterfaceFlags, sys::socket::SockaddrIn6}; @@ -276,7 +277,7 @@ fn initialize_network() -> Result<(Ipv4Addr, Ipv6Addr), Error> { iname, ip, ipv6 ); - Ok((ip, ipv6)) + Ok((ip, ipv6, 0 as _)) } #[cfg(target_os = "espidf")] @@ -287,7 +288,7 @@ fn initialize_logger() { #[cfg(target_os = "espidf")] #[inline(never)] -fn initialize_network() -> Result<(Ipv4Addr, Ipv6Addr), Error> { +fn initialize_network() -> Result<(Ipv4Addr, Ipv6Addr, u32), Error> { use core::time::Duration; use embedded_svc::wifi::{AuthMethod, ClientConfiguration, Configuration}; @@ -379,9 +380,11 @@ fn initialize_network() -> Result<(Ipv4Addr, Ipv6Addr), Error> { ipv6.addr[3].to_le_bytes()[3], ]; + let interface = wifi.sta_netif().get_index(); + // Not OK of course, but for a demo this is good enough // Wifi will continue to be available and working in the background core::mem::forget(wifi); - Ok((ipv4_octets.into(), ipv6_octets.into())) + Ok((ipv4_octets.into(), ipv6_octets.into(), interface)) } diff --git a/matter/src/mdns/astro.rs b/matter/src/mdns/astro.rs index 12426cb..e7ae4c2 100644 --- a/matter/src/mdns/astro.rs +++ b/matter/src/mdns/astro.rs @@ -23,6 +23,7 @@ impl<'a> Mdns<'a> { _hostname: &str, _ip: [u8; 4], _ipv6: Option<[u8; 16]>, + _interface: u32, dev_det: &'a BasicInfoConfig<'a>, matter_port: u16, ) -> Self { diff --git a/matter/src/mdns/builtin.rs b/matter/src/mdns/builtin.rs index 95c6ad6..7b6f891 100644 --- a/matter/src/mdns/builtin.rs +++ b/matter/src/mdns/builtin.rs @@ -19,21 +19,19 @@ use super::{ Service, ServiceMode, }; -const IP_BROADCAST_ADDRS: [(IpAddr, u16); 2] = [ - (IpAddr::V4(Ipv4Addr::new(224, 0, 0, 251)), 5353), - ( - IpAddr::V6(Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0x00fb)), - 5353, - ), -]; +const IP_BIND_ADDR: IpAddr = IpAddr::V6(Ipv6Addr::UNSPECIFIED); -const IP_BIND_ADDR: (IpAddr, u16) = (IpAddr::V6(Ipv6Addr::UNSPECIFIED), 5353); +const IP_BROADCAST_ADDR: Ipv4Addr = Ipv4Addr::new(224, 0, 0, 251); +const IPV6_BROADCAST_ADDR: Ipv6Addr = Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0x00fb); + +const PORT: u16 = 5353; type MdnsTxBuf = MaybeUninit<[u8; MAX_TX_BUF_SIZE]>; type MdnsRxBuf = MaybeUninit<[u8; MAX_RX_BUF_SIZE]>; pub struct Mdns<'a> { host: Host<'a>, + interface: u32, dev_det: &'a BasicInfoConfig<'a>, matter_port: u16, services: RefCell, ServiceMode), 4>>, @@ -47,6 +45,7 @@ impl<'a> Mdns<'a> { hostname: &'a str, ip: [u8; 4], ipv6: Option<[u8; 16]>, + interface: u32, dev_det: &'a BasicInfoConfig<'a>, matter_port: u16, ) -> Self { @@ -57,6 +56,7 @@ impl<'a> Mdns<'a> { ip, ipv6, }, + interface, dev_det, matter_port, services: RefCell::new(heapless::Vec::new()), @@ -121,11 +121,10 @@ impl<'a> MdnsRunner<'a> { let tx_pipe = &tx_pipe; let rx_pipe = &rx_pipe; - let mut udp = UdpListener::new(SocketAddr::new(IP_BIND_ADDR.0, IP_BIND_ADDR.1)).await?; + let mut udp = UdpListener::new(SocketAddr::new(IP_BIND_ADDR, PORT)).await?; - for (ip, _) in IP_BROADCAST_ADDRS { - udp.join_multicast(ip).await?; - } + udp.join_multicast_v6(IPV6_BROADCAST_ADDR, self.0.interface)?; + udp.join_multicast_v4(IP_BROADCAST_ADDR, Ipv4Addr::from(self.0.host.ip))?; let udp = &udp; @@ -188,7 +187,10 @@ impl<'a> MdnsRunner<'a> { ) .await; - for (addr, port) in IP_BROADCAST_ADDRS { + for addr in [ + IpAddr::V4(IP_BROADCAST_ADDR), + IpAddr::V6(IPV6_BROADCAST_ADDR), + ] { loop { let sent = { let mut data = tx_pipe.data.lock().await; @@ -197,12 +199,12 @@ impl<'a> MdnsRunner<'a> { let len = self.0.host.broadcast(&self.0, data.buf, 60)?; if len > 0 { - info!("Broadasting mDNS entry to {}:{}", addr, port); + info!("Broadasting mDNS entry to {}:{}", addr, PORT); data.chunk = Some(Chunk { start: 0, end: len, - addr: Address::Udp(SocketAddr::new(addr, port)), + addr: Address::Udp(SocketAddr::new(addr, PORT)), }); tx_pipe.data_supplied_notification.signal(()); diff --git a/matter/src/transport/udp.rs b/matter/src/transport/udp.rs index 0aa6573..bf3f36f 100644 --- a/matter/src/transport/udp.rs +++ b/matter/src/transport/udp.rs @@ -27,7 +27,7 @@ mod smol_udp { use log::{debug, info, warn}; use smol::net::UdpSocket; - use crate::transport::network::{IpAddr, Ipv4Addr, SocketAddr}; + use crate::transport::network::{Ipv4Addr, Ipv6Addr, SocketAddr}; pub struct UdpListener { socket: UdpSocket, @@ -44,15 +44,71 @@ mod smol_udp { Ok(listener) } - pub async fn join_multicast(&mut self, ip_addr: IpAddr) -> Result<(), Error> { - match ip_addr { - IpAddr::V4(ip_addr) => self - .socket - .join_multicast_v4(ip_addr, Ipv4Addr::UNSPECIFIED)?, - IpAddr::V6(ip_addr) => self.socket.join_multicast_v6(&ip_addr, 0)?, + pub fn join_multicast_v6( + &mut self, + multiaddr: Ipv6Addr, + interface: u32, + ) -> Result<(), Error> { + self.socket.join_multicast_v6(&multiaddr, interface)?; + + info!("Joined IPV6 multicast {}/{}", multiaddr, interface); + + Ok(()) + } + + pub fn join_multicast_v4( + &mut self, + multiaddr: Ipv4Addr, + interface: Ipv4Addr, + ) -> Result<(), Error> { + #[cfg(not(target_os = "espidf"))] + self.socket.join_multicast_v4(multiaddr, interface)?; + + // join_multicast_v4() is broken for ESP-IDF, most likely due to wrong `ip_mreq` signature in the `libc` crate + // Note that also most *_multicast_v4 and *_multicast_v6 methods are broken as well in Rust STD for the ESP-IDF + // due to mismatch w.r.t. sizes (u8 expected but u32 passed to setsockopt() and sometimes the other way around) + #[cfg(target_os = "espidf")] + { + fn esp_setsockopt( + socket: &mut UdpSocket, + proto: u32, + option: u32, + value: T, + ) -> Result<(), Error> { + use std::os::fd::AsRawFd; + + esp_idf_sys::esp!(unsafe { + esp_idf_sys::lwip_setsockopt( + socket.as_raw_fd(), + proto as _, + option as _, + &value as *const _ as *const _, + core::mem::size_of::() as _, + ) + }) + .map_err(|_| ErrorCode::StdIoError)?; + + Ok(()) + } + + let mreq = esp_idf_sys::ip_mreq { + imr_multiaddr: esp_idf_sys::in_addr { + s_addr: u32::from_ne_bytes(multiaddr.octets()), + }, + imr_interface: esp_idf_sys::in_addr { + s_addr: u32::from_ne_bytes(interface.octets()), + }, + }; + + esp_setsockopt( + &mut self.socket, + esp_idf_sys::IPPROTO_IP, + esp_idf_sys::IP_ADD_MEMBERSHIP, + mreq, + )?; } - info!("Joining multicast on {:?}", ip_addr); + info!("Joined IP multicast {}/{}", multiaddr, interface); Ok(()) }