Commit 2d8bdd8f authored by Ansis Atteka's avatar Ansis Atteka

ovs-l3ping: A new test utility that allows to detect L3 tunneling issues

ovs-l3ping is similar to ovs-test, but the main difference
is that it does not require administrator to open firewall
holes for the XML/RPC control connection. This is achieved
by encapsulating the Control Connection over the L3 tunnel

This tool is not intended as a replacement for ovs-test,
because ovs-test covers much broader set of test cases.

Sample usage:
Node1: ovs-l3ping -s, -t gre
Node2: ovs-l3ping -c,, -t gre

Signed-off-by: default avatarAnsis Atteka <>
parent 969e46a2
- New FAQ. Please send updates and additions!
- ovs-l3ping:
- A new test utility that can create L3 tunnel between two Open
vSwitches and detect connectivity issues.
- ovs-ofctl:
- "mod-port" command can now control all OpenFlow config flags.
- OpenFlow:
usr/share/openvswitch/python/ovstest usr/lib/python2.6/dist-packages/
......@@ -120,6 +120,14 @@ utilities/
utilities/ovs-l3ping.8: \
utilities/ \
lib/ \
utilities/ovs-ofctl.8: \
utilities/ \
lib/ \
......@@ -3,6 +3,7 @@ ovstest_pyfiles = \
python/ovstest/ \
python/ovstest/ \
python/ovstest/ \
python/ovstest/ \
python/ovstest/ \
python/ovstest/ \
......@@ -78,6 +78,23 @@ def ip_optional_port(string, default_port, ip_callback):
"must be colon-separated")
def ip_optional_port_port(string, default_port1, default_port2, ip_callback):
"""Convert a string into IP, Port1, Port2 tuple. If any of ports were
missing, then default ports will be used. The fourth argument is a
callback that verifies whether IP address is given in the expected
value = string.split(':')
if len(value) == 1:
return (ip_callback(value[0]), default_port1, default_port2)
elif len(value) == 2:
return (ip_callback(value[0]), port(value[1]), default_port2)
elif len(value) == 3:
return (ip_callback(value[0]), port(value[1]), port(value[2]))
raise argparse.ArgumentTypeError("Expected IP address and at most "
"two colon-separated ports")
def vlan_tag(string):
This function verifies whether given string is a correct VLAN tag.
......@@ -154,6 +171,37 @@ def tunnel_types(string):
return string.split(',')
def l3_endpoint_client(string):
This function parses command line argument string in
ControlPort[:TestPort]] format.
remote_ip, me, he = string.split(',')
except ValueError:
raise argparse.ArgumentTypeError("All 3 IP addresses must be comma "
r = (ip_address(remote_ip),
ip_optional_port_port(me, CONTROL_PORT, DATA_PORT, ip_optional_mask),
ip_optional_port_port(he, CONTROL_PORT, DATA_PORT, ip_address))
return r
def l3_endpoint_server(string):
This function parses a command line argument string in
remoteIP,localInnerIP[/mask][:ControlPort] format.
remote_ip, me = string.split(',')
except ValueError:
raise argparse.ArgumentTypeError("Both IP addresses must be comma "
return (ip_address(remote_ip),
ip_optional_port(me, CONTROL_PORT, ip_optional_mask))
def ovs_initialize_args():
Initialize argument parsing for ovs-test utility.
......@@ -197,3 +245,37 @@ def ovs_initialize_args():
'ovs-test server in the client mode by using as '
return parser.parse_args()
def l3_initialize_args():
Initialize argument parsing for ovs-l3ping utility.
parser = argparse.ArgumentParser(description='Test L3 tunnel '
'connectivity between two Open vSwitch instances.')
parser.add_argument('-v', '--version', action='version',
version='ovs-l3ping (Open vSwitch) @VERSION@')
parser.add_argument("-b", "--bandwidth", action='store',
dest="targetBandwidth", default="1M", type=bandwidth,
help='Target bandwidth for UDP tests in bits/second. Use '
'postfix M or K to alter unit magnitude.')
parser.add_argument("-i", "--interval", action='store',
dest="testInterval", default=5, type=int,
help='Interval for how long to run each test in seconds.')
parser.add_argument("-t", "--tunnel-mode", action='store',
dest="tunnelMode", required=True,
help='Do L3 tests with this tunnel type.')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-s", "--server", action="store", dest="server",
help='Run in server mode and wait for the client to '
group.add_argument('-c', "--client", action="store", dest="client",
help='Run in client mode and connect to the server.')
return parser.parse_args()
......@@ -343,6 +343,12 @@ class TestArena(xmlrpc.XMLRPC):
return util.get_driver(iface)
def xmlrpc_get_interface_from_routing_decision(self, ip):
Returns driver version
return util.get_interface_from_routing_decision(ip)
def start_rpc_server(port):
import math
import time
import ovstest.util as util
DEFAULT_TEST_BRIDGE = "ovstestbr0"
DEFAULT_TEST_PORT = "ovstestport0"
DEFAULT_TEST_TUN = "ovstestport1"
def do_udp_tests(receiver, sender, tbwidth, duration, port_sizes):
"""Schedule UDP tests between receiver and sender"""
server1 = util.rpc_client(receiver[0], receiver[1])
server2 = util.rpc_client(sender[0], sender[1])
udpformat = '{0:>15} {1:>15} {2:>15} {3:>15} {4:>15}'
print ("UDP test from %s:%u to %s:%u with target bandwidth %s" %
(sender[0], sender[1], receiver[0], receiver[1],
print udpformat.format("Datagram Size", "Snt Datagrams", "Rcv Datagrams",
"Datagram Loss", "Bandwidth")
for size in port_sizes:
listen_handle = NO_HANDLE
send_handle = NO_HANDLE
packetcnt = (tbwidth * duration) / size
listen_handle = server1.create_udp_listener(receiver[3])
if listen_handle == NO_HANDLE:
print ("Server could not open UDP listening socket on port"
" %u. Try to restart the server.\n" % receiver[3])
send_handle = server2.create_udp_sender(
receiver[3]), packetcnt, size,
# Using sleep here because there is no other synchronization
# source that would notify us when all sent packets were received
time.sleep(duration + 1)
rcv_packets = server1.get_udp_listener_results(listen_handle)
snt_packets = server2.get_udp_sender_results(send_handle)
loss = math.ceil(((snt_packets - rcv_packets) * 10000.0) /
snt_packets) / 100
bwidth = (rcv_packets * size) / duration
print udpformat.format(size, snt_packets, rcv_packets,
'%.2f%%' % loss, util.bandwidth_to_string(bwidth))
if listen_handle != NO_HANDLE:
if send_handle != NO_HANDLE:
print "\n"
def do_tcp_tests(receiver, sender, duration):
"""Schedule TCP tests between receiver and sender"""
server1 = util.rpc_client(receiver[0], receiver[1])
server2 = util.rpc_client(sender[0], sender[1])
tcpformat = '{0:>15} {1:>15} {2:>15}'
print "TCP test from %s:%u to %s:%u (full speed)" % (sender[0], sender[1],
receiver[0], receiver[1])
print tcpformat.format("Snt Bytes", "Rcv Bytes", "Bandwidth")
listen_handle = NO_HANDLE
send_handle = NO_HANDLE
listen_handle = server1.create_tcp_listener(receiver[3])
if listen_handle == NO_HANDLE:
print ("Server was unable to open TCP listening socket on port"
" %u. Try to restart the server.\n" % receiver[3])
send_handle = server2.create_tcp_sender(util.ip_from_cidr(receiver[2]),
receiver[3], duration)
time.sleep(duration + 1)
rcv_bytes = long(server1.get_tcp_listener_results(listen_handle))
snt_bytes = long(server2.get_tcp_sender_results(send_handle))
bwidth = rcv_bytes / duration
print tcpformat.format(snt_bytes, rcv_bytes,
if listen_handle != NO_HANDLE:
if send_handle != NO_HANDLE:
print "\n"
def do_l3_tests(node1, node2, bandwidth, duration, ps, type):
Do L3 tunneling tests. Each node is given as 4 tuple - physical
interface IP, control port, test IP and test port.
server1 = util.rpc_client(node1[0], node1[1])
server2 = util.rpc_client(node2[0], node2[1])
servers_with_bridges = []
server1.interface_assign_ip(DEFAULT_TEST_BRIDGE, node1[2], None)
server2.interface_assign_ip(DEFAULT_TEST_BRIDGE, node2[2], None)
server1.add_port_to_bridge(DEFAULT_TEST_BRIDGE, DEFAULT_TEST_TUN)
server2.add_port_to_bridge(DEFAULT_TEST_BRIDGE, DEFAULT_TEST_TUN)
server1.ovs_vsctl_set("Interface", DEFAULT_TEST_TUN, "type",
None, type)
server2.ovs_vsctl_set("Interface", DEFAULT_TEST_TUN, "type",
None, type)
server1.ovs_vsctl_set("Interface", DEFAULT_TEST_TUN, "options",
"remote_ip", node2[0])
server2.ovs_vsctl_set("Interface", DEFAULT_TEST_TUN, "options",
"remote_ip", node1[0])
do_udp_tests(node1, node2, bandwidth, duration, ps)
do_udp_tests(node2, node1, bandwidth, duration, ps)
do_tcp_tests(node1, node2, duration)
do_tcp_tests(node2, node1, duration)
for server in servers_with_bridges:
def do_vlan_tests(node1, node2, bandwidth, duration, ps, tag):
Do VLAN tests between node1 and node2. Each node is given
as 4 tuple - physical interface IP, control port, test IP and
test port.
server1 = util.rpc_client(node1[0], node1[1])
server2 = util.rpc_client(node2[0], node2[1])
br_name1 = None
br_name2 = None
servers_with_test_ports = []
interface_node1 = server1.get_interface(node1[0])
interface_node2 = server2.get_interface(node2[0])
if server1.is_ovs_bridge(interface_node1):
br_name1 = interface_node1
server1.create_test_bridge(br_name1, interface_node1)
if server2.is_ovs_bridge(interface_node2):
br_name2 = interface_node2
server2.create_test_bridge(br_name2, interface_node2)
server1.add_port_to_bridge(br_name1, DEFAULT_TEST_PORT)
server2.add_port_to_bridge(br_name2, DEFAULT_TEST_PORT)
server1.ovs_vsctl_set("Port", DEFAULT_TEST_PORT, "tag", None, tag)
server2.ovs_vsctl_set("Port", DEFAULT_TEST_PORT, "tag", None, tag)
server1.ovs_vsctl_set("Interface", DEFAULT_TEST_PORT, "type", None,
server2.ovs_vsctl_set("Interface", DEFAULT_TEST_PORT, "type", None,
server1.interface_assign_ip(DEFAULT_TEST_PORT, node1[2], None)
server2.interface_assign_ip(DEFAULT_TEST_PORT, node2[2], None)
do_udp_tests(node1, node2, bandwidth, duration, ps)
do_udp_tests(node2, node1, bandwidth, duration, ps)
do_tcp_tests(node1, node2, duration)
do_tcp_tests(node2, node1, duration)
for server in servers_with_test_ports:
if br_name1 == DEFAULT_TEST_BRIDGE:
server1.del_test_bridge(br_name1, interface_node1)
if br_name2 == DEFAULT_TEST_BRIDGE:
server2.del_test_bridge(br_name2, interface_node2)
def do_direct_tests(node1, node2, bandwidth, duration, ps):
Do tests between outer IPs without involving Open vSwitch. Each
node is given as 4 tuple - physical interface IP, control port,
test IP and test port. Direct tests will use physical interface
IP as the test IP address.
n1 = (node1[0], node1[1], node1[0], node1[3])
n2 = (node2[0], node2[1], node2[0], node2[3])
do_udp_tests(n1, n2, bandwidth, duration, ps)
do_udp_tests(n2, n1, bandwidth, duration, ps)
do_tcp_tests(n1, n2, duration)
do_tcp_tests(n2, n1, duration)
def configure_l3(conf, tunnel_mode):
This function creates a temporary test bridge and adds an L3 tunnel.
s = util.start_local_server(conf[1][1])
server = util.rpc_client("", conf[1][1])
server.add_port_to_bridge(DEFAULT_TEST_BRIDGE, DEFAULT_TEST_PORT)
server.interface_assign_ip(DEFAULT_TEST_BRIDGE, conf[1][0],
server.ovs_vsctl_set("Interface", DEFAULT_TEST_PORT, "type",
None, tunnel_mode)
server.ovs_vsctl_set("Interface", DEFAULT_TEST_PORT, "options",
"remote_ip", conf[0])
return s
......@@ -19,10 +19,13 @@ import array
import exceptions
import fcntl
import os
import select
import socket
import struct
import signal
import subprocess
import re
import xmlrpclib
def str_ip(ip_address):
......@@ -147,3 +150,81 @@ def move_routes(iface1, iface2):
for route in out.splitlines():
args = ["ip", "route", "replace", "dev", iface2] + route.split()
def get_interface_from_routing_decision(ip):
This function returns the interface through which the given ip address
is reachable.
args = ["ip", "route", "get", ip]
ret, out, _err = start_process(args)
if ret == 0:
iface ='dev (\S+)', out)
if iface:
return None
def rpc_client(ip, port):
return xmlrpclib.Server("http://%s:%u/" % (ip, port), allow_none=True)
def sigint_intercept():
Intercept SIGINT from child (the local ovs-test server process).
signal.signal(signal.SIGINT, signal.SIG_IGN)
def start_local_server(port):
This function spawns an ovs-test server that listens on specified port
and blocks till the spawned ovs-test server is ready to accept XML RPC
p = subprocess.Popen(["ovs-test", "-s", str(port)],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
fcntl.fcntl( p.stdout.fileno(),fcntl.F_SETFL,
fcntl.fcntl(p.stdout.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK)
while p.poll() is None:
fd =[p.stdout.fileno()], [], [])[0]
if fd:
out = p.stdout.readline()
if out.startswith("Starting RPC server"):
if p.poll() is not None:
raise RuntimeError("Couldn't start local instance of ovs-test server")
return p
def get_datagram_sizes(mtu1, mtu2):
This function calculates all the "interesting" datagram sizes so that
we test both - receive and send side with different packets sizes.
s1 = set([8, mtu1 - 100, mtu1 - 28, mtu1])
s2 = set([8, mtu2 - 100, mtu2 - 28, mtu2])
return sorted(s1.union(s2))
def ip_from_cidr(string):
This function removes the netmask (if present) from the given string and
returns the IP address.
token = string.split("/")
return token[0]
def bandwidth_to_string(bwidth):
"""Convert bandwidth from long to string and add units."""
bwidth = bwidth * 8 # Convert back to bits/second
if bwidth >= 10000000:
return str(int(bwidth / 1000000)) + "Mbps"
elif bwidth > 10000:
return str(int(bwidth / 1000)) + "Kbps"
return str(int(bwidth)) + "bps"
......@@ -59,7 +59,9 @@ rm \
$RPM_BUILD_ROOT/usr/bin/ovs-controller \
$RPM_BUILD_ROOT/usr/share/man/man8/ovs-controller.8 \
$RPM_BUILD_ROOT/usr/bin/ovs-test \
$RPM_BUILD_ROOT/usr/bin/ovs-l3ping \
$RPM_BUILD_ROOT/usr/share/man/man8/ovs-test.8 \
$RPM_BUILD_ROOT/usr/share/man/man8/ovs-l3ping.8 \
$RPM_BUILD_ROOT/usr/sbin/ovs-vlan-bug-workaround \
......@@ -13,6 +13,8 @@
......@@ -7,6 +7,7 @@ bin_PROGRAMS += \
bin_SCRIPTS += utilities/ovs-pki utilities/ovs-vsctl utilities/ovs-parse-leaks
bin_SCRIPTS += \
utilities/ovs-l3ping \
utilities/ovs-pcap \
utilities/ovs-tcpundump \
utilities/ovs-test \
......@@ -22,6 +23,7 @@ scripts_DATA += utilities/ovs-lib
utilities/ \
utilities/ \
utilities/ \
utilities/ \
utilities/ \
utilities/ \
......@@ -37,6 +39,7 @@ MAN_ROOTS += \
utilities/ \
utilities/ovs-ctl.8 \
utilities/ \
utilities/ \
utilities/ \
utilities/ovs-parse-leaks.8 \
utilities/ \
......@@ -54,6 +57,8 @@ DISTCLEANFILES += \
utilities/ovs-check-dead-ifs \
utilities/ovs-controller.8 \
utilities/ovs-dpctl.8 \
utilities/ovs-l3ping \
utilities/ovs-l3ping.8 \
utilities/ovs-lib \
utilities/ovs-ofctl.8 \
utilities/ovs-parse-leaks \
......@@ -76,6 +81,7 @@ man_MANS += \
utilities/ovs-benchmark.1 \
utilities/ovs-controller.8 \
utilities/ovs-dpctl.8 \
utilities/ovs-l3ping.8 \
utilities/ovs-ofctl.8 \
utilities/ovs-parse-leaks.8 \
utilities/ovs-pcap.1 \
.de IQ
. br
. ns
. IP "\\$1"
.TH ovs\-l3ping 1 "June 2012" "Open vSwitch" "Open vSwitch Manual"
\fBovs\-l3ping\fR \- check network deployment for L3 tunneling
\fBovs\-l3ping\fR \fB\-s\fR \fITunnelRemoteIP,InnerIP[/mask]\fR \fB\-t\fR \fItunnelmode\fR
\fBovs\-l3ping\fR \fB\-s\fR \fITunnelRemoteIP,InnerIP[/mask][:ControlPort]\fR \fB\-t\fR \fItunnelmode\fR
\fBovs\-l3ping\fR \fB\-c\fR \fITunnelRemoteIP,InnerIP[/mask],RemoteInnerIP\fR \fB\-t\fR \fItunnelmode\fR
\fBovs\-l3ping\fR \fB\-c\fR \fITunnelRemoteIP,InnerIP[/mask][:ControlPort\
[\fB\-b\fR \fItargetbandwidth\fR] [\fB\-i\fR \fItestinterval\fR]
\fB\-t\fR \fItunnelmode\fR
.so lib/
The \fBovs\-l3ping\fR program may be used to check for problems that could
be caused by invalid routing policy, misconfigured firewall in the tunnel
path or a bad NIC driver. On one of the nodes, run \fBovs\-l3ping\fR in
server mode and on the other node run it in client mode. The client and
server will establish L3 tunnel, over which client will give further testing
instructions. The \fBovs\-l3ping\fR client will perform UDP and TCP tests.
This tool is different from \fBovs\-test\fR that it encapsulates XML/RPC
control connection over the tunnel, so there is no need to open special holes
in firewall.
UDP tests can report packet loss and achieved bandwidth for various
datagram sizes. By default target bandwidth for UDP tests is 1Mbit/s.
TCP tests report only achieved bandwidth, because kernel TCP stack
takes care of flow control and packet loss.
.SS "Client Mode"
An \fBovs\-l3ping\fR client will create a L3 tunnel and connect over it to the
\fBovs\-l3ping\fR server to schedule the tests. \fITunnelRemoteIP\fR is the
peer's IP address, where tunnel will be terminated. \fIInnerIP\fR is the
address that will be temporarily assigned during testing. All test traffic
originating from this IP address to the \fIRemoteInnerIP\fR will be tunneled.
It is possible to override default \fIControlPort\fR and \fIDataPort\fR, if
there is any other application that already listens on those two ports.
.SS "Server Mode"
To conduct tests, \fBovs\-l3ping\fR server must be running. It is required
that both client and server \fIInnerIP\fR addresses are in the same subnet.
It is possible to specify \fIInnerIP\fR with netmask in CIDR format.
One of \fB\-s\fR or \fB\-c\fR is required. The \fB\-t\fR option is
also required.
.IP "\fB\-s \fITunnelRemoteIP,InnerIP[/mask][:ControlPort]\fR"
.IQ "\fB\-\-server\fR \fITunnelRemoteIP,InnerIP[/mask][:ControlPort]\fR"
Run in server mode and create L3 tunnel with the client that will be
accepting tunnel at \fITunnelRemoteIP\fR address. The socket on
\fIInnerIP[:ControlPort]\fR will be used to receive further instructions
from the client.
.IP "\fB\-c \fITunnelRemoteIP,InnerIP[/mask][:ControlPort\
.IQ "\fB\-\-client \fITunnelRemoteIP,InnerIP[/mask][:ControlPort\
Run in client mode and create L3 tunnel with the server on
\fITunnelRemoteIP\fR. The client will use \fIInnerIP\fR to generate test
traffic with the server's \fIRemoteInnerIP\fR.
.IP "\fB\-b \fItargetbandwidth\fR"
.IQ "\fB\-\-bandwidth\fR \fItargetbandwidth\fR"
Target bandwidth for UDP tests. The \fItargetbandwidth\fR must be given in
bits per second. It is possible to use postfix M or K to alter the target
bandwidth magnitude.
.IP "\fB\-i \fItestinterval\fR"
.IQ "\fB\-\-interval\fR \fItestinterval\fR"
How long each test should run. By default 5 seconds.
.IP "\fB\-t \fItunnelmode\fR"
.IQ "\fB\-\-tunnel\-mode\fR \fItunnelmode\fR"
Specify the tunnel type. This option must match on server and client.
.so lib/
On host start \fBovs\-l3ping\fR in server mode. This command
will create a temporary GRE tunnel with the host and assign as the inner IP address, where client will have to connect:
.B ovs\-l3ping -s, -t gre
On host start \fBovs\-l3ping\fR in client mode. This command
will use as the local inner IP address and will connect over the
L3 tunnel to the server's inner IP address at
.B ovs\-l3ping -c,, -t gre
.BR ovs\-vswitchd (8),
.BR ovs\-ofctl (8),
.BR ovs\-vsctl (8),
.BR ovs\-vlan\-test (8),
.BR ovs\-test (8),
.BR ethtool (8),
.BR uname (1)
# 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:
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# See the License for the specific language governing permissions and
# limitations under the License.
ovs L3 ping utility allows to do tests between two remote hosts without
opening holes in the firewall for the XML RPC control connection. This is
achieved by tunneling the control connection inside the tunnel itself.
import socket
import xmlrpclib
import ovstest.args as args
import ovstest.tests as tests
import ovstest.util as util
def get_packet_sizes(me, he, remote_ip):
This function retrieves MTUs from both hosts and returns a list of
packet sizes, that are more likely to uncover possible configuration
mtu_node1 = 1500
mtu_node2 = 1500
server1 = util.rpc_client(me[0], me[1])
server2 = util.rpc_client(he[0], he[1])
iface1 = server2.get_interface(remote_ip)
iface2 = server1.get_interface_from_routing_decision(remote_ip)
if iface1:
mtu_node1 = server2.get_interface_mtu(iface1)
if iface2:
mtu_node2 = server1.get_interface_mtu(iface2)
return util.get_datagram_sizes(mtu_node1, mtu_node2)
if __name__ == '__main__':
local_server = None
args = args.l3_initialize_args()
tunnel_mode = args.tunnelMode
if args.server is not None: # Start in server mode
local_server = tests.configure_l3(args.server, tunnel_mode)
elif args.client is not None: # Run in client mode
bandwidth = args.targetBandwidth
interval = args.testInterval
me = (util.ip_from_cidr(args.client[1][0]), args.client[1][1],
args.client[1][0], args.client[1][2])
he = (args.client[2][0], args.client[2][1],
args.client[2][0], args.client[2][2])
local_server = tests. configure_l3(args.client, tunnel_mode)
ps = get_packet_sizes(me, he, args.client[0])
tests.do_direct_tests(me, he, bandwidth, interval, ps)
except KeyboardInterrupt:
print "Terminating"
except xmlrpclib.Fault:
print "Couldn't contact peer"
except socket.error:
print "Couldn't contact peer"
except xmlrpclib.ProtocolError:
print "XMLRPC control channel was abruptly terminated"
if local_server is not None: