diff options
Diffstat (limited to 'chromium/third_party/webpagereplay/platformsettings.py')
-rw-r--r-- | chromium/third_party/webpagereplay/platformsettings.py | 334 |
1 files changed, 198 insertions, 136 deletions
diff --git a/chromium/third_party/webpagereplay/platformsettings.py b/chromium/third_party/webpagereplay/platformsettings.py index 143ff9955ff..5e9b6deced8 100644 --- a/chromium/third_party/webpagereplay/platformsettings.py +++ b/chromium/third_party/webpagereplay/platformsettings.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Provides cross-platform utility fuctions. +"""Provides cross-platform utility functions. Example: import platformsettings @@ -26,12 +26,14 @@ For the full list of functions, see the bottom of the file. """ import atexit +import distutils.spawn import fileinput import logging import os import platform import re import socket +import stat import subprocess import sys import tempfile @@ -69,29 +71,23 @@ class CalledProcessError(PlatformSettingsError): ' '.join(self.cmd), self.returncode) -def _check_output(*args): - """Run Popen(*args) and return its output as a byte string. +def FindExecutable(executable): + """Finds the given executable in PATH. - Python 2.7 has subprocess.check_output. This is essentially the same - except that, as a convenience, all the positional args are used as - command arguments. + Since WPR may be invoked as sudo, meaning PATH is empty, we also hardcode a + few common paths. - Args: - *args: sequence of program arguments - Raises: - CalledProcessError if the program returns non-zero exit status. Returns: - output as a byte string. + The fully qualified path with .exe appended if appropriate or None if it + doesn't exist. """ - command_args = [str(a) for a in args] - logging.debug(' '.join(command_args)) - process = subprocess.Popen(command_args, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - output = process.communicate()[0] - retcode = process.poll() - if retcode: - raise CalledProcessError(retcode, command_args) - return output + return distutils.spawn.find_executable(executable, + os.pathsep.join([os.environ['PATH'], + '/sbin', + '/usr/bin', + '/usr/sbin/', + '/usr/local/sbin', + ])) class _BasePlatformSettings(object): @@ -127,25 +123,51 @@ class _BasePlatformSettings(object): raise NotImplementedError def ipfw(self, *args): - ipfw_cmd = self._ipfw_cmd() + args - return _check_output(*ipfw_cmd) + ipfw_cmd = (self._ipfw_cmd(), ) + args + return self._check_output(*ipfw_cmd, elevate_privilege=True) - def ping_rtt(self, hostname): - """Pings the hostname by calling the OS system ping command. - Also stores the result internally. + def _get_cwnd(self): + return None + + def _set_cwnd(self, args): + pass + + def _elevate_privilege_for_cmd(self, args): + return args + + def _check_output(self, *args, **kwargs): + """Run Popen(*args) and return its output as a byte string. + + Python 2.7 has subprocess.check_output. This is essentially the same + except that, as a convenience, all the positional args are used as + command arguments and the |elevate_privilege| kwarg is supported. Args: - hostname: hostname of the server to be pinged + *args: sequence of program arguments + elevate_privilege: Run the command with elevated privileges. + Raises: + CalledProcessError if the program returns non-zero exit status. Returns: - round trip time to the server in seconds, or 0 if unable to calculate RTT + output as a byte string. """ - raise NotImplementedError + command_args = [str(a) for a in args] - def _get_cwnd(self): - return None + if os.path.sep not in command_args[0]: + qualified_command = FindExecutable(command_args[0]) + assert qualified_command, 'Failed to find %s in path' % command_args[0] + command_args[0] = qualified_command - def _set_cwnd(self, args): - pass + if kwargs.get('elevate_privilege'): + command_args = self._elevate_privilege_for_cmd(command_args) + + logging.debug(' '.join(command_args)) + process = subprocess.Popen( + command_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + output = process.communicate()[0] + retcode = process.poll() + if retcode: + raise CalledProcessError(retcode, command_args) + return output def set_temporary_tcp_init_cwnd(self, cwnd): cwnd = int(cwnd) @@ -170,6 +192,12 @@ class _BasePlatformSettings(object): """ logging.error('Platform does not support loopback configuration.') + def _save_primary_interface_properties(self): + self._orig_nameserver = self.get_original_primary_nameserver() + + def _restore_primary_interface_properties(self): + self._set_primary_nameserver(self._orig_nameserver) + def _get_primary_nameserver(self): raise NotImplementedError @@ -184,21 +212,16 @@ class _BasePlatformSettings(object): return self._original_nameserver def set_temporary_primary_nameserver(self, nameserver): - orig_nameserver = self.get_original_primary_nameserver() + self._save_primary_interface_properties() self._set_primary_nameserver(nameserver) if self._get_primary_nameserver() == nameserver: logging.info('Changed temporary primary nameserver to %s', nameserver) - atexit.register(self._set_primary_nameserver, orig_nameserver) + atexit.register(self._restore_primary_interface_properties) else: raise self._get_dns_update_error() class _PosixPlatformSettings(_BasePlatformSettings): - PING_PATTERN = r'rtt min/avg/max/mdev = \d+\.\d+/(\d+\.\d+)/\d+\.\d+/\d+\.\d+' - PING_CMD = ('ping', '-c', '3', '-i', '0.2', '-W', '1') - # For OsX Lion non-root: - PING_RESTRICTED_CMD = ('ping', '-c', '1', '-i', '1', '-W', '1') - SUDO_PATH = '/usr/bin/sudo' def rerun_as_administrator(self): """If needed, rerun the program with administrative privileges. @@ -207,79 +230,45 @@ class _PosixPlatformSettings(_BasePlatformSettings): """ if os.geteuid() != 0: logging.warn('Rerunning with sudo: %s', sys.argv) - os.execv(self.SUDO_PATH, ['--'] + sys.argv) + os.execv('/usr/bin/sudo', ['--'] + sys.argv) + + def _elevate_privilege_for_cmd(self, args): + def IsSetUID(path): + return (os.stat(path).st_mode & stat.S_ISUID) == stat.S_ISUID + + def IsElevated(): + p = subprocess.Popen( + ['sudo', '-nv'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + stdout = p.communicate()[0] + # Some versions of sudo set the returncode based on whether sudo requires + # a password currently. Other versions return output when password is + # required and no output when the user is already authenticated. + return not p.returncode and not stdout + + if not IsSetUID(args[0]): + args = ['sudo'] + args + + if not IsElevated(): + print 'WPR needs to run %s under sudo. Please authenticate.' % args[1] + subprocess.check_call(['sudo', '-v']) # Synchronously authenticate. + + prompt = ('Would you like to always allow %s to run without sudo ' + '(via `sudo chmod +s %s`)? (y/N)' % (args[1], args[1])) + if raw_input(prompt).lower() == 'y': + subprocess.check_call(['sudo', 'chmod', '+s', args[1]]) + return args def _ipfw_cmd(self): - for ipfw_path in ['/usr/local/sbin/ipfw', '/sbin/ipfw']: - if os.path.exists(ipfw_path): - ipfw_cmd = (self.SUDO_PATH, ipfw_path) - self._ipfw_cmd = lambda: ipfw_cmd # skip rechecking paths - return ipfw_cmd - raise PlatformSettingsError('ipfw not found.') - - def _ping(self, hostname): - """Return ping output or None if ping fails. - - Initially pings 'localhost' to test for ping command that works. - If the tests fails, subsequent calls will return None without calling ping. - - Args: - hostname: host to ping - Returns: - ping stdout string, or None if ping unavailable - Raises: - CalledProcessError if ping returns non-zero exit - """ - if not hasattr(self, 'ping_cmd'): - test_host = 'localhost' - for self.ping_cmd in (self.PING_CMD, self.PING_RESTRICTED_CMD): - try: - if self._ping(test_host): - break - except (CalledProcessError, OSError) as e: - last_ping_error = e - else: - logging.critical('Ping configuration failed: %s', last_ping_error) - self.ping_cmd = None - if self.ping_cmd: - cmd = list(self.ping_cmd) + [hostname] - return self._check_output(*cmd) - return None - - def ping_rtt(self, hostname): - """Pings the hostname by calling the OS system ping command. - - Args: - hostname: hostname of the server to be pinged - Returns: - round trip time to the server in milliseconds, or 0 if unavailable - """ - rtt = 0 - output = None - try: - output = self._ping(hostname) - except CalledProcessError as e: - logging.critical('Ping failed: %s', e) - if output: - match = re.search(self.PING_PATTERN, output) - if match: - rtt = float(match.groups()[0]) - else: - logging.warning('Unable to ping %s: %s', hostname, output) - return rtt - + return 'ipfw' def _get_dns_update_error(self): return DnsUpdateError('Did you run under sudo?') - @classmethod - def _sysctl(cls, *args, **kwargs): - sysctl_args = [] + def _sysctl(self, *args, **kwargs): + sysctl_args = [FindExecutable('sysctl')] if kwargs.get('use_sudo'): - sysctl_args.append(cls.SUDO_PATH) - sysctl_args.append('/usr/sbin/sysctl') - if not os.path.exists(sysctl_args[-1]): - sysctl_args[-1] = '/sbin/sysctl' + sysctl_args = self._elevate_privilege_for_cmd(sysctl_args) sysctl_args.extend(str(a) for a in args) sysctl = subprocess.Popen( sysctl_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) @@ -306,21 +295,17 @@ class _PosixPlatformSettings(_BasePlatformSettings): logging.error('Unable to get sysctl %s: %s', name, rv) return None - def _check_output(self, *args): - """Allow tests to override this.""" - return _check_output(*args) - class _OsxPlatformSettings(_PosixPlatformSettings): LOCAL_SLOWSTART_MIB_NAME = 'net.inet.tcp.local_slowstart_flightsize' def _scutil(self, cmd): - scutil = subprocess.Popen( - ['/usr/sbin/scutil'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) + scutil = subprocess.Popen([FindExecutable('scutil')], + stdin=subprocess.PIPE, stdout=subprocess.PIPE) return scutil.communicate(cmd)[0] def _ifconfig(self, *args): - return _check_output(self.SUDO_PATH, '/sbin/ifconfig', *args) + return self._check_output('ifconfig', *args, elevate_privilege=True) def set_sysctl(self, name, value): rv = self._sysctl('-w', '%s=%s' % (name, value), use_sudo=True)[0] @@ -388,6 +373,41 @@ class _OsxPlatformSettings(_PosixPlatformSettings): self._scutil(command) +class _FreeBSDPlatformSettings(_PosixPlatformSettings): + """Partial implementation for FreeBSD. Does not allow a DNS server to be + launched nor ipfw to be used. + """ + RESOLV_CONF = '/etc/resolv.conf' + + def _get_default_route_line(self): + raise NotImplementedError + + def _set_cwnd(self, cwnd): + raise NotImplementedError + + def _get_cwnd(self): + raise NotImplementedError + + def setup_temporary_loopback_config(self): + raise NotImplementedError + + def _write_resolve_conf(self, dns): + raise NotImplementedError + + def _get_primary_nameserver(self): + try: + resolv_file = open(self.RESOLV_CONF) + except IOError: + raise DnsReadError() + for line in resolv_file: + if line.startswith('nameserver '): + return line.split()[1] + raise DnsReadError() + + def _set_primary_nameserver(self, dns): + raise NotImplementedError + + class _LinuxPlatformSettings(_PosixPlatformSettings): """The following thread recommends a way to update DNS on Linux: @@ -527,13 +547,13 @@ class _WindowsPlatformSettings(_BasePlatformSettings): return time.clock() def _arp(self, *args): - return _check_output('arp', *args) + return self._check_output('arp', *args) def _route(self, *args): - return _check_output('route', *args) + return self._check_output('route', *args) def _ipconfig(self, *args): - return _check_output('ipconfig', *args) + return self._check_output('ipconfig', *args) def _get_mac_address(self, ip): """Return the MAC address for the given ip.""" @@ -581,28 +601,69 @@ class _WindowsPlatformSettings(_BasePlatformSettings): DNS servers configured through DHCP: 192.168.1.1 Register with which suffix: Primary only """ - return _check_output('netsh', 'interface', 'ip', 'show', 'dns') - + return self._check_output('netsh', 'interface', 'ip', 'show', 'dns') + + def _netsh_set_dns(self, iface_name, addr): + """Modify DNS information on the primary interface.""" + output = self._check_output('netsh', 'interface', 'ip', 'set', 'dns', + iface_name, 'static', addr) + + def _netsh_set_dns_dhcp(self, iface_name): + """Modify DNS information on the primary interface.""" + output = self._check_output('netsh', 'interface', 'ip', 'set', 'dns', + iface_name, 'dhcp') + + def _get_interfaces_with_dns(self): + output = self._netsh_show_dns() + lines = output.split('\n') + iface_re = re.compile(r'^Configuration for interface \"(?P<name>.*)\"') + dns_re = re.compile(r'(?P<kind>.*):\s+(?P<dns>\d+\.\d+\.\d+\.\d+)') + iface_name = None + iface_dns = None + iface_kind = None + ifaces = [] + for line in lines: + iface_match = iface_re.match(line) + if iface_match: + iface_name = iface_match.group('name') + dns_match = dns_re.match(line) + if dns_match: + iface_dns = dns_match.group('dns') + iface_dns_config = dns_match.group('kind').strip() + if iface_dns_config == "Statically Configured DNS Servers": + iface_kind = "static" + elif iface_dns_config == "DNS servers configured through DHCP": + iface_kind = "dhcp" + if iface_name and iface_dns and iface_kind: + ifaces.append( (iface_dns, iface_name, iface_kind) ) + iface_name = None + iface_dns = None + return ifaces + + def _save_primary_interface_properties(self): + # TODO(etienneb): On windows, an interface can have multiple DNS server + # configured. We should save/restore all of them. + ifaces = self._get_interfaces_with_dns() + self._primary_interfaces = ifaces + + def _restore_primary_interface_properties(self): + for iface in self._primary_interfaces: + (iface_dns, iface_name, iface_kind) = iface + self._netsh_set_dns(iface_name, iface_dns) + if iface_kind == "dhcp": + self._netsh_set_dns_dhcp(iface_name) + def _get_primary_nameserver(self): - match = re.search(r':\s+(\d+\.\d+\.\d+\.\d+)', self._netsh_show_dns()) - return match.group(1) if match else None - + ifaces = self._get_interfaces_with_dns() + if not len(ifaces): + raise DnsUpdateError("Interface with valid DNS configured not found.") + (iface_dns, iface_name, iface_kind) = ifaces[0] + return iface_dns + def _set_primary_nameserver(self, dns): - vbs = """ -Set objWMIService = GetObject( _ - "winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2") -Set colNetCards = objWMIService.ExecQuery( _ - "Select * From Win32_NetworkAdapterConfiguration Where IPEnabled = True") -For Each objNetCard in colNetCards - arrDNSServers = Array("%s") - objNetCard.SetDNSServerSearchOrder(arrDNSServers) -Next -""" % dns - vbs_file = tempfile.NamedTemporaryFile(suffix='.vbs', delete=False) - vbs_file.write(vbs) - vbs_file.close() - subprocess.check_call(['cscript', '//nologo', vbs_file.name]) - os.remove(vbs_file.name) + for iface in self._primary_interfaces: + (iface_dns, iface_name, iface_kind) = iface + self._netsh_set_dns(iface_name, dns) class _WindowsXpPlatformSettings(_WindowsPlatformSettings): @@ -620,6 +681,8 @@ def _new_platform_settings(system, release): return _WindowsXpPlatformSettings() if system == 'Windows': return _WindowsPlatformSettings() + if system == 'FreeBSD': + return _FreeBSDPlatformSettings() raise NotImplementedError('Sorry %s %s is not supported.' % (system, release)) @@ -634,7 +697,6 @@ timer = _inst.timer get_server_ip_address = _inst.get_server_ip_address get_httpproxy_ip_address = _inst.get_httpproxy_ip_address ipfw = _inst.ipfw -ping_rtt = _inst.ping_rtt set_temporary_tcp_init_cwnd = _inst.set_temporary_tcp_init_cwnd setup_temporary_loopback_config = _inst.setup_temporary_loopback_config |