diff --git a/Makefile b/Makefile index 21cd720a..59a0f0d0 100644 --- a/Makefile +++ b/Makefile @@ -161,15 +161,17 @@ watch: ci: fmt-check clippy test doc-build webapp @echo "$(GREEN)✓ Toutes les vérifications CI passées$(NC)" -## run: Lance le binaire en mode debug +## run: Compile en debug et lance via Terminal.app (requis pour le multicast sur macOS Sequoia+) run: debug - @echo "$(YELLOW)→ Lancement de l'application...$(NC)" - ./target/debug/$(BINARY_NAME) + @echo "$(YELLOW)→ Lancement de l'application via Terminal.app...$(NC)" + @echo "$(BLUE) (Terminal.app est nécessaire pour le multicast sur macOS Sequoia+)$(NC)" + @osascript -e 'tell application "Terminal" to do script "cd \"$(CURDIR)\" && ./target/debug/$(BINARY_NAME) 2>&1 | tee pmomusic.log; exit"' -## run-release: Lance le binaire en mode release +## run-release: Compile en release et lance via Terminal.app (requis pour le multicast sur macOS Sequoia+) run-release: release - @echo "$(YELLOW)→ Lancement de l'application (release)...$(NC)" - ./$(RUST_TARGET)/$(BINARY_NAME) + @echo "$(YELLOW)→ Lancement de l'application (release) via Terminal.app...$(NC)" + @echo "$(BLUE) (Terminal.app est nécessaire pour le multicast sur macOS Sequoia+)$(NC)" + @osascript -e 'tell application "Terminal" to do script "cd \"$(CURDIR)\" && ./$(RUST_TARGET)/$(BINARY_NAME) 2>&1 | tee pmomusic.log; exit"' ## size: Affiche la taille du binaire size: diff --git a/pmoupnp/src/ssdp/client.rs b/pmoupnp/src/ssdp/client.rs index 44a91fac..b7f4a767 100644 --- a/pmoupnp/src/ssdp/client.rs +++ b/pmoupnp/src/ssdp/client.rs @@ -75,27 +75,42 @@ impl SsdpClient { let bind_addr: SocketAddr = "0.0.0.0:0".parse().unwrap(); socket2.bind(&bind_addr.into())?; - // Le client SSDP n'a pas besoin de join_multicast_v4 : - // il envoie des M-SEARCH et reçoit des réponses unicast. - // Le join est seulement utile pour recevoir les NOTIFY multicast, - // ce dont le SsdpServer s'occupe. - // - // On configure uniquement l'interface de sortie multicast sur - // l'IP principale pour que send_to vers 239.255.255.250 fonctionne. - // Sur macOS, join_multicast_v4 sur des interfaces bridge/VM écrase - // IP_MULTICAST_IF et provoque EHOSTUNREACH (errno 65). + let socket: UdpSocket = socket2.into(); + socket.set_read_timeout(Some(Duration::from_secs(1)))?; + socket.set_multicast_loop_v4(true)?; // utile en dev local + + let multicast_addr = SSDP_MULTICAST_ADDR.parse().unwrap(); + for iface in get_if_addrs::get_if_addrs()? { + if let std::net::IpAddr::V4(ipv4) = iface.ip() { + if !ipv4.is_loopback() { + match socket.join_multicast_v4(&multicast_addr, &ipv4) { + Ok(()) => { + debug!("SSDP: joined {} on {}", SSDP_MULTICAST_ADDR, ipv4); + } + Err(e) => { + warn!( + "SSDP: failed to join {} on {}: {}", + SSDP_MULTICAST_ADDR, ipv4, e + ); + } + } + } + } + } + + // Sur macOS, chaque join_multicast_v4 écrase IP_MULTICAST_IF. + // On remet explicitement l'interface de sortie sur l'IP principale + // pour que send_to vers 239.255.255.250 utilise la bonne interface. let local_ip: std::net::Ipv4Addr = pmoutils::guess_local_ip() .parse() - .unwrap_or("127.0.0.1".parse().unwrap()); + .unwrap_or("0.0.0.0".parse().unwrap()); + let socket2 = Socket::from(socket); socket2.set_multicast_if_v4(&local_ip)?; debug!( "SSDP client: multicast outgoing interface set to {}", local_ip ); - let socket: UdpSocket = socket2.into(); - socket.set_read_timeout(Some(Duration::from_secs(1)))?; - socket.set_multicast_loop_v4(true)?; // utile en dev local info!("✅ SSDP client ready on {}", addr); diff --git a/pmoupnp/src/ssdp/server.rs b/pmoupnp/src/ssdp/server.rs index 3926e6db..6fe2c6a0 100644 --- a/pmoupnp/src/ssdp/server.rs +++ b/pmoupnp/src/ssdp/server.rs @@ -74,30 +74,33 @@ impl SsdpServer { let bind_addr: SocketAddr = format!("0.0.0.0:{}", SSDP_PORT).parse().unwrap(); socket2.bind(&bind_addr.into())?; - // Configurer l'interface de sortie multicast sur l'IP principale. - // Sur macOS, sans cela le kernel peut router les paquets multicast - // via une interface bridge/VM qui n'a pas de route, causant - // EHOSTUNREACH (errno 65). - let local_ip: std::net::Ipv4Addr = pmoutils::guess_local_ip() - .parse() - .unwrap_or("127.0.0.1".parse().unwrap()); - socket2.set_multicast_if_v4(&local_ip)?; - debug!( - "SSDP server: multicast outgoing interface set to {}", - local_ip - ); - // Convertir en UdpSocket standard - let socket: UdpSocket = socket2.into(); + let mut socket: UdpSocket = socket2.into(); - // Rejoindre le groupe multicast via INADDR_ANY (l'OS choisit l'interface) + // Rejoindre le groupe multicast socket.join_multicast_v4( &SSDP_MULTICAST_ADDR.parse().unwrap(), &"0.0.0.0".parse().unwrap(), )?; + // Sur macOS, join_multicast_v4 peut positionner IP_MULTICAST_IF + // sur une interface bridge/VM. On remet explicitement l'interface + // de sortie sur l'IP principale. + let local_ip: std::net::Ipv4Addr = pmoutils::guess_local_ip() + .parse() + .unwrap_or("0.0.0.0".parse().unwrap()); + { + let socket2 = Socket::from(socket); + socket2.set_multicast_if_v4(&local_ip)?; + debug!( + "SSDP server: multicast outgoing interface set to {}", + local_ip + ); + socket = socket2.into(); + } + socket.set_read_timeout(Some(Duration::from_secs(1)))?; - socket.set_multicast_loop_v4(true)?; // utile en dev local + socket.set_multicast_loop_v4(false)?; let socket = Arc::new(socket); self.socket = Some(socket.clone());