def find(self, servicename=None): ''' Method: find Description: finds the named Auto Install service Returns: True -- if the service is found -- OR -- False -- if the service is not found Raises: AImDNSError - if there are no service references available ''' self.sdrefs = dict() self._found = False self._lookup = True self.servicename = servicename if servicename else self.servicename # only find over the number of interfaces available self.count = len(self.interfaces) list_sdrefs = list() for inf in self.interfaces: # register the service on the appropriate interface index try: interfaceindex = netif.if_nametoindex(inf) except netif.NetIFError, err: raise AIMDNSError(err) sdref = pyb.DNSServiceResolve(0, interfaceindex, servicename, regtype=common.REGTYPE, domain=common.DOMAIN, callBack=self._resolve_callback) list_sdrefs.append(sdref)
def test_if_interfaces(self): '''method to test the netif if_indextoname and if_nametoindex ''' interfaces = if_nameindex() for inter in interfaces: assert interfaces[inter] == if_indextoname(inter), \ 'Unable to match interface %s' % interfaces[inter] assert inter == if_nametoindex(interfaces[inter]), \ 'Unable to match %d index' % inter
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)