Only grant permissions to new extensions from sync if they have the expected version
[chromium-blink-merge.git] / tools / telemetry / third_party / webpagereplay / platformsettings.py
blob81cb56c760ec7863220ebd15c3dd7297e62654c5
1 #!/usr/bin/env python
2 # Copyright 2010 Google Inc. All Rights Reserved.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 """Provides cross-platform utility functions.
18 Example:
19 import platformsettings
20 ip = platformsettings.get_server_ip_address()
22 Functions with "_temporary_" in their name automatically clean-up upon
23 termination (via the atexit module).
25 For the full list of functions, see the bottom of the file.
26 """
28 import atexit
29 import distutils.spawn
30 import distutils.version
31 import fileinput
32 import logging
33 import os
34 import platform
35 import re
36 import socket
37 import stat
38 import subprocess
39 import sys
40 import time
41 import urlparse
44 class PlatformSettingsError(Exception):
45 """Module catch-all error."""
46 pass
49 class DnsReadError(PlatformSettingsError):
50 """Raised when unable to read DNS settings."""
51 pass
54 class DnsUpdateError(PlatformSettingsError):
55 """Raised when unable to update DNS settings."""
56 pass
59 class NotAdministratorError(PlatformSettingsError):
60 """Raised when not running as administrator."""
61 pass
64 class CalledProcessError(PlatformSettingsError):
65 """Raised when a _check_output() process returns a non-zero exit status."""
66 def __init__(self, returncode, cmd):
67 super(CalledProcessError, self).__init__()
68 self.returncode = returncode
69 self.cmd = cmd
71 def __str__(self):
72 return 'Command "%s" returned non-zero exit status %d' % (
73 ' '.join(self.cmd), self.returncode)
76 def FindExecutable(executable):
77 """Finds the given executable in PATH.
79 Since WPR may be invoked as sudo, meaning PATH is empty, we also hardcode a
80 few common paths.
82 Returns:
83 The fully qualified path with .exe appended if appropriate or None if it
84 doesn't exist.
85 """
86 return distutils.spawn.find_executable(executable,
87 os.pathsep.join([os.environ['PATH'],
88 '/sbin',
89 '/usr/bin',
90 '/usr/sbin/',
91 '/usr/local/sbin',
92 ]))
94 def HasSniSupport():
95 try:
96 import OpenSSL
97 return (distutils.version.StrictVersion(OpenSSL.__version__) >=
98 distutils.version.StrictVersion('0.13'))
99 except ImportError:
100 return False
103 def SupportsFdLimitControl():
104 """Whether the platform supports changing the process fd limit."""
105 return os.name is 'posix'
108 def GetFdLimit():
109 """Returns a tuple of (soft_limit, hard_limit)."""
110 import resource
111 return resource.getrlimit(resource.RLIMIT_NOFILE)
114 def AdjustFdLimit(new_soft_limit, new_hard_limit):
115 """Sets a new soft and hard limit for max number of fds."""
116 import resource
117 resource.setrlimit(resource.RLIMIT_NOFILE, (new_soft_limit, new_hard_limit))
120 class SystemProxy(object):
121 """A host/port pair for a HTTP or HTTPS proxy configuration."""
123 def __init__(self, host, port):
124 """Initialize a SystemProxy instance.
126 Args:
127 host: a host name or IP address string (e.g. "example.com" or "1.1.1.1").
128 port: a port string or integer (e.g. "8888" or 8888).
130 self.host = host
131 self.port = int(port) if port else None
133 def __nonzero__(self):
134 """True if the host is set."""
135 return bool(self.host)
137 @classmethod
138 def from_url(cls, proxy_url):
139 """Create a SystemProxy instance.
141 If proxy_url is None, an empty string, or an invalid URL, the
142 SystemProxy instance with have None and None for the host and port
143 (no exception is raised).
145 Args:
146 proxy_url: a proxy url string such as "http://proxy.com:8888/".
147 Returns:
148 a System proxy instance.
150 if proxy_url:
151 parse_result = urlparse.urlparse(proxy_url)
152 return cls(parse_result.hostname, parse_result.port)
153 return cls(None, None)
156 class _BasePlatformSettings(object):
158 def get_system_logging_handler(self):
159 """Return a handler for the logging module (optional)."""
160 return None
162 def rerun_as_administrator(self):
163 """If needed, rerun the program with administrative privileges.
165 Raises NotAdministratorError if unable to rerun.
167 pass
169 def timer(self):
170 """Return the current time in seconds as a floating point number."""
171 return time.time()
173 def get_server_ip_address(self, is_server_mode=False):
174 """Returns the IP address to use for dnsproxy and ipfw."""
175 if is_server_mode:
176 return socket.gethostbyname(socket.gethostname())
177 return '127.0.0.1'
179 def get_httpproxy_ip_address(self, is_server_mode=False):
180 """Returns the IP address to use for httpproxy."""
181 if is_server_mode:
182 return '0.0.0.0'
183 return '127.0.0.1'
185 def get_system_proxy(self, use_ssl):
186 """Returns the system HTTP(S) proxy host, port."""
187 del use_ssl
188 return SystemProxy(None, None)
190 def _ipfw_cmd(self):
191 raise NotImplementedError
193 def ipfw(self, *args):
194 ipfw_cmd = (self._ipfw_cmd(), ) + args
195 return self._check_output(*ipfw_cmd, elevate_privilege=True)
197 def has_ipfw(self):
198 try:
199 self.ipfw('list')
200 return True
201 except AssertionError as e:
202 logging.warning('Failed to start ipfw command. '
203 'Error: %s' % e.message)
204 return False
206 def _get_cwnd(self):
207 return None
209 def _set_cwnd(self, args):
210 pass
212 def _elevate_privilege_for_cmd(self, args):
213 return args
215 def _check_output(self, *args, **kwargs):
216 """Run Popen(*args) and return its output as a byte string.
218 Python 2.7 has subprocess.check_output. This is essentially the same
219 except that, as a convenience, all the positional args are used as
220 command arguments and the |elevate_privilege| kwarg is supported.
222 Args:
223 *args: sequence of program arguments
224 elevate_privilege: Run the command with elevated privileges.
225 Raises:
226 CalledProcessError if the program returns non-zero exit status.
227 Returns:
228 output as a byte string.
230 command_args = [str(a) for a in args]
232 if os.path.sep not in command_args[0]:
233 qualified_command = FindExecutable(command_args[0])
234 assert qualified_command, 'Failed to find %s in path' % command_args[0]
235 command_args[0] = qualified_command
237 if kwargs.get('elevate_privilege'):
238 command_args = self._elevate_privilege_for_cmd(command_args)
240 logging.debug(' '.join(command_args))
241 process = subprocess.Popen(
242 command_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
243 output = process.communicate()[0]
244 retcode = process.poll()
245 if retcode:
246 raise CalledProcessError(retcode, command_args)
247 return output
249 def set_temporary_tcp_init_cwnd(self, cwnd):
250 cwnd = int(cwnd)
251 original_cwnd = self._get_cwnd()
252 if original_cwnd is None:
253 raise PlatformSettingsError('Unable to get current tcp init_cwnd.')
254 if cwnd == original_cwnd:
255 logging.info('TCP init_cwnd already set to target value: %s', cwnd)
256 else:
257 self._set_cwnd(cwnd)
258 if self._get_cwnd() == cwnd:
259 logging.info('Changed cwnd to %s', cwnd)
260 atexit.register(self._set_cwnd, original_cwnd)
261 else:
262 logging.error('Unable to update cwnd to %s', cwnd)
264 def setup_temporary_loopback_config(self):
265 """Setup the loopback interface similar to real interface.
267 We use loopback for much of our testing, and on some systems, loopback
268 behaves differently from real interfaces.
270 logging.error('Platform does not support loopback configuration.')
272 def _save_primary_interface_properties(self):
273 self._orig_nameserver = self.get_original_primary_nameserver()
275 def _restore_primary_interface_properties(self):
276 self._set_primary_nameserver(self._orig_nameserver)
278 def _get_primary_nameserver(self):
279 raise NotImplementedError
281 def _set_primary_nameserver(self, _):
282 raise NotImplementedError
284 def get_original_primary_nameserver(self):
285 if not hasattr(self, '_original_nameserver'):
286 self._original_nameserver = self._get_primary_nameserver()
287 logging.info('Saved original primary DNS nameserver: %s',
288 self._original_nameserver)
289 return self._original_nameserver
291 def set_temporary_primary_nameserver(self, nameserver):
292 self._save_primary_interface_properties()
293 self._set_primary_nameserver(nameserver)
294 if self._get_primary_nameserver() == nameserver:
295 logging.info('Changed temporary primary nameserver to %s', nameserver)
296 atexit.register(self._restore_primary_interface_properties)
297 else:
298 raise self._get_dns_update_error()
301 class _PosixPlatformSettings(_BasePlatformSettings):
303 # pylint: disable=abstract-method
304 # Suppress lint check for _get_primary_nameserver & _set_primary_nameserver
306 def rerun_as_administrator(self):
307 """If needed, rerun the program with administrative privileges.
309 Raises NotAdministratorError if unable to rerun.
311 if os.geteuid() != 0:
312 logging.warn('Rerunning with sudo: %s', sys.argv)
313 os.execv('/usr/bin/sudo', ['--'] + sys.argv)
315 def _elevate_privilege_for_cmd(self, args):
316 def IsSetUID(path):
317 return (os.stat(path).st_mode & stat.S_ISUID) == stat.S_ISUID
319 def IsElevated():
320 p = subprocess.Popen(
321 ['sudo', '-nv'], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
322 stderr=subprocess.STDOUT)
323 stdout = p.communicate()[0]
324 # Some versions of sudo set the returncode based on whether sudo requires
325 # a password currently. Other versions return output when password is
326 # required and no output when the user is already authenticated.
327 return not p.returncode and not stdout
329 if not IsSetUID(args[0]):
330 args = ['sudo'] + args
332 if not IsElevated():
333 print 'WPR needs to run %s under sudo. Please authenticate.' % args[1]
334 subprocess.check_call(['sudo', '-v']) # Synchronously authenticate.
336 prompt = ('Would you like to always allow %s to run without sudo '
337 '(via `sudo chmod +s %s`)? (y/N)' % (args[1], args[1]))
338 if raw_input(prompt).lower() == 'y':
339 subprocess.check_call(['sudo', 'chmod', '+s', args[1]])
340 return args
342 def get_system_proxy(self, use_ssl):
343 """Returns the system HTTP(S) proxy host, port."""
344 proxy_url = os.environ.get('https_proxy' if use_ssl else 'http_proxy')
345 return SystemProxy.from_url(proxy_url)
347 def _ipfw_cmd(self):
348 return 'ipfw'
350 def _get_dns_update_error(self):
351 return DnsUpdateError('Did you run under sudo?')
353 def _sysctl(self, *args, **kwargs):
354 sysctl_args = [FindExecutable('sysctl')]
355 if kwargs.get('use_sudo'):
356 sysctl_args = self._elevate_privilege_for_cmd(sysctl_args)
357 sysctl_args.extend(str(a) for a in args)
358 sysctl = subprocess.Popen(
359 sysctl_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
360 stdout = sysctl.communicate()[0]
361 return sysctl.returncode, stdout
363 def has_sysctl(self, name):
364 if not hasattr(self, 'has_sysctl_cache'):
365 self.has_sysctl_cache = {}
366 if name not in self.has_sysctl_cache:
367 self.has_sysctl_cache[name] = self._sysctl(name)[0] == 0
368 return self.has_sysctl_cache[name]
370 def set_sysctl(self, name, value):
371 rv = self._sysctl('%s=%s' % (name, value), use_sudo=True)[0]
372 if rv != 0:
373 logging.error('Unable to set sysctl %s: %s', name, rv)
375 def get_sysctl(self, name):
376 rv, value = self._sysctl('-n', name)
377 if rv == 0:
378 return value
379 else:
380 logging.error('Unable to get sysctl %s: %s', name, rv)
381 return None
384 class _OsxPlatformSettings(_PosixPlatformSettings):
385 LOCAL_SLOWSTART_MIB_NAME = 'net.inet.tcp.local_slowstart_flightsize'
387 def _scutil(self, cmd):
388 scutil = subprocess.Popen([FindExecutable('scutil')],
389 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
390 return scutil.communicate(cmd)[0]
392 def _ifconfig(self, *args):
393 return self._check_output('ifconfig', *args, elevate_privilege=True)
395 def set_sysctl(self, name, value):
396 rv = self._sysctl('-w', '%s=%s' % (name, value), use_sudo=True)[0]
397 if rv != 0:
398 logging.error('Unable to set sysctl %s: %s', name, rv)
400 def _get_cwnd(self):
401 return int(self.get_sysctl(self.LOCAL_SLOWSTART_MIB_NAME))
403 def _set_cwnd(self, size):
404 self.set_sysctl(self.LOCAL_SLOWSTART_MIB_NAME, size)
406 def _get_loopback_mtu(self):
407 config = self._ifconfig('lo0')
408 match = re.search(r'\smtu\s+(\d+)', config)
409 return int(match.group(1)) if match else None
411 def setup_temporary_loopback_config(self):
412 """Configure loopback to temporarily use reasonably sized frames.
414 OS X uses jumbo frames by default (16KB).
416 TARGET_LOOPBACK_MTU = 1500
417 original_mtu = self._get_loopback_mtu()
418 if original_mtu is None:
419 logging.error('Unable to read loopback mtu. Setting left unchanged.')
420 return
421 if original_mtu == TARGET_LOOPBACK_MTU:
422 logging.debug('Loopback MTU already has target value: %d', original_mtu)
423 else:
424 self._ifconfig('lo0', 'mtu', TARGET_LOOPBACK_MTU)
425 if self._get_loopback_mtu() == TARGET_LOOPBACK_MTU:
426 logging.debug('Set loopback MTU to %d (was %d)',
427 TARGET_LOOPBACK_MTU, original_mtu)
428 atexit.register(self._ifconfig, 'lo0', 'mtu', original_mtu)
429 else:
430 logging.error('Unable to change loopback MTU from %d to %d',
431 original_mtu, TARGET_LOOPBACK_MTU)
433 def _get_dns_service_key(self):
434 output = self._scutil('show State:/Network/Global/IPv4')
435 lines = output.split('\n')
436 for line in lines:
437 key_value = line.split(' : ')
438 if key_value[0] == ' PrimaryService':
439 return 'State:/Network/Service/%s/DNS' % key_value[1]
440 raise DnsReadError('Unable to find DNS service key: %s', output)
442 def _get_primary_nameserver(self):
443 output = self._scutil('show %s' % self._get_dns_service_key())
444 match = re.search(
445 br'ServerAddresses\s+:\s+<array>\s+{\s+0\s+:\s+((\d{1,3}\.){3}\d{1,3})',
446 output)
447 if match:
448 return match.group(1)
449 else:
450 raise DnsReadError('Unable to find primary DNS server: %s', output)
452 def _set_primary_nameserver(self, dns):
453 command = '\n'.join([
454 'd.init',
455 'd.add ServerAddresses * %s' % dns,
456 'set %s' % self._get_dns_service_key()
458 self._scutil(command)
461 class _FreeBSDPlatformSettings(_PosixPlatformSettings):
462 """Partial implementation for FreeBSD. Does not allow a DNS server to be
463 launched nor ipfw to be used.
465 RESOLV_CONF = '/etc/resolv.conf'
467 def _get_default_route_line(self):
468 raise NotImplementedError
470 def _set_cwnd(self, cwnd):
471 raise NotImplementedError
473 def _get_cwnd(self):
474 raise NotImplementedError
476 def setup_temporary_loopback_config(self):
477 raise NotImplementedError
479 def _write_resolve_conf(self, dns):
480 raise NotImplementedError
482 def _get_primary_nameserver(self):
483 try:
484 resolv_file = open(self.RESOLV_CONF)
485 except IOError:
486 raise DnsReadError()
487 for line in resolv_file:
488 if line.startswith('nameserver '):
489 return line.split()[1]
490 raise DnsReadError()
492 def _set_primary_nameserver(self, dns):
493 raise NotImplementedError
496 class _LinuxPlatformSettings(_PosixPlatformSettings):
497 """The following thread recommends a way to update DNS on Linux:
499 http://ubuntuforums.org/showthread.php?t=337553
501 sudo cp /etc/dhcp3/dhclient.conf /etc/dhcp3/dhclient.conf.bak
502 sudo gedit /etc/dhcp3/dhclient.conf
503 #prepend domain-name-servers 127.0.0.1;
504 prepend domain-name-servers 208.67.222.222, 208.67.220.220;
506 prepend domain-name-servers 208.67.222.222, 208.67.220.220;
507 request subnet-mask, broadcast-address, time-offset, routers,
508 domain-name, domain-name-servers, host-name,
509 netbios-name-servers, netbios-scope;
510 #require subnet-mask, domain-name-servers;
512 sudo /etc/init.d/networking restart
514 The code below does not try to change dchp and does not restart networking.
515 Update this as needed to make it more robust on more systems.
517 RESOLV_CONF = '/etc/resolv.conf'
518 ROUTE_RE = re.compile('initcwnd (\d+)')
519 TCP_BASE_MSS = 'net.ipv4.tcp_base_mss'
520 TCP_MTU_PROBING = 'net.ipv4.tcp_mtu_probing'
522 def _get_default_route_line(self):
523 stdout = self._check_output('ip', 'route')
524 for line in stdout.split('\n'):
525 if line.startswith('default'):
526 return line
527 return None
529 def _set_cwnd(self, cwnd):
530 default_line = self._get_default_route_line()
531 self._check_output(
532 'ip', 'route', 'change', default_line, 'initcwnd', str(cwnd))
534 def _get_cwnd(self):
535 default_line = self._get_default_route_line()
536 m = self.ROUTE_RE.search(default_line)
537 if m:
538 return int(m.group(1))
539 # If 'initcwnd' wasn't found, then 0 means it's the system default.
540 return 0
542 def setup_temporary_loopback_config(self):
543 """Setup Linux to temporarily use reasonably sized frames.
545 Linux uses jumbo frames by default (16KB), using the combination
546 of MTU probing and a base MSS makes it use normal sized packets.
548 The reason this works is because tcp_base_mss is only used when MTU
549 probing is enabled. And since we're using the max value, it will
550 always use the reasonable size. This is relevant for server-side realism.
551 The client-side will vary depending on the client TCP stack config.
553 ENABLE_MTU_PROBING = 2
554 original_probing = self.get_sysctl(self.TCP_MTU_PROBING)
555 self.set_sysctl(self.TCP_MTU_PROBING, ENABLE_MTU_PROBING)
556 atexit.register(self.set_sysctl, self.TCP_MTU_PROBING, original_probing)
558 TCP_FULL_MSS = 1460
559 original_mss = self.get_sysctl(self.TCP_BASE_MSS)
560 self.set_sysctl(self.TCP_BASE_MSS, TCP_FULL_MSS)
561 atexit.register(self.set_sysctl, self.TCP_BASE_MSS, original_mss)
563 def _write_resolve_conf(self, dns):
564 is_first_nameserver_replaced = False
565 # The fileinput module uses sys.stdout as the edited file output.
566 for line in fileinput.input(self.RESOLV_CONF, inplace=1, backup='.bak'):
567 if line.startswith('nameserver ') and not is_first_nameserver_replaced:
568 print 'nameserver %s' % dns
569 is_first_nameserver_replaced = True
570 else:
571 print line,
572 if not is_first_nameserver_replaced:
573 raise DnsUpdateError('Could not find a suitable nameserver entry in %s' %
574 self.RESOLV_CONF)
576 def _get_primary_nameserver(self):
577 try:
578 resolv_file = open(self.RESOLV_CONF)
579 except IOError:
580 raise DnsReadError()
581 for line in resolv_file:
582 if line.startswith('nameserver '):
583 return line.split()[1]
584 raise DnsReadError()
586 def _set_primary_nameserver(self, dns):
587 """Replace the first nameserver entry with the one given."""
588 try:
589 self._write_resolve_conf(dns)
590 except OSError, e:
591 if 'Permission denied' in e:
592 raise self._get_dns_update_error()
593 raise
596 class _WindowsPlatformSettings(_BasePlatformSettings):
598 # pylint: disable=abstract-method
599 # Suppress lint check for _ipfw_cmd
601 def get_system_logging_handler(self):
602 """Return a handler for the logging module (optional).
604 For Windows, output can be viewed with DebugView.
605 http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx
607 import ctypes
608 output_debug_string = ctypes.windll.kernel32.OutputDebugStringA
609 output_debug_string.argtypes = [ctypes.c_char_p]
610 class DebugViewHandler(logging.Handler):
611 def emit(self, record):
612 output_debug_string('[wpr] ' + self.format(record))
613 return DebugViewHandler()
615 def rerun_as_administrator(self):
616 """If needed, rerun the program with administrative privileges.
618 Raises NotAdministratorError if unable to rerun.
620 import ctypes
621 if not ctypes.windll.shell32.IsUserAnAdmin():
622 raise NotAdministratorError('Rerun with administrator privileges.')
623 #os.execv('runas', sys.argv) # TODO: replace needed Windows magic
625 def timer(self):
626 """Return the current time in seconds as a floating point number.
628 From time module documentation:
629 On Windows, this function [time.clock()] returns wall-clock
630 seconds elapsed since the first call to this function, as a
631 floating point number, based on the Win32 function
632 QueryPerformanceCounter(). The resolution is typically better
633 than one microsecond.
635 return time.clock()
637 def _arp(self, *args):
638 return self._check_output('arp', *args)
640 def _route(self, *args):
641 return self._check_output('route', *args)
643 def _ipconfig(self, *args):
644 return self._check_output('ipconfig', *args)
646 def _get_mac_address(self, ip):
647 """Return the MAC address for the given ip."""
648 ip_re = re.compile(r'^\s*IP(?:v4)? Address[ .]+:\s+([0-9.]+)')
649 for line in self._ipconfig('/all').splitlines():
650 if line[:1].isalnum():
651 current_ip = None
652 current_mac = None
653 elif ':' in line:
654 line = line.strip()
655 ip_match = ip_re.match(line)
656 if ip_match:
657 current_ip = ip_match.group(1)
658 elif line.startswith('Physical Address'):
659 current_mac = line.split(':', 1)[1].lstrip()
660 if current_ip == ip and current_mac:
661 return current_mac
662 return None
664 def setup_temporary_loopback_config(self):
665 """On Windows, temporarily route the server ip to itself."""
666 ip = self.get_server_ip_address()
667 mac_address = self._get_mac_address(ip)
668 if self.mac_address:
669 self._arp('-s', ip, self.mac_address)
670 self._route('add', ip, ip, 'mask', '255.255.255.255')
671 atexit.register(self._arp, '-d', ip)
672 atexit.register(self._route, 'delete', ip, ip, 'mask', '255.255.255.255')
673 else:
674 logging.warn('Unable to configure loopback: MAC address not found.')
675 # TODO(slamm): Configure cwnd, MTU size
677 def _get_dns_update_error(self):
678 return DnsUpdateError('Did you run as administrator?')
680 def _netsh_show_dns(self):
681 """Return DNS information:
683 Example output:
684 Configuration for interface "Local Area Connection 3"
685 DNS servers configured through DHCP: None
686 Register with which suffix: Primary only
688 Configuration for interface "Wireless Network Connection 2"
689 DNS servers configured through DHCP: 192.168.1.1
690 Register with which suffix: Primary only
692 return self._check_output('netsh', 'interface', 'ip', 'show', 'dns')
694 def _netsh_set_dns(self, iface_name, addr):
695 """Modify DNS information on the primary interface."""
696 output = self._check_output('netsh', 'interface', 'ip', 'set', 'dns',
697 iface_name, 'static', addr)
699 def _netsh_set_dns_dhcp(self, iface_name):
700 """Modify DNS information on the primary interface."""
701 output = self._check_output('netsh', 'interface', 'ip', 'set', 'dns',
702 iface_name, 'dhcp')
704 def _get_interfaces_with_dns(self):
705 output = self._netsh_show_dns()
706 lines = output.split('\n')
707 iface_re = re.compile(r'^Configuration for interface \"(?P<name>.*)\"')
708 dns_re = re.compile(r'(?P<kind>.*):\s+(?P<dns>\d+\.\d+\.\d+\.\d+)')
709 iface_name = None
710 iface_dns = None
711 iface_kind = None
712 ifaces = []
713 for line in lines:
714 iface_match = iface_re.match(line)
715 if iface_match:
716 iface_name = iface_match.group('name')
717 dns_match = dns_re.match(line)
718 if dns_match:
719 iface_dns = dns_match.group('dns')
720 iface_dns_config = dns_match.group('kind').strip()
721 if iface_dns_config == "Statically Configured DNS Servers":
722 iface_kind = "static"
723 elif iface_dns_config == "DNS servers configured through DHCP":
724 iface_kind = "dhcp"
725 if iface_name and iface_dns and iface_kind:
726 ifaces.append((iface_dns, iface_name, iface_kind))
727 iface_name = None
728 iface_dns = None
729 return ifaces
731 def _save_primary_interface_properties(self):
732 # TODO(etienneb): On windows, an interface can have multiple DNS server
733 # configured. We should save/restore all of them.
734 ifaces = self._get_interfaces_with_dns()
735 self._primary_interfaces = ifaces
737 def _restore_primary_interface_properties(self):
738 for iface in self._primary_interfaces:
739 (iface_dns, iface_name, iface_kind) = iface
740 self._netsh_set_dns(iface_name, iface_dns)
741 if iface_kind == "dhcp":
742 self._netsh_set_dns_dhcp(iface_name)
744 def _get_primary_nameserver(self):
745 ifaces = self._get_interfaces_with_dns()
746 if not len(ifaces):
747 raise DnsUpdateError("Interface with valid DNS configured not found.")
748 (iface_dns, iface_name, iface_kind) = ifaces[0]
749 return iface_dns
751 def _set_primary_nameserver(self, dns):
752 for iface in self._primary_interfaces:
753 (iface_dns, iface_name, iface_kind) = iface
754 self._netsh_set_dns(iface_name, dns)
757 class _WindowsXpPlatformSettings(_WindowsPlatformSettings):
758 def _ipfw_cmd(self):
759 return (r'third_party\ipfw_win32\ipfw.exe',)
762 def _new_platform_settings(system, release):
763 """Make a new instance of PlatformSettings for the current system."""
764 if system == 'Darwin':
765 return _OsxPlatformSettings()
766 if system == 'Linux':
767 return _LinuxPlatformSettings()
768 if system == 'Windows' and release == 'XP':
769 return _WindowsXpPlatformSettings()
770 if system == 'Windows':
771 return _WindowsPlatformSettings()
772 if system == 'FreeBSD':
773 return _FreeBSDPlatformSettings()
774 raise NotImplementedError('Sorry %s %s is not supported.' % (system, release))
777 # Create one instance of the platform-specific settings and
778 # make the functions available at the module-level.
779 _inst = _new_platform_settings(platform.system(), platform.release())
781 get_system_logging_handler = _inst.get_system_logging_handler
782 rerun_as_administrator = _inst.rerun_as_administrator
783 timer = _inst.timer
785 get_server_ip_address = _inst.get_server_ip_address
786 get_httpproxy_ip_address = _inst.get_httpproxy_ip_address
787 get_system_proxy = _inst.get_system_proxy
788 ipfw = _inst.ipfw
789 has_ipfw = _inst.has_ipfw
790 set_temporary_tcp_init_cwnd = _inst.set_temporary_tcp_init_cwnd
791 setup_temporary_loopback_config = _inst.setup_temporary_loopback_config
793 get_original_primary_nameserver = _inst.get_original_primary_nameserver
794 set_temporary_primary_nameserver = _inst.set_temporary_primary_nameserver