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.
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.
29 import distutils
.spawn
30 import distutils
.version
44 class PlatformSettingsError(Exception):
45 """Module catch-all error."""
49 class DnsReadError(PlatformSettingsError
):
50 """Raised when unable to read DNS settings."""
54 class DnsUpdateError(PlatformSettingsError
):
55 """Raised when unable to update DNS settings."""
59 class NotAdministratorError(PlatformSettingsError
):
60 """Raised when not running as administrator."""
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
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
83 The fully qualified path with .exe appended if appropriate or None if it
86 return distutils
.spawn
.find_executable(executable
,
87 os
.pathsep
.join([os
.environ
['PATH'],
97 return (distutils
.version
.StrictVersion(OpenSSL
.__version
__) >=
98 distutils
.version
.StrictVersion('0.13'))
103 def SupportsFdLimitControl():
104 """Whether the platform supports changing the process fd limit."""
105 return os
.name
is 'posix'
109 """Returns a tuple of (soft_limit, hard_limit)."""
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."""
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.
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).
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
)
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).
146 proxy_url: a proxy url string such as "http://proxy.com:8888/".
148 a System proxy instance.
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)."""
162 def rerun_as_administrator(self
):
163 """If needed, rerun the program with administrative privileges.
165 Raises NotAdministratorError if unable to rerun.
170 """Return the current time in seconds as a floating point number."""
173 def get_server_ip_address(self
, is_server_mode
=False):
174 """Returns the IP address to use for dnsproxy and ipfw."""
176 return socket
.gethostbyname(socket
.gethostname())
179 def get_httpproxy_ip_address(self
, is_server_mode
=False):
180 """Returns the IP address to use for httpproxy."""
185 def get_system_proxy(self
, use_ssl
):
186 """Returns the system HTTP(S) proxy host, port."""
188 return SystemProxy(None, None)
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)
201 except AssertionError as e
:
202 logging
.warning('Failed to start ipfw command. '
203 'Error: %s' % e
.message
)
209 def _set_cwnd(self
, args
):
212 def _elevate_privilege_for_cmd(self
, 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.
223 *args: sequence of program arguments
224 elevate_privilege: Run the command with elevated privileges.
226 CalledProcessError if the program returns non-zero exit status.
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()
246 raise CalledProcessError(retcode
, command_args
)
249 def set_temporary_tcp_init_cwnd(self
, 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
)
258 if self
._get
_cwnd
() == cwnd
:
259 logging
.info('Changed cwnd to %s', cwnd
)
260 atexit
.register(self
._set
_cwnd
, original_cwnd
)
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
)
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
):
317 return (os
.stat(path
).st_mode
& stat
.S_ISUID
) == stat
.S_ISUID
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
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]])
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
)
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]
373 logging
.error('Unable to set sysctl %s: %s', name
, rv
)
375 def get_sysctl(self
, name
):
376 rv
, value
= self
._sysctl
('-n', name
)
380 logging
.error('Unable to get sysctl %s: %s', name
, rv
)
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]
398 logging
.error('Unable to set sysctl %s: %s', name
, rv
)
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.')
421 if original_mtu
== TARGET_LOOPBACK_MTU
:
422 logging
.debug('Loopback MTU already has target value: %d', original_mtu
)
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
)
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')
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
())
445 br
'ServerAddresses\s+:\s+<array>\s+{\s+0\s+:\s+((\d{1,3}\.){3}\d{1,3})',
448 return match
.group(1)
450 raise DnsReadError('Unable to find primary DNS server: %s', output
)
452 def _set_primary_nameserver(self
, dns
):
453 command
= '\n'.join([
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
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
):
484 resolv_file
= open(self
.RESOLV_CONF
)
487 for line
in resolv_file
:
488 if line
.startswith('nameserver '):
489 return line
.split()[1]
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'):
529 def _set_cwnd(self
, cwnd
):
530 default_line
= self
._get
_default
_route
_line
()
532 'ip', 'route', 'change', default_line
, 'initcwnd', str(cwnd
))
535 default_line
= self
._get
_default
_route
_line
()
536 m
= self
.ROUTE_RE
.search(default_line
)
538 return int(m
.group(1))
539 # If 'initcwnd' wasn't found, then 0 means it's the system default.
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
)
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
572 if not is_first_nameserver_replaced
:
573 raise DnsUpdateError('Could not find a suitable nameserver entry in %s' %
576 def _get_primary_nameserver(self
):
578 resolv_file
= open(self
.RESOLV_CONF
)
581 for line
in resolv_file
:
582 if line
.startswith('nameserver '):
583 return line
.split()[1]
586 def _set_primary_nameserver(self
, dns
):
587 """Replace the first nameserver entry with the one given."""
589 self
._write
_resolve
_conf
(dns
)
591 if 'Permission denied' in e
:
592 raise self
._get
_dns
_update
_error
()
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
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.
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
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.
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():
655 ip_match
= ip_re
.match(line
)
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
:
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
)
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')
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:
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',
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+)')
714 iface_match
= iface_re
.match(line
)
716 iface_name
= iface_match
.group('name')
717 dns_match
= dns_re
.match(line
)
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":
725 if iface_name
and iface_dns
and iface_kind
:
726 ifaces
.append((iface_dns
, iface_name
, iface_kind
))
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
()
747 raise DnsUpdateError("Interface with valid DNS configured not found.")
748 (iface_dns
, iface_name
, iface_kind
) = ifaces
[0]
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
):
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
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
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