Fix SSDP multicast on macOS Sequoia+

Corrige les problèmes de multicast SSDP sur macOS Sequoia+ en ajustant les interfaces multicast et en utilisant Terminal.app pour l'exécution des binaires en debug et release.

- Dans le Makefile : modifie les cibles 'run' et 'run-release' pour lancer l'application via Terminal.app, nécessaire pour le multicast sur macOS Sequoia+
- Dans pmoupnp/src/ssdp/client.rs : améliore la gestion des interfaces multicast en rejoignant le groupe sur toutes les interfaces non loopback et en configurant explicitement l'interface de sortie
- Dans pmoupnp/src/ssdp/server.rs : corrige la configuration multicast du serveur en maintenant l'interface de sortie correcte et en désactivant le multicast loopback
This commit is contained in:
2026-02-19 16:23:02 +01:00
parent 1e882ba6c3
commit 7276945b92
3 changed files with 55 additions and 35 deletions

View File

@@ -161,15 +161,17 @@ watch:
ci: fmt-check clippy test doc-build webapp ci: fmt-check clippy test doc-build webapp
@echo "$(GREEN)✓ Toutes les vérifications CI passées$(NC)" @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 run: debug
@echo "$(YELLOW)→ Lancement de l'application...$(NC)" @echo "$(YELLOW)→ Lancement de l'application via Terminal.app...$(NC)"
./target/debug/$(BINARY_NAME) @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 run-release: release
@echo "$(YELLOW)→ Lancement de l'application (release)...$(NC)" @echo "$(YELLOW)→ Lancement de l'application (release) via Terminal.app...$(NC)"
./$(RUST_TARGET)/$(BINARY_NAME) @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: Affiche la taille du binaire
size: size:

View File

@@ -75,27 +75,42 @@ impl SsdpClient {
let bind_addr: SocketAddr = "0.0.0.0:0".parse().unwrap(); let bind_addr: SocketAddr = "0.0.0.0:0".parse().unwrap();
socket2.bind(&bind_addr.into())?; socket2.bind(&bind_addr.into())?;
// Le client SSDP n'a pas besoin de join_multicast_v4 : let socket: UdpSocket = socket2.into();
// il envoie des M-SEARCH et reçoit des réponses unicast. socket.set_read_timeout(Some(Duration::from_secs(1)))?;
// Le join est seulement utile pour recevoir les NOTIFY multicast, socket.set_multicast_loop_v4(true)?; // utile en dev local
// ce dont le SsdpServer s'occupe.
// let multicast_addr = SSDP_MULTICAST_ADDR.parse().unwrap();
// On configure uniquement l'interface de sortie multicast sur for iface in get_if_addrs::get_if_addrs()? {
// l'IP principale pour que send_to vers 239.255.255.250 fonctionne. if let std::net::IpAddr::V4(ipv4) = iface.ip() {
// Sur macOS, join_multicast_v4 sur des interfaces bridge/VM écrase if !ipv4.is_loopback() {
// IP_MULTICAST_IF et provoque EHOSTUNREACH (errno 65). 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() let local_ip: std::net::Ipv4Addr = pmoutils::guess_local_ip()
.parse() .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)?; socket2.set_multicast_if_v4(&local_ip)?;
debug!( debug!(
"SSDP client: multicast outgoing interface set to {}", "SSDP client: multicast outgoing interface set to {}",
local_ip local_ip
); );
let socket: UdpSocket = socket2.into(); 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); info!("✅ SSDP client ready on {}", addr);

View File

@@ -74,30 +74,33 @@ impl SsdpServer {
let bind_addr: SocketAddr = format!("0.0.0.0:{}", SSDP_PORT).parse().unwrap(); let bind_addr: SocketAddr = format!("0.0.0.0:{}", SSDP_PORT).parse().unwrap();
socket2.bind(&bind_addr.into())?; 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 // 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( socket.join_multicast_v4(
&SSDP_MULTICAST_ADDR.parse().unwrap(), &SSDP_MULTICAST_ADDR.parse().unwrap(),
&"0.0.0.0".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_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); let socket = Arc::new(socket);
self.socket = Some(socket.clone()); self.socket = Some(socket.clone());