blob: 2ee1036c850af45ccd471d6262107b563161c89a [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
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +02005import dbus, logging, os, socket, stat, sys, threading, time
6from dbus.mainloop.glib import DBusGMainLoop
barfab@chromium.orgb6d29932012-04-11 09:46:43 +02007
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +02008import common, constants, flimflam_test_path
Eric Lif7b81922011-03-04 14:39:35 -08009from autotest_lib.client.bin import utils
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +020010import flimflam # Requires flimflam_test_path to be imported first.
Chris Masonef7ae5252010-04-21 08:21:25 -070011
12class LocalDns(object):
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +020013 """A wrapper around miniFakeDns that runs the server in a separate thread
14 and redirects all DNS queries to it.
Chris Masonef7ae5252010-04-21 08:21:25 -070015 """
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +020016 # This is a symlink. We look up the real path at runtime by following it.
17 _resolv_bak_file = 'resolv.conf.bak'
18
19 def __connect_to_flimflam(self):
20 """Connect to the network manager via DBus.
21
22 Stores dbus connection in self._flim upon success, throws on failure.
23 """
24 self._bus_loop = DBusGMainLoop(set_as_default=True)
25 self._system_bus = dbus.SystemBus(mainloop=self._bus_loop)
26 self._flim = flimflam.FlimFlam(self._system_bus)
Chris Masonef7ae5252010-04-21 08:21:25 -070027
Chris Masone7d04f572010-05-30 18:07:11 -070028 def __init__(self, fake_ip="127.0.0.1", local_port=53):
Chris Masonef7ae5252010-04-21 08:21:25 -070029 import miniFakeDns # So we don't need to install it in the chroot.
Chris Masonee0d39132011-07-29 19:06:42 -070030 self._dns = miniFakeDns.DNSServer(fake_ip=fake_ip, port=local_port)
Chris Masonef7ae5252010-04-21 08:21:25 -070031 self._stopper = threading.Event()
32 self._thread = threading.Thread(target=self._dns.run,
33 args=(self._stopper,))
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +020034 self.__connect_to_flimflam()
Chris Masonef7ae5252010-04-21 08:21:25 -070035
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +020036 def __get_host_by_name(self, hostname):
37 """Resolve the dotted-quad IPv4 address of |hostname|
38
39 This used to use suave python code, like this:
40 hosts = socket.getaddrinfo(hostname, 80, socket.AF_INET)
41 (fam, socktype, proto, canonname, (host, port)) = hosts[0]
42 return host
43
44 But that hangs sometimes, and we don't understand why. So, use
45 a subprocess with a timeout.
46 """
47 try:
48 host = utils.system_output('%s -c "import socket; '
49 'print socket.gethostbyname(\'%s\')"' % (
50 sys.executable, hostname),
51 ignore_status=True, timeout=2)
52 except Exception as e:
53 logging.warning(e)
54 return None
55 return host or None
56
57 def __attempt_resolve(self, hostname, ip, expected=True):
58 logging.debug('Attempting to resolve %s to %s' % (hostname, ip))
59 host = self.__get_host_by_name(hostname)
60 logging.debug('Resolve attempt for %s got %s' % (hostname, host))
61 return host and (host == ip) == expected
Chris Masonef7ae5252010-04-21 08:21:25 -070062
63 def run(self):
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +020064 """Start the mock DNS server and redirect all queries to it."""
Chris Masonef7ae5252010-04-21 08:21:25 -070065 self._thread.start()
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +020066 # Turn off captive portal checking, until we fix
67 # http://code.google.com/p/chromium-os/issues/detail?id=19640
68 self.check_portal_list = self._flim.GetCheckPortalList()
69 self._flim.SetCheckPortalList('')
70 # Redirect all DNS queries to the mock DNS server.
71 try:
72 # Follow resolv.conf symlink.
73 resolv = os.path.realpath(constants.RESOLV_CONF_FILE)
74 # Grab path to the real file, do following work in that directory.
75 resolv_dir = os.path.dirname(resolv)
76 resolv_bak = os.path.join(resolv_dir, self._resolv_bak_file)
77 resolv_contents = 'nameserver 127.0.0.1'
78 # Test to make sure the current resolv.conf isn't already our
79 # specially modified version. If this is the case, we have
80 # probably been interrupted while in the middle of this test
81 # in a previous run. The last thing we want to do at this point
82 # is to overwrite a legitimate backup.
83 if (utils.read_one_line(resolv) == resolv_contents and
84 os.path.exists(resolv_bak)):
85 logging.error('Current resolv.conf is setup for our local '
86 'server, and a backup already exists! '
87 'Skipping the backup step.')
88 else:
89 # Back up the current resolv.conf.
90 os.rename(resolv, resolv_bak)
91 # To stop flimflam from editing resolv.conf while we're working
92 # with it, we want to make the directory -r-xr-xr-x. Open an
93 # fd to the file first, so that we'll retain the ability to
94 # alter it.
95 resolv_fd = open(resolv, 'w')
96 self._resolv_dir_mode = os.stat(resolv_dir).st_mode
97 os.chmod(resolv_dir, (stat.S_IRUSR | stat.S_IXUSR |
98 stat.S_IRGRP | stat.S_IXGRP |
99 stat.S_IROTH | stat.S_IXOTH))
100 resolv_fd.write(resolv_contents)
101 resolv_fd.close()
102 assert utils.read_one_line(resolv) == resolv_contents
103 except Exception as e:
104 logging.error(str(e))
105 raise e
Chris Masonef7ae5252010-04-21 08:21:25 -0700106
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +0200107 utils.poll_for_condition(
108 lambda: self.__attempt_resolve('www.google.com.', '127.0.0.1'),
109 utils.TimeoutError('Timed out waiting for DNS changes.'),
110 timeout=10)
Chris Masonef7ae5252010-04-21 08:21:25 -0700111
112 def stop(self):
Bartosz Fabianowski6fe8dee2012-06-21 16:35:57 +0200113 """Restore the backed-up DNS settings and stop the mock DNS server."""
114 try:
115 # Follow resolv.conf symlink.
116 resolv = os.path.realpath(constants.RESOLV_CONF_FILE)
117 # Grab path to the real file, do following work in that directory.
118 resolv_dir = os.path.dirname(resolv)
119 resolv_bak = os.path.join(resolv_dir, self._resolv_bak_file)
120 os.chmod(resolv_dir, self._resolv_dir_mode)
121 os.rename(resolv_bak, resolv)
122
123 utils.poll_for_condition(
124 lambda: self.__attempt_resolve('www.google.com.',
125 '127.0.0.1',
126 expected=False),
127 utils.TimeoutError('Timed out waiting to revert DNS. '
128 'resolv.conf contents are: ' +
129 utils.read_one_line(resolv)),
130 timeout=10)
131 finally:
132 # Set captive portal checking to whatever it was at the start.
133 self._flim.SetCheckPortalList(self.check_portal_list)
134 # Stop the DNS server.
135 self._stopper.set()
136 self._thread.join()