Last active
May 17, 2024 19:16
-
-
Save billchurch/8dc2c8835dedf140075dbc60ed483b05 to your computer and use it in GitHub Desktop.
Python script to find all sonos units on your network with uPNP and then connect to a unifi switch to tell you what switch port they're on
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import socket | |
import requests | |
import paramiko | |
import time | |
import xml.etree.ElementTree as ET | |
def discover_sonos_devices(): | |
MCAST_GRP = '239.255.255.250' | |
MCAST_PORT = 1900 | |
msg = ( | |
'M-SEARCH * HTTP/1.1\r\n' | |
'HOST: 239.255.255.250:1900\r\n' | |
'MAN: "ssdp:discover"\r\n' | |
'MX: 1\r\n' | |
'ST: urn:schemas-upnp-org:device:ZonePlayer:1\r\n' | |
'\r\n' | |
) | |
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) | |
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) | |
sock.settimeout(2) | |
sock.sendto(msg.encode('utf-8'), (MCAST_GRP, MCAST_PORT)) | |
devices = {} | |
try: | |
while True: | |
data, addr = sock.recvfrom(1024) | |
response = data.decode('utf-8') | |
if 'Sonos' in response: | |
for line in response.split('\r\n'): | |
if line.startswith('LOCATION:'): | |
location = line.split(' ')[1].strip() | |
devices[addr[0]] = location | |
except socket.timeout: | |
pass | |
return devices | |
def get_mac_address_from_description(url): | |
try: | |
response = requests.get(url) | |
if response.status_code == 200: | |
root = ET.fromstring(response.content) | |
ns = {'upnp': 'urn:schemas-upnp-org:device-1-0'} | |
mac_address = root.find('.//upnp:MACAddress', ns).text | |
return mac_address | |
else: | |
print(f'Failed to get device description from {url}') | |
return None | |
except requests.RequestException as e: | |
print(f'Error getting device description from {url}: {e}') | |
return None | |
def ssh_to_telnet(host, username, password, telnet_command): | |
try: | |
# Establish SSH connection | |
ssh = paramiko.SSHClient() | |
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) | |
ssh.connect(host, username=username, password=password) | |
# Start an interactive session | |
channel = ssh.invoke_shell() | |
# Wait for the SSH prompt | |
time.sleep(1) | |
channel.recv(9999) | |
# Enter telnet mode | |
channel.send('telnet localhost\n') | |
time.sleep(1) | |
# Wait for the Telnet prompt | |
channel.recv(9999) | |
# Send Telnet command | |
channel.send(telnet_command + '\n') | |
time.sleep(2) | |
# Receive output | |
output = channel.recv(9999).decode('ascii') | |
# Close the Telnet session | |
channel.send('exit\n') | |
time.sleep(1) | |
# Close the SSH session | |
channel.send('exit\n') | |
ssh.close() | |
return output | |
except Exception as e: | |
print(f'Error executing SSH to Telnet command: {e}') | |
return None | |
def get_mac_address_table_via_telnet(host, username, password, mac_address): | |
telnet_command = f'show mac-addr-table {mac_address}' | |
result = ssh_to_telnet(host, username, password, telnet_command) | |
return result | |
def parse_mac_table_output(output): | |
mac_port_mapping = {} | |
lines = output.split('\n') | |
for line in lines: | |
parts = line.split() | |
if len(parts) > 4 and parts[1].count(':') == 5: # Ensuring it's a MAC address line | |
mac = parts[1].strip() | |
port = parts[2].strip() | |
mac_port_mapping[mac] = port | |
return mac_port_mapping | |
if __name__ == '__main__': | |
# Discover Sonos devices | |
sonos_devices = discover_sonos_devices() | |
mac_addresses = [] | |
if sonos_devices: | |
print(f'Discovered Sonos devices: {list(sonos_devices.keys())}') | |
for ip, location_url in sonos_devices.items(): | |
mac_address = get_mac_address_from_description(location_url) | |
if mac_address: | |
mac_addresses.append(mac_address) | |
print(f'MAC: {mac_address}') | |
else: | |
print('No Sonos devices found on the network.') | |
# Connect to UniFi switch and find ports for MAC addresses | |
switch_ip = '192.168.0.30' # Replace with your switch IP address | |
username = 'your-username' # Replace with your SSH username | |
password = 'your-password' # Replace with your SSH password | |
for mac in mac_addresses: | |
output = get_mac_address_table_via_telnet(switch_ip, username, password, mac) | |
if output: | |
mac_port_mapping = parse_mac_table_output(output) | |
for mac, port in mac_port_mapping.items(): | |
print(f'MAC address {mac} is connected to port {port}') | |
else: | |
print(f'No output for MAC address {mac}') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Sample output