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)))
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)
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()))
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
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)
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
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)
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()
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)
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
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
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 = []
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()
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()
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()
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)
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()
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)
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:
def updateRecord(self, **values): for k in values: self.txt[k] = values[k] pybonjour.DNSServiceUpdateRecord(self.sdRef, None, 0, pybonjour.TXTRecord(self.txt))
def bonjoursocket(port): sdRef = pybonjour.DNSServiceRegister(name = name, regtype = regtype, port = port, txtRecord = pybonjour.TXTRecord(data), callBack = register_callback) return sdRef
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
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)
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:
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
def text(self): return pybonjour.TXTRecord(items=self._text)