Ejemplo n.º 1
0
    def add_service(self, service: ServiceConfig) -> None:
        type = DNSLabel(service.type)
        name = type.add(DNSLabel(service.name))
        type_suffixed = self.hostname.add(type)
        name_suffixed = self.hostname.add(name)
        port = service.port
        props_list = []
        for key, value in (service.properties or {}).items():
            prop = key
            if isinstance(value, bool):
                if not value:
                    prop += '='
            else:
                prop += '=' + str(value)
            if len(prop) > 255:
                self.logger.error('Property too large to add to txt record %s', key)
                continue
            props_list.append(prop)

        props = ''.join(
            chr(len(prop)) + prop
            for prop in props_list
        )

        self.add_record(
            type,
            QTYPE.PTR,
            dnslib.PTR(name_suffixed))
        self.add_record(
            '_services._dns-sd._udp',
            QTYPE.PTR,
            dnslib.PTR(type_suffixed))
        self.add_record(
            name,
            QTYPE.SRV,
            dnslib.SRV(0, 0, port, self.hostname))
        self.add_record(
            name,
            QTYPE.TXT,
            dnslib.TXT(props))
Ejemplo n.º 2
0
class SCIONDnsServer(SCIONElement):
    """
    SCION DNS Server. Responsible for starting the DNS resolver threads, and
    frequently updating the shared instance data from ZK.

    :cvar float SYNC_TIME: How frequently (in seconds) to update the shared
                           instance data from ZK.
    :cvar list SRV_TYPES: Service types to monitor/export
    """
    SERVICE_TYPE = DNS_SERVICE
    SYNC_TIME = 1.0
    SRV_TYPES = (BEACON_SERVICE, CERTIFICATE_SERVICE,
                 DNS_SERVICE, PATH_SERVICE, SIBRA_SERVICE)

    def __init__(self, server_id, conf_dir, setup=False):  # pragma: no cover
        """
        :param str server_id: server identifier.
        :param str conf_dir: configuration directory.
        :param bool setup: should setup() be called?
        """
        super().__init__(server_id, conf_dir)
        self.domain = DNSLabel(self.topology.dns_domain)
        self.lock = threading.Lock()
        self.services = {}
        if setup:
            self.setup()

    def setup(self):
        """
        Set up various servers and connections required.
        """
        self.resolver = ZoneResolver(self.lock, self.domain)
        self.udp_server = DNSServer(self.resolver, port=SCION_DNS_PORT,
                                    address=str(self.addr.host),
                                    server=SCIONDnsUdpServer,
                                    logger=SCIONDnsLogger())
        self.tcp_server = DNSServer(self.resolver, port=SCION_DNS_PORT,
                                    address=str(self.addr.host),
                                    server=SCIONDnsTcpServer,
                                    logger=SCIONDnsLogger())
        self.name_addrs = "\0".join([self.id, str(SCION_DNS_PORT),
                                     str(self.addr.host)])
        self.zk = Zookeeper(self.topology.isd_as, DNS_SERVICE, self.name_addrs,
                            self.topology.zookeepers)
        self._parties = {}
        self._setup_parties()

    def _setup_parties(self):
        """
        Join all the necessary ZK parties.
        """
        logging.debug("Joining parties")
        for type_ in self.SRV_TYPES:
            prefix = "/%s/%s" % (self.addr.isd_as, type_)
            autojoin = False
            # Join only the DNS service party, for the rest we just want to
            # setup the party so we can monitor the members.
            if type_ == DNS_SERVICE:
                autojoin = True
            self._parties[type_] = self.zk.retry(
                "Joining %s party" % type_, self.zk.party_setup, prefix=prefix,
                autojoin=autojoin)

    def _sync_zk_state(self):
        """
        Update shared instance data from ZK.
        """
        # Clear existing state
        self.services = {}

        try:
            self.zk.wait_connected(timeout=10.0)
        except ZkNoConnection:
            logging.warning("No connection to Zookeeper, can't update services")
            return

        # Retrieve alive instance details from ZK for each service.
        for srv_type in self.SRV_TYPES:
            srv_domain = self.domain.add(srv_type)
            self.services[srv_domain] = []
            party = self._parties[srv_type]
            try:
                srvs = party.list()
            except ZkNoConnection:
                # If the connection drops, don't update
                return
            for i in srvs:
                self._parse_srv_inst(i, srv_domain)

        # Update DNS zone data
        with self.lock:
            self.resolver.services = self.services

    def _parse_srv_inst(self, inst, srv_domain):
        """
        Parse a server instance block into name/port/addresses,
        and add them to the services list.

        :param str inst: Server instance block (null-seperated strings)
        :param dnslib.DNSLabel srv_domain: service domain
        """
        name, port, *addresses = inst.split("\0")
        self.services[srv_domain].extend(addresses)

    def handle_request(self, packet, sender, from_local_socket=True):
        raise NotImplementedError

    def run(self):
        """
        Run SCION Dns server.
        """
        self._sync_zk_state()
        self.udp_server.start_thread()
        self.tcp_server.start_thread()
        while self.udp_server.isAlive() and self.tcp_server.isAlive():
            self._sync_zk_state()
            sleep(self.SYNC_TIME)
