Esempio n. 1
0
        def test_update_updates(self, spawn, sleep, select,
                                dnsserviceupdaterecord, dnsserviceregister,
                                dnsserviceprocessresult):
            txtRecord = {'name': "Test Text Record", 'foo': 'bar'}
            newTxtRecord = {'name': "Test Text Record", 'foo': 'baz'}
            callback = mock.MagicMock(name="callback")

            select.side_effect = [[[
                dnsserviceregister.return_value,
            ], [], []], ExitTestNormally]

            UUT = MDNSEngine()

            UUT.start()
            run = spawn.call_args[0][0]

            self.assertEqual(
                dnsserviceregister.return_value,
                UUT.register(mock.sentinel.name,
                             mock.sentinel.regtype,
                             mock.sentinel.port,
                             txtRecord=txtRecord,
                             callback=callback))
            dnsserviceregister.assert_called_once_with(
                name=mock.sentinel.name,
                regtype=mock.sentinel.regtype,
                port=mock.sentinel.port,
                callBack=callback,
                txtRecord=mock.ANY)
            # pybonjour.TXTRecord doesn't correctly define equality comparisons, this will check it
            self.assertEqual(str(dnsserviceregister.call_args[1]["txtRecord"]),
                             str(pybonjour.TXTRecord(txtRecord)))

            try:
                run()
            except ExitTestNormally:
                pass
            except:
                self.fail(msg="Run raised unexpected error: %s" %
                          (traceback.print_exc(), ))

            dnsserviceprocessresult.assert_called_once_with(
                dnsserviceregister.return_value)

            self.assertTrue(
                UUT.update(mock.sentinel.name,
                           mock.sentinel.regtype,
                           txtRecord=newTxtRecord))
            dnsserviceupdaterecord.assert_called_once_with(
                dnsserviceregister.return_value, None, 0, mock.ANY, 0)
            self.assertEqual(str(dnsserviceupdaterecord.call_args[0][3]),
                             str(pybonjour.TXTRecord(newTxtRecord)))
Esempio n. 2
0
    def create_service(self):
        txt = {}

        #remove empty keys
        for key, val in self.txt:
            if val:
                txt[key] = val

        txt['port.p2pj'] = self.port
        txt['version'] = 1
        txt['txtvers'] = 1

        # replace gajim's show messages with compatible ones
        if 'status' in self.txt:
            txt['status'] = self.replace_show(self.txt['status'])
        else:
            txt['status'] = 'avail'

        self.txt = pybonjour.TXTRecord(txt, strict=True)

        try:
            sdRef = pybonjour.DNSServiceRegister(
                name=self.name,
                regtype=self.stype,
                port=self.port,
                txtRecord=self.txt,
                callBack=self.service_added_callback)
            self.service_sdRef = sdRef
        except pybonjour.BonjourError, e:
            self.service_add_fail_callback(e)
Esempio n. 3
0
	def zeroconf_register(self, reg_type, name=None, port=None, txt_record=None):
		"""
		Registers a new service with Zeroconf/Bonjour/Avahi.

		:param reg_type: type of service to register, e.g. "_gntp._tcp"
		:param name: displayable name of the service, if not given defaults to the OctoPrint instance name
		:param port: port to register for the service, if not given defaults to OctoPrint's (public) port
		:param txt_record: optional txt record to attach to the service, dictionary of key-value-pairs
		"""

		if not pybonjour:
			return

		if not name:
			name = self.get_instance_name()
		if not port:
			port = self.port

		params = dict(
			name=name,
			regtype=reg_type,
			port=port
		)
		if txt_record:
			params["txtRecord"] = pybonjour.TXTRecord(txt_record)

		key = (reg_type, port)
		self._sd_refs[key] = pybonjour.DNSServiceRegister(**params)
		self._logger.info("Registered {name} for {reg_type}".format(**locals()))
Esempio n. 4
0
        def publish(self):
            import pybonjour
            # records as in mt-daapd
            txtRecord = pybonjour.TXTRecord()
            txtRecord['txtvers'] = '1'
            txtRecord['iTSh Version'] = '131073'  #'196609'
            txtRecord['Machine Name'] = self.name
            txtRecord['Password'] = '******'  # 'False' ?

            #txtRecord['Database ID']        = '' # 16 hex digits
            #txtRecord['Version']            = '196616'
            #txtRecord['iTSh Version']       =
            #txtRecord['Machine ID']         = '' # 12 hex digits
            #txtRecord['Media Kinds Shared'] = '0'
            #txtRecord['OSsi']               = '0x1F6' #?
            #txtRecord['MID']                = '0x3AA6175DD7155BA7', = database id - 2 ?
            #txtRecord['dmv']                = '131077'

            def register_callback(sdRef, flags, errorCode, name, regtype,
                                  domain):
                pass

            self.sdRef = pybonjour.DNSServiceRegister(
                name=self.name,
                regtype="_daap._tcp",
                port=self.port,
                callBack=register_callback,
                txtRecord=txtRecord)

            while True:
                ready = select.select([self.sdRef], [], [])
                if self.sdRef in ready[0]:
                    pybonjour.DNSServiceProcessResult(self.sdRef)
                    break
Esempio n. 5
0
    def run(self):
        # Register on bonjour chat
        self.id = random.randint(100000000000, 999999999999)
        txt = {}
        txt['1st'] = self.fullname
        txt['last'] = ""
        txt['status'] = 'avail'
        txt['port.p2pj'] = self.port
        txt['nick'] = self.fullname
        txt['jid'] = self.node
        txt['email'] = self.node
        txt['version'] = 1
        txt['txtvers'] = 1

        txt = pybonjour.TXTRecord(txt, strict=True)

        self.sdRef = pybonjour.DNSServiceRegister(name=self.node,
                                regtype='_presence._tcp',
                                port=self.port,
                                txtRecord=txt,
                                callBack=self._register_callback
                                )
        # Bonjour chat handcheck
        self.must_run = True
        while self.must_run:
            ready = select.select([self.sdRef], [], [], 1)
            if self.sdRef in ready[0]:
                pybonjour.DNSServiceProcessResult(self.sdRef)
Esempio n. 6
0
 def update(self, name, regtype, txtRecord=None):
     recRef = self._find_record(name, regtype)
     if not recRef:
         return False
     if txtRecord is None:
         txtRecord = {}
     if not isinstance(txtRecord, pybonjour.TXTRecord):
         txtRecord = pybonjour.TXTRecord(txtRecord)
     pybonjour.DNSServiceUpdateRecord(recRef[0], None, 0, txtRecord, 0)
     return True
    def update_txt(self, show=None):
        if show:
            self.txt['status'] = self.replace_show(show)

        txt = pybonjour.TXTRecord(self.txt, strict=True)
        try:
            pybonjour.DNSServiceUpdateRecord(self.service_sdRef, None, 0, txt)
        except pybonjour.BonjourError as e:
            log.error('Error when updating TXT Record: %s', e)
            return False
        return True
 def register(self, name, regtype, port, txtRecord=None, callback=None):
     if callback is None:
         callback = register_callback
     if txtRecord is None:
         txtRecord = {}
     if not isinstance(txtRecord, pybonjour.TXTRecord):
         txtRecord = pybonjour.TXTRecord(txtRecord)
     sdRef = pybonjour.DNSServiceRegister(name=name,
                                          regtype=regtype,
                                          port=port,
                                          callBack=callback,
                                          txtRecord=txtRecord)
     self.rlist.append([sdRef, pybonjour.DNSServiceProcessResult, name, regtype])
     return sdRef
Esempio n. 9
0
    def register(self, type, name, port, properties=None):
        def callback(sdref, flags, error, name, regtype, domain):
            self.logger.info(
                'Registered service {0} (regtype {1}, domain {2})'.format(
                    name, regtype, domain))

        regtype = '_{0}._tcp'.format(type)
        txt = pybonjour.TXTRecord(items=(properties or {}))
        sdref = pybonjour.DNSServiceRegister(name=name,
                                             regtype=regtype,
                                             port=port,
                                             callBack=callback,
                                             txtRecord=txt)
        self.event_loop.register(sdref)
Esempio n. 10
0
 def run(self):
     name = 'TellStick'  # TODO
     self.sdRef = pybonjour.DNSServiceRegister(
         name=name,
         regtype='_hap._tcp',
         port=self.port,
         txtRecord=pybonjour.TXTRecord(self.txt),
         callBack=self.register)
     try:
         while True:
             ready = select.select([self.sdRef], [], [])
             if self.sdRef in ready[0]:
                 pybonjour.DNSServiceProcessResult(self.sdRef)
     finally:
         self.sdRef.close()
Esempio n. 11
0
        def test_register_without_callback(self, spawn, sleep, select,
                                           dnsserviceregister,
                                           dnsserviceprocessresult):
            txtRecord = {'name': "Test Text Record", 'foo': 'bar'}

            select.side_effect = [[[
                dnsserviceregister.return_value,
            ], [], []], ExitTestNormally]

            UUT = MDNSEngine()

            UUT.start()
            run = spawn.call_args[0][0]

            self.assertEqual(
                dnsserviceregister.return_value,
                UUT.register(mock.sentinel.name,
                             mock.sentinel.regtype,
                             mock.sentinel.port,
                             txtRecord=txtRecord))
            dnsserviceregister.assert_called_once_with(
                name=mock.sentinel.name,
                regtype=mock.sentinel.regtype,
                port=mock.sentinel.port,
                callBack=mock.ANY,
                txtRecord=mock.ANY)
            # pybonjour.TXTRecord doesn't correctly define equality comparisons, this will check it
            self.assertEqual(str(dnsserviceregister.call_args[1]["txtRecord"]),
                             str(pybonjour.TXTRecord(txtRecord)))
            callback = dnsserviceregister.call_args[1]["callBack"]
            try:
                callback()
            except:
                self.fail(
                    msg="default callback supplied by module throws exception")

            try:
                run()
            except ExitTestNormally:
                pass
            except:
                self.fail(msg="Run raised unexpected error: %s" %
                          (traceback.print_exc(), ))

            dnsserviceprocessresult.assert_called_once_with(
                dnsserviceregister.return_value)
Esempio n. 12
0
    def zeroconf_register(self,
                          reg_type,
                          name=None,
                          port=None,
                          txt_record=None):
        """
		Registers a new service with Zeroconf/Bonjour/Avahi.

		:param reg_type: type of service to register, e.g. "_gntp._tcp"
		:param name: displayable name of the service, if not given defaults to the OctoPrint instance name
		:param port: port to register for the service, if not given defaults to OctoPrint's (public) port
		:param txt_record: optional txt record to attach to the service, dictionary of key-value-pairs
		"""

        if not pybonjour:
            return

        if not name:
            name = self.get_instance_name()
        if not port:
            port = self.port

        params = dict(name=name, regtype=reg_type, port=port)
        if txt_record:
            params["txtRecord"] = pybonjour.TXTRecord(txt_record)

        key = (reg_type, port)

        counter = 1
        while True:
            try:
                self._sd_refs[key] = pybonjour.DNSServiceRegister(**params)
                self._logger.info(
                    u"Registered '{name}' for {regtype}".format(**params))
                return True
            except pybonjour.BonjourError as be:
                if be.errorCode == pybonjour.kDNSServiceErr_NameConflict:
                    # Name already registered by different service, let's try a counter postfix. See #2852
                    counter += 1
                    params["name"] = u"{} ({})".format(name, counter)
                else:
                    raise
Esempio n. 13
0
def broadcast(reactor, regtype, port, name=None, records={}):
    def _callback(sdref, flags, errorCode, name, regtype, domain):
        if errorCode == pybonjour.kDNSServiceErr_NoError:
            d.callback((sdref, name, regtype, domain))
        else:
            d.errback(errorCode)

    d = Deferred()
    sdref = pybonjour.DNSServiceRegister(name=name,
                                         regtype=regtype,
                                         port=port,
                                         callBack=_callback)
    recs = pybonjour.TXTRecord()
    for k, v in records.iteritems():
        recs[k] = str(v)
    pybonjour.DNSServiceAddRecord(sdref, 
                                  rrtype=pybonjour.kDNSServiceType_TXT,
                                  rdata=str(recs))

    reactor.addReader(BonjourServiceDescriptor(sdref))
    return d
Esempio n. 14
0
    def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB,
                 disconnected_CB, error_CB, name, host, port):
        self.domain = None  # specific domain to browse
        self.stype = '_presence._tcp'
        self.port = port  # listening port that gets announced
        self.username = name
        self.host = host
        self.txt = pybonjour.TXTRecord()  # service data

        # XXX these CBs should be set to None when we destroy the object
        # (go offline), because they create a circular reference
        self.new_serviceCB = new_serviceCB
        self.remove_serviceCB = remove_serviceCB
        self.name_conflictCB = name_conflictCB
        self.disconnected_CB = disconnected_CB
        self.error_CB = error_CB

        self.contacts = {}  # all current local contacts with data
        self.connected = False
        self.announced = False
        self.invalid_self_contact = {}
        self.resolved = []
Esempio n. 15
0
def register(name, regtype, port, host=None, txt=None):
    if txt:
        txt = pybonjour.TXTRecord(txt)
    else:
        txt = ''
    sdRef = pybonjour.DNSServiceRegister(name=name,
                                         interfaceIndex=0,
                                         regtype=regtype,
                                         port=port,
                                         host=host,
                                         txtRecord=txt,
                                         callBack=None)
    try:
        try:
            while True:
                ready = select.select([sdRef], [], [])
                if sdRef in ready[0]:
                    pybonjour.DNSServiceProcessResult(sdRef)
        except KeyboardInterrupt:
            pass
    finally:
        sdRef.close()
Esempio n. 16
0
    def create_service(self):
        txt = {}

        # remove empty keys
        for key, val in self.txt.items():
            if val:
                txt[key] = val

        txt['port.p2pj'] = self.port
        txt['version'] = 1
        txt['txtvers'] = 1

        # replace gajim's show messages with compatible ones
        if 'status' in self.txt:
            txt['status'] = self.replace_show(self.txt['status'])
        else:
            txt['status'] = 'avail'
        self.txt = txt
        try:
            self.service_sdRef = pybonjour.DNSServiceRegister(
                name=self.name,
                regtype=self.stype,
                port=self.port,
                txtRecord=pybonjour.TXTRecord(self.txt, strict=True),
                callBack=self.service_added_callback)

            log.info('Publishing service %s of type %s', self.name, self.stype)

            ready = select.select([self.service_sdRef], [], [])
            if self.service_sdRef in ready[0]:
                pybonjour.DNSServiceProcessResult(self.service_sdRef)

        except pybonjour.BonjourError as error:
            if error.errorCode == pybonjour.kDNSServiceErr_ServiceNotRunning:
                log.info('Service not running')
            else:
                self.error_CB(_('Error while adding service. %s') % error)
            self.disconnect()
Esempio n. 17
0
def register_service(name, regtype, port):
    def register_callback(sdRef, flags, errorCode, name, regtype, domain):
        if errorCode == pybonjour.kDNSServiceErr_NoError:
            logger.debug('Registered bonjour service %s.%s', name, regtype)

    record = pybonjour.TXTRecord(appletv.DEVICE_INFO)

    service = pybonjour.DNSServiceRegister(name=name,
                                           regtype=regtype,
                                           port=port,
                                           txtRecord=record,
                                           callBack=register_callback)

    try:
        try:
            while True:
                ready = select.select([service], [], [])
                if service in ready[0]:
                    pybonjour.DNSServiceProcessResult(service)
        except KeyboardInterrupt:
            pass
    finally:
        service.close()
Esempio n. 18
0
    def create_service(self):
        txt = {}

        #remove empty keys
        for key, val in self.txt:
            if val:
                txt[key] = val

        txt['port.p2pj'] = self.port
        txt['version'] = 1
        txt['txtvers'] = 1

        # replace gajim's show messages with compatible ones
        if 'status' in self.txt:
            txt['status'] = self.replace_show(self.txt['status'])
        else:
            txt['status'] = 'avail'

        self.txt = pybonjour.TXTRecord(txt, strict=True)

        try:
            sdRef = pybonjour.DNSServiceRegister(
                name=self.name,
                regtype=self.stype,
                port=self.port,
                txtRecord=self.txt,
                callBack=self.service_added_callback)
            self.service_sdRef = sdRef
        except pybonjour.BonjourError as e:
            self.service_add_fail_callback(e)
        else:
            gajim.log.debug('Publishing service %s of type %s' %
                            (self.name, self.stype))

            ready = select.select([sdRef], [], [], resolve_timeout)
            if sdRef in ready[0]:
                pybonjour.DNSServiceProcessResult(sdRef)
