use host to resolve DNS if it's available.

This commit is contained in:
Yann Leboulanger 2014-04-09 17:04:03 +02:00
parent 46e9fc1f0a
commit 5f1c570744

View file

@ -51,8 +51,13 @@ except ImportError:
def get_resolver(idlequeue): def get_resolver(idlequeue):
if USE_LIBASYNCNS: if USE_LIBASYNCNS:
log.info('Using LibAsyncNSResolver')
return LibAsyncNSResolver() return LibAsyncNSResolver()
else: else:
if helpers.is_in_path('host'):
log.info('Using HostResolver')
return HostResolver(idlequeue)
log.info('Using NSLookupResolver')
return NSLookupResolver(idlequeue) return NSLookupResolver(idlequeue)
class CommonResolver(): class CommonResolver():
@ -62,43 +67,43 @@ class CommonResolver():
# dict {"host+type" : list of callbacks} # dict {"host+type" : list of callbacks}
self.handlers = {} self.handlers = {}
def resolve(self, host, on_ready, type='srv'): def resolve(self, host, on_ready, type_='srv'):
host = host.lower() host = host.lower()
log.debug('resolve %s type=%s' % (host, type)) log.debug('resolve %s type=%s' % (host, type_))
assert(type in ['srv', 'txt']) assert(type_ in ['srv', 'txt'])
if not host: if not host:
# empty host, return empty list of srv records # empty host, return empty list of srv records
on_ready([]) on_ready([])
return return
if host + type in self.resolved_hosts: if host + type_ in self.resolved_hosts:
# host is already resolved, return cached values # host is already resolved, return cached values
log.debug('%s already resolved: %s' % (host, log.debug('%s already resolved: %s' % (host,
self.resolved_hosts[host + type])) self.resolved_hosts[host + type_]))
on_ready(host, self.resolved_hosts[host + type]) on_ready(host, self.resolved_hosts[host + type_])
return return
if host + type in self.handlers: if host + type_ in self.handlers:
# host is about to be resolved by another connection, # host is about to be resolved by another connection,
# attach our callback # attach our callback
log.debug('already resolving %s' % host) log.debug('already resolving %s' % host)
self.handlers[host + type].append(on_ready) self.handlers[host + type_].append(on_ready)
else: else:
# host has never been resolved, start now # host has never been resolved, start now
log.debug('Starting to resolve %s using %s' % (host, self)) log.debug('Starting to resolve %s using %s' % (host, self))
self.handlers[host + type] = [on_ready] self.handlers[host + type_] = [on_ready]
self.start_resolve(host, type) self.start_resolve(host, type_)
def _on_ready(self, host, type, result_list): def _on_ready(self, host, type_, result_list):
# practically it is impossible to be the opposite, but who knows :) # practically it is impossible to be the opposite, but who knows :)
host = host.lower() host = host.lower()
log.debug('Resolving result for %s: %s' % (host, result_list)) log.debug('Resolving result for %s: %s' % (host, result_list))
if host + type not in self.resolved_hosts: if host + type_ not in self.resolved_hosts:
self.resolved_hosts[host + type] = result_list self.resolved_hosts[host + type_] = result_list
if host + type in self.handlers: if host + type_ in self.handlers:
for callback in self.handlers[host + type]: for callback in self.handlers[host + type_]:
callback(host, result_list) callback(host, result_list)
del(self.handlers[host + type]) del(self.handlers[host + type_])
def start_resolve(self, host, type): def start_resolve(self, host, type_):
pass pass
# FIXME: API usage is not consistent! This one requires that process is called # FIXME: API usage is not consistent! This one requires that process is called
@ -113,22 +118,22 @@ class LibAsyncNSResolver(CommonResolver):
self.asyncns = libasyncns.Asyncns() self.asyncns = libasyncns.Asyncns()
CommonResolver.__init__(self) CommonResolver.__init__(self)
def start_resolve(self, host, type): def start_resolve(self, host, type_):
type = libasyncns.ns_t_srv type_ = libasyncns.ns_t_srv
if type == 'txt': type = libasyncns.ns_t_txt if type_ == 'txt': type_ = libasyncns.ns_t_txt
resq = self.asyncns.res_query(host, libasyncns.ns_c_in, type) resq = self.asyncns.res_query(host, libasyncns.ns_c_in, type_)
resq.userdata = {'host':host, 'type':type} resq.userdata = {'host':host, 'type':type_}
# getaddrinfo to be done # getaddrinfo to be done
#def resolve_name(self, dname, callback): #def resolve_name(self, dname, callback):
#resq = self.asyncns.getaddrinfo(dname) #resq = self.asyncns.getaddrinfo(dname)
#resq.userdata = {'callback':callback, 'dname':dname} #resq.userdata = {'callback':callback, 'dname':dname}
def _on_ready(self, host, type, result_list): def _on_ready(self, host, type_, result_list):
if type == libasyncns.ns_t_srv: type = 'srv' if type_ == libasyncns.ns_t_srv: type_ = 'srv'
elif type == libasyncns.ns_t_txt: type = 'txt' elif type_ == libasyncns.ns_t_txt: type_ = 'txt'
CommonResolver._on_ready(self, host, type, result_list) CommonResolver._on_ready(self, host, type_, result_list)
def process(self): def process(self):
try: try:
@ -153,7 +158,7 @@ class LibAsyncNSResolver(CommonResolver):
continue continue
r['prio'] = r['pref'] r['prio'] = r['pref']
hosts.append(r) hosts.append(r)
self._on_ready(host=requested_host, type=requested_type, self._on_ready(host=requested_host, type_=requested_type,
result_list=hosts) result_list=hosts)
try: try:
resq = self.asyncns.get_next() resq = self.asyncns.get_next()
@ -276,27 +281,98 @@ class NSLookupResolver(CommonResolver):
'prio': prio}) 'prio': prio})
return hosts return hosts
def _on_ready(self, host, type, result): def _on_ready(self, host, type_, result):
# nslookup finished, parse the result and call the handlers # nslookup finished, parse the result and call the handlers
result_list = self.parse_srv_result(host, result) result_list = self.parse_srv_result(host, result)
CommonResolver._on_ready(self, host, type, result_list) CommonResolver._on_ready(self, host, type_, result_list)
def start_resolve(self, host, type): def start_resolve(self, host, type_):
""" """
Spawn new nslookup process and start waiting for results Spawn new nslookup process and start waiting for results
""" """
ns = NsLookup(self._on_ready, host, type) ns = NsLookup(self._on_ready, host, type_)
ns.set_idlequeue(self.idlequeue) ns.set_idlequeue(self.idlequeue)
ns.commandtimeout = 20 ns.commandtimeout = 20
ns.start() ns.start()
class HostResolver(CommonResolver):
"""
Asynchronous DNS resolver calling host. Processing of pending requests
is invoked from idlequeue which is watching file descriptor of pipe of
stdout of host process.
"""
def __init__(self, idlequeue):
self.idlequeue = idlequeue
self.process = False
CommonResolver.__init__(self)
def parse_srv_result(self, fqdn, result):
"""
Parse the output of host command and return list of properties:
'host', 'port','weight', 'priority' corresponding to the found srv hosts
"""
# typical output of host command:
# _xmpp-client._tcp.jabber.org has SRV record 30 30 5222 jabber.org.
if not result:
return []
ufqdn = helpers.ascii_to_idn(fqdn) # Unicode domain name
hosts = []
lines = result.split('\n')
for line in lines:
if line == '':
continue
domain = None
if line.startswith(fqdn):
domain = fqdn # For nslookup 9.5
elif helpers.decode_string(line).startswith(ufqdn):
line = helpers.decode_string(line)
domain = ufqdn # For nslookup 9.6
if domain:
# add 4 for ' has' after domain name
rest = line[len(domain)+4:].split('=')
if len(rest) != 2:
continue
answer_type, props_str = rest
if answer_type.strip() != 'SRV':
continue
props = props_str.strip().split(' ')
if len(props) < 4:
continue
prio, weight, port, host = props[-4:]
if host[-1] == '.':
host = host[:-1]
try:
prio = int(prio)
weight = int(weight)
port = int(port)
except ValueError:
continue
hosts.append({'host': host, 'port': port, 'weight': weight,
'prio': prio})
return hosts
def _on_ready(self, host, type_, result):
# host finished, parse the result and call the handlers
result_list = self.parse_srv_result(host, result)
CommonResolver._on_ready(self, host, type_, result_list)
def start_resolve(self, host, type_):
"""
Spawn new nslookup process and start waiting for results
"""
ns = Host(self._on_ready, host, type_)
ns.set_idlequeue(self.idlequeue)
ns.commandtimeout = 20
ns.start()
class NsLookup(IdleCommand): class NsLookup(IdleCommand):
def __init__(self, on_result, host='_xmpp-client', type='srv'): def __init__(self, on_result, host='_xmpp-client', type_='srv'):
IdleCommand.__init__(self, on_result) IdleCommand.__init__(self, on_result)
self.commandtimeout = 10 self.commandtimeout = 10
self.host = host.lower() self.host = host.lower()
self.type_ = type.lower() self.type_ = type_.lower()
if not host_pattern.match(self.host): if not host_pattern.match(self.host):
# invalid host name # invalid host name
log.error('Invalid host: %s' % self.host) log.error('Invalid host: %s' % self.host)
@ -315,6 +391,11 @@ class NsLookup(IdleCommand):
self.result_handler(self.host, self.type_, self.result) self.result_handler(self.host, self.type_, self.result)
self.result_handler = None self.result_handler = None
class Host(NsLookup):
def _compose_command_args(self):
return ['host', '-t', self.type_, self.host]
# below lines is on how to use API and assist in testing # below lines is on how to use API and assist in testing
if __name__ == '__main__': if __name__ == '__main__':
from gi.repository import GLib from gi.repository import GLib