blob: d997a960a7b98f10b7c502f86ef6fb022b5e2568 [file] [log] [blame]
# Copyright (c) 2011 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 re
from autotest_lib.client.common_lib import error
from autotest_lib.server import site_linux_system
from autotest_lib.server.cros import remote_command
from autotest_lib.server.cros import wifi_test_utils
class LinuxServer(site_linux_system.LinuxSystem):
"""
Linux Server: A machine which hosts network services.
"""
COMMAND_PING = '/usr/bin/ping'
COMMAND_NETPERF = '/usr/bin/netperf'
COMMAND_NETSERVER = '/usr/bin/netserver'
COMMAND_IP = '/usr/sbin/ip'
COMMAND_IPERF = '/usr/bin/iperf'
def __init__(self, server, config):
site_linux_system.LinuxSystem.__init__(self, server, {}, "server")
self._server = server # Server host.
self.vpn_kind = None
self.config = config
self.openvpn_config = {}
self.radvd_config = {'file':'/tmp/radvd-test.conf',
'server':'/usr/sbin/radvd'}
# Check that tools we require from WiFi test servers exist.
self._cmd_netperf = wifi_test_utils.must_be_installed(
self._server, LinuxServer.COMMAND_NETPERF)
self._cmd_netserver = wifi_test_utils.must_be_installed(
self._server, LinuxServer.COMMAND_NETSERVER)
self._cmd_ip = wifi_test_utils.must_be_installed(
self._server, LinuxServer.COMMAND_IP)
self._cmd_iperf = wifi_test_utils.must_be_installed(
self._server, LinuxServer.COMMAND_IPERF)
# /usr/bin/ping is preferred, as it is likely to be iputils.
if wifi_test_utils.is_installed(self._server,
LinuxServer.COMMAND_PING):
self._cmd_ping = LinuxServer.COMMAND_PING
else:
self._cmd_ping = 'ping'
self._ping_bg_job = None
self._wifi_ip = None
@property
def cmd_netperf(self):
""" @return string full path to start netperf. """
return self._cmd_netperf
@property
def cmd_netserv(self):
""" @return string full path to start netserv. """
return self._cmd_netserver
@property
def cmd_iperf(self):
""" @return string full path to start iperf. """
return self._cmd_iperf
@property
def cmd_ping(self):
""" @return string full path to start ping. """
return self._cmd_ping
@property
def server(self):
""" @return Host object for this remote server. """
return self._server
@property
def wifi_ip(self):
"""
Returns an IP address pingable from the client DUT.
Throws an error if no interface is configured with a potentially
pingable IP.
@return String IP address on the WiFi subnet.
"""
if not self._wifi_ip:
addrs = self._get_system_ipv4_addrs(self.server)
# Discard loopback IPs and the control network IP.
valid_addrs = filter(
lambda addr: not addr.startswith('127.0.0') and
not addr.startswith(self.server.ip),
addrs)
if not valid_addrs:
raise error.TestFailed('No configured interfaces.')
if len(valid_addrs) > 1:
logging.warning('Multiple interfaces configured on server; '
'taking first.')
self._wifi_ip = valid_addrs[0]
return self._wifi_ip
def _get_system_ipv4_addrs(self, host):
"""
Returns a list of IPs configured on host.
@param host Host object representing a remote machine.
@return list of IPv4 addresses as strings.
"""
ip_output = host.run('%s -4 addr show' % self._cmd_ip).stdout
regex = re.compile('^inet ([0-9]{1,3}(\\.[0-9]{1,3}){3})')
ips = []
for line in ip_output.splitlines():
ip = re.search(regex, line.strip())
if ip:
# Group 1 will be the IP address following 'inet addr:'.
ips.append(ip.group(1))
return ips
def vpn_server_config(self, params):
""" Configure & launch the server side of the VPN.
Parameters, in 'params':
kind : required
The kind of VPN which should be configured and
launched.
Valid values:
openvpn
l2tpipsec (StrongSwan PSK or certificates)
config: required
The configuration information associated with
the VPN server.
This is a dict which contains key/value pairs
representing the VPN's configuration.
The values stored in the 'config' param must all be
supported by the specified VPN kind.
"""
self.vpn_server_kill({}) # Must be first. Relies on self.vpn_kind.
self.vpn_kind = params.get('kind', None)
# Launch specified VPN server.
if self.vpn_kind is None:
raise error.TestFail('No VPN kind specified for this test.')
elif self.vpn_kind == 'openvpn':
# Read config information & create server configuration file.
for k, v in params.get('config', {}).iteritems():
self.openvpn_config[k] = v
self.server.run("cat <<EOF >/tmp/vpn-server.conf\n%s\nEOF\n" %
('\n'.join( "%s %s" % kv for kv in
self.openvpn_config.iteritems())))
self.server.run("/usr/sbin/openvpn "
"--config /tmp/vpn-server.conf &")
elif self.vpn_kind in ('l2tpipsec-psk', 'l2tpipsec-cert'):
configs = {
"/etc/xl2tpd/xl2tpd.conf" :
"[global]\n"
"\n"
"[lns default]\n"
" ip range = 192.168.1.128-192.168.1.254\n"
" local ip = 192.168.1.99\n"
" require chap = yes\n"
" refuse pap = yes\n"
" require authentication = yes\n"
" name = LinuxVPNserver\n"
" ppp debug = yes\n"
" pppoptfile = /etc/ppp/options.xl2tpd\n"
" length bit = yes\n",
"/etc/xl2tpd/l2tp-secrets" :
"* them l2tp-secret",
"/etc/ppp/chap-secrets" :
"chapuser * chapsecret *",
"/etc/ppp/options.xl2tpd" :
"ipcp-accept-local\n"
"ipcp-accept-remote\n"
"noccp\n"
"auth\n"
"crtscts\n"
"idle 1800\n"
"mtu 1410\n"
"mru 1410\n"
"nodefaultroute\n"
"debug\n"
"lock\n"
"proxyarp\n"
"connect-delay 5000\n"
}
config_choices = {
'l2tpipsec-psk': {
"/etc/ipsec.conf" :
"config setup\n"
" charonstart=no\n"
" plutostart=yes\n"
" plutodebug=%(@plutodebug@)s\n"
" plutostderrlog=/var/log/pluto.log\n"
"conn L2TP\n"
" keyexchange=ikev1\n"
" authby=psk\n"
" pfs=no\n"
" rekey=no\n"
" left=%(@local-listen-ip@)s\n"
" leftprotoport=17/1701\n"
" right=%%any\n"
" rightprotoport=17/%%any\n"
" auto=add\n",
"/etc/ipsec.secrets" :
"%(@ipsec-secrets@)s %%any : PSK \"password\"",
},
'l2tpipsec-cert': {
"/etc/ipsec.conf" :
"config setup\n"
" charonstart=no\n"
" plutostart=yes\n"
" plutodebug=%(@plutodebug@)s\n"
" plutostderrlog=/var/log/pluto.log\n"
"conn L2TP\n"
" keyexchange=ikev1\n"
" left=%(@local-listen-ip@)s\n"
" leftcert=server.crt\n"
" leftid=\"C=US, ST=California, L=Mountain View, "
"CN=chromelab-wifi-testbed-server.mtv.google.com\"\n"
" leftprotoport=17/1701\n"
" right=%%any\n"
" rightca=\"C=US, ST=California, L=Mountain View, "
"CN=chromelab-wifi-testbed-root.mtv.google.com\"\n"
" rightprotoport=17/%%any\n"
" auto=add\n"
" pfs=no\n",
"/etc/ipsec.secrets" : ": RSA server.key \"\"\n",
},
}
configs.update(config_choices[self.vpn_kind])
replacements = params.get("replacements", {})
# These two replacements must match up to the same
# adapter, or a connection will not be established.
replacements["@local-listen-ip@"] = "%defaultroute"
replacements["@ipsec-secrets@"] = self.server.ip
for cfg, template in configs.iteritems():
contents = template % (replacements)
self.server.run("cat <<EOF >%s\n%s\nEOF\n" % (cfg, contents))
self.server.run("/usr/sbin/ipsec restart")
# Restart xl2tpd to ensure use of newly-created config files.
self.server.run("sh /etc/init.d/xl2tpd restart")
else:
raise error.TestFail('(internal error): No config case '
'for VPN kind (%s)' % self.vpn_kind)
def vpn_server_kill(self, params):
""" Kill the VPN server. """
if self.vpn_kind is not None:
if self.vpn_kind == 'openvpn':
self.server.run("pkill /usr/sbin/openvpn")
elif self.vpn_kind in ('l2tpipsec-psk', 'l2tpipsec-cert'):
self.server.run("/usr/sbin/ipsec stop")
else:
raise error.TestFail('(internal error): No kill case '
'for VPN kind (%s)' % self.vpn_kind)
self.vpn_kind = None
def ipv6_server_config(self, params):
self.ipv6_server_kill({})
radvd_opts = { 'interface': self.config.get('server_dev', 'eth0'),
'adv_send_advert': 'on',
'min_adv_interval': '3',
'max_adv_interval': '10',
# NB: Addresses below are within the 2001:0db8/32
# "documentation only" prefix (RFC3849), which is
# guaranteed never to be assigned to a real network.
'prefix': '2001:0db8:0100:f101::/64',
'adv_on_link': 'on',
'adv_autonomous': 'on',
'adv_router_addr': 'on',
'rdnss_servers': '2001:0db8:0100:f101::0001 '
'2001:0db8:0100:f101::0002',
'adv_rdnss_lifetime': 'infinity',
'dnssl_list': 'a.com b.com' }
radvd_opts.update(params)
config = ('interface %(interface)s {\n'
' AdvSendAdvert %(adv_send_advert)s;\n'
' MinRtrAdvInterval %(min_adv_interval)s;\n'
' MaxRtrAdvInterval %(max_adv_interval)s;\n'
' prefix %(prefix)s {\n'
' AdvOnLink %(adv_on_link)s;\n'
' AdvAutonomous %(adv_autonomous)s;\n'
' AdvRouterAddr %(adv_router_addr)s;\n'
' };\n'
' RDNSS %(rdnss_servers)s {\n'
' AdvRDNSSLifetime %(adv_rdnss_lifetime)s;\n'
' };\n'
' DNSSL %(dnssl_list)s {\n'
' };\n'
'};\n') % radvd_opts
cfg_file = params.get('config_file', self.radvd_config['file'])
self.server.run('cat <<EOF >%s\n%s\nEOF\n' % (cfg_file, config))
self.server.run('%s -C %s\n' % (self.radvd_config['server'], cfg_file))
def ipv6_server_kill(self, params):
self.server.run('pkill %s >/dev/null 2>&1' %
self.radvd_config['server'], ignore_status=True)
def ping(self, ping_ip, count, ping_params):
"""
Ping a client from the server.
Ping |ping_ip| |count| times using options to ping specified in
|ping_params|.
@param ping_ip String containing a client IP address.
@param count String number of times to ping the client.
@param ping_params dict of settings for ping.
@return dict of ping statistics.
"""
# set timeout for 3s / ping packet
ping_params = ping_params.copy()
ping_params['count'] = str(count)
cmd = "%s %s %s" % (self.cmd_ping,
wifi_test_utils.ping_args(ping_params),
ping_ip)
result = self.server.run(cmd, timeout=3*int(count))
return wifi_test_utils.parse_ping_output(result.stdout)
def ping_bg(self, ping_ip, ping_params):
"""
Ping a client from the server in a background thread.
Ping |ping_ip| using options to ping specified in
|ping_params| in the background. This call does not block.
@param ping_ip String containing a client IP address.
@param ping_params dict of settings for ping.
"""
cmd = "%s %s %s" % (self.cmd_ping,
wifi_test_utils.ping_args(ping_params),
ping_ip)
self._ping_bg_job = remote_command.Command(self.server, cmd)
def ping_bg_stop(self):
"""
Stop pinging the client in the background.
"""
if self._ping_bg_job is not None:
self._ping_bg_job.join()
self._ping_bg_job = None