Ejemplo n.º 3
0
class InterfaceDNSServer(BaseDNSServer):
    def __init__(self, hostname: str, bind: TIfaceAddress, port: int = 53):
        super().__init__(bind.ip, port)
        self.hostname = DNSLabel(f'{hostname}.zerowire.')
        self.network = bind.network
        self.add_record(
            '_services._dns-sd._udp',
            QTYPE.PTR,
            dnslib.PTR(
                self.hostname.add('_services._dns-sd._udp')))
        self.add_record(
            'b._dns-sd._udp',
            QTYPE.PTR,
            dnslib.PTR(self.hostname))
        self.add_record(
            'lb._dns-sd._udp',
            QTYPE.PTR,
            dnslib.PTR(self.hostname))

    def add_service(self, service: ServiceConfig) -> None:
        type = DNSLabel(service.type)
        name = type.add(DNSLabel(service.name))
        type_suffixed = self.hostname.add(type)
        name_suffixed = self.hostname.add(name)
        port = service.port
        props_list = []
        for key, value in (service.properties or {}).items():
            prop = key
            if isinstance(value, bool):
                if not value:
                    prop += '='
            else:
                prop += '=' + str(value)
            if len(prop) > 255:
                self.logger.error('Property too large to add to txt record %s', key)
                continue
            props_list.append(prop)

        props = ''.join(
            chr(len(prop)) + prop
            for prop in props_list
        )

        self.add_record(
            type,
            QTYPE.PTR,
            dnslib.PTR(name_suffixed))
        self.add_record(
            '_services._dns-sd._udp',
            QTYPE.PTR,
            dnslib.PTR(type_suffixed))
        self.add_record(
            name,
            QTYPE.SRV,
            dnslib.SRV(0, 0, port, self.hostname))
        self.add_record(
            name,
            QTYPE.TXT,
            dnslib.TXT(props))

    async def handle_query(
        self,
        request: DNSRecord,
        source: TSource,
    ) -> DNSRecord:
        if source[0] not in self.network:
            return None
        reply = request.reply()
        gave_answers = False

        for question in request.questions:
            orig_qname = question.qname
            self.validate_query_label(orig_qname)
            if not orig_qname.matchSuffix(self.hostname):
                raise Exception('Request for non local domain.')
            qname = orig_qname.stripSuffix(self.hostname)
            qtype = question.qtype
            self.logger.debug('Question %r %r', qname, QTYPE[qtype])

            records = self.get_records(qname, qtype)
            for record in records:
                reply.add_answer(dnslib.RR(
                    rname=orig_qname,
                    rtype=qtype,
                    rdata=record,
                ))
                gave_answers = True

        if not gave_answers:
            self.logger.debug('No answers')
            reply.header.set_rcode(RCODE.NXDOMAIN)

        return reply