Esempio n. 1
0
 def publish(self):
     import zeroconf
     
     # zeroconf doesn't do this for us
     # .. pick one at random? Ideally, zeroconf would publish all valid
     #    addresses as the A record.. but doesn't seem to do so
     addrs = zeroconf.get_all_addresses(socket.AF_INET)
     address = None
     if addrs:
         for addr in addrs:
             if addr != '127.0.0.1':
                 address = socket.inet_aton(addrs[0])
     
     type_ = self.stype + ".local."
     self.info = zeroconf.ServiceInfo(
         type_,
         self.name + "." + type_,
         address=address,
         port=self.port,
         properties=self.text,
         server=self.host if self.host else None
     )
     
     self.zc = zeroconf.Zeroconf()
     self.zc.register_service(self.info)
Esempio n. 2
0
 def _register_zeroconf(self):
     if self.labthing:
         host = f"{self.labthing.safe_title}._labthing._tcp.local."
         if len(host) > 63:
             host = (
                 f"{hashlib.sha1(host.encode()).hexdigest()}._labthing._tcp.local."
             )
         print(f"Registering zeroconf {host}")
         # Get list of host addresses
         mdns_addresses = {
             socket.inet_aton(i)
             for i in get_all_addresses()
             if i not in ("127.0.0.1", "0.0.0.0")
         }
         # LabThing service
         self.service_infos.append(
             ServiceInfo(
                 "_labthing._tcp.local.",
                 host,
                 port=self.port,
                 properties={
                     "path": self.labthing.url_prefix,
                     "id": self.labthing.id,
                 },
                 addresses=mdns_addresses,
             ))
         self.zeroconf_server = Zeroconf(ip_version=IPVersion.V4Only)
         for service in self.service_infos:
             self.zeroconf_server.register_service(service)
Esempio n. 3
0
    def add_service(self, zeroconf, type, name):
        timeout = 10000
        info = zeroconf.get_service_info(type, name, timeout=timeout)
        if info is None:
            logger.warn(
                "Zeroconf network service information could not be retrieved within {} seconds"
                .format(str(timeout / 1000.0)))
            return
        id = _id_from_name(name)
        ip = socket.inet_ntoa(info.address)

        base_url = "http://{ip}:{port}/".format(ip=ip, port=info.port)

        zeroconf_service = ZEROCONF_STATE.get("service")
        is_self = zeroconf_service and zeroconf_service.id == id

        instance = {
            "id": id,
            "ip": ip,
            "local": ip in get_all_addresses(),
            "port": info.port,
            "host": info.server.strip("."),
            "base_url": base_url,
            "self": is_self,
        }

        device_info = parse_device_info(info)

        instance.update(device_info)
        self.instances[id] = instance

        if not is_self:

            try:

                DynamicNetworkLocation.objects.update_or_create(dict(
                    base_url=base_url, **device_info),
                                                                id=id)

                logger.info(
                    "Kolibri instance '%s' joined zeroconf network; service info: %s"
                    % (id, self.instances[id]))

            except ValidationError:
                import traceback

                logger.warn(
                    """
                        A new Kolibri instance '%s' was seen on the zeroconf network,
                        but we had trouble getting the information we needed about it.
                        Service info:
                        %s
                        The following exception was raised:
                        %s
                        """ %
                    (id, self.instances[id], traceback.format_exc(limit=1)))
            finally:
                connection.close()
Esempio n. 4
0
    def IfacesMonitor(self, event):
        NewState = get_all_addresses(socket.AF_INET)

        if self.IfacesMonitorState != NewState:
            if self.IfacesMonitorState is not None:
                # refresh only if a new address appeared
                for addr in NewState:
                    if addr not in self.IfacesMonitorState:
                        self.RefreshList()
                        break
            self.IfacesMonitorState = NewState
        event.Skip()
Esempio n. 5
0
 def _get_sockets(self):
     sockets = []
     for addr in zeroconf.get_all_addresses():
         try:
             sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
             # Set the time-to-live for messages for local network
             sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL,
                             self.SSDP_MX)
             sock.bind((addr, 0))
             sockets.append(sock)
         except socket.error:
             pass
     
     return sockets