Esempio n. 19
0
        def assert_callback_on_service_is_correct(self,
                                                  UUT,
                                                  run,
                                                  regtype,
                                                  DNSServiceEnumerateDomains,
                                                  DNSServiceProcessResult,
                                                  DNSServiceBrowse,
                                                  DNSServiceResolve,
                                                  DNSServiceQueryRecord,
                                                  registerOnly=True,
                                                  domain=None):
            callback = mock.MagicMock(name="callback")
            UUT.callback_on_services(regtype,
                                     callback,
                                     registerOnly=registerOnly,
                                     domain=domain)
            if domain is None:
                DNSServiceBrowse.assert_called_once_with(regtype=regtype,
                                                         callBack=mock.ANY)
                browse_callback = DNSServiceBrowse.call_args[1]["callBack"]
                DNSServiceEnumerateDomains.assert_called_once_with(
                    pybonjour.kDNSServiceFlagsBrowseDomains, callBack=mock.ANY)
                domain_callback = DNSServiceEnumerateDomains.call_args[1][
                    "callBack"]
            else:
                DNSServiceBrowse.assert_called_once_with(regtype=regtype,
                                                         callBack=mock.ANY,
                                                         domain=domain)
                browse_callback = DNSServiceBrowse.call_args[1]["callBack"]
                domain_callback = None

            with mock.patch('select.select',
                            side_effect=[[[
                                DNSServiceBrowse.return_value,
                            ], [], []], ExitTestNormally]) as select:
                with self.assertRaises(ExitTestNormally):
                    run()
                self.assertIn(DNSServiceBrowse.return_value,
                              select.call_args[0][0])
            DNSServiceProcessResult.assert_called_once_with(
                DNSServiceBrowse.return_value)
            # In real code this would then trigger the assigned callback, so let's test its behaviour

            # First check an error code results in a bugout
            callback.reset_mock()
            browse_callback(mock.sentinel.sdRef, mock.sentinel.flags,
                            mock.sentinel.interfaceIndex,
                            mock.sentinel.errorCode, mock.sentinel.serviceName,
                            regtype, mock.sentinel.replyDomain)
            callback.assert_not_called()

            # Next check that a remove is handled as a bugout or a remove
            callback.reset_mock()
            browse_callback(mock.sentinel.sdRef, 0,
                            mock.sentinel.interfaceIndex,
                            pybonjour.kDNSServiceErr_NoError,
                            mock.sentinel.serviceName, regtype,
                            mock.sentinel.replyDomain)
            if not registerOnly:
                callback.assert_called_once_with({
                    "action": "remove",
                    "name": mock.sentinel.serviceName,
                    "type": regtype
                })
            else:
                callback.assert_not_called()

            # Next check that an add on .local is handled
            callback.reset_mock()
            browse_callback(mock.sentinel.sdRef, pybonjour.kDNSServiceFlagsAdd,
                            mock.sentinel.interfaceIndex,
                            pybonjour.kDNSServiceErr_NoError,
                            mock.sentinel.serviceName, regtype, "local.")
            DNSServiceResolve.assert_called_once_with(
                0, mock.sentinel.interfaceIndex, mock.sentinel.serviceName,
                regtype, "local.", mock.ANY)
            resolve_callback = DNSServiceResolve.call_args[0][5]
            with mock.patch('select.select',
                            side_effect=[[[
                                DNSServiceResolve.return_value,
                            ], [], []], ExitTestNormally]) as select:
                with self.assertRaises(ExitTestNormally):
                    run()
                self.assertIn(DNSServiceResolve.return_value,
                              select.call_args[0][0])
            DNSServiceProcessResult.assert_called_with(
                DNSServiceResolve.return_value)

            # Next check that an add on .example is handled
            callback.reset_mock()
            browse_callback(mock.sentinel.sdRef, pybonjour.kDNSServiceFlagsAdd,
                            mock.sentinel.interfaceIndex,
                            pybonjour.kDNSServiceErr_NoError, "service_name",
                            regtype, "example")
            DNSServiceQueryRecord.assert_called_once_with(
                interfaceIndex=mock.sentinel.interfaceIndex,
                fullname='service_name.example',
                rrtype=pybonjour.kDNSServiceType_SRV,
                callBack=mock.ANY)
            query_SRV_callback = DNSServiceQueryRecord.call_args[1]["callBack"]
            with mock.patch('select.select',
                            side_effect=[[[
                                DNSServiceQueryRecord.return_value,
                            ], [], []], ExitTestNormally]) as select:
                with self.assertRaises(ExitTestNormally):
                    run()
                self.assertIn(DNSServiceQueryRecord.return_value,
                              select.call_args[0][0])
            DNSServiceProcessResult.assert_called_with(
                DNSServiceQueryRecord.return_value)

            #Now check the query_SRV_callback behaves correctly
            query_SRV_callback(
                DNSServiceQueryRecord.return_value, mock.sentinel.flags,
                mock.sentinel.interfaceIndex, pybonjour.kDNSServiceErr_NoError,
                mock.sentinel.fullname, mock.sentinel.rrtype,
                mock.sentinel.rrclass, mock.sentinel.rdata, mock.sentinel.ttl)
            DNSServiceQueryRecord.return_value.close.assert_called_once_with()

            # Now to the given callback for resolve

            # Check an error results in a bugout
            callback.reset_mock()
            resolve_callback(mock.sentinel.sdRef, mock.sentinel.flags,
                             mock.sentinel.interfaceIndex,
                             mock.sentinel.errorCode, mock.sentinel.fullname,
                             mock.sentinel.hosttarget, mock.sentinel.port,
                             mock.sentinel.txtRecord)
            callback.assert_not_called()

            # Check a non error calls through to a query
            DNSServiceQueryRecord.reset_mock()
            callback.reset_mock()
            DNSServiceQueryRecord.return_value = mock.MagicMock(
                name="DNSServiceQueryRecord()")
            resolve_callback(DNSServiceResolve.return_value,
                             mock.sentinel.flags, mock.sentinel.interfaceIndex,
                             pybonjour.kDNSServiceErr_NoError,
                             mock.sentinel.fullname, mock.sentinel.hosttarget,
                             mock.sentinel.port, pybonjour.TXTRecord())
            callback.assert_not_called()
            DNSServiceQueryRecord.assert_called_once_with(
                interfaceIndex=mock.sentinel.interfaceIndex,
                fullname=mock.sentinel.hosttarget,
                rrtype=pybonjour.kDNSServiceType_A,
                callBack=mock.ANY)
            query_record_callback = DNSServiceQueryRecord.call_args[1][
                "callBack"]
            with mock.patch('select.select',
                            side_effect=[[[
                                DNSServiceQueryRecord.return_value,
                            ], [], []], ExitTestNormally]) as select:
                with self.assertRaises(ExitTestNormally):
                    run()
                self.assertIn(DNSServiceQueryRecord.return_value,
                              select.call_args[0][0])
            DNSServiceProcessResult.assert_called_with(
                DNSServiceQueryRecord.return_value)
            DNSServiceResolve.return_value.close.assert_called_once_with()

            # Next check query_record_callback
            callback.reset_mock()
            with mock.patch('socket.inet_ntoa') as inet_ntoa:
                query_record_callback(
                    DNSServiceQueryRecord.return_value, mock.sentinel.flags,
                    mock.sentinel.interfaceIndex,
                    pybonjour.kDNSServiceErr_NoError, mock.sentinel.fullname,
                    mock.sentinel.rrtype, mock.sentinel.rrclass,
                    mock.sentinel.rdata, mock.sentinel.ttl)
                inet_ntoa.assert_called_once_with(mock.sentinel.rdata)
                callback.assert_called_once_with({
                    "action":
                    "add",
                    "name":
                    mock.sentinel.serviceName,
                    "type":
                    regtype,
                    "address":
                    inet_ntoa.return_value,
                    "port":
                    mock.sentinel.port,
                    "txt":
                    dict(pybonjour.TXTRecord()),
                    "interface":
                    mock.sentinel.interfaceIndex
                })
            DNSServiceQueryRecord.return_value.close.assert_called_once_with()

            if domain_callback is not None:
                DNSServiceBrowse.reset_mock()
                domain_callback(DNSServiceEnumerateDomains.return_value,
                                mock.sentinel.flags,
                                mock.sentinel.interfaceIndex,
                                pybonjour.kDNSServiceErr_NoError,
                                mock.sentinel.new_domain)
                DNSServiceBrowse.assert_called_once_with(
                    regtype=regtype,
                    callBack=mock.ANY,
                    domain=mock.sentinel.new_domain)

                DNSServiceBrowse.reset_mock()
                domain_callback(DNSServiceEnumerateDomains.return_value,
                                mock.sentinel.flags,
                                mock.sentinel.interfaceIndex,
                                pybonjour.kDNSServiceErr_Invalid,
                                mock.sentinel.new_domain)
                DNSServiceBrowse.assert_not_called()
