--- /dev/null
+.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)
--- /dev/null
+#! @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)