Esempio n. 6
0
def get_local_ip():
    """
    Get local ip address of the localhost.
    Example: "10.0.0.2"
    :return: local ip address
    :rtype: str
    """

    ranges = ["10.", "172.", "192."]
    all_addresses = zeroconf.get_all_addresses(netifaces.AF_INET)
    for address in all_addresses:
        for ran in ranges:
            if address.startswith(ran):
                return address
    return None
Esempio n. 7
0
 def add_service(self, zeroconf, type, name):
     info = zeroconf.get_service_info(type, name)
     id = _id_from_name(name)
     ip = socket.inet_ntoa(info.address)
     self.instances[id] = {
         "id": id,
         "ip": ip,
         "local": ip in get_all_addresses(),
         "port": info.port,
         "host": info.server.strip("."),
         "data":
         {key: json.loads(val)
          for (key, val) in info.properties.items()},
         "base_url": "http://{ip}:{port}/".format(ip=ip, port=info.port),
     }
     logger.info(
         "Kolibri instance '%s' joined zeroconf network; service info: %s\n"
         % (id, self.instances[id]))
Esempio n. 8
0
def get_urls(listen_port=None):
    """
    :param listen_port: if set, will not try to determine the listen port from
                        other running instances.
    """
    try:
        if listen_port:
            port = listen_port
        else:
            __, __, port = get_status()
        urls = []
        if port:
            try:
                for ip in get_all_addresses():
                    urls.append("http://{}:{}/".format(ip, port))
            except RuntimeError:
                logger.error("Error retrieving network interface list!")
        return STATUS_RUNNING, urls
    except NotRunning as e:
        return e.status_code, []
Esempio n. 9
0
def _get_interface_ip(interface: str) -> List[str]:
    if interface is None:
        return get_all_addresses()

    # check if the interface was passed as an IP
    try:
        IPv4Address(interface)
        return [interface]
    except AddressValueError:
        pass

    # pyzeroconf only supports ipv4 so limit to that
    ipv4s = list({
        addr.ip
        for iface in ifaddr.get_adapters() for addr in iface.ips
        # Host only netmask 255.255.255.255
        if addr.is_IPv4 and iface.nice_name == interface
        and addr.network_prefix != 32
    })

    if len(ipv4s) == 0:
        raise Exception("unable to find an ipv4 address for interface")

    return ipv4s
Esempio n. 10
0
def scan(timeout=DISCOVER_TIMEOUT):
    """Send a message over the network to discover uPnP devices.

    Inspired by Crimsdings
    https://github.com/crimsdings/ChromeCast/blob/master/cc_discovery.py

    Protocol explanation:
    https://embeddedinn.wordpress.com/tutorials/upnp-device-architecture/
    """
    ssdp_requests = ssdp_request(ST_ALL), ssdp_request(ST_ROOTDEVICE)

    stop_wait = datetime.now() + timedelta(seconds=timeout)

    sockets = []
    for addr in zeroconf.get_all_addresses():
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

            # Set the time-to-live for messages for local network
            sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL,
                            SSDP_MX)
            sock.bind((addr, 0))
            sockets.append(sock)
        except socket.error:
            pass

    entries = {}
    for sock in [s for s in sockets]:
        try:
            for req in ssdp_requests:
                sock.sendto(req, SSDP_TARGET)
            sock.setblocking(False)
        except socket.error:
            sockets.remove(sock)
            sock.close()

    try:
        while sockets:
            time_diff = stop_wait - datetime.now()
            seconds_left = time_diff.total_seconds()
            if seconds_left <= 0:
                break

            ready = select.select(sockets, [], [], seconds_left)[0]

            for sock in ready:
                try:
                    data, address = sock.recvfrom(1024)
                    response = data.decode("utf-8")
                except UnicodeDecodeError:
                    logging.getLogger(__name__).debug(
                        'Ignoring invalid unicode response from %s', address)
                    continue
                except socket.error:
                    logging.getLogger(__name__).exception(
                        "Socket error while discovering SSDP devices")
                    sockets.remove(sock)
                    sock.close()
                    continue

                entry = UPNPEntry.from_response(response)
                entries[(entry.st, entry.location)] = entry

    finally:
        for s in sockets:
            s.close()

    return sorted(entries.values(), key=lambda entry: entry.location or '')
Esempio n. 11
0
    def publish(self, daap_server, preferred_database=None):
        """
        Publish a given `DAAPServer` instance.

        The given instances should be fully configured, including the provider.
        By default Zeroconf only advertises the first database, but the DAAP
        protocol has support for multiple databases. Therefore, the parameter
        `preferred_database` can be set to choose which database ID will be
        served.

        If the provider is not fully configured (in other words, if the
        preferred database cannot be found), this method will not publish this
        server. In this case, simply call this method again when the provider
        is ready.

        If the server was already published, it will be unpublished first.

        :param DAAPServer daap_server: DAAP Server instance to publish.
        :param int preferred_database: ID of the database to advertise.
        """

        if daap_server in self.daap_servers:
            self.unpublish(daap_server)

        # Zeroconf can advertise the information for one database only. Since
        # the protocol supports multiple database, let the user decide which
        # database to advertise. If none is specified, take the first one.
        provider = daap_server.provider

        try:
            if preferred_database is not None:
                database = provider.server.databases[preferred_database]
            else:
                database = provider.server.databases.values()[0]
        except LookupError:
            # The server may not have any databases (yet).
            return

        # The IP 0.0.0.0 tells this server to bind to all interfaces. However,
        # Bonjour advertises itself to others, so others need an actual IP.
        # There is definately a better way, but it works.
        if daap_server.ip == "0.0.0.0":
            addresses = []

            for address in zeroconf.get_all_addresses(socket.AF_INET):
                if not address == "127.0.0.1":
                    addresses.append(socket.inet_aton(address))
        else:
            addresses = [socket.inet_aton(daap_server.ip)]

        # Determine machine ID and database ID, depending on the provider. If
        # the provider has no support for persistent IDs, generate a random
        # ID.
        if provider.supports_persistent_id:
            machine_id = hex(provider.server.persistent_id)
            database_id = hex(database.persistent_id)
        else:
            machine_id = hex(generate_persistent_id())
            database_id = hex(generate_persistent_id())

        # iTunes 11+ uses more properties, but this seems to be sufficient.
        description = {
            "txtvers": "1",
            "Password": str(int(bool(daap_server.password))),
            "Machine Name": provider.server.name,
            "Machine ID": machine_id.upper(),
            "Database ID": database_id.upper()
        }

        # Test is zeroconf supports multiple addresses or not. For
        # compatibility with zeroconf 0.17.3 or less.
        if not hasattr(zeroconf.ServiceInfo("", ""), "addresses"):
            addresses = addresses[0]

        self.daap_servers[daap_server] = zeroconf.ServiceInfo(
            type="_daap._tcp.local.",
            name=provider.server.name + "._daap._tcp.local.",
            address=addresses,
            port=daap_server.port,
            properties=description)
        self.zeroconf.register_service(self.daap_servers[daap_server])
Esempio n. 12
0
    def publish(self, daap_server, preferred_database=None):
        """
        Publish a given `DAAPServer` instance.

        The given instances should be fully configured, including the provider.
        By default Zeroconf only advertises the first database, but the DAAP
        protocol has support for multiple databases. Therefore, the parameter
        `preferred_database` can be set to choose which database ID will be
        served.

        If the provider is not fully configured (in other words, if the
        preferred database cannot be found), this method will not publish this
        server. In this case, simply call this method again when the provider
        is ready.

        If the server was already published, it will be unpublished first.

        :param DAAPServer daap_server: DAAP Server instance to publish.
        :param int preferred_database: ID of the database to advertise.
        """

        if daap_server in self.daap_servers:
            self.unpublish(daap_server)

        # Zeroconf can advertise the information for one database only. Since
        # the protocol supports multiple database, let the user decide which
        # database to advertise. If none is specified, take the first one.
        provider = daap_server.provider

        try:
            if preferred_database is not None:
                database = provider.server.databases[preferred_database]
            else:
                database = provider.server.databases.values()[0]
        except LookupError:
            # The server may not have any databases (yet).
            return

        # The IP 0.0.0.0 tells this server to bind to all interfaces. However,
        # Bonjour advertises itself to others, so others need an actual IP.
        # There is definately a better way, but it works.
        if daap_server.ip == "0.0.0.0":
            addresses = []

            for address in zeroconf.get_all_addresses(socket.AF_INET):
                if not address == "127.0.0.1":
                    addresses.append(socket.inet_aton(address))
        else:
            addresses = [socket.inet_aton(daap_server.ip)]

        # Determine machine ID and database ID, depending on the provider. If
        # the provider has no support for persistent IDs, generate a random
        # ID.
        if provider.supports_persistent_id:
            machine_id = hex(provider.server.persistent_id)
            database_id = hex(database.persistent_id)
        else:
            machine_id = hex(generate_persistent_id())
            database_id = hex(generate_persistent_id())

        # iTunes 11+ uses more properties, but this seems to be sufficient.
        description = {
            "txtvers": "1",
            "Password": str(int(bool(daap_server.password))),
            "Machine Name": provider.server.name,
            "Machine ID": machine_id.upper(),
            "Database ID": database_id.upper()
        }

        # Test is zeroconf supports multiple addresses or not. For
        # compatibility with zeroconf 0.17.3 or less.
        if not hasattr(zeroconf.ServiceInfo("", ""), "addresses"):
            addresses = addresses[0]

        self.daap_servers[daap_server] = zeroconf.ServiceInfo(
            type="_daap._tcp.local.",
            name=provider.server.name + "._daap._tcp.local.",
            address=addresses,
            port=daap_server.port,
            properties=description)
        self.zeroconf.register_service(self.daap_servers[daap_server])
Esempio n. 13
0
    def publish(self, daap_server, preferred_database=None):
        """
        Publish a given `DAAPServer` instance.

        The given instances should be fully configured, including the provider.
        By default Zeroconf only advertises the first database, but the DAAP
        protocol has support for multiple databases. Therefore, the parameter
        `preferred_database` can be set to choose which database ID will be
        served.

        If the server was already published, it will be unpublished first.

        :param DAAPServer daap_server: DAAP Server instance to publish.
        :param int preferred_database: ID of the database to advertise.
        """

        if daap_server in self.daap_servers:
            self.unpublish(daap_server)

        # The IP 0.0.0.0 tells SubDaap to bind to all interfaces. However,
        # Bonjour advertises itself to others, so others need an actual IP.
        # There is definately a better way, but it works.
        address = daap_server.ip

        if daap_server.ip == "0.0.0.0":
            for ip in zeroconf.get_all_addresses(socket.AF_INET):
                if ip != "127.0.0.1":
                    address = ip
                    break

        # Zeroconf can advertise the information for one database only. Since
        # the protocol supports multiple database, let the user decide which
        # database to advertise. If none is specified, take the first one.
        provider = daap_server.provider

        if preferred_database is not None:
            database = provider.server.databases[preferred_database]
        else:
            database = provider.server.databases.values()[0]

        # Determine machine ID and database ID, depending on the provider. If
        # the provider has no support for persistent IDs, generate a random
        # ID.
        if provider.supports_persistent_id:
            machine_id = hex(provider.server.persistent_id)
            database_id = hex(database.persistent_id)
        else:
            machine_id = hex(generate_persistent_id())
            database_id = hex(generate_persistent_id())

        # iTunes 11+ uses more properties, but this seems to be sufficient.
        description = {
            "txtvers": "1",
            "Password": str(int(bool(daap_server.password))),
            "Machine Name": provider.server.name,
            "Machine ID": machine_id.upper(),
            "Database ID": database_id.upper()
        }

        self.daap_servers[daap_server] = zeroconf.ServiceInfo(
            type="_daap._tcp.local.",
            name=provider.server.name + "._daap._tcp.local.",
            address=socket.inet_aton(address),
            port=daap_server.port,
            properties=description)
        self.zeroconf.register_service(self.daap_servers[daap_server])