blob: ece6be5786084499509dd4917a727185a78bf179 [file] [log] [blame]
barfab@chromium.orgb6d29932012-04-11 09:46:43 +02001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Chris Masonef7ae5252010-04-21 08:21:25 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Eric Carusoa4ac7a82015-08-06 10:53:54 -07005import common, logging, os, socket, stat, sys, threading, time
barfab@chromium.orgb6d29932012-04-11 09:46:43 +02006
Eric Lif7b81922011-03-04 14:39:35 -08007from autotest_lib.client.bin import utils
Paul Stewart0b76ab32012-11-07 12:04:04 -08008from autotest_lib.client.common_lib import error
Eric Carusoa4ac7a82015-08-06 10:53:54 -07009from autotest_lib.client.common_lib.cros import constants
Chris Masonef7ae5252010-04-21 08:21:25 -070010
11class LocalDns(object):
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +020012 """A wrapper around miniFakeDns that runs the server in a separate thread
13 and redirects all DNS queries to it.
Chris Masonef7ae5252010-04-21 08:21:25 -070014 """
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +020015 # This is a symlink. We look up the real path at runtime by following it.
16 _resolv_bak_file = 'resolv.conf.bak'
17
Chris Masone7d04f572010-05-30 18:07:11 -070018 def __init__(self, fake_ip="127.0.0.1", local_port=53):
Chris Masonef7ae5252010-04-21 08:21:25 -070019 import miniFakeDns # So we don't need to install it in the chroot.
Chris Masonee0d39132011-07-29 19:06:42 -070020 self._dns = miniFakeDns.DNSServer(fake_ip=fake_ip, port=local_port)
Chris Masonef7ae5252010-04-21 08:21:25 -070021 self._stopper = threading.Event()
22 self._thread = threading.Thread(target=self._dns.run,
23 args=(self._stopper,))
24
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +020025 def __get_host_by_name(self, hostname):
26 """Resolve the dotted-quad IPv4 address of |hostname|
27
28 This used to use suave python code, like this:
29 hosts = socket.getaddrinfo(hostname, 80, socket.AF_INET)
30 (fam, socktype, proto, canonname, (host, port)) = hosts[0]
31 return host
32
33 But that hangs sometimes, and we don't understand why. So, use
34 a subprocess with a timeout.
35 """
36 try:
37 host = utils.system_output('%s -c "import socket; '
38 'print socket.gethostbyname(\'%s\')"' % (
39 sys.executable, hostname),
40 ignore_status=True, timeout=2)
41 except Exception as e:
42 logging.warning(e)
43 return None
44 return host or None
45
46 def __attempt_resolve(self, hostname, ip, expected=True):
47 logging.debug('Attempting to resolve %s to %s' % (hostname, ip))
48 host = self.__get_host_by_name(hostname)
49 logging.debug('Resolve attempt for %s got %s' % (hostname, host))
50 return host and (host == ip) == expected
Chris Masonef7ae5252010-04-21 08:21:25 -070051
52 def run(self):
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +020053 """Start the mock DNS server and redirect all queries to it."""
Chris Masonef7ae5252010-04-21 08:21:25 -070054 self._thread.start()
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +020055 # Redirect all DNS queries to the mock DNS server.
56 try:
57 # Follow resolv.conf symlink.
58 resolv = os.path.realpath(constants.RESOLV_CONF_FILE)
59 # Grab path to the real file, do following work in that directory.
60 resolv_dir = os.path.dirname(resolv)
61 resolv_bak = os.path.join(resolv_dir, self._resolv_bak_file)
62 resolv_contents = 'nameserver 127.0.0.1'
63 # Test to make sure the current resolv.conf isn't already our
64 # specially modified version. If this is the case, we have
65 # probably been interrupted while in the middle of this test
66 # in a previous run. The last thing we want to do at this point
67 # is to overwrite a legitimate backup.
68 if (utils.read_one_line(resolv) == resolv_contents and
69 os.path.exists(resolv_bak)):
70 logging.error('Current resolv.conf is setup for our local '
71 'server, and a backup already exists! '
72 'Skipping the backup step.')
73 else:
74 # Back up the current resolv.conf.
75 os.rename(resolv, resolv_bak)
76 # To stop flimflam from editing resolv.conf while we're working
77 # with it, we want to make the directory -r-xr-xr-x. Open an
78 # fd to the file first, so that we'll retain the ability to
79 # alter it.
80 resolv_fd = open(resolv, 'w')
81 self._resolv_dir_mode = os.stat(resolv_dir).st_mode
82 os.chmod(resolv_dir, (stat.S_IRUSR | stat.S_IXUSR |
83 stat.S_IRGRP | stat.S_IXGRP |
84 stat.S_IROTH | stat.S_IXOTH))
85 resolv_fd.write(resolv_contents)
86 resolv_fd.close()
87 assert utils.read_one_line(resolv) == resolv_contents
88 except Exception as e:
89 logging.error(str(e))
90 raise e
Chris Masonef7ae5252010-04-21 08:21:25 -070091
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +020092 utils.poll_for_condition(
93 lambda: self.__attempt_resolve('www.google.com.', '127.0.0.1'),
94 utils.TimeoutError('Timed out waiting for DNS changes.'),
95 timeout=10)
Chris Masonef7ae5252010-04-21 08:21:25 -070096
97 def stop(self):
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +020098 """Restore the backed-up DNS settings and stop the mock DNS server."""
99 try:
100 # Follow resolv.conf symlink.
101 resolv = os.path.realpath(constants.RESOLV_CONF_FILE)
102 # Grab path to the real file, do following work in that directory.
103 resolv_dir = os.path.dirname(resolv)
104 resolv_bak = os.path.join(resolv_dir, self._resolv_bak_file)
105 os.chmod(resolv_dir, self._resolv_dir_mode)
Paul Stewart0b76ab32012-11-07 12:04:04 -0800106 if os.path.exists(resolv_bak):
107 os.rename(resolv_bak, resolv)
108 else:
109 # This probably means shill restarted during the execution
110 # of our test, and has cleaned up the .bak file we created.
111 raise error.TestError('Backup file %s no longer exists! '
112 'Connection manager probably crashed '
113 'during the test run.' %
114 resolv_bak)
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +0200115
116 utils.poll_for_condition(
117 lambda: self.__attempt_resolve('www.google.com.',
118 '127.0.0.1',
119 expected=False),
120 utils.TimeoutError('Timed out waiting to revert DNS. '
121 'resolv.conf contents are: ' +
122 utils.read_one_line(resolv)),
123 timeout=10)
124 finally:
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +0200125 # Stop the DNS server.
126 self._stopper.set()
127 self._thread.join()