Esempio n. 20
0
class AImDNS(object):
    ''' Class: AImDNS - base class for registering, browsing and looking up
                        AI and ad hoc mDNS records.
    '''
    # a _handle_event() loop control variable, used to restart the loop
    # after modification to the self.sdrefs variable, private
    _restart_loop = False

    # find/browse mode variables, private
    _do_lookup = False
    _found = False

    # mDNS record resolved variable, used as a stack to indicate that the
    # service has been found, private
    _resolved = list()

    def __init__(self, servicename=None, domain='local', comment=None):
        '''Method: __init__, class private
           Parameters:
                        servicename - the AI servicename
                        domain      - the domain for the registered service
                        comment     - the text comment for the service
           Raises:
               AImDNSError - when unable to retrieve setup information from
                             the host about the available interfaces or the
                             AI SMF service.
        '''
        gettext.install("ai", "/usr/lib/locale")

        # find sdref handle
        self._find = None
        self._lookup = False
        self.services = dict()
        self.servicename = servicename
        self.domain = domain
        self.txt = comment
        self.inter = None
        self.port = 0
        self.verbose = False
        self.timeout = 5
        self.done = False
        self.count = 0

        self.sdrefs = dict()

        self.interfaces = libaimdns.getifaddrs()

        self.register_initialized = False
        self.exclude = False
        self.networks = ['0.0.0.0/0']

        self.instance = None
        self.instance_services = None

    def __del__(self):
        '''Method: __del__
           Parameters:
                None

           Raises:
                None
        '''
        self.done = True
        self.clear_sdrefs()

    def _resolve_callback(self, sdref, flags, interfaceindex, errorcode,
                          fullname, hosttarget, port, txtrecord):
        '''Method: _resolve_callback, class private
        Description:
            DNS Callback for the resolve process, stories the service
            information within the self.services variable.

        Args
            sdref          - service reference,
                             standard argument for callback, not used
            flags          - flag to determine what action is taking place
                             standard argument for callback, not used
            interfaceindex - the index for the interface that the service was
                             found on
            errorcode      - flag to determine if a registration error occurred
            fullname       - name of the service, should be
                             <service>._OSInstall._tcp.local.
            hosttarget     - name of the host, should be <nodename>.local.
            port           - the service port being used
            txtrecord      - the text record associated with the service,
                             standard argument for callback

        Returns
            None

        Raises
            None
        '''
        # handle errors from within the _browse_callback
        # after the select() call
        if errorcode == pyb.kDNSServiceErr_NoError:
            self._found = True
            # get the interface name for the index
            interface = netif.if_indextoname(interfaceindex)
            # interested in the service name and the domain only
            parts = fullname.split('.')
            service = dict()
            service['flags'] = not (flags & pyb.kDNSServiceFlagsAdd)
            service['hosttarget'] = hosttarget
            service['servicename'] = parts[0]
            service['domain'] = parts[-2]
            service['port'] = port
            service['comments'] = str(pyb.TXTRecord.parse(txtrecord))[1:]
            self.services.setdefault(interface, list()).append(service)

            # update the resolve stack flag
            self._resolved.append(True)

    def _browse_callback(self, sdref, flags, interfaceindex, errorcode,
                         servicename, regtype, replydomain):
        '''Method: _browse_callback, class private
        Description:
            DNS Callback for the browse process

        Args
            sdref          - service reference,
                             standard argument for callback, not used
            flags          - flag to determine what action is taking place
                             standard argument for callback, not used
            interfaceindex - the index for the interface that the service was
                             found on
            errorcode      - flag to determine if a registration error occurred
            servicename    - name of the service
            hosttarget     - name of the host, should be <nodename>.local.
            regtype        - registration type, should be _OSInstall._tcp.
            replydomain    - DNS domain, either local or remote

        Returns
            None

        Raises
            None
        '''
        if errorcode != pyb.kDNSServiceErr_NoError:
            return  # error handled in the _handle_event() method

        if self._lookup and servicename != self.servicename:
            return

        resolve_sdref = pyb.DNSServiceResolve(0, interfaceindex, servicename,
                                              regtype, replydomain,
                                              self._resolve_callback)

        # wait for and process resolve the current request
        try:
            while not self._resolved:
                try:
                    ready = select.select([resolve_sdref], list(), list(),
                                          self.timeout)
                except select.error:
                    # purposely ignore errors.
                    continue

                if resolve_sdref not in ready[0]:
                    # not a catastrophic error for the class, therefore,
                    # simply warn that the mDNS service record needed
                    # additional time to process and do not issue an
                    # exception.
                    sys.stderr.write(
                        cw(
                            _('warning: unable to resolve "%s", '
                              'try using a longer timeout\n') % servicename))
                    break
                # process the service
                pyb.DNSServiceProcessResult(resolve_sdref)
            else:
                self._resolved.pop()
        # allow exceptions to fall through
        finally:
            # clean up when there is no exception
            resolve_sdref.close()

    def _handle_events(self):
        ''' Method: __handle_events, class private
            Description:
                Handle the event processing for the registered service
                requests.

            Args
                None

            Returns
                None

            Raises
                None
        '''
        self.done = False
        while not self.done:
            # The self.sdrefs is a dictionary of the form:
            #
            #   for the find mode:
            #       { 'find':[list of sdrefs] }
            #
            #   OR for the browse mode:
            #       { 'browse':[list of sdrefs] }
            #
            #   OR for the register mode:
            #       { <service-name>:[list of sdrefs] }
            #
            #   OR for the register all mode:
            #       { <service-name1>:[list of sdrefs],
            #         <service-name2>:[list of sdrefs],
            #         ... }
            #
            # This must be converted to a simple list of sdrefs for the
            # select() call.
            therefs = list()
            # iterate through the dictionary
            for srv in self.sdrefs:
                for refs in self.sdrefs.get(srv, list()):
                    if refs is not None:
                        therefs.append(refs)

            # loop until done or we need to redo the service reference
            # list mentioned above.  The service reference list will be
            # updated when the SMF service is refreshed which sends a
            # SIGHUP to the application in daemon mode.  This processing
            # of the SIGHUP is done in the signal_hup() method below.
            self._restart_loop = False
            count = 0
            while not self._restart_loop and not self.done:
                try:
                    # process the appropriate service reference
                    try:
                        ready = select.select(therefs, list(), list(),
                                              self.timeout)
                    except select.error:
                        continue

                    # check to ensure that the __del__ method was not called
                    # between the select and the DNS processing.
                    if self.done:
                        continue

                    for sdref in therefs:
                        if sdref in ready[0]:
                            pyb.DNSServiceProcessResult(sdref)

                    # if browse or find loop then loop only long enough to
                    # ensure that all the registered mDNS records are
                    # retrieved per interface configured
                    if self._do_lookup is True:
                        count += 1
                        if count >= self.count:
                            self.done = True

                # <CTL>-C will exit the loop, application
                # needed for command line invocation
                except KeyboardInterrupt:
                    self.done = True

    def _register_callback(self, sdref, flags, errorcode, name, regtype,
                           domain):
        '''Method: _register_callback, private to class
           Description:
                DNS Callback for the registration process

            Args
                sdref       - service reference
                              standard argument for callback, not used
                flags       - flag to determine what action is taking place
                              standard argument for callback, not used
                errorcode   - flag to determine if a registration error
                              occurred
                name        - name of the service
                regtype     - registration type, should be _OSInstall._tcp.
                domain      - DNS domain, either local or remote

            Returns
                None

            Raises
                None
        '''
        # note: DNSService Errors are ignored here and handled elsewhere.
        if errorcode == pyb.kDNSServiceErr_NoError and \
           self.verbose:
            print _('Registered service:')
            print _('\tname    = %s') % name
            print _('\tregtype = %s') % regtype
            print _('\tdomain  = %s') % domain

    def _register_a_service(self,
                            name,
                            interfaces=None,
                            port=0,
                            comments=None):
        '''Method: _register_a_service, private to class

        Description:
            Register a single service on the interfaces

        Args
            interfaces - the interfaces to register the service on
            instance   - the SMF service instance handle
            name       - the service name to be registered
            port       - the port that the service is listening on, if
                         port is 0 then registering a service listed in
                         the AI SMF service instance.
            comments   - comments for the ad hoc registered service

        Returns
            list_sdrefs - list of service references

        Raises
            AImDNSError - if SMF status property does not exist, OR
                          if SMF txt_record property does not exist, OR
                          if SMF port property does not exist.
        '''
        if not self.register_initialized:
            self.exclude = libaimdns.getboolean_property(
                common.SRVINST, common.EXCLPROP)
            self.networks = libaimdns.getstrings_property(
                common.SRVINST, common.NETSPROP)
            self.register_initialized = True

        smf_port = None
        # if port is 0 then processing an AI service
        if port == 0:
            serv = config.get_service_props(name)
            if not serv:
                raise AIMDNSError(
                    cw(
                        _('error: aiMDNSError: no such '
                          'installation service "%s"') % name))

            # ensure the service is enabled
            if config.PROP_STATUS not in serv:
                raise AIMDNSError(
                    cw(
                        _('error: aiMDNSError: installation '
                          'service key "status" property does '
                          'not exist')))

            if serv[config.PROP_STATUS] != config.STATUS_ON:
                print(
                    cw(
                        _('warning: Installation service "%s" is not enabled '
                          % name)))
                return None

            smf_port = config.get_service_port(name)
            if not smf_port:
                try:
                    smf_port = libaimdns.getinteger_property(
                        common.SRVINST, common.PORTPROP)
                    smf_port = str(smf_port)
                except libaimdns.aiMDNSError, err:
                    raise AIMDNSError(
                        cw(
                            _('error: aiMDNSError: port property '
                              'failure (%s)') % err))

        # iterate over the interfaces saving the service references
        list_sdrefs = list()
        valid_networks = common.get_valid_networks()
        for inf in interfaces:
            include_it = False
            for ip in valid_networks:
                if interfaces[inf].startswith(ip):
                    include_it = True
                    break

            if not include_it:
                continue

            if self.verbose:
                print cw(_('Registering %s on %s (%s)') % \
                           (name, inf, interfaces[inf]))

            if smf_port is not None:
                # comments are part of the service record
                commentkey = serv[config.PROP_TXT_RECORD].split('=')[0]
                commenttxt = interfaces[inf].split('/')[0] + ':' + smf_port
                text = pyb.TXTRecord({commentkey: commenttxt})
                try:
                    port = int(smf_port)
                except ValueError:
                    # not a catastrophic error, just
                    # assume the default port of 5555.
                    port = common.DEFAULT_PORT
            # processing an ad hoc registration
            elif comments is None:
                adhoc_dict = {'service': 'ad hoc registration'}
                text = pyb.TXTRecord(adhoc_dict)
            else:
                text = pyb.TXTRecord({'service': comments})

            # register the service on the appropriate interface index
            try:
                interfaceindex = netif.if_nametoindex(inf)
            except netif.NetIFError, err:
                raise AIMDNSError(err)

            sdref = pyb.DNSServiceRegister(name=name,
                                           interfaceIndex=interfaceindex,
                                           regtype=common.REGTYPE,
                                           port=port,
                                           callBack=self._register_callback,
                                           txtRecord=text)

            # DNSServiceUpdateRecord will update the default record if
            # RecordRef is None. Time-to-live (ttl) for the record is being
            # set to 10 seconds.  This value allows enough time for the
            # record to be looked up and it is short enough that when the
            # service is deleted then the mdns daemon will remove it from
            # the cache after this value expires but prior to another service
            # with the same name being created.
            pyb.DNSServiceUpdateRecord(sdRef=sdref,
                                       RecordRef=None,
                                       rdata=text,
                                       ttl=10)

            # save the registered service reference
            list_sdrefs.append(sdref)
