249 lines
8.7 KiB
Python
249 lines
8.7 KiB
Python
# Copyright 2015 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import logging
|
|
import sys
|
|
import threading
|
|
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.cros.enterprise import enterprise_policy_base
|
|
from autotest_lib.client.cros.enterprise.network_config import ProxyConfig
|
|
from SocketServer import ThreadingTCPServer, StreamRequestHandler
|
|
from telemetry.core import exceptions as telemetry_exceptions
|
|
|
|
|
|
class ProxyHandler(StreamRequestHandler):
|
|
"""Provide request handler for the Threaded Proxy Listener."""
|
|
def handle(self):
|
|
"""
|
|
Get URL of request from first line.
|
|
|
|
Read the first line of the request, up to 40 characters, and look
|
|
for the URL of the request. If found, save it to the URL list.
|
|
|
|
Note: All requests are sent an HTTP 504 error.
|
|
|
|
"""
|
|
# Capture URL in first 40 chars of request.
|
|
data = self.rfile.readline(40).strip()
|
|
logging.debug('ProxyHandler::handle(): <%s>', data)
|
|
self.server.store_requests_received(data)
|
|
self.wfile.write('HTTP/1.1 504 Gateway Timeout\r\n'
|
|
'Connection: close\r\n\r\n')
|
|
|
|
|
|
class ThreadedProxyServer(ThreadingTCPServer):
|
|
"""
|
|
Provide a Threaded Proxy Server to service and save requests.
|
|
|
|
Define a Threaded Proxy Server which services requests, and allows the
|
|
handler to save all requests.
|
|
|
|
"""
|
|
def __init__(self, server_address, HandlerClass):
|
|
"""
|
|
Constructor.
|
|
|
|
@param server_address: tuple of server IP and port to listen on.
|
|
@param HandlerClass: the RequestHandler class to instantiate per req.
|
|
|
|
"""
|
|
self.requests_received = []
|
|
ThreadingTCPServer.allow_reuse_address = True
|
|
ThreadingTCPServer.__init__(self, server_address, HandlerClass)
|
|
|
|
def store_requests_received(self, request):
|
|
"""
|
|
Add receieved request to list.
|
|
|
|
@param request: request received by the proxy server.
|
|
|
|
"""
|
|
self.requests_received.append(request)
|
|
|
|
|
|
class ProxyListener(object):
|
|
"""
|
|
Provide a Proxy Listener to detect connect requests.
|
|
|
|
Define a proxy listener to detect when a CONNECT request is seen at the
|
|
given |server_address|, and record all requests received. Requests
|
|
received are exposed to the caller.
|
|
|
|
"""
|
|
def __init__(self, server_address):
|
|
"""
|
|
Constructor.
|
|
|
|
@param server_address: tuple of server IP and port to listen on.
|
|
|
|
"""
|
|
self._server = ThreadedProxyServer(server_address, ProxyHandler)
|
|
self._thread = threading.Thread(target=self._server.serve_forever)
|
|
|
|
def run(self):
|
|
"""Start the server by activating its thread."""
|
|
self._thread.start()
|
|
|
|
def stop(self):
|
|
"""Stop the server and its threads."""
|
|
self._server.server_close()
|
|
self._thread.join()
|
|
|
|
def get_requests_received(self):
|
|
"""Get list of received requests."""
|
|
return self._server.requests_received
|
|
|
|
def reset_requests_received(self):
|
|
"""Clear list of received requests."""
|
|
self._server.requests_received = []
|
|
|
|
|
|
class policy_ProxySettings(enterprise_policy_base.EnterprisePolicyTest):
|
|
"""
|
|
Test effect of ProxySettings policy on Chrome OS behavior.
|
|
|
|
This test verifies the behavior of Chrome OS for specific configurations
|
|
of the ProxySettings use policy: None (undefined), ProxyMode=direct,
|
|
ProxyMode=fixed_servers, ProxyMode=pac_script. None means that the policy
|
|
value is not set. This induces the default behavior, equivalent to what is
|
|
seen by an un-managed user.
|
|
|
|
When ProxySettings is None (undefined) or ProxyMode=direct, then no proxy
|
|
server should be used. When ProxyMode=fixed_servers or pac_script, then
|
|
the proxy server address specified by the ProxyServer or ProxyPacUrl
|
|
entry should be used.
|
|
|
|
"""
|
|
version = 1
|
|
|
|
def initialize(self, **kwargs):
|
|
"""Initialize this test."""
|
|
self._initialize_test_constants()
|
|
super(policy_ProxySettings, self).initialize(**kwargs)
|
|
self._proxy_server = ProxyListener(('', self.PROXY_PORT))
|
|
self._proxy_server.run()
|
|
self.start_webserver()
|
|
|
|
|
|
def _initialize_test_constants(self):
|
|
"""Initialize test-specific constants, some from class constants."""
|
|
self.POLICY_NAME = 'ProxySettings'
|
|
self.PROXY_PORT = 3128
|
|
self.PAC_FILE = 'proxy_test.pac'
|
|
self.PAC_URL = '%s/%s' % (self.WEB_HOST, self.PAC_FILE)
|
|
self.BYPASS_URLS = ['www.google.com', 'www.googleapis.com']
|
|
self.FIXED_PROXY = {
|
|
'ProxyBypassList': ','.join(self.BYPASS_URLS),
|
|
'ProxyMode': 'fixed_servers',
|
|
'ProxyServer': 'localhost:%s' % self.PROXY_PORT
|
|
}
|
|
self.PAC_PROXY = {
|
|
'ProxyMode': 'pac_script',
|
|
'ProxyPacUrl': self.PAC_URL
|
|
}
|
|
self.DIRECT_PROXY = {
|
|
'ProxyMode': 'direct'
|
|
}
|
|
self.TEST_URL = 'http://www.cnn.com/'
|
|
self.TEST_CASES = {
|
|
'FixedProxy_UseFixedProxy': self.FIXED_PROXY,
|
|
'PacProxy_UsePacFile': self.PAC_PROXY,
|
|
'DirectProxy_UseNoProxy': self.DIRECT_PROXY,
|
|
'NotSet_UseNoProxy': None,
|
|
}
|
|
self.PROXY_CONFIGS = {
|
|
'DirectProxy_UseNoProxy_ONC': ProxyConfig(type='Direct'),
|
|
'PacProxy_UsePacFile_ONC': ProxyConfig(type='PAC',
|
|
pac_url=self.PAC_URL),
|
|
'FixedProxy_UseFixedProxy_ONC': ProxyConfig(type='Manual',
|
|
host='localhost',
|
|
port=self.PROXY_PORT,
|
|
exclude_urls=self.BYPASS_URLS)
|
|
}
|
|
|
|
|
|
def cleanup(self):
|
|
"""Stop proxy server and cleanup."""
|
|
self._proxy_server.stop()
|
|
super(policy_ProxySettings, self).cleanup()
|
|
|
|
|
|
def navigate_to_url_with_retry(self, url, total_tries=1):
|
|
"""
|
|
Navigate to url, attempting retry_count times if it fails to load.
|
|
|
|
@param url: string of the url to load.
|
|
@param total_tries: number of attempts to load the page.
|
|
|
|
@raises: error.TestError if page load times out.
|
|
|
|
"""
|
|
for i in xrange(total_tries):
|
|
try:
|
|
self.navigate_to_url(url)
|
|
except telemetry_exceptions.TimeoutError as e:
|
|
if i == total_tries - 1:
|
|
logging.error('Timeout error: %s [%s].', str(e),
|
|
sys.exc_info())
|
|
raise error.TestError('Could not load %s after '
|
|
'%s tries.' % (url, total_tries))
|
|
else:
|
|
logging.debug('Retrying page load of %s.', url)
|
|
logging.debug('Timeout error: %s.', str(e))
|
|
else:
|
|
break
|
|
|
|
|
|
def _test_proxy_configuration(self, mode):
|
|
"""
|
|
Verify CrOS enforces the specified ProxySettings configuration.
|
|
|
|
@param mode: Type of proxy.
|
|
|
|
@raises error.TestFail if behavior does not match expected.
|
|
|
|
"""
|
|
self._proxy_server.reset_requests_received()
|
|
self.navigate_to_url_with_retry(url=self.TEST_URL, total_tries=2)
|
|
proxied_requests = self._proxy_server.get_requests_received()
|
|
|
|
matching_requests = [request for request in proxied_requests
|
|
if self.TEST_URL in request]
|
|
logging.info('matching_requests: %s', matching_requests)
|
|
|
|
if mode is None or mode == 'direct':
|
|
if matching_requests:
|
|
raise error.TestFail('Requests should not have been sent '
|
|
'through the proxy server.')
|
|
elif mode == 'fixed_servers' or mode == 'pac_script':
|
|
if not matching_requests:
|
|
raise error.TestFail('Requests should have been sent '
|
|
'through the proxy server.')
|
|
else:
|
|
raise error.TestFail('Unrecognized Mode %s' % mode)
|
|
|
|
|
|
def run_once(self, case):
|
|
"""
|
|
Setup and run the test configured for the specified test case.
|
|
|
|
Sets up a proxy using either the ProxyMode policy or ONC policy.
|
|
|
|
@param case: Name of the test case to run: see TEST_CASES.
|
|
|
|
"""
|
|
if case.endswith('_ONC'):
|
|
proxy = self.PROXY_CONFIGS[case]
|
|
self.setup_case(user_policies={
|
|
'OpenNetworkConfiguration': proxy.policy()
|
|
})
|
|
mode = proxy.mode()
|
|
else:
|
|
case_value = self.TEST_CASES[case]
|
|
self.setup_case(user_policies={self.POLICY_NAME: case_value})
|
|
mode = case_value['ProxyMode'] if case_value else None
|
|
|
|
self._test_proxy_configuration(mode)
|