#!/usr/bin/env python3
"""
UPnP Device Discovery Tool
Envoie une requête M-SEARCH SSDP et collecte les réponses des devices
"""
import socket
import struct
import time
import sys
from urllib.parse import urlparse
from urllib.request import urlopen
SSDP_ADDR = "239.255.255.250"
SSDP_PORT = 1900
SSDP_MX = 3
SSDP_ST = "ssdp:all"
M_SEARCH = f"""M-SEARCH * HTTP/1.1
HOST: {SSDP_ADDR}:{SSDP_PORT}
MAN: "ssdp:discover"
MX: {SSDP_MX}
ST: {SSDP_ST}
USER-AGENT: PMOMusic UPnP Discovery Tool
"""
def discover_upnp_devices(timeout=5):
"""Découvre les devices UPnP sur le réseau local"""
devices = {}
# Créer le socket UDP
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.settimeout(timeout)
# Envoyer la requête M-SEARCH
print(f"🔍 Envoi de la requête M-SEARCH sur {SSDP_ADDR}:{SSDP_PORT}...")
print(f"⏱️ Timeout: {timeout}s\n")
message = M_SEARCH.replace('\n', '\r\n').encode('utf-8')
sock.sendto(message, (SSDP_ADDR, SSDP_PORT))
# Collecter les réponses
start_time = time.time()
while time.time() - start_time < timeout:
try:
data, addr = sock.recvfrom(65507)
response = data.decode('utf-8', errors='ignore')
# Parser la réponse
location = None
server = None
st = None
usn = None
for line in response.split('\r\n'):
if line.lower().startswith('location:'):
location = line.split(':', 1)[1].strip()
elif line.lower().startswith('server:'):
server = line.split(':', 1)[1].strip()
elif line.lower().startswith('st:'):
st = line.split(':', 1)[1].strip()
elif line.lower().startswith('usn:'):
usn = line.split(':', 1)[1].strip()
if location and location not in devices:
devices[location] = {
'location': location,
'server': server,
'st': st,
'usn': usn,
'from': addr[0]
}
except socket.timeout:
break
except Exception as e:
print(f"⚠️ Erreur lors de la réception: {e}")
sock.close()
return devices
def fetch_device_description(location):
"""Récupère la description XML du device"""
try:
response = urlopen(location, timeout=3)
return response.read().decode('utf-8')
except Exception as e:
return f"Error: {e}"
def main():
print("=" * 70)
print(" 🔍 UPnP Device Discovery Tool")
print("=" * 70)
print()
devices = discover_upnp_devices(timeout=5)
# Filtrer pour ne garder que les MediaServers
media_servers = {}
for loc, info in devices.items():
if 'MediaServer' in str(info.get('st', '')):
media_servers[loc] = info
print(f"\n📊 Résultats:")
print(f" Total devices trouvés: {len(devices)}")
print(f" MediaServers trouvés: {len(media_servers)}\n")
if not media_servers:
print("❌ Aucun MediaServer trouvé!\n")
print("📋 Tous les devices trouvés:")
for loc, info in devices.items():
print(f"\n - Location: {loc}")
print(f" ST: {info.get('st', 'N/A')}")
print(f" Server: {info.get('server', 'N/A')}")
return
# Analyser chaque MediaServer
for idx, (location, info) in enumerate(media_servers.items(), 1):
print("=" * 70)
print(f"📡 MediaServer #{idx}")
print("=" * 70)
print(f"Location: {location}")
print(f"From IP: {info['from']}")
print(f"Server: {info.get('server', 'N/A')}")
print(f"USN: {info.get('usn', 'N/A')}")
print()
# Récupérer la description
print("📄 Fetching device description...")
desc = fetch_device_description(location)
# Analyser la description
if desc and not desc.startswith("Error"):
print("\n📝 Device Description XML:")
print("-" * 70)
# Afficher les premières lignes
lines = desc.split('\n')
for line in lines[:50]: # Limiter à 50 lignes
print(line)
if len(lines) > 50:
print(f"... ({len(lines) - 50} more lines)")
print("-" * 70)
# Extraire les infos importantes
import re
friendly_name = re.search(r'([^<]+)', desc)
manufacturer = re.search(r'([^<]+)', desc)
model_name = re.search(r'([^<]+)', desc)
udn = re.search(r'([^<]+)', desc)
print("\n📋 Device Info:")
if friendly_name:
print(f" Friendly Name: {friendly_name.group(1)}")
if manufacturer:
print(f" Manufacturer: {manufacturer.group(1)}")
if model_name:
print(f" Model Name: {model_name.group(1)}")
if udn:
print(f" UDN: {udn.group(1)}")
# Vérifier le format de l'UDN
udn_value = udn.group(1)
if not udn_value.startswith('uuid:'):
print(f" ⚠️ WARNING: UDN ne commence pas par 'uuid:' !")
else:
print(f"❌ Erreur lors de la récupération: {desc}")
print("\n")
if __name__ == "__main__":
main()