Esempio n. 21
0
import select
import pybonjour


SERVICE_NAME = 'RingoXMPPServer'
SERVICE_TYPE = '_xmpp-server._tcp'
SERVICE_PORT = 5222
XMPP_MUC_NAME = 'Ringo'
XMPP_MUC_HOST = 'conference'
TXTRECORD = pybonjour.TXTRecord({'muc_name': XMPP_MUC_NAME, 'muc_host': XMPP_MUC_HOST})


def register_callback(ref, flags, error_code, name, regtype, domain):
    if error_code == pybonjour.kDNSServiceErr_NoError:
        print 'Ringo service registered:'
        print '  name    =', name
        print '  regtype =', regtype
        print '  domain  =', domain

serviceRef = pybonjour.DNSServiceRegister(name=SERVICE_NAME,
                                          regtype=SERVICE_TYPE,
                                          port=SERVICE_PORT,
                                          txtRecord=TXTRECORD,
                                          callBack=register_callback)

try:
    while True:
        ready = select.select([serviceRef], [], [], 10)
        if serviceRef in ready[0]:
            pybonjour.DNSServiceProcessResult(serviceRef)
except KeyboardInterrupt:
Esempio n. 22
0
 def updateRecord(self, **values):
     for k in values:
         self.txt[k] = values[k]
     pybonjour.DNSServiceUpdateRecord(self.sdRef, None, 0,
                                      pybonjour.TXTRecord(self.txt))
Esempio n. 23
0
 def bonjoursocket(port):
     sdRef = pybonjour.DNSServiceRegister(name = name,
                                  regtype = regtype,
                                  port = port, txtRecord = pybonjour.TXTRecord(data),
                                  callBack = register_callback)
     return sdRef
