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