use host to resolve DNS if it's available.
This commit is contained in:
parent
46e9fc1f0a
commit
5f1c570744
1 changed files with 114 additions and 33 deletions
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue