From: Ethan Jackson Date: Wed, 8 Dec 2010 02:49:28 +0000 (-0800) Subject: utilities: Implement ovs-vlan-test script X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=01ca1bfcbdd315519e7da58052a9c1359f9e72f7;p=openvswitch utilities: Implement ovs-vlan-test script This patch implements a script which may be used to check for connectivity issues caused by bugs in Linux drivers relating to VLAN traffic. --- diff --git a/debian/openvswitch-common.manpages b/debian/openvswitch-common.manpages index 8f7e64df..ba94a52b 100644 --- a/debian/openvswitch-common.manpages +++ b/debian/openvswitch-common.manpages @@ -1,5 +1,6 @@ _debian/ovsdb/ovsdb-client.1 _debian/ovsdb/ovsdb-tool.1 +_debian/ovsdb/ovs-vlan-test.8 _debian/utilities/ovs-appctl.8 _debian/utilities/ovs-ofctl.8 _debian/utilities/ovs-pki.8 diff --git a/debian/openvswitch-switch.install b/debian/openvswitch-switch.install index 08ce5764..c38cdd46 100644 --- a/debian/openvswitch-switch.install +++ b/debian/openvswitch-switch.install @@ -5,4 +5,5 @@ _debian/utilities/ovs-kill usr/sbin _debian/utilities/ovs-vsctl usr/sbin _debian/utilities/ovs-pcap usr/bin _debian/utilities/ovs-tcpundump usr/bin +_debian/utilities/ovs-vlan-test usr/bin _debian/vswitchd/ovs-vswitchd usr/sbin diff --git a/utilities/automake.mk b/utilities/automake.mk index 9a334e33..7aa5800d 100644 --- a/utilities/automake.mk +++ b/utilities/automake.mk @@ -9,7 +9,10 @@ bin_PROGRAMS += \ utilities/ovs-vsctl bin_SCRIPTS += utilities/ovs-pki utilities/ovs-vsctl if HAVE_PYTHON -bin_SCRIPTS += utilities/ovs-pcap utilities/ovs-tcpundump +bin_SCRIPTS += \ + utilities/ovs-pcap \ + utilities/ovs-tcpundump \ + utilities/ovs-vlan-test endif noinst_SCRIPTS += utilities/ovs-pki-cgi utilities/ovs-parse-leaks @@ -30,6 +33,8 @@ EXTRA_DIST += \ utilities/ovs-pki.in \ utilities/ovs-tcpundump.1.in \ utilities/ovs-tcpundump.in \ + utilities/ovs-vlan-test.in \ + utilities/ovs-vlan-test.8.in \ utilities/ovs-vsctl.8.in DISTCLEANFILES += \ utilities/ovs-appctl.8 \ @@ -47,6 +52,8 @@ DISTCLEANFILES += \ utilities/ovs-pki.8 \ utilities/ovs-tcpundump \ utilities/ovs-tcpundump.1 \ + utilities/ovs-vlan-test \ + utilities/ovs-vlan-test.8 \ utilities/ovs-vsctl.8 man_MANS += \ @@ -61,6 +68,7 @@ man_MANS += \ utilities/ovs-pcap.1 \ utilities/ovs-pki.8 \ utilities/ovs-tcpundump.1 \ + utilities/ovs-vlan-test.8 \ utilities/ovs-vsctl.8 utilities_ovs_appctl_SOURCES = utilities/ovs-appctl.c diff --git a/utilities/ovs-vlan-test.8.in b/utilities/ovs-vlan-test.8.in new file mode 100644 index 00000000..d3cd0316 --- /dev/null +++ b/utilities/ovs-vlan-test.8.in @@ -0,0 +1,83 @@ +.TH ovs\-vlan\-test 1 "December 2010" "Open vSwitch" "Open vSwitch Manual" +. +.SH NAME +\fBovs\-vlan\-test\fR \- check Linux drivers for problems with vlan traffic +. +.SH SYNOPSIS +\fBovs\-vlan\-test\fR [\fB\-s\fR | \fB\-\-server\fR] \fIcontrol_ip\fR \fIvlan_ip\fR +.so lib/common-syn.man +. +.SH DESCRIPTION +The \fBovs\-vlan\-test\fR program may be used to check for problems sending +802.1Q traffic which may occur when running Open vSwitch. These problems can +occur when Open vSwitch is used to send 802.1Q traffic through physical +interfaces running certain drivers of certain Linux kernel versions. To run a +test, configure Open vSwitch to tag traffic originating from \fIvlan_ip\fR and +forward it out the target interface. Then run the \fBovs\-vlan\-test\fR in +client mode connecting to an \fBovs\-vlan\-test\fR server. +\fBovs\-vlan\-test\fR will display "OK" if it did not detect problems. +. +.SS "Client Mode" +An \fBovs\-vlan\-test\fR client may be run on a host to check for VLAN +connectivity problems. The client must be able to establish HTTP connections +with an \fBovs\-vlan\-test\fR server located at the specified \fIcontrol_ip\fR +address. UDP traffic sourced at \fIvlan_ip\fR should be tagged and directed out +the interface whose connectivity is being tested. +. +.SS "Server Mode" +To conduct tests, an \fBovs\-vlan\-test\fR server must be running on a host +known not to have VLAN connectivity problems. The server must have a +\fIcontrol_ip\fR on a non\-VLAN network which clients can establish +connectivity with. It must also have a \fIvlan_ip\fR address on a VLAN network +which clients will use to test their VLAN connectivity. Multiple clients may +test against a single \fBovs\-vlan\-test\fR server concurrently. +. +.SH OPTIONS +. +.TP +\fB\-s\fR, \fB\-\-server\fR +Run in server mode. +. +.so lib/common.man +.SH EXAMPLES +Display the Linux kernel version and driver of \fBeth1\fR. +.IP +.B uname \-r +.IP +.B ethtool \-i eth1 +. +.PP +Set up a bridge which forwards traffic originating from \fB1.2.3.4\fR out +\fBeth1\fR with VLAN tag 10. +.IP +.B ovs\-vsctl \-\- add\-br vlan\-br \(rs +.IP +.B \-\- add\-port vlan\-br eth1 \(rs +.IP +.B \-\- add\-port vlan\-br vlan\-br\-tag tag=10 \(rs +.IP +.B \-\- set Interface vlan\-br\-tag type=internal +.IP +.B ifconfig vlan\-br\-tag up 5.6.7.8 +. +.PP +Run an \fBovs\-vlan\-test\fR server listening for client control traffic on +5.6.7.8 port 80 and VLAN traffic on the default port of 1.2.3.0. +.IP +.B ovs\-vlan\-test \-s 5.6.7.8:80 1.2.3.0 +. +.PP +Run an \fBovs\-vlan\-test\fR client with a control server located at 5.6.7.8 +port 80 and a local VLAN ip of 1.2.3.4. +.IP +.B ovs\-vlan\-test 5.6.7.8:80 1.2.3.4 +. +.TP + +.SH SEE ALSO +. +.BR ovs\-vswitchd (8), +.BR ovs\-ofctl (8), +.BR ovs\-vsctl (8), +.BR ethtool (8), +.BR uname (1) diff --git a/utilities/ovs-vlan-test.in b/utilities/ovs-vlan-test.in new file mode 100755 index 00000000..8489e68c --- /dev/null +++ b/utilities/ovs-vlan-test.in @@ -0,0 +1,434 @@ +#! @PYTHON@ +# +# Copyright (c) 2010 Nicira Networks. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import BaseHTTPServer +import getopt +import httplib +import os +import threading +import time +import signal #Causes keyboard interrupts to go to the main thread. +import socket +import sys + +print_safe_lock = threading.Lock() +def print_safe(s): + print_safe_lock.acquire() + print(s) + print_safe_lock.release() + +def start_thread(target, args): + t = threading.Thread(target=target, args=args) + t.setDaemon(True) + t.start() + return t + +#Caller is responsible for catching socket.error exceptions. +def send_packet(key, length, dest_ip, dest_port): + + length -= 20 + 8 #IP and UDP headers. + + packet = str(key) + packet += chr(0) * (length - len(packet)) + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.sendto(packet, (dest_ip, dest_port)) + sock.close() + +#UDP Receiver +class UDPReceiver: + def __init__(self, vlan_ip, vlan_port): + self.vlan_ip = vlan_ip + self.vlan_port = vlan_port + self.recv_callbacks = {} + self.udp_run = False + + def recv_packet(self, key, success_callback, timeout_callback): + + event = threading.Event() + + def timeout_cb(): + timeout_callback() + event.set() + + timer = threading.Timer(30, timeout_cb) + timer.daemon = True + + def success_cb(): + timer.cancel() + success_callback() + event.set() + + # Start the timer first to avoid a timer.cancel() race condition. + timer.start() + self.recv_callbacks[key] = success_cb + return event + + def udp_receiver(self): + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(1) + + try: + sock.bind((self.vlan_ip, self.vlan_port)) + except socket.error, e: + print_safe('Failed to bind to %s:%d with error: %s' + % (self.vlan_ip, self.vlan_port, e)) + os._exit(1) #sys.exit only exits the current thread. + + while self.udp_run: + + try: + data, _ = sock.recvfrom(4096) + except socket.timeout: + continue + except socket.error, e: + print_safe('Failed to receive from %s:%d with error: %s' + % (self.vlan_ip, self.vlan_port, e)) + os._exit(1) + + data_str = data.split(chr(0))[0] + + if not data_str.isdigit(): + continue + + key = int(data_str) + + if key in self.recv_callbacks: + self.recv_callbacks[key]() + del self.recv_callbacks[key] + + def start(self): + self.udp_run = True + start_thread(self.udp_receiver, ()) + + def stop(self): + self.udp_run = False + +#Server +vlan_server = None +class VlanServer: + + def __init__(self, server_ip, server_port, vlan_ip, vlan_port): + global vlan_server + + vlan_server = self + + self.server_ip = server_ip + self.server_port = server_port + + self.recv_response = '%s:%d:' % (vlan_ip, vlan_port) + + self.result = {} + self.result_lock = threading.Lock() + + self._test_id = 0 + self._test_id_lock = threading.Lock() + + self.udp_recv = UDPReceiver(vlan_ip, vlan_port) + + def get_test_id(self): + self._test_id_lock.acquire() + + self._test_id += 1 + ret = self._test_id + + self._test_id_lock.release() + return ret + + def set_result(self, key, value): + + self.result_lock.acquire() + + if key not in self.result: + self.result[key] = value + + self.result_lock.release() + + def recv(self, test_id): + self.udp_recv.recv_packet(test_id, + lambda : self.set_result(test_id, 'Success'), + lambda : self.set_result(test_id, 'Timeout')) + + return self.recv_response + str(test_id) + + def send(self, test_id, data): + try: + ip, port, size = data.split(':') + port = int(port) + size = int(size) + except ValueError: + self.set_result(test_id, + 'Server failed to parse send request: %s' % data) + return + + def send_thread(): + send_time = 10 + for _ in range(send_time * 2): + try: + send_packet(test_id, size, ip, port) + except socket.error, e: + self.set_result(test_id, 'Failure: ' + str(e)) + return + time.sleep(.5) + + self.set_result(test_id, 'Success') + + start_thread(send_thread, ()) + + return str(test_id) + + def run(self): + self.udp_recv.start() + try: + BaseHTTPServer.HTTPServer((self.server_ip, self.server_port), + VlanServerHandler).serve_forever() + except socket.error, e: + print_safe('Failed to start control server: %s' % e) + self.udp_recv.stop() + + return 1 + +class VlanServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + + #Guarantee three arguments. + path = (self.path.lower().lstrip('/') + '//').split('/') + + resp = 404 + body = None + + if path[0] == 'start': + test_id = vlan_server.get_test_id() + + if path[1] == 'recv': + resp = 200 + body = vlan_server.recv(test_id) + elif path[1] == 'send': + resp = 200 + body = vlan_server.send(test_id, path[2]) + elif (path[0] == 'result' + and path[1].isdigit() + and int(path[1]) in vlan_server.result): + resp = 200 + body = vlan_server.result[int(path[1])] + elif path[0] == 'ping': + resp = 200 + body = 'pong' + + self.send_response(resp) + self.end_headers() + + if body: + self.wfile.write(body) + +#Client +class VlanClient: + + def __init__(self, server_ip, server_port, vlan_ip, vlan_port): + self.server_ip_port = '%s:%d' % (server_ip, server_port) + self.vlan_ip_port = "%s:%d" % (vlan_ip, vlan_port) + self.udp_recv = UDPReceiver(vlan_ip, vlan_port) + + def request(self, resource): + conn = httplib.HTTPConnection(self.server_ip_port) + conn.request('GET', resource) + return conn + + def send(self, size): + + def error_msg(e): + print_safe('Send size %d unsuccessful: %s' % (size, e)) + + try: + conn = self.request('/start/recv') + data = conn.getresponse().read() + except (socket.error, httplib.HTTPException), e: + error_msg(e) + return False + + try: + ip, port, test_id = data.split(':') + port = int(port) + test_id = int(test_id) + except ValueError: + error_msg("Received invalid response from control server (%s)" % + data) + return False + + send_time = 5 + + for _ in range(send_time * 4): + + try: + send_packet(test_id, size, ip, port) + resp = self.request('/result/%d' % test_id).getresponse() + data = resp.read() + except (socket.error, httplib.HTTPException), e: + error_msg(e) + return False + + if resp.status == 200 and data == 'Success': + print_safe('Send size %d successful' % size) + return True + elif resp.status == 200: + error_msg(data) + return False + + time.sleep(.25) + + error_msg('Timeout') + return False + + def recv(self, size): + + def error_msg(e): + print_safe('Receive size %d unsuccessful: %s' % (size, e)) + + resource = '/start/send/%s:%d' % (self.vlan_ip_port, size) + try: + conn = self.request(resource) + test_id = conn.getresponse().read() + except (socket.error, httplib.HTTPException), e: + error_msg(e) + return False + + if not test_id.isdigit(): + error_msg('Invalid response %s' % test_id) + return False + + success = [False] #Primitive datatypes can't be set from closures. + + def success_cb(): + success[0] = True + + def failure_cb(): + success[0] = False + + self.udp_recv.recv_packet(int(test_id), success_cb, failure_cb).wait() + + if success[0]: + print_safe('Receive size %d successful' % size) + else: + error_msg('Timeout') + + return success[0] + + def server_up(self): + + def error_msg(e): + print_safe('Failed control server connectivity test: %s' % e) + + try: + resp = self.request('/ping').getresponse() + data = resp.read() + except (socket.error, httplib.HTTPException), e: + error_msg(e) + return False + + if resp.status != 200: + error_msg('Invalid status %d' % resp.status) + elif data != 'pong': + error_msg('Invalid response %s' % data) + + return True + + def run(self): + + if not self.server_up(): + return 1 + + self.udp_recv.start() + + success = True + for size in [50, 500, 1000, 1500]: + success = self.send(size) and success + success = self.recv(size) and success + + self.udp_recv.stop() + + if success: + print_safe('OK') + return 0 + + return 1 + +def usage(): + print_safe("""\ +%(argv0)s: Test vlan connectivity +usage: %(argv0)s server vlan + +The following options are also available: + -s, --server run in server mode + -h, --help display this help message + -V, --version display version information\ +""" % {'argv0': sys.argv[0]}) + +def main(): + + try: + options, args = getopt.gnu_getopt(sys.argv[1:], 'hVs', + ['help', 'version', 'server']) + except getopt.GetoptError, geo: + print_safe('%s: %s\n' % (sys.argv[0], geo.msg)) + return 1 + + server = False + for key, _ in options: + if key in ['-h', '--help']: + usage() + return 0 + elif key in ['-V', '--version']: + print_safe('ovs-vlan-test (Open vSwitch) @VERSION@') + return 0 + elif key in ['-s', '--server']: + server = True + else: + print_safe('Unexpected option %s. (use --help for help)' % key) + return 1 + + if len(args) != 2: + print_safe('Expecting two arguments. (use --help for help)') + return 1 + + try: + server_ip, server_port = args[0].split(':') + server_port = int(server_port) + except ValueError: + server_ip = args[0] + server_port = 80 + + try: + vlan_ip, vlan_port = args[1].split(':') + vlan_port = int(vlan_port) + except ValueError: + vlan_ip = args[1] + vlan_port = 15213 + + if server: + return VlanServer(server_ip, server_port, vlan_ip, vlan_port).run() + else: + return VlanClient(server_ip, server_port, vlan_ip, vlan_port).run() + +if __name__ == '__main__': + main_ret = main() + + # Python can throw exceptions if threads are running at exit. + for th in threading.enumerate(): + if th != threading.currentThread(): + th.join() + + sys.exit(main_ret) diff --git a/xenserver/openvswitch-xen.spec b/xenserver/openvswitch-xen.spec index 2a33d5dc..deb1fd3d 100644 --- a/xenserver/openvswitch-xen.spec +++ b/xenserver/openvswitch-xen.spec @@ -411,6 +411,7 @@ fi /usr/bin/ovs-ofctl /usr/bin/ovs-pcap /usr/bin/ovs-tcpundump +/usr/bin/ovs-vlan-test /usr/bin/ovs-vsctl /usr/bin/ovsdb-client /usr/bin/ovsdb-tool @@ -426,6 +427,7 @@ fi /usr/share/man/man8/ovs-parse-leaks.8.gz /usr/share/man/man1/ovs-pcap.1.gz /usr/share/man/man1/ovs-tcpundump.1.gz +/usr/share/man/man8/ovs-vlan-test.8.gz /usr/share/man/man8/ovs-vsctl.8.gz /usr/share/man/man8/ovs-vswitchd.8.gz /var/lib/openvswitch