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