import socket from http.client import HTTPResponse from io import BytesIO, StringIO import click PARADROP_URN = "urn:schemas-upnp-org:service:Paradrop:1" class SsdpResponse(HTTPResponse): def __init__(self, response_text, source): self.fp = StringIO(response_text) self.debuglevel = 0 self.strict = 0 self.msg = None self._method = None self.source = source self.begin() def perform_ssdp_discovery(service, timeout=5, retries=5, mx=3): # Standard multicast IP address and port for SSDP. group = ("239.255.255.250", 1900) message = "\r\n".join([ "M-SEARCH * HTTP/1.1", "HOST: {0}:{1}", 'MAN: "ssdp:discover"', "ST: {st}", "MX: {mx}" ]).format(*group, st=service, mx=mx) socket.setdefaulttimeout(timeout) sources = set() for _ in range(retries): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) sock.sendto(message.encode('utf-8'), group) while True: try: data, addr = sock.recvfrom(1024) response = SsdpResponse(data, addr) if addr[0] not in sources: # We only want to return each node once. sources.add(addr[0]) yield response except socket.timeout: break @click.command('discover') @click.pass_context def root(ctx): """ Discover Paradrop nodes on the network. """ click.echo("Discovering...") for response in perform_ssdp_discovery(PARADROP_URN): click.echo("Response from {:16s}".format(response.source[0])) server = response.getheader("SERVER") if server is not None: click.echo(" {}".format(server))