utilities: Implement ovs-vlan-test script
authorEthan Jackson <ethan@nicira.com>
Wed, 8 Dec 2010 02:49:28 +0000 (18:49 -0800)
committerEthan Jackson <ethan@nicira.com>
Fri, 24 Dec 2010 02:57:20 +0000 (21:57 -0500)
This patch implements a script which may be used to check for
connectivity issues caused by bugs in Linux drivers relating to
VLAN traffic.

debian/openvswitch-common.manpages
debian/openvswitch-switch.install
utilities/automake.mk
utilities/ovs-vlan-test.8.in [new file with mode: 0644]
utilities/ovs-vlan-test.in [new file with mode: 0755]
xenserver/openvswitch-xen.spec

index 8f7e64df74331316425b66c5a3cb6a68e950d729..ba94a52be4ba02df9071dad2fbd9cac8efb6a88c 100644 (file)
@@ -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
index 08ce576437eba396baeca07e79f050aaccb79f34..c38cdd46657fafe85aea6685622a3f079d796f05 100644 (file)
@@ -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
index 9a334e3390539d84e9077c3d8057c85ecf6801b5..7aa5800d1672d0ef7e59ce6cd838f7781a713cb0 100644 (file)
@@ -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 (file)
index 0000000..d3cd031
--- /dev/null
@@ -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 (executable)
index 0000000..8489e68
--- /dev/null
@@ -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()
+\f
+#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
+\f
+#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)
+\f
+#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
+\f
+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)
index 2a33d5dcbf66887caa2095c717ba294f15a7ec31..deb1fd3db6b147db6c1e107298fbe5605ec40604 100644 (file)
@@ -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