Esempio n. 24
0
def beacon(config):
    '''
    Broadcast values via zeroconf

    If the announced values are static, it is advised to set run_once: True
    (do not poll) on the beacon configuration.

    The following are required configuration settings:
        'servicetype': The service type to announce.
        'port': The port of the service to announce.
        'txt': The TXT record of the service being announced as a dict.
               Grains can be used to define TXT values using the syntax:
                   grains.<grain_name>
               or:
                   grains.<grain_name>[i]
               where i is an integer representing the index of the grain to
               use. If the grain is not a list, the index is ignored.

    The following are optional configuration settings:
        'servicename': Set the name of the service. Will use the hostname from
                       __grains__['host'] if not set.
        'reset_on_change': If true and there is a change in TXT records
                           detected, it will stop announcing the service and
                           then restart announcing the service. This
                           interruption in service announcement may be
                           desirable if the client relies on changes in the
                           browse records to update its cache of the TXT
                           records.
                           Defaults to False.
        'reset_wait': The number of seconds to wait after announcement stops
                      announcing and before it restarts announcing in the
                      case where there is a change in TXT records detected
                      and 'reset_on_change' is True.
                      Defaults to 0.
        'copy_grains': If set to True, it will copy the grains passed into
                       the beacon when it backs them up to check for changes
                       on the next iteration. Normally, instead of copy, it
                       would use straight value assignment. This will allow
                       detection of changes to grains where the grains are
                       modified in-place instead of completely replaced.
                       In-place grains changes are not currently done in the
                       main Salt code but may be done due to a custom
                       plug-in.
                       Defaults to False.

    Example Config

    .. code-block:: yaml

       beacons:
         bonjour_announce:
           run_once: True
           servicetype: _demo._tcp
           port: 1234
           txt:
             ProdName: grains.productname
             SerialNo: grains.serialnumber
             Comments: 'this is a test'
    '''
    ret = []
    changes = {}
    txt = {}

    global LAST_GRAINS
    global SD_REF

    _validate = __validate__(config)
    if not _validate[0]:
        log.warning('Beacon {0} configuration invalid, '
                    'not adding. {1}'.format(__virtualname__, _validate[1]))
        return ret

    if 'servicename' in config:
        servicename = config['servicename']
    else:
        servicename = __grains__['host']
        # Check for hostname change
        if LAST_GRAINS and LAST_GRAINS['host'] != servicename:
            changes['servicename'] = servicename

    if LAST_GRAINS and config.get('reset_on_change', False):
        # Check for IP address change in the case when we reset on change
        if LAST_GRAINS.get('ipv4', []) != __grains__.get('ipv4', []):
            changes['ipv4'] = __grains__.get('ipv4', [])
        if LAST_GRAINS.get('ipv6', []) != __grains__.get('ipv6', []):
            changes['ipv6'] = __grains__.get('ipv6', [])

    for item in config['txt']:
        if config['txt'][item].startswith('grains.'):
            grain = config['txt'][item][7:]
            grain_index = None
            square_bracket = grain.find('[')
            if square_bracket != -1 and grain[-1] == ']':
                grain_index = int(grain[square_bracket + 1:-1])
                grain = grain[:square_bracket]

            grain_value = __grains__.get(grain, '')
            if isinstance(grain_value, list):
                if grain_index is not None:
                    grain_value = grain_value[grain_index]
                else:
                    grain_value = ','.join(grain_value)
            txt[item] = _enforce_txt_record_maxlen(item, grain_value)
            if LAST_GRAINS and (LAST_GRAINS.get(grain, '') != __grains__.get(
                    grain, '')):
                changes[str('txt.' + item)] = txt[item]
        else:
            txt[item] = _enforce_txt_record_maxlen(item, config['txt'][item])

        if not LAST_GRAINS:
            changes[str('txt.' + item)] = txt[item]

    if changes:
        txt_record = pybonjour.TXTRecord(items=txt)
        if not LAST_GRAINS:
            changes['servicename'] = servicename
            changes['servicetype'] = config['servicetype']
            changes['port'] = config['port']
            changes['ipv4'] = __grains__.get('ipv4', [])
            changes['ipv6'] = __grains__.get('ipv6', [])
            SD_REF = pybonjour.DNSServiceRegister(
                name=servicename,
                regtype=config['servicetype'],
                port=config['port'],
                txtRecord=txt_record,
                callBack=_register_callback)
            atexit.register(_close_sd_ref)
            ready = select.select([SD_REF], [], [])
            if SD_REF in ready[0]:
                pybonjour.DNSServiceProcessResult(SD_REF)
        elif config.get('reset_on_change', False) or 'servicename' in changes:
            # A change in 'servicename' requires a reset because we can only
            # directly update TXT records
            SD_REF.close()
            SD_REF = None
            reset_wait = config.get('reset_wait', 0)
            if reset_wait > 0:
                time.sleep(reset_wait)
            SD_REF = pybonjour.DNSServiceRegister(
                name=servicename,
                regtype=config['servicetype'],
                port=config['port'],
                txtRecord=txt_record,
                callBack=_register_callback)
            ready = select.select([SD_REF], [], [])
            if SD_REF in ready[0]:
                pybonjour.DNSServiceProcessResult(SD_REF)
        else:
            txt_record_raw = str(txt_record).encode('utf-8')
            pybonjour.DNSServiceUpdateRecord(SD_REF,
                                             RecordRef=None,
                                             flags=0,
                                             rdata=txt_record_raw)

        ret.append({'tag': 'result', 'changes': changes})

    if config.get('copy_grains', False):
        LAST_GRAINS = __grains__.copy()
    else:
        LAST_GRAINS = __grains__

    return ret
Esempio n. 25
0
    def _generate_txtRecord(self):
        """
        Device Info:
        -------------------------
        The TXTRecord string here determines the icon that will be displayed in Finder on MacOS
        clients. Default is to use MacRack which will display the icon for a rackmounted server.


        Time Machine (adisk):
        -------------------------
        sys=adVF=0x100 -- this is required when _adisk._tcp is present on device. When it is
        set, the MacOS client will send a NetShareEnumAll IOCTL and shares will be visible.
        Otherwise, Finder will only see the Time Machine share. In the absence of _adisk._tcp
        MacOS will _always_ send NetShareEnumAll IOCTL.

        waMa=0 -- MacOS server uses waMa=0, while embedded devices have it set to their Mac Address.
        Speculation in Samba-Technical indicates that this stands for "Wireless ADisk Mac Address".

        adVU -- ADisk Volume UUID.

        dk(n)=adVF=
        0xa1, 0x81 - AFP support
        0xa2, 0x82 - SMB support
        0xa3, 0x83 - AFP and SMB support

        adVN -- AirDisk Volume Name. We set this to the share name.
        network analysis indicates that current MacOS Time Machine shares set the port for adisk to 311.
        """
        if self.service not in ['ADISK', 'DEV_INFO']:
            return ('', self._get_interfaceindex())

        txtrecord = pybonjour.TXTRecord()
        if self.service == 'DEV_INFO':
            txtrecord['model'] = DevType.RACKMAC
            return (txtrecord, [kDNSServiceInterfaceIndexAny])

        if self.service == 'ADISK':
            iindex = [kDNSServiceInterfaceIndexAny]
            afp_shares = self.middleware.call_sync(
                'sharing.afp.query', [('timemachine', '=', True)])
            smb_shares = self.middleware.call_sync(
                'sharing.smb.query', [('timemachine', '=', True)])
            afp = set([(x['name'], x['path']) for x in afp_shares])
            smb = set([(x['name'], x['path']) for x in smb_shares])
            if len(afp | smb) == 0:
                return (None, [kDNSServiceInterfaceIndexAny])

            mixed_shares = afp & smb
            afp.difference_update(mixed_shares)
            smb.difference_update(mixed_shares)
            if afp_shares or smb_shares:
                iindex = []
                dkno = 0
                txtrecord['sys'] = 'waMa=0,adVF=0x100'
                for i in mixed_shares:
                    smb_advu = (list(
                        filter(lambda x: i[0] == x['name'],
                               smb_shares)))[0]['vuid']
                    txtrecord[
                        f'dk{dkno}'] = f'adVN={i[0]},adVF=0x83,adVU={smb_advu}'
                    dkno += 1

                for i in smb:
                    smb_advu = (list(
                        filter(lambda x: i[0] == x['name'],
                               smb_shares)))[0]['vuid']
                    txtrecord[
                        f'dk{dkno}'] = f'adVN={i[0]},adVF=0x82,adVU={smb_advu}'
                    dkno += 1

                for i in afp:
                    afp_advu = (list(
                        filter(lambda x: i[0] == x['name'],
                               afp_shares)))[0]['vuid']
                    txtrecord[
                        f'dk{dkno}'] = f'adVN={i[0]},adVF=0x81,adVU={afp_advu}'
                    dkno += 1

                if smb:
                    smb_iindex = self._get_interfaceindex('SMB')
                    if smb_iindex != [kDNSServiceInterfaceIndexAny]:
                        iindex.extend(smb_iindex)
                if afp:
                    afp_iindex = self._get_interfaceindex('AFP')
                    if afp_iindex != [kDNSServiceInterfaceIndexAny]:
                        iindex.extend(afp_iindex)

                if not iindex:
                    iindex = [kDNSServiceInterfaceIndexAny]

            return (txtrecord, iindex)
Esempio n. 26
0
    sys.exit(1)


def register_callback(sdRef, flags, errorCode, name, regtype, domain):
    if errorCode == pybonjour.kDNSServiceErr_NoError:
        print 'Registered service:'
        print '  name    =', name
        print '  regtype =', regtype
        print '  domain  =', domain


try:
    sdRef1 = pybonjour.DNSServiceRegister(regtype='_axis-video._tcp',
                                          port=80,
                                          txtRecord=pybonjour.TXTRecord(
                                              {'macaddress': mac}),
                                          callBack=register_callback)

    sdRef2 = pybonjour.DNSServiceRegister(regtype='_http._tcp',
                                          port=80,
                                          txtRecord=pybonjour.TXTRecord(
                                              {'macaddress': mac}),
                                          callBack=register_callback)
except:
    print 'cannot register DNS service'

    sys.exit(1)

try:
    try:
        while True:
Esempio n. 27
0
def beacon(config):
    """
    Broadcast values via zeroconf

    If the announced values are static, it is advised to set run_once: True
    (do not poll) on the beacon configuration.

    The following are required configuration settings:

    - ``servicetype`` - The service type to announce
    - ``port`` - The port of the service to announce
    - ``txt`` - The TXT record of the service being announced as a dict. Grains
      can be used to define TXT values using one of following two formats:

      - ``grains.<grain_name>``
      - ``grains.<grain_name>[i]`` where i is an integer representing the
        index of the grain to use. If the grain is not a list, the index is
        ignored.

    The following are optional configuration settings:

    - ``servicename`` - Set the name of the service. Will use the hostname from
      the minion's ``host`` grain if this value is not set.
    - ``reset_on_change`` - If ``True`` and there is a change in TXT records
      detected, it will stop announcing the service and then restart announcing
      the service. This interruption in service announcement may be desirable
      if the client relies on changes in the browse records to update its cache
      of TXT records. Defaults to ``False``.
    - ``reset_wait`` - The number of seconds to wait after announcement stops
      announcing and before it restarts announcing in the case where there is a
      change in TXT records detected and ``reset_on_change`` is ``True``.
      Defaults to ``0``.
    - ``copy_grains`` - If ``True``, Salt will copy the grains passed into the
      beacon when it backs them up to check for changes on the next iteration.
      Normally, instead of copy, it would use straight value assignment. This
      will allow detection of changes to grains where the grains are modified
      in-place instead of completely replaced.  In-place grains changes are not
      currently done in the main Salt code but may be done due to a custom
      plug-in. Defaults to ``False``.

    Example Config

    .. code-block:: yaml

       beacons:
         bonjour_announce:
           - run_once: True
           - servicetype: _demo._tcp
           - port: 1234
           - txt:
               ProdName: grains.productname
               SerialNo: grains.serialnumber
               Comments: 'this is a test'
    """
    ret = []
    changes = {}
    txt = {}

    global LAST_GRAINS
    global SD_REF

    _config = {}
    list(map(_config.update, config))

    if "servicename" in _config:
        servicename = _config["servicename"]
    else:
        servicename = __grains__["host"]
        # Check for hostname change
        if LAST_GRAINS and LAST_GRAINS["host"] != servicename:
            changes["servicename"] = servicename

    if LAST_GRAINS and _config.get("reset_on_change", False):
        # Check for IP address change in the case when we reset on change
        if LAST_GRAINS.get("ipv4", []) != __grains__.get("ipv4", []):
            changes["ipv4"] = __grains__.get("ipv4", [])
        if LAST_GRAINS.get("ipv6", []) != __grains__.get("ipv6", []):
            changes["ipv6"] = __grains__.get("ipv6", [])

    for item in _config["txt"]:
        changes_key = "txt." + salt.utils.stringutils.to_unicode(item)
        if _config["txt"][item].startswith("grains."):
            grain = _config["txt"][item][7:]
            grain_index = None
            square_bracket = grain.find("[")
            if square_bracket != -1 and grain[-1] == "]":
                grain_index = int(grain[square_bracket + 1:-1])
                grain = grain[:square_bracket]

            grain_value = __grains__.get(grain, "")
            if isinstance(grain_value, list):
                if grain_index is not None:
                    grain_value = grain_value[grain_index]
                else:
                    grain_value = ",".join(grain_value)
            txt[item] = _enforce_txt_record_maxlen(item, grain_value)
            if LAST_GRAINS and (LAST_GRAINS.get(grain, "") != __grains__.get(
                    grain, "")):
                changes[changes_key] = txt[item]
        else:
            txt[item] = _enforce_txt_record_maxlen(item, _config["txt"][item])

        if not LAST_GRAINS:
            changes[changes_key] = txt[item]

    if changes:
        txt_record = pybonjour.TXTRecord(items=txt)
        if not LAST_GRAINS:
            changes["servicename"] = servicename
            changes["servicetype"] = _config["servicetype"]
            changes["port"] = _config["port"]
            changes["ipv4"] = __grains__.get("ipv4", [])
            changes["ipv6"] = __grains__.get("ipv6", [])
            SD_REF = pybonjour.DNSServiceRegister(
                name=servicename,
                regtype=_config["servicetype"],
                port=_config["port"],
                txtRecord=txt_record,
                callBack=_register_callback,
            )
            atexit.register(_close_sd_ref)
            ready = select.select([SD_REF], [], [])
            if SD_REF in ready[0]:
                pybonjour.DNSServiceProcessResult(SD_REF)
        elif _config.get("reset_on_change", False) or "servicename" in changes:
            # A change in 'servicename' requires a reset because we can only
            # directly update TXT records
            SD_REF.close()
            SD_REF = None
            reset_wait = _config.get("reset_wait", 0)
            if reset_wait > 0:
                time.sleep(reset_wait)
            SD_REF = pybonjour.DNSServiceRegister(
                name=servicename,
                regtype=_config["servicetype"],
                port=_config["port"],
                txtRecord=txt_record,
                callBack=_register_callback,
            )
            ready = select.select([SD_REF], [], [])
            if SD_REF in ready[0]:
                pybonjour.DNSServiceProcessResult(SD_REF)
        else:
            txt_record_raw = str(txt_record).encode("utf-8")
            pybonjour.DNSServiceUpdateRecord(SD_REF,
                                             RecordRef=None,
                                             flags=0,
                                             rdata=txt_record_raw)

        ret.append({"tag": "result", "changes": changes})

    if _config.get("copy_grains", False):
        LAST_GRAINS = __grains__.copy()
    else:
        LAST_GRAINS = __grains__

    return ret
Esempio n. 28
0
 def text(self):
     return pybonjour.TXTRecord(items=self._text)