126 lines
5.9 KiB
Python
126 lines
5.9 KiB
Python
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import common, constants, logging, os, socket, stat, sys, threading, time
|
|
|
|
from autotest_lib.client.bin import utils
|
|
from autotest_lib.client.common_lib import error
|
|
|
|
class LocalDns(object):
|
|
"""A wrapper around miniFakeDns that runs the server in a separate thread
|
|
and redirects all DNS queries to it.
|
|
"""
|
|
# This is a symlink. We look up the real path at runtime by following it.
|
|
_resolv_bak_file = 'resolv.conf.bak'
|
|
|
|
def __init__(self, fake_ip="127.0.0.1", local_port=53):
|
|
import miniFakeDns # So we don't need to install it in the chroot.
|
|
self._dns = miniFakeDns.DNSServer(fake_ip=fake_ip, port=local_port)
|
|
self._stopper = threading.Event()
|
|
self._thread = threading.Thread(target=self._dns.run,
|
|
args=(self._stopper,))
|
|
|
|
def __get_host_by_name(self, hostname):
|
|
"""Resolve the dotted-quad IPv4 address of |hostname|
|
|
|
|
This used to use suave python code, like this:
|
|
hosts = socket.getaddrinfo(hostname, 80, socket.AF_INET)
|
|
(fam, socktype, proto, canonname, (host, port)) = hosts[0]
|
|
return host
|
|
|
|
But that hangs sometimes, and we don't understand why. So, use
|
|
a subprocess with a timeout.
|
|
"""
|
|
try:
|
|
host = utils.system_output('%s -c "import socket; '
|
|
'print socket.gethostbyname(\'%s\')"' % (
|
|
sys.executable, hostname),
|
|
ignore_status=True, timeout=2)
|
|
except Exception as e:
|
|
logging.warning(e)
|
|
return None
|
|
return host or None
|
|
|
|
def __attempt_resolve(self, hostname, ip, expected=True):
|
|
logging.debug('Attempting to resolve %s to %s' % (hostname, ip))
|
|
host = self.__get_host_by_name(hostname)
|
|
logging.debug('Resolve attempt for %s got %s' % (hostname, host))
|
|
return host and (host == ip) == expected
|
|
|
|
def run(self):
|
|
"""Start the mock DNS server and redirect all queries to it."""
|
|
self._thread.start()
|
|
# Redirect all DNS queries to the mock DNS server.
|
|
try:
|
|
# Follow resolv.conf symlink.
|
|
resolv = os.path.realpath(constants.RESOLV_CONF_FILE)
|
|
# Grab path to the real file, do following work in that directory.
|
|
resolv_dir = os.path.dirname(resolv)
|
|
resolv_bak = os.path.join(resolv_dir, self._resolv_bak_file)
|
|
resolv_contents = 'nameserver 127.0.0.1'
|
|
# Test to make sure the current resolv.conf isn't already our
|
|
# specially modified version. If this is the case, we have
|
|
# probably been interrupted while in the middle of this test
|
|
# in a previous run. The last thing we want to do at this point
|
|
# is to overwrite a legitimate backup.
|
|
if (utils.read_one_line(resolv) == resolv_contents and
|
|
os.path.exists(resolv_bak)):
|
|
logging.error('Current resolv.conf is setup for our local '
|
|
'server, and a backup already exists! '
|
|
'Skipping the backup step.')
|
|
else:
|
|
# Back up the current resolv.conf.
|
|
os.rename(resolv, resolv_bak)
|
|
# To stop flimflam from editing resolv.conf while we're working
|
|
# with it, we want to make the directory -r-xr-xr-x. Open an
|
|
# fd to the file first, so that we'll retain the ability to
|
|
# alter it.
|
|
resolv_fd = open(resolv, 'w')
|
|
self._resolv_dir_mode = os.stat(resolv_dir).st_mode
|
|
os.chmod(resolv_dir, (stat.S_IRUSR | stat.S_IXUSR |
|
|
stat.S_IRGRP | stat.S_IXGRP |
|
|
stat.S_IROTH | stat.S_IXOTH))
|
|
resolv_fd.write(resolv_contents)
|
|
resolv_fd.close()
|
|
assert utils.read_one_line(resolv) == resolv_contents
|
|
except Exception as e:
|
|
logging.error(str(e))
|
|
raise e
|
|
|
|
utils.poll_for_condition(
|
|
lambda: self.__attempt_resolve('www.google.com.', '127.0.0.1'),
|
|
utils.TimeoutError('Timed out waiting for DNS changes.'),
|
|
timeout=10)
|
|
|
|
def stop(self):
|
|
"""Restore the backed-up DNS settings and stop the mock DNS server."""
|
|
try:
|
|
# Follow resolv.conf symlink.
|
|
resolv = os.path.realpath(constants.RESOLV_CONF_FILE)
|
|
# Grab path to the real file, do following work in that directory.
|
|
resolv_dir = os.path.dirname(resolv)
|
|
resolv_bak = os.path.join(resolv_dir, self._resolv_bak_file)
|
|
os.chmod(resolv_dir, self._resolv_dir_mode)
|
|
if os.path.exists(resolv_bak):
|
|
os.rename(resolv_bak, resolv)
|
|
else:
|
|
# This probably means shill restarted during the execution
|
|
# of our test, and has cleaned up the .bak file we created.
|
|
raise error.TestError('Backup file %s no longer exists! '
|
|
'Connection manager probably crashed '
|
|
'during the test run.' %
|
|
resolv_bak)
|
|
|
|
utils.poll_for_condition(
|
|
lambda: self.__attempt_resolve('www.google.com.',
|
|
'127.0.0.1',
|
|
expected=False),
|
|
utils.TimeoutError('Timed out waiting to revert DNS. '
|
|
'resolv.conf contents are: ' +
|
|
utils.read_one_line(resolv)),
|
|
timeout=10)
|
|
finally:
|
|
# Stop the DNS server.
|
|
self._stopper.set()
|
|
self._thread.join()
|