Initial set of Fabric switch test cases
Change-Id: I86fd2b67d3b773aa496f5ef61f1e1fdf51fd9925
diff --git a/Fabric/Utilities/src/python/oftest/testutils.py b/Fabric/Utilities/src/python/oftest/testutils.py
new file mode 100755
index 0000000..3a147c7
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/testutils.py
@@ -0,0 +1,2262 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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 sys
+import copy
+import logging
+import types
+import time
+import re
+from Queue import Queue
+
+import packet as scapy
+
+import oftest
+import oftest.controller
+import oftest.dataplane
+import oftest.parse
+import oftest.ofutils
+import ofp
+
+global skipped_test_count
+skipped_test_count = 0
+
+_import_blacklist = set(locals().keys())
+
+# Some useful defines
+IP_ETHERTYPE = 0x800
+TCP_PROTOCOL = 0x6
+UDP_PROTOCOL = 0x11
+
+MINSIZE = 0
+
+def delete_all_flows(ctrl, send_barrier=True):
+ """
+ Delete all flows on the switch
+ @param ctrl The controller object for the test
+ @param send_barrier Whether or not to send a barrier message
+ """
+
+ logging.info("Deleting all flows")
+ msg = ofp.message.flow_delete()
+ if ofp.OFP_VERSION in [1, 2]:
+ msg.match.wildcards = ofp.OFPFW_ALL
+ msg.out_port = ofp.OFPP_NONE
+ msg.buffer_id = 0xffffffff
+ elif ofp.OFP_VERSION >= 3:
+ msg.table_id = ofp.OFPTT_ALL
+ msg.buffer_id = ofp.OFP_NO_BUFFER
+ msg.out_port = ofp.OFPP_ANY
+ msg.out_group = ofp.OFPG_ANY
+ ctrl.message_send(msg)
+ if send_barrier:
+ do_barrier(ctrl)
+ return 0 # for backwards compatibility
+
+def delete_all_groups(ctrl):
+ """
+ Delete all groups on the switch
+ @param ctrl The controller object for the test
+ """
+
+ logging.info("Deleting all groups")
+ msg = ofp.message.group_delete(group_id=ofp.OFPG_ALL)
+ ctrl.message_send(msg)
+ do_barrier(ctrl)
+
+def delete_groups(ctrl, group_queue=Queue()):
+ """
+ Delete all groups on list
+ @param ctrl The controller object for the test
+ :param group_queue:
+ """
+ logging.info("Deleting groups")
+ while (not group_queue.empty()):
+ msg = ofp.message.group_delete(group_id=group_queue.get())
+ ctrl.message_send(msg)
+ do_barrier(ctrl)
+
+def delete_group(ctrl, group_id):
+ """
+ Delete a single group
+ @param ctrl The controller object for the test
+ :param group_id
+ """
+ logging.info("Deleting a single group with groupId:" + str(group_id))
+ msg = ofp.message.group_delete(group_id=group_id)
+ ctrl.message_send(msg)
+ do_barrier(ctrl)
+
+def required_wildcards(parent):
+ w = test_param_get('required_wildcards', default='default')
+ if w == 'l3-l4':
+ return (ofp.OFPFW_NW_SRC_ALL | ofp.OFPFW_NW_DST_ALL | ofp.OFPFW_NW_TOS
+ | ofp.OFPFW_NW_PROTO | ofp.OFPFW_TP_SRC | ofp.OFPFW_TP_DST)
+ else:
+ return 0
+
+def simple_packet(content='00 00 00 11 33 55 00 00 00 11 22 33 81 00 00 03 '
+ '08 00 45 00 00 2e 04 d2 00 00 7f 00 b2 47 c0 a8 '
+ '01 64 c0 a8 02 02 00 00 00 00 00 00 00 00 00 00 '
+ '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00'):
+
+ pkt = ''.join(content.split(" ")).decode('hex')
+ pkt = scapy.Ether(pkt)
+ if len(pkt) < 64:
+ pkt = pkt/("D" * (64 - len(pkt)))
+ #scapy.hexdump(pkt)
+ return pkt
+
+def simple_tcp_packet(pktlen=100,
+ eth_dst='00:01:02:03:04:05',
+ eth_src='00:06:07:08:09:0a',
+ dl_vlan_enable=False,
+ vlan_vid=0,
+ outer_vlan=None,
+ vlan_pcp=0,
+ dl_vlan_cfi=0,
+ ip_src='192.168.0.1',
+ ip_dst='192.168.0.2',
+ ip_tos=0,
+ ip_ttl=64,
+ tcp_sport=1234,
+ tcp_dport=80,
+ tcp_flags="S",
+ ip_ihl=None,
+ ip_options=False
+ ):
+ """
+ Return a simple dataplane TCP packet
+
+ Supports a few parameters:
+ @param len Length of packet in bytes w/o CRC
+ @param eth_dst Destinatino MAC
+ @param eth_src Source MAC
+ @param dl_vlan_enable True if the packet is with vlan, False otherwise
+ @param vlan_vid VLAN ID
+ @param vlan_pcp VLAN priority
+ @param ip_src IP source
+ @param ip_dst IP destination
+ @param ip_tos IP ToS
+ @param ip_ttl IP TTL
+ @param tcp_dport TCP destination port
+ @param tcp_sport TCP source port
+ @param tcp_flags TCP Control flags
+
+ Generates a simple TCP request. Users
+ shouldn't assume anything about this packet other than that
+ it is a valid ethernet/IP/TCP frame.
+ """
+
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ # Note Dot1Q.id is really CFI
+ if (dl_vlan_enable):
+ pkt = scapy.Ether( dst=eth_dst, src=eth_src )
+ if outer_vlan:
+ pkt = pkt/scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=outer_vlan)
+
+ pkt = pkt/scapy.Dot1Q( prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid )/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+ else:
+ if not ip_options:
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+ else:
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl, options=ip_options)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+
+ pkt = pkt/("D" * (pktlen - len(pkt)))
+
+ return pkt
+
+def simple_tcpv6_packet(pktlen=100,
+ eth_dst='00:01:02:03:04:05',
+ eth_src='00:06:07:08:09:0a',
+ dl_vlan_enable=False,
+ vlan_vid=0,
+ vlan_pcp=0,
+ ipv6_src='2001:db8:85a3::8a2e:370:7334',
+ ipv6_dst='2001:db8:85a3::8a2e:370:7335',
+ ipv6_tc=0,
+ ipv6_hlim=64,
+ ipv6_fl=0,
+ tcp_sport=1234,
+ tcp_dport=80,
+ tcp_flags="S"):
+ """
+ Return a simple IPv6/TCP packet
+
+ Supports a few parameters:
+ @param len Length of packet in bytes w/o CRC
+ @param eth_dst Destination MAC
+ @param eth_src Source MAC
+ @param dl_vlan_enable True if the packet is with vlan, False otherwise
+ @param vlan_vid VLAN ID
+ @param vlan_pcp VLAN priority
+ @param ipv6_src IPv6 source
+ @param ipv6_dst IPv6 destination
+ @param ipv6_tc IPv6 traffic class
+ @param ipv6_ttl IPv6 hop limit
+ @param ipv6_fl IPv6 flow label
+ @param tcp_dport TCP destination port
+ @param tcp_sport TCP source port
+ @param tcp_flags TCP Control flags
+
+ Generates a simple TCP request. Users shouldn't assume anything about this
+ packet other than that it is a valid ethernet/IPv6/TCP frame.
+ """
+
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+ if dl_vlan_enable or vlan_vid or vlan_pcp:
+ pkt /= scapy.Dot1Q(vlan=vlan_vid, prio=vlan_pcp)
+ pkt /= scapy.IPv6(src=ipv6_src, dst=ipv6_dst, fl=ipv6_fl, tc=ipv6_tc, hlim=ipv6_hlim)
+ pkt /= scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+ pkt /= ("D" * (pktlen - len(pkt)))
+
+ return pkt
+
+def simple_udp_packet(pktlen=100,
+ eth_dst='00:01:02:03:04:05',
+ eth_src='00:06:07:08:09:0a',
+ dl_vlan_enable=False,
+ vlan_vid=0,
+ vlan_pcp=0,
+ dl_vlan_cfi=0,
+ ip_src='192.168.0.1',
+ ip_dst='192.168.0.2',
+ ip_tos=0,
+ ip_ttl=64,
+ udp_sport=1234,
+ udp_dport=80,
+ ip_ihl=None,
+ ip_options=False
+ ):
+ """
+ Return a simple dataplane UDP packet
+
+ Supports a few parameters:
+ @param len Length of packet in bytes w/o CRC
+ @param eth_dst Destination MAC
+ @param eth_src Source MAC
+ @param dl_vlan_enable True if the packet is with vlan, False otherwise
+ @param vlan_vid VLAN ID
+ @param vlan_pcp VLAN priority
+ @param ip_src IP source
+ @param ip_dst IP destination
+ @param ip_tos IP ToS
+ @param ip_ttl IP TTL
+ @param udp_dport UDP destination port
+ @param udp_sport UDP source port
+
+ Generates a simple UDP packet. Users shouldn't assume anything about
+ this packet other than that it is a valid ethernet/IP/UDP frame.
+ """
+
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ # Note Dot1Q.id is really CFI
+ if (dl_vlan_enable):
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+ scapy.UDP(sport=udp_sport, dport=udp_dport)
+ else:
+ if not ip_options:
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+ scapy.UDP(sport=udp_sport, dport=udp_dport)
+ else:
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl, options=ip_options)/ \
+ scapy.UDP(sport=udp_sport, dport=udp_dport)
+
+ pkt = pkt/("D" * (pktlen - len(pkt)))
+
+ return pkt
+
+
+def simple_tcp_packet_two_vlan(pktlen=100,
+ eth_dst='00:01:02:03:04:05',
+ eth_src='00:06:07:08:09:0a',
+ out_dl_vlan_enable=False,
+ in_dl_vlan_enable=False,
+ out_vlan_vid=0,
+ out_vlan_pcp=0,
+ out_vlan_tpid=0x8100,
+ out_dl_vlan_cfi=0,
+ in_vlan_vid=0,
+ in_vlan_pcp=0,
+ in_dl_vlan_cfi=0,
+ ip_src='192.168.0.1',
+ ip_dst='192.168.0.2',
+ ip_tos=0,
+ ip_ttl=64,
+ tcp_sport=1234,
+ tcp_dport=80,
+ tcp_flags="S",
+ ip_ihl=None,
+ ip_options=False
+ ):
+ """
+ Return a simple dataplane TCP packet
+
+ Supports a few parameters:
+ @param len Length of packet in bytes w/o CRC
+ @param eth_dst Destinatino MAC
+ @param eth_src Source MAC
+ @param dl_vlan_enable True if the packet is with vlan, False otherwise
+ @param vlan_vid VLAN ID
+ @param vlan_pcp VLAN priority
+ @param ip_src IP source
+ @param ip_dst IP destination
+ @param ip_tos IP ToS
+ @param ip_ttl IP TTL
+ @param tcp_dport TCP destination port
+ @param tcp_sport TCP source port
+ @param tcp_flags TCP Control flags
+
+ Generates a simple TCP request. Users
+ shouldn't assume anything about this packet other than that
+ it is a valid ethernet/IP/TCP frame.
+ """
+
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ # Note Dot1Q.id is really CFI
+ if (out_dl_vlan_enable and in_dl_vlan_enable):
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src, type=out_vlan_tpid)/ \
+ scapy.Dot1Q(prio=out_vlan_pcp, id=out_dl_vlan_cfi, vlan=out_vlan_vid)
+
+ if in_dl_vlan_enable:
+ pkt = pkt/scapy.Dot1Q(prio=in_vlan_pcp, id=in_dl_vlan_cfi, vlan=in_vlan_vid)
+
+ pkt = pkt/scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+ elif (out_dl_vlan_enable and (not in_dl_vlan_enable)):
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.Dot1Q(prio=out_vlan_pcp, id=out_dl_vlan_cfi, vlan=out_vlan_vid)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+ elif ((not out_dl_vlan_enable) and in_dl_vlan_enable):
+ assert(0) #shall not have this caes
+ else:
+ if not ip_options:
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+ else:
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl, options=ip_options)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+
+ pkt = pkt/("D" * (pktlen - len(pkt)))
+
+ return pkt
+
+def simple_vxlan_packet(eth_dst='00:01:02:03:04:05',
+ eth_src='00:06:07:08:09:0a',
+ dl_vlan_enable=False,
+ vlan_vid=0,
+ vlan_pcp=0,
+ dl_vlan_cfi=0,
+ ip_src='192.168.0.1',
+ ip_dst='192.168.0.2',
+ ip_tos=0,
+ ip_ttl=64,
+ udp_sport=1234,
+ udp_dport=4789,
+ vnid=1,
+ inner_payload=None,
+ ip_ihl=None,
+ ip_options=False
+ ):
+ """
+ Return a simple dataplane UDP packet
+
+ Supports a few parameters:
+ @param len Length of packet in bytes w/o CRC
+ @param eth_dst Destination MAC
+ @param eth_src Source MAC
+ @param dl_vlan_enable True if the packet is with vlan, False otherwise
+ @param vlan_vid VLAN ID
+ @param vlan_pcp VLAN priority
+ @param ip_src IP source
+ @param ip_dst IP destination
+ @param ip_tos IP ToS
+ @param ip_ttl IP TTL
+ @param udp_dport UDP destination port
+ @param udp_sport UDP source port
+ @param inner_pyload inner pacekt content
+ Generates a simple UDP packet. Users shouldn't assume anything about
+ this packet other than that it is a valid ethernet/IP/UDP frame.
+ """
+
+ # Note Dot1Q.id is really CFI
+ if (dl_vlan_enable):
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+ scapy.UDP(sport=udp_sport, dport=udp_dport)
+ else:
+ if not ip_options:
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+ scapy.UDP(sport=udp_sport, dport=udp_dport)
+ else:
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl, options=ip_options)/ \
+ scapy.UDP(sport=udp_sport, dport=udp_dport)
+
+ #add vxlan header
+ pkt = pkt/scapy.VXLAN(vni=vnid)
+ #add innder payload
+ if inner_payload!=None:
+ pkt=pkt/inner_payload
+
+ return pkt
+
+def mpls_packet(pktlen=100,
+ eth_dst='00:01:02:03:04:05',
+ eth_src='00:06:07:08:09:0a',
+ dl_vlan_enable=False,
+ vlan_vid=0,
+ vlan_pcp=0,
+ dl_vlan_cfi=0,
+ ip_src='192.168.0.1',
+ ip_dst='192.168.0.2',
+ ip_tos=0,
+ ip_ttl=64,
+ tcp_sport=1234,
+ tcp_dport=80,
+ tcp_flags="S",
+ ip_ihl=None,
+ ip_options=False,
+ label=None,
+ inner_payload=True,
+ encapsulated_ethernet=False,
+ encapsulated_eth_src='01:02:03:04:05:11',
+ encapsulated_eth_dst='01:02:03:04:05:22'
+ ):
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ # Note Dot1Q.id is really CFI
+ if (dl_vlan_enable):
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid)
+ else:
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+
+ #add MPLS header
+ for i in range(len(label)):
+ l,c,s,t=label[i]
+ pkt = pkt/scapy.MPLS(label=l, cos=c, s=s, ttl=t)
+
+ #add innder payload
+ if inner_payload!=None:
+ if not encapsulated_ethernet:
+ pkt=pkt / \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+ else:
+ pkt=pkt / \
+ scapy.Ether(dst=encapsulated_eth_dst, src=encapsulated_eth_src)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+
+ pkt = pkt/("D" * (pktlen - len(pkt)))
+
+ return pkt
+
+def pw_packet(pktlen=100,
+ out_eth_dst='00:01:02:03:04:05',
+ out_eth_src='00:06:07:08:09:0a',
+ label=None,
+ cw=None,
+ in_eth_dst='00:01:02:03:04:05',
+ in_eth_src='00:06:07:08:09:0a',
+ out_dl_vlan_enable=False,
+ in_dl_vlan_enable=False,
+ out_vlan_vid=0,
+ out_vlan_pcp=0,
+ out_dl_vlan_cfi=0,
+ in_vlan_vid=0,
+ in_vlan_pcp=0,
+ in_dl_vlan_cfi=0,
+ ip_src='192.168.0.1',
+ ip_dst='192.168.0.2',
+ ip_tos=0,
+ ip_ttl=64,
+ tcp_sport=1234,
+ tcp_dport=80,
+ tcp_flags="S",
+ ip_ihl=None,
+ ip_options=False
+ ):
+ """
+ Return a simple dataplane TCP packet encapsulated
+ in a pw packet
+ """
+
+ # Add the outer ethernet header
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ pkt = scapy.Ether(dst=out_eth_dst, src=out_eth_src)
+
+ #add MPLS header
+ for i in range(len(label)):
+ l,c,s,t=label[i]
+ pkt = pkt/scapy.MPLS(label=l, cos=c, s=s, ttl=t)
+
+ #add the PW CW
+ l,c,s,t=cw
+ pkt = pkt/scapy.MPLS(label=l, cos=c, s=s, ttl=t)
+
+ # Note Dot1Q.id is really CFI
+ if (out_dl_vlan_enable and in_dl_vlan_enable):
+
+ pkt = pkt/scapy.Ether(dst=in_eth_dst, src=in_eth_src)/ \
+ scapy.Dot1Q(prio=out_vlan_pcp, id=out_dl_vlan_cfi, vlan=out_vlan_vid)
+
+ pkt = pkt/scapy.Dot1Q(prio=in_vlan_pcp, id=in_dl_vlan_cfi, vlan=in_vlan_vid)
+
+ pkt = pkt/scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+
+ elif (out_dl_vlan_enable and (not in_dl_vlan_enable)):
+
+ pkt = pkt/scapy.Ether(dst=in_eth_dst, src=in_eth_src)/ \
+ scapy.Dot1Q(prio=out_vlan_pcp, id=out_dl_vlan_cfi, vlan=out_vlan_vid)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+
+ elif ((not out_dl_vlan_enable) and in_dl_vlan_enable):
+
+ assert(0) #shall not have this caes
+
+ else:
+ if not ip_options:
+ pkt = pkt/scapy.Ether(dst=in_eth_dst, src=in_eth_src)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+ else:
+ pkt = pkt/scapy.Ether(dst=in_eth_dst, src=in_eth_src)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl, options=ip_options)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+
+ pkt = pkt/("D" * (pktlen - len(pkt)))
+
+ return pkt
+
+def mplsv6_packet(pktlen=100,
+ eth_dst='00:01:02:03:04:05',
+ eth_src='00:06:07:08:09:0a',
+ dl_vlan_enable=False,
+ vlan_vid=0,
+ vlan_pcp=0,
+ dl_vlan_cfi=0,
+ ipv6_src='2001:db8:85a3::8a2e:370:7334',
+ ipv6_dst='2001:db8:85a3::8a2e:370:7335',
+ ipv6_tc=0,
+ ipv6_hlim=64,
+ ipv6_fl=0,
+ tcp_sport=1234,
+ tcp_dport=80,
+ tcp_flags="S",
+ label=None,
+ inner_payload=True
+ ):
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ # Note Dot1Q.id is really CFI
+ if (dl_vlan_enable):
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid)
+ else:
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+
+ #add MPLS header
+ for i in range(len(label)):
+ l,c,s,t=label[i]
+ pkt = pkt/scapy.MPLS(label=l, cos=c, s=s, ttl=t)
+
+ #add innder payload
+ if inner_payload!=None:
+ pkt=pkt / \
+ scapy.IPv6(src=ipv6_src, dst=ipv6_dst, fl=ipv6_fl, tc=ipv6_tc, hlim=ipv6_hlim)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+
+ pkt = pkt/("D" * (pktlen - len(pkt)))
+
+ return pkt
+
+def simple_mpls_packet(eth_dst='00:01:02:03:04:05',
+ eth_src='00:06:07:08:09:0a',
+ dl_vlan_enable=False,
+ vlan_vid=0,
+ vlan_pcp=0,
+ dl_vlan_cfi=0,
+ label=None,
+ inner_payload=None
+ ):
+ """
+ Return a simple dataplane MPLS packet
+
+ Supports a few parameters:
+ @param len Length of packet in bytes w/o CRC
+ @param eth_dst Destination MAC
+ @param eth_src Source MAC
+ @param dl_vlan_enable True if the packet is with vlan, False otherwise
+ @param vlan_vid VLAN ID
+ @param vlan_pcp VLAN priority
+ @param inner_pyload inner pacekt content
+ Generates a simple MPLS packet. Users shouldn't assume anything about
+ this packet other than that it is a valid ethernet/IP/UDP frame.
+ """
+
+ # Note Dot1Q.id is really CFI
+ if (dl_vlan_enable):
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid)
+ else:
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+
+ #add MPLS header
+ for i in range(len(label)):
+ l,c,s,t=label[i]
+ pkt = pkt/scapy.MPLS(label=l, cos=c, s=s, ttl=t)
+
+ #add innder payload
+ if inner_payload!=None:
+ pkt=pkt/inner_payload
+
+ return pkt
+
+def simple_udpv6_packet(pktlen=100,
+ eth_dst='00:01:02:03:04:05',
+ eth_src='00:06:07:08:09:0a',
+ dl_vlan_enable=False,
+ vlan_vid=0,
+ vlan_pcp=0,
+ ipv6_src='2001:db8:85a3::8a2e:370:7334',
+ ipv6_dst='2001:db8:85a3::8a2e:370:7335',
+ ipv6_tc=0,
+ ipv6_hlim=64,
+ ipv6_fl=0,
+ udp_sport=1234,
+ udp_dport=80):
+ """
+ Return a simple IPv6/UDP packet
+
+ Supports a few parameters:
+ @param len Length of packet in bytes w/o CRC
+ @param eth_dst Destination MAC
+ @param eth_src Source MAC
+ @param dl_vlan_enable True if the packet is with vlan, False otherwise
+ @param vlan_vid VLAN ID
+ @param vlan_pcp VLAN priority
+ @param ipv6_src IPv6 source
+ @param ipv6_dst IPv6 destination
+ @param ipv6_tc IPv6 traffic class
+ @param ipv6_ttl IPv6 hop limit
+ @param ipv6_fl IPv6 flow label
+ @param udp_dport UDP destination port
+ @param udp_sport UDP source port
+
+ Generates a simple UDP request. Users shouldn't assume anything about this
+ packet other than that it is a valid ethernet/IPv6/UDP frame.
+ """
+
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+ if dl_vlan_enable or vlan_vid or vlan_pcp:
+ pkt /= scapy.Dot1Q(vlan=vlan_vid, prio=vlan_pcp)
+ pkt /= scapy.IPv6(src=ipv6_src, dst=ipv6_dst, fl=ipv6_fl, tc=ipv6_tc, hlim=ipv6_hlim)
+ pkt /= scapy.UDP(sport=udp_sport, dport=udp_dport)
+ pkt /= ("D" * (pktlen - len(pkt)))
+
+ return pkt
+
+def simple_icmp_packet(pktlen=60,
+ eth_dst='00:01:02:03:04:05',
+ eth_src='00:06:07:08:09:0a',
+ dl_vlan_enable=False,
+ vlan_vid=0,
+ vlan_pcp=0,
+ ip_src='192.168.0.1',
+ ip_dst='192.168.0.2',
+ ip_tos=0,
+ ip_ttl=64,
+ ip_id=1,
+ icmp_type=8,
+ icmp_code=0,
+ icmp_data=''):
+ """
+ Return a simple ICMP packet
+
+ Supports a few parameters:
+ @param len Length of packet in bytes w/o CRC
+ @param eth_dst Destinatino MAC
+ @param eth_src Source MAC
+ @param dl_vlan_enable True if the packet is with vlan, False otherwise
+ @param vlan_vid VLAN ID
+ @param vlan_pcp VLAN priority
+ @param ip_src IP source
+ @param ip_dst IP destination
+ @param ip_tos IP ToS
+ @param ip_ttl IP TTL
+ @param ip_id IP Identification
+ @param icmp_type ICMP type
+ @param icmp_code ICMP code
+ @param icmp_data ICMP data
+
+ Generates a simple ICMP ECHO REQUEST. Users
+ shouldn't assume anything about this packet other than that
+ it is a valid ethernet/ICMP frame.
+ """
+
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ if (dl_vlan_enable):
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.Dot1Q(prio=vlan_pcp, id=0, vlan=vlan_vid)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, ttl=ip_ttl, tos=ip_tos, id=ip_id)/ \
+ scapy.ICMP(type=icmp_type, code=icmp_code)/ icmp_data
+ else:
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, ttl=ip_ttl, tos=ip_tos, id=ip_id)/ \
+ scapy.ICMP(type=icmp_type, code=icmp_code)/ icmp_data
+
+ pkt = pkt/("0" * (pktlen - len(pkt)))
+
+ return pkt
+
+def simple_icmpv6_packet(pktlen=100,
+ eth_dst='00:01:02:03:04:05',
+ eth_src='00:06:07:08:09:0a',
+ dl_vlan_enable=False,
+ vlan_vid=0,
+ vlan_pcp=0,
+ ipv6_src='2001:db8:85a3::8a2e:370:7334',
+ ipv6_dst='2001:db8:85a3::8a2e:370:7335',
+ ipv6_tc=0,
+ ipv6_hlim=64,
+ ipv6_fl=0,
+ icmp_type=8,
+ icmp_code=0):
+ """
+ Return a simple ICMPv6 packet
+
+ Supports a few parameters:
+ @param len Length of packet in bytes w/o CRC
+ @param eth_dst Destination MAC
+ @param eth_src Source MAC
+ @param dl_vlan_enable True if the packet is with vlan, False otherwise
+ @param vlan_vid VLAN ID
+ @param vlan_pcp VLAN priority
+ @param ipv6_src IPv6 source
+ @param ipv6_dst IPv6 destination
+ @param ipv6_tc IPv6 traffic class
+ @param ipv6_ttl IPv6 hop limit
+ @param ipv6_fl IPv6 flow label
+ @param icmp_type ICMP type
+ @param icmp_code ICMP code
+
+ Generates a simple ICMP ECHO REQUEST. Users shouldn't assume anything
+ about this packet other than that it is a valid ethernet/IPv6/ICMP frame.
+ """
+
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+ if dl_vlan_enable or vlan_vid or vlan_pcp:
+ pkt /= scapy.Dot1Q(vlan=vlan_vid, prio=vlan_pcp)
+ pkt /= scapy.IPv6(src=ipv6_src, dst=ipv6_dst, fl=ipv6_fl, tc=ipv6_tc, hlim=ipv6_hlim)
+ pkt /= scapy.ICMPv6Unknown(type=icmp_type, code=icmp_code)
+ pkt /= ("D" * (pktlen - len(pkt)))
+
+ return pkt
+
+def simple_arp_packet(pktlen=60,
+ eth_dst='ff:ff:ff:ff:ff:ff',
+ eth_src='00:06:07:08:09:0a',
+ vlan_vid=0,
+ vlan_pcp=0,
+ arp_op=1,
+ ip_snd='192.168.0.1',
+ ip_tgt='192.168.0.2',
+ hw_snd='00:06:07:08:09:0a',
+ hw_tgt='00:00:00:00:00:00',
+ ):
+ """
+ Return a simple ARP packet
+
+ Supports a few parameters:
+ @param len Length of packet in bytes w/o CRC
+ @param eth_dst Destinatino MAC
+ @param eth_src Source MAC
+ @param arp_op Operation (1=request, 2=reply)
+ @param ip_snd Sender IP
+ @param ip_tgt Target IP
+ @param hw_snd Sender hardware address
+ @param hw_tgt Target hardware address
+
+ Generates a simple ARP REQUEST. Users
+ shouldn't assume anything about this packet other than that
+ it is a valid ethernet/ARP frame.
+ """
+
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+ if vlan_vid or vlan_pcp:
+ pkt /= scapy.Dot1Q(vlan=vlan_vid, prio=vlan_pcp)
+ pkt /= scapy.ARP(hwsrc=hw_snd, hwdst=hw_tgt, pdst=ip_tgt, psrc=ip_snd, op=arp_op)
+
+ pkt = pkt/("\0" * (pktlen - len(pkt)))
+
+ return pkt
+
+def simple_eth_packet(pktlen=60,
+ eth_dst='00:01:02:03:04:05',
+ eth_src='00:06:07:08:09:0a',
+ eth_type=0x88cc):
+
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src, type=eth_type)
+
+ pkt = pkt/("0" * (pktlen - len(pkt)))
+
+ return pkt
+
+def qinq_tcp_packet(pktlen=100,
+ eth_dst='00:01:02:03:04:05',
+ eth_src='00:06:07:08:09:0a',
+ dl_vlan_outer=20,
+ dl_vlan_pcp_outer=0,
+ dl_vlan_cfi_outer=0,
+ vlan_vid=10,
+ vlan_pcp=0,
+ dl_vlan_cfi=0,
+ ip_src='192.168.0.1',
+ ip_dst='192.168.0.2',
+ ip_tos=0,
+ ip_ttl=64,
+ tcp_sport=1234,
+ tcp_dport=80,
+ ip_ihl=None,
+ ip_options=False
+ ):
+ """
+ Return a doubly tagged dataplane TCP packet
+
+ Supports a few parameters:
+ @param len Length of packet in bytes w/o CRC
+ @param eth_dst Destinatino MAC
+ @param eth_src Source MAC
+ @param dl_vlan_outer Outer VLAN ID
+ @param dl_vlan_pcp_outer Outer VLAN priority
+ @param dl_vlan_cfi_outer Outer VLAN cfi bit
+ @param vlan_vid Inner VLAN ID
+ @param vlan_pcp VLAN priority
+ @param dl_vlan_cfi VLAN cfi bit
+ @param ip_src IP source
+ @param ip_dst IP destination
+ @param ip_tos IP ToS
+ @param tcp_dport TCP destination port
+ @param ip_sport TCP source port
+
+ Generates a TCP request. Users
+ shouldn't assume anything about this packet other than that
+ it is a valid ethernet/IP/TCP frame.
+ """
+
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ # Note Dot1Q.id is really CFI
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+ scapy.Dot1Q(prio=dl_vlan_pcp_outer, id=dl_vlan_cfi_outer, vlan=dl_vlan_outer)/ \
+ scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport)
+
+ pkt = pkt/("D" * (pktlen - len(pkt)))
+
+ return pkt
+
+def qinq_packet(pktlen=100,
+ type=0x0800,
+ eth_dst='00:01:02:03:04:05',
+ eth_src='00:06:07:08:09:0a',
+ dl_vlan_outer=20,
+ dl_vlan_pcp_outer=0,
+ dl_vlan_cfi_outer=0,
+ vlan_vid=10,
+ vlan_pcp=0,
+ dl_vlan_cfi=0,
+ ):
+ """
+ Return a doubly tagged dataplane qinq packet
+
+ Supports a few parameters:
+ @param len Length of packet in bytes w/o CRC
+ @param type ethernet type
+ @param eth_dst Destinatino MAC
+ @param eth_src Source MAC
+ @param dl_vlan_outer Outer VLAN ID
+ @param dl_vlan_pcp_outer Outer VLAN priority
+ @param dl_vlan_cfi_outer Outer VLAN cfi bit
+ @param vlan_vid Inner VLAN ID
+ @param vlan_pcp VLAN priority
+ @param dl_vlan_cfi VLAN cfi bit
+
+ Generates a qinq request.
+ """
+
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ # Note Dot1Q.id is really CFI
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src, type=type) / \
+ scapy.Dot1Q(prio=dl_vlan_pcp_outer, id=dl_vlan_cfi_outer, vlan=dl_vlan_outer) / \
+ scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid) / \
+ scapy.IP()
+
+ pkt = pkt / ("D" * (pktlen - len(pkt)))
+ return pkt
+
+def simple_ether_packet_two_vlan(pktlen=100,
+ eth_dst='00:01:02:03:04:05',
+ eth_src='00:06:07:08:09:0a',
+ out_dl_vlan_enable=True,
+ in_dl_vlan_enable=True,
+ out_vlan_vid=100,
+ out_vlan_pcp=0,
+ out_vlan_tpid=0x8100,
+ out_dl_vlan_cfi=0,
+ in_vlan_vid=100,
+ in_vlan_pcp=0,
+ in_vlan_tpid=0x0800,
+ in_dl_vlan_cfi=0):
+ """
+ Return a simple dataplane ether packet
+
+ Supports a few parameters:
+ @param len Length of packet in bytes w/o CRC
+ @param eth_dst Destinatino MAC
+ @param eth_src Source MAC
+ @param dl_vlan_enable True if the packet is with vlan, False otherwise
+ @param vlan_vid VLAN ID
+ @param vlan_pcp VLAN priority
+ """
+
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ if (out_dl_vlan_enable and in_dl_vlan_enable):
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src, type=out_vlan_tpid) / \
+ scapy.Dot1Q(prio=out_vlan_pcp, id=out_dl_vlan_cfi, vlan=out_vlan_vid)
+ pkt = pkt / scapy.Dot1Q(prio=in_vlan_pcp, id=in_dl_vlan_cfi, vlan=in_vlan_vid, type=in_vlan_tpid)
+
+ elif (out_dl_vlan_enable and (not in_dl_vlan_enable)):
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src) / \
+ scapy.Dot1Q(prio=out_vlan_pcp, id=out_dl_vlan_cfi, vlan=out_vlan_vid)
+
+ elif ((not out_dl_vlan_enable) and in_dl_vlan_enable):
+ assert (0) # shall not have this case
+ else:
+ pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+
+ pkt = pkt / ("D" * (pktlen - len(pkt)))
+ return pkt
+
+
+def do_barrier(ctrl, timeout=-1):
+ """
+ Do a barrier command
+ Return 0 on success, -1 on error
+ """
+ b = ofp.message.barrier_request()
+ (resp, pkt) = ctrl.transact(b, timeout=timeout)
+ if resp is None:
+ raise AssertionError("barrier failed")
+ # We'll trust the transaction processing in the controller that xid matched
+ return 0 # for backwards compatibility
+
+def port_config_get(controller, port_no):
+ """
+ Get a port's configuration
+
+ Gets the switch feature configuration and grabs one port's
+ configuration
+
+ @returns (hwaddr, config, advert) The hwaddress, configuration and
+ advertised values
+ """
+
+ if ofp.OFP_VERSION <= 3:
+ request = ofp.message.features_request()
+ reply, _ = controller.transact(request)
+ if reply is None:
+ logging.warn("Get feature request failed")
+ return None, None, None
+ logging.debug(reply.show())
+ ports = reply.ports
+ else:
+ request = ofp.message.port_desc_stats_request()
+ # TODO do multipart correctly
+ reply, _ = controller.transact(request)
+ if reply is None:
+ logging.warn("Port desc stats request failed")
+ return None, None, None
+ logging.debug(reply.show())
+ ports = reply.entries
+
+ for port in ports:
+ if port.port_no == port_no:
+ return (port.hw_addr, port.config, port.advertised)
+
+ logging.warn("Did not find port number for port config")
+ return None, None, None
+
+def port_config_set(controller, port_no, config, mask):
+ """
+ Set the port configuration according the given parameters
+
+ Gets the switch feature configuration and updates one port's
+ configuration value according to config and mask
+ """
+ logging.info("Setting port " + str(port_no) + " to config " + str(config))
+
+ hw_addr, _, _ = port_config_get(controller, port_no)
+
+ mod = ofp.message.port_mod()
+ mod.port_no = port_no
+ if hw_addr != None:
+ mod.hw_addr = hw_addr
+ mod.config = config
+ mod.mask = mask
+ mod.advertise = 0 # No change
+ controller.message_send(mod)
+ return 0
+
+def receive_pkt_check(dp, pkt, yes_ports, no_ports, assert_if):
+ """
+ Check for proper receive packets across all ports
+ @param dp The dataplane object
+ @param pkt Expected packet; may be None if yes_ports is empty
+ @param yes_ports Set or list of ports that should recieve packet
+ @param no_ports Set or list of ports that should not receive packet
+ @param assert_if Object that implements assertXXX
+
+ DEPRECATED in favor in verify_packets
+ """
+
+ exp_pkt_arg = None
+ if oftest.config["relax"]:
+ exp_pkt_arg = pkt
+
+ for ofport in yes_ports:
+ logging.debug("Checking for pkt on port " + str(ofport))
+ (rcv_port, rcv_pkt, pkt_time) = dp.poll(
+ port_number=ofport, exp_pkt=exp_pkt_arg)
+ assert_if.assertTrue(rcv_pkt is not None,
+ "Did not receive pkt on " + str(ofport))
+ if not oftest.dataplane.match_exp_pkt(pkt, rcv_pkt):
+ logging.debug("Expected %s" % format_packet(pkt))
+ logging.debug("Received %s" % format_packet(rcv_pkt))
+ assert_if.assertTrue(oftest.dataplane.match_exp_pkt(pkt, rcv_pkt),
+ "Received packet does not match expected packet " +
+ "on port " + str(ofport))
+ if len(no_ports) > 0:
+ time.sleep(oftest.ofutils.default_negative_timeout)
+ for ofport in no_ports:
+ logging.debug("Negative check for pkt on port " + str(ofport))
+ (rcv_port, rcv_pkt, pkt_time) = dp.poll(
+ port_number=ofport, timeout=0, exp_pkt=exp_pkt_arg)
+ assert_if.assertTrue(rcv_pkt is None,
+ "Unexpected pkt on port " + str(ofport))
+
+
+def receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port):
+ """
+ Receive a packet and verify it matches an expected value
+ @param egr_port A single port or list of ports
+
+ parent must implement dataplane, assertTrue and assertEqual
+
+ DEPRECATED in favor in verify_packets
+ """
+ exp_pkt_arg = None
+ if oftest.config["relax"]:
+ exp_pkt_arg = exp_pkt
+
+ if type(egr_ports) == type([]):
+ egr_port_list = egr_ports
+ else:
+ egr_port_list = [egr_ports]
+
+ # Expect a packet from each port on egr port list
+ for egr_port in egr_port_list:
+ check_port = egr_port
+ if egr_port == ofp.OFPP_IN_PORT:
+ check_port = ing_port
+ (rcv_port, rcv_pkt, pkt_time) = parent.dataplane.poll(
+ port_number=check_port, exp_pkt=exp_pkt_arg)
+
+ if rcv_pkt is None:
+ logging.error("ERROR: No packet received from " +
+ str(check_port))
+
+ parent.assertTrue(rcv_pkt is not None,
+ "Did not receive packet port " + str(check_port))
+ logging.debug("Packet len " + str(len(rcv_pkt)) + " in on " +
+ str(rcv_port))
+
+ if str(exp_pkt) != str(rcv_pkt):
+ logging.error("ERROR: Packet match failed.")
+ logging.debug("Expected len " + str(len(exp_pkt)) + ": "
+ + str(exp_pkt).encode('hex'))
+ logging.debug("Received len " + str(len(rcv_pkt)) + ": "
+ + str(rcv_pkt).encode('hex'))
+ logging.debug("Expected packet: " + inspect_packet(scapy.Ether(str(exp_pkt))))
+ logging.debug("Received packet: " + inspect_packet(scapy.Ether(str(rcv_pkt))))
+ parent.assertEqual(str(exp_pkt), str(rcv_pkt),
+ "Packet match error on port " + str(check_port))
+
+def match_verify(parent, req_match, res_match):
+ """
+ Verify flow matches agree; if they disagree, report where
+
+ parent must implement assertEqual
+ Use str() to ensure content is compared and not pointers
+ """
+
+ parent.assertEqual(req_match.wildcards, res_match.wildcards,
+ 'Match failed: wildcards: ' + hex(req_match.wildcards) +
+ " != " + hex(res_match.wildcards))
+ parent.assertEqual(req_match.in_port, res_match.in_port,
+ 'Match failed: in_port: ' + str(req_match.in_port) +
+ " != " + str(res_match.in_port))
+ parent.assertEqual(str(req_match.eth_src), str(res_match.eth_src),
+ 'Match failed: eth_src: ' + str(req_match.eth_src) +
+ " != " + str(res_match.eth_src))
+ parent.assertEqual(str(req_match.eth_dst), str(res_match.eth_dst),
+ 'Match failed: eth_dst: ' + str(req_match.eth_dst) +
+ " != " + str(res_match.eth_dst))
+ parent.assertEqual(req_match.vlan_vid, res_match.vlan_vid,
+ 'Match failed: vlan_vid: ' + str(req_match.vlan_vid) +
+ " != " + str(res_match.vlan_vid))
+ parent.assertEqual(req_match.vlan_pcp, res_match.vlan_pcp,
+ 'Match failed: vlan_pcp: ' +
+ str(req_match.vlan_pcp) + " != " +
+ str(res_match.vlan_pcp))
+ parent.assertEqual(req_match.eth_type, res_match.eth_type,
+ 'Match failed: eth_type: ' + str(req_match.eth_type) +
+ " != " + str(res_match.eth_type))
+
+ if (not(req_match.wildcards & ofp.OFPFW_DL_TYPE)
+ and (req_match.eth_type == IP_ETHERTYPE)):
+ parent.assertEqual(req_match.ip_dscp, res_match.ip_dscp,
+ 'Match failed: ip_dscp: ' + str(req_match.ip_dscp) +
+ " != " + str(res_match.ip_dscp))
+ parent.assertEqual(req_match.ip_proto, res_match.ip_proto,
+ 'Match failed: ip_proto: ' + str(req_match.ip_proto) +
+ " != " + str(res_match.ip_proto))
+ parent.assertEqual(req_match.ipv4_src, res_match.ipv4_src,
+ 'Match failed: ipv4_src: ' + str(req_match.ipv4_src) +
+ " != " + str(res_match.ipv4_src))
+ parent.assertEqual(req_match.ipv4_dst, res_match.ipv4_dst,
+ 'Match failed: ipv4_dst: ' + str(req_match.ipv4_dst) +
+ " != " + str(res_match.ipv4_dst))
+
+ if (not(req_match.wildcards & ofp.OFPFW_NW_PROTO)
+ and ((req_match.ip_proto == TCP_PROTOCOL)
+ or (req_match.ip_proto == UDP_PROTOCOL))):
+ parent.assertEqual(req_match.tcp_src, res_match.tcp_src,
+ 'Match failed: tcp_src: ' +
+ str(req_match.tcp_src) +
+ " != " + str(res_match.tcp_src))
+ parent.assertEqual(req_match.tcp_dst, res_match.tcp_dst,
+ 'Match failed: tcp_dst: ' +
+ str(req_match.tcp_dst) +
+ " != " + str(res_match.tcp_dst))
+
+def packet_to_flow_match(parent, packet):
+ match = oftest.parse.packet_to_flow_match(packet)
+ if ofp.OFP_VERSION in [1, 2]:
+ match.wildcards |= required_wildcards(parent)
+ else:
+ # TODO remove incompatible OXM entries
+ pass
+ return match
+
+def flow_msg_create(parent, pkt, ing_port=None, action_list=None, wildcards=None,
+ egr_ports=None, egr_queue=None, check_expire=False, in_band=False):
+ """
+ Create a flow message
+
+ Match on packet with given wildcards.
+ See flow_match_test for other parameter descriptoins
+ @param egr_queue if not None, make the output an enqueue action
+ @param in_band if True, do not wildcard ingress port
+ @param egr_ports None (drop), single port or list of ports
+ """
+ match = oftest.parse.packet_to_flow_match(pkt)
+ parent.assertTrue(match is not None, "Flow match from pkt failed")
+ if wildcards is None:
+ wildcards = required_wildcards(parent)
+ if in_band:
+ wildcards &= ~ofp.OFPFW_IN_PORT
+ match.wildcards = wildcards
+ match.in_port = ing_port
+
+ if type(egr_ports) == type([]):
+ egr_port_list = egr_ports
+ else:
+ egr_port_list = [egr_ports]
+
+ request = ofp.message.flow_add()
+ request.match = match
+ request.buffer_id = 0xffffffff
+ if check_expire:
+ request.flags |= ofp.OFPFF_SEND_FLOW_REM
+ request.hard_timeout = 1
+
+ if ofp.OFP_VERSION == 1:
+ actions = request.actions
+ else:
+ actions = []
+ request.instructions.append(ofp.instruction.apply_actions(actions))
+
+ if action_list is not None:
+ actions.extend(action_list)
+
+ # Set up output/enqueue action if directed
+ if egr_queue is not None:
+ parent.assertTrue(egr_ports is not None, "Egress port not set")
+ act = ofp.action.enqueue()
+ for egr_port in egr_port_list:
+ act.port = egr_port
+ act.queue_id = egr_queue
+ actions.append(act)
+ elif egr_ports is not None:
+ for egr_port in egr_port_list:
+ act = ofp.action.output()
+ act.port = egr_port
+ actions.append(act)
+
+ logging.debug(request.show())
+
+ return request
+
+def flow_msg_install(parent, request, clear_table_override=None):
+ """
+ Install a flow mod message in the switch
+
+ @param parent Must implement controller, assertEqual, assertTrue
+ @param request The request, all set to go
+ @param clear_table If true, clear the flow table before installing
+ """
+
+ clear_table = test_param_get('clear_table', default=True)
+ if(clear_table_override != None):
+ clear_table = clear_table_override
+
+ if clear_table:
+ logging.debug("Clear flow table")
+ delete_all_flows(parent.controller)
+
+ logging.debug("Insert flow")
+ parent.controller.message_send(request)
+
+ do_barrier(parent.controller)
+
+def flow_match_test_port_pair(parent, ing_port, egr_ports, wildcards=None,
+ vlan_vid=-1, pkt=None, exp_pkt=None,
+ action_list=None):
+ """
+ Flow match test on single TCP packet
+ @param egr_ports A single port or list of ports
+
+ Run test with packet through switch from ing_port to egr_port
+ See flow_match_test for parameter descriptions
+ """
+
+ if wildcards is None:
+ wildcards = required_wildcards(parent)
+ logging.info("Pkt match test: " + str(ing_port) + " to " +
+ str(egr_ports))
+ logging.debug(" WC: " + hex(wildcards) + " vlan: " + str(vlan_vid))
+ if pkt is None:
+ pkt = simple_tcp_packet(dl_vlan_enable=(vlan_vid >= 0), vlan_vid=vlan_vid)
+ if exp_pkt is None:
+ exp_pkt = pkt
+
+ request = flow_msg_create(parent, pkt, ing_port=ing_port,
+ wildcards=wildcards, egr_ports=egr_ports,
+ action_list=action_list)
+
+ flow_msg_install(parent, request)
+
+ logging.debug("Send packet: " + str(ing_port) + " to " +
+ str(egr_ports))
+ parent.dataplane.send(ing_port, str(pkt))
+
+ exp_ports = [ing_port if port == ofp.OFPP_IN_PORT else port for port in egr_ports]
+ verify_packets(parent, exp_pkt, exp_ports)
+
+def flow_match_test_pktout(parent, ing_port, egr_ports,
+ vlan_vid=-1, pkt=None, exp_pkt=None,
+ action_list=None):
+ """
+ Packet-out test on single TCP packet
+ @param egr_ports A single port or list of ports
+
+ Run test sending packet-out to egr_ports. The goal is to test the actions
+ taken on the packet, not the matching which is of course irrelevant.
+ See flow_match_test for parameter descriptions
+ """
+
+ if pkt is None:
+ pkt = simple_tcp_packet(dl_vlan_enable=(vlan_vid >= 0), vlan_vid=vlan_vid)
+ if exp_pkt is None:
+ exp_pkt = pkt
+
+ msg = ofp.message.packet_out()
+ msg.in_port = ing_port
+ msg.buffer_id = 0xffffffff
+ msg.data = str(pkt)
+ if action_list is not None:
+ for act in action_list:
+ msg.actions.append(act)
+
+ # Set up output action
+ if egr_ports is not None:
+ for egr_port in egr_ports:
+ act = ofp.action.output()
+ act.port = egr_port
+ msg.actions.append(act)
+
+ logging.debug(msg.show())
+ parent.controller.message_send(msg)
+
+ exp_ports = [ing_port if port == ofp.OFPP_IN_PORT else port for port in egr_ports]
+ verify_packets(parent, exp_pkt, exp_ports)
+
+def get_egr_list(parent, of_ports, how_many, exclude_list=[]):
+ """
+ Generate a list of ports avoiding those in the exclude list
+ @param parent Supplies logging
+ @param of_ports List of OF port numbers
+ @param how_many Number of ports to be added to the list
+ @param exclude_list List of ports not to be used
+ @returns An empty list if unable to find enough ports
+ """
+
+ if how_many == 0:
+ return []
+
+ count = 0
+ egr_ports = []
+ for egr_idx in range(len(of_ports)):
+ if of_ports[egr_idx] not in exclude_list:
+ egr_ports.append(of_ports[egr_idx])
+ count += 1
+ if count >= how_many:
+ return egr_ports
+ logging.debug("Could not generate enough egress ports for test")
+ return []
+
+def flow_match_test(parent, port_map, wildcards=None, vlan_vid=-1, pkt=None,
+ exp_pkt=None, action_list=None,
+ max_test=0, egr_count=1, ing_port=False):
+ """
+ Run flow_match_test_port_pair on all port pairs and packet-out
+
+ @param max_test If > 0 no more than this number of tests are executed.
+ @param parent Must implement controller, dataplane, assertTrue, assertEqual
+ and logging
+ @param pkt If not None, use this packet for ingress
+ @param wildcards For flow match entry
+ @param vlan_vid If not -1, and pkt is None, create a pkt w/ VLAN tag
+ @param exp_pkt If not None, use this as the expected output pkt; els use pkt
+ @param action_list Additional actions to add to flow mod
+ @param egr_count Number of egress ports; -1 means get from config w/ dflt 2
+ """
+ if wildcards is None:
+ wildcards = required_wildcards(parent)
+ of_ports = port_map.keys()
+ of_ports.sort()
+ parent.assertTrue(len(of_ports) > 1, "Not enough ports for test")
+ test_count = 0
+
+ if egr_count == -1:
+ egr_count = test_param_get('egr_count', default=2)
+
+ for ing_idx in range(len(of_ports)):
+ ingress_port = of_ports[ing_idx]
+ egr_ports = get_egr_list(parent, of_ports, egr_count,
+ exclude_list=[ingress_port])
+ if ing_port:
+ egr_ports.append(ofp.OFPP_IN_PORT)
+ if len(egr_ports) == 0:
+ parent.assertTrue(0, "Failed to generate egress port list")
+
+ flow_match_test_port_pair(parent, ingress_port, egr_ports,
+ wildcards=wildcards, vlan_vid=vlan_vid,
+ pkt=pkt, exp_pkt=exp_pkt,
+ action_list=action_list)
+ test_count += 1
+ if (max_test > 0) and (test_count > max_test):
+ logging.info("Ran " + str(test_count) + " tests; exiting")
+ break
+
+ if not test_param_get('pktout_actions', default=True):
+ return
+
+ ingress_port = of_ports[0]
+ egr_ports = get_egr_list(parent, of_ports, egr_count,
+ exclude_list=[ingress_port])
+ if ing_port:
+ egr_ports.append(ofp.OFPP_IN_PORT)
+ flow_match_test_pktout(parent, ingress_port, egr_ports,
+ vlan_vid=vlan_vid,
+ pkt=pkt, exp_pkt=exp_pkt,
+ action_list=action_list)
+
+def test_param_get(key, default=None):
+ """
+ Return value passed via test-params if present
+
+ @param key The lookup key
+ @param default Default value to use if not found
+
+ If the pair 'key=val' appeared in the string passed to --test-params
+ on the command line, return val (as interpreted by exec). Otherwise
+ return default value.
+
+ WARNING: TEST PARAMETERS MUST BE PYTHON IDENTIFIERS;
+ eg egr_count, not egr-count.
+ """
+ try:
+ exec oftest.config["test_params"]
+ except:
+ return default
+
+ try:
+ return eval(str(key))
+ except:
+ return default
+
+def action_generate(parent, field_to_mod, mod_field_vals):
+ """
+ Create an action to modify the field indicated in field_to_mod
+
+ @param parent Must implement, assertTrue
+ @param field_to_mod The field to modify as a string name
+ @param mod_field_vals Hash of values to use for modified values
+ """
+
+ act = None
+
+ if field_to_mod in ['pktlen']:
+ return None
+
+ if field_to_mod == 'eth_dst':
+ act = ofp.action.set_dl_dst()
+ act.dl_addr = oftest.parse.parse_mac(mod_field_vals['eth_dst'])
+ elif field_to_mod == 'eth_src':
+ act = ofp.action.set_dl_src()
+ act.dl_addr = oftest.parse.parse_mac(mod_field_vals['eth_src'])
+ elif field_to_mod == 'dl_vlan_enable':
+ if not mod_field_vals['dl_vlan_enable']: # Strip VLAN tag
+ act = ofp.action.strip_vlan()
+ # Add VLAN tag is handled by vlan_vid field
+ # Will return None in this case
+ elif field_to_mod == 'vlan_vid':
+ act = ofp.action.set_vlan_vid()
+ act.vlan_vid = mod_field_vals['vlan_vid']
+ elif field_to_mod == 'vlan_pcp':
+ act = ofp.action.set_vlan_pcp()
+ act.vlan_pcp = mod_field_vals['vlan_pcp']
+ elif field_to_mod == 'ip_src':
+ act = ofp.action.set_nw_src()
+ act.nw_addr = oftest.parse.parse_ip(mod_field_vals['ip_src'])
+ elif field_to_mod == 'ip_dst':
+ act = ofp.action.set_nw_dst()
+ act.nw_addr = oftest.parse.parse_ip(mod_field_vals['ip_dst'])
+ elif field_to_mod == 'ip_tos':
+ act = ofp.action.set_nw_tos()
+ act.nw_tos = mod_field_vals['ip_tos']
+ elif field_to_mod == 'tcp_sport':
+ act = ofp.action.set_tp_src()
+ act.tp_port = mod_field_vals['tcp_sport']
+ elif field_to_mod == 'tcp_dport':
+ act = ofp.action.set_tp_dst()
+ act.tp_port = mod_field_vals['tcp_dport']
+ elif field_to_mod == 'udp_sport':
+ act = ofp.action.set_tp_src()
+ act.tp_port = mod_field_vals['udp_sport']
+ elif field_to_mod == 'udp_dport':
+ act = ofp.action.set_tp_dst()
+ act.tp_port = mod_field_vals['udp_dport']
+ else:
+ parent.assertTrue(0, "Unknown field to modify: " + str(field_to_mod))
+
+ return act
+
+def pkt_action_setup(parent, start_field_vals={}, mod_field_vals={},
+ mod_fields=[], tp="tcp", check_test_params=False):
+ """
+ Set up the ingress and expected packet and action list for a test
+
+ @param parent Must implement assertTrue
+ @param start_field_values Field values to use for ingress packet (optional)
+ @param mod_field_values Field values to use for modified packet (optional)
+ @param mod_fields The list of fields to be modified by the switch in the test.
+ @params check_test_params If True, will check the parameters vid, add_vlan
+ and strip_vlan from the command line.
+
+ Returns a triple: pkt-to-send, expected-pkt, action-list
+ """
+
+ new_actions = []
+
+ base_pkt_params = {}
+ base_pkt_params['pktlen'] = 100
+ base_pkt_params['eth_dst'] = '00:DE:F0:12:34:56'
+ base_pkt_params['eth_src'] = '00:23:45:67:89:AB'
+ base_pkt_params['dl_vlan_enable'] = False
+ base_pkt_params['vlan_vid'] = 2
+ base_pkt_params['vlan_pcp'] = 0
+ base_pkt_params['ip_src'] = '192.168.0.1'
+ base_pkt_params['ip_dst'] = '192.168.0.2'
+ base_pkt_params['ip_tos'] = 0
+ if tp == "tcp":
+ base_pkt_params['tcp_sport'] = 1234
+ base_pkt_params['tcp_dport'] = 80
+ elif tp == "udp":
+ base_pkt_params['udp_sport'] = 1234
+ base_pkt_params['udp_dport'] = 80
+ for keyname in start_field_vals.keys():
+ base_pkt_params[keyname] = start_field_vals[keyname]
+
+ mod_pkt_params = {}
+ mod_pkt_params['pktlen'] = 100
+ mod_pkt_params['eth_dst'] = '00:21:0F:ED:CB:A9'
+ mod_pkt_params['eth_src'] = '00:ED:CB:A9:87:65'
+ mod_pkt_params['dl_vlan_enable'] = False
+ mod_pkt_params['vlan_vid'] = 3
+ mod_pkt_params['vlan_pcp'] = 7
+ mod_pkt_params['ip_src'] = '10.20.30.40'
+ mod_pkt_params['ip_dst'] = '50.60.70.80'
+ mod_pkt_params['ip_tos'] = 0xf0
+ if tp == "tcp":
+ mod_pkt_params['tcp_sport'] = 4321
+ mod_pkt_params['tcp_dport'] = 8765
+ elif tp == "udp":
+ mod_pkt_params['udp_sport'] = 4321
+ mod_pkt_params['udp_dport'] = 8765
+ for keyname in mod_field_vals.keys():
+ mod_pkt_params[keyname] = mod_field_vals[keyname]
+
+ # Check for test param modifications
+ strip = False
+ if check_test_params:
+ add_vlan = test_param_get('add_vlan')
+ strip_vlan = test_param_get('strip_vlan')
+ vid = test_param_get('vid')
+
+ if add_vlan and strip_vlan:
+ parent.assertTrue(0, "Add and strip VLAN both specified")
+
+ if vid:
+ base_pkt_params['dl_vlan_enable'] = True
+ base_pkt_params['vlan_vid'] = vid
+ if 'vlan_vid' in mod_fields:
+ mod_pkt_params['vlan_vid'] = vid + 1
+
+ if add_vlan:
+ base_pkt_params['dl_vlan_enable'] = False
+ mod_pkt_params['dl_vlan_enable'] = True
+ mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] + 4
+ mod_fields.append('pktlen')
+ mod_fields.append('dl_vlan_enable')
+ if 'vlan_vid' not in mod_fields:
+ mod_fields.append('vlan_vid')
+ elif strip_vlan:
+ base_pkt_params['dl_vlan_enable'] = True
+ mod_pkt_params['dl_vlan_enable'] = False
+ mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] - 4
+ mod_fields.append('dl_vlan_enable')
+ mod_fields.append('pktlen')
+
+ if tp == "tcp":
+ packet_builder = simple_tcp_packet
+ elif tp == "udp":
+ packet_builder = simple_udp_packet
+ else:
+ raise NotImplementedError("unknown transport protocol %s" % tp)
+
+ # Build the ingress packet
+ ingress_pkt = packet_builder(**base_pkt_params)
+
+ # Build the expected packet, modifying the indicated fields
+ for item in mod_fields:
+ base_pkt_params[item] = mod_pkt_params[item]
+ act = action_generate(parent, item, mod_pkt_params)
+ if act:
+ new_actions.append(act)
+
+ expected_pkt = packet_builder(**base_pkt_params)
+
+ return (ingress_pkt, expected_pkt, new_actions)
+
+# Generate a simple "drop" flow mod
+# If in_band is true, then only drop from first test port
+def flow_mod_gen(port_map, in_band):
+ request = ofp.message.flow_add()
+ request.match.wildcards = ofp.OFPFW_ALL
+ if in_band:
+ request.match.wildcards = ofp.OFPFW_ALL - ofp.OFPFW_IN_PORT
+ for of_port, ifname in port_map.items(): # Grab first port
+ break
+ request.match.in_port = of_port
+ request.buffer_id = 0xffffffff
+ return request
+
+def skip_message_emit(parent, s):
+ """
+ Print out a 'skipped' message to stderr
+
+ @param s The string to print out to the log file
+ """
+ global skipped_test_count
+
+ skipped_test_count += 1
+ logging.info("Skipping: " + s)
+ if oftest.config["debug"] < logging.WARNING:
+ sys.stderr.write("(skipped) ")
+ else:
+ sys.stderr.write("(S)")
+
+
+def all_stats_get(parent):
+ """
+ Get the aggregate stats for all flows in the table
+ @param parent Test instance with controller connection and assert
+ @returns dict with keys flows, packets, bytes, active (flows),
+ lookups, matched
+ """
+ stat_req = ofp.message.aggregate_stats_request()
+ stat_req.match = ofp.match()
+ stat_req.match.wildcards = ofp.OFPFW_ALL
+ stat_req.table_id = 0xff
+ stat_req.out_port = ofp.OFPP_NONE
+
+ rv = {}
+
+ (reply, pkt) = parent.controller.transact(stat_req)
+ parent.assertTrue(len(reply.entries) == 1, "Did not receive flow stats reply")
+
+ for obj in reply.entries:
+ (rv["flows"], rv["packets"], rv["bytes"]) = (obj.flow_count,
+ obj.packet_count, obj.byte_count)
+ break
+
+ request = ofp.message.table_stats_request()
+ (reply , pkt) = parent.controller.transact(request)
+
+
+ (rv["active"], rv["lookups"], rv["matched"]) = (0,0,0)
+ for obj in reply.entries:
+ rv["active"] += obj.active_count
+ rv["lookups"] += obj.lookup_count
+ rv["matched"] += obj.matched_count
+
+ return rv
+
+_import_blacklist.add('FILTER')
+FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.'
+ for x in range(256)])
+
+def hex_dump_buffer(src, length=16):
+ """
+ Convert src to a hex dump string and return the string
+ @param src The source buffer
+ @param length The number of bytes shown in each line
+ @returns A string showing the hex dump
+ """
+ result = ["\n"]
+ for i in xrange(0, len(src), length):
+ chars = src[i:i+length]
+ hex = ' '.join(["%02x" % ord(x) for x in chars])
+ printable = ''.join(["%s" % ((ord(x) <= 127 and
+ FILTER[ord(x)]) or '.') for x in chars])
+ result.append("%04x %-*s %s\n" % (i, length*3, hex, printable))
+ return ''.join(result)
+
+def format_packet(pkt):
+ return "Packet length %d \n%s" % (len(str(pkt)),
+ hex_dump_buffer(str(pkt)))
+
+def inspect_packet(pkt):
+ """
+ Wrapper around scapy's show() method.
+ @returns A string showing the dissected packet.
+ """
+ from cStringIO import StringIO
+ out = None
+ backup = sys.stdout
+ try:
+ tmp = StringIO()
+ sys.stdout = tmp
+ pkt.show2()
+ out = tmp.getvalue()
+ tmp.close()
+ finally:
+ sys.stdout = backup
+ return out
+
+def nonstandard(cls):
+ """
+ Testcase decorator that marks the test as being non-standard.
+ These tests are not automatically added to the "standard" group.
+ """
+ cls._nonstandard = True
+ return cls
+
+def disabled(cls):
+ """
+ Testcase decorator that marks the test as being disabled.
+ These tests are not automatically added to the "standard" group or
+ their module's group.
+ """
+ cls._disabled = True
+ return cls
+
+def group(name):
+ """
+ Testcase decorator that adds the test to a group.
+ """
+ def fn(cls):
+ if not hasattr(cls, "_groups"):
+ cls._groups = []
+ cls._groups.append(name)
+ return cls
+ return fn
+
+def version(ver):
+ """
+ Testcase decorator that specifies which versions of OpenFlow the test
+ supports. The default is 1.0+. This decorator may only be used once.
+
+ Supported syntax:
+ 1.0 -> 1.0
+ 1.0,1.2,1.3 -> 1.0, 1.2, 1.3
+ 1.0+ -> 1.0, 1.1, 1.2, 1.3
+ """
+ versions = parse_version(ver)
+ def fn(cls):
+ cls._versions = versions
+ return cls
+ return fn
+
+def parse_version(ver):
+ allowed_versions = ["1.0", "1.1", "1.2", "1.3"]
+ if re.match("^1\.\d+$", ver):
+ versions = set([ver])
+ elif re.match("^(1\.\d+)\+$", ver):
+ if not ver[:-1] in allowed_versions:
+ raise ValueError("invalid OpenFlow version %s" % ver[:-1])
+ versions = set()
+ if ver != "1.1+": versions.add("1.0")
+ if ver != "1.2+": versions.add("1.1")
+ if ver != "1.3+": versions.add("1.2")
+ versions.add("1.3")
+ else:
+ versions = set(ver.split(','))
+
+ for version in versions:
+ if not version in allowed_versions:
+ raise ValueError("invalid OpenFlow version %s" % version)
+
+ return versions
+
+assert(parse_version("1.0") == set(["1.0"]))
+assert(parse_version("1.0,1.2,1.3") == set(["1.0", "1.2", "1.3"]))
+assert(parse_version("1.0+") == set(["1.0", "1.1", "1.2", "1.3"]))
+
+def get_stats(test, req):
+ """
+ Retrieve a list of stats entries. Handles OFPSF_REPLY_MORE.
+ """
+ msgtype = ofp.OFPT_STATS_REPLY
+ more_flag = ofp.OFPSF_REPLY_MORE
+ stats = []
+ reply, _ = test.controller.transact(req)
+ test.assertTrue(reply is not None, "No response to stats request")
+ test.assertEquals(reply.type, msgtype, "Response had unexpected message type")
+ stats.extend(reply.entries)
+ while reply.flags & more_flag != 0:
+ reply, pkt = test.controller.poll(exp_msg=msgtype)
+ test.assertTrue(reply is not None, "No response to stats request")
+ stats.extend(reply.entries)
+ return stats
+
+def get_flow_stats(test, match, table_id=None,
+ out_port=None, out_group=None,
+ cookie=0, cookie_mask=0):
+ """
+ Retrieve a list of flow stats entries.
+ """
+
+ if table_id == None:
+ if ofp.OFP_VERSION <= 2:
+ table_id = 0xff
+ else:
+ table_id = ofp.OFPTT_ALL
+
+ if out_port == None:
+ if ofp.OFP_VERSION == 1:
+ out_port = ofp.OFPP_NONE
+ else:
+ out_port = ofp.OFPP_ANY
+
+ if out_group == None:
+ if ofp.OFP_VERSION > 1:
+ out_group = ofp.OFPP_ANY
+
+ req = ofp.message.flow_stats_request(match=match,
+ table_id=table_id,
+ out_port=out_port)
+ if ofp.OFP_VERSION > 1:
+ req.out_group = out_group
+ req.cookie = cookie
+ req.cookie_mask = cookie_mask
+
+ return get_stats(test, req)
+
+def get_port_stats(test, port_no):
+ """
+ Retrieve a list of port stats entries.
+ """
+ req = ofp.message.port_stats_request(port_no=port_no)
+ return get_stats(test, req)
+
+def get_queue_stats(test, port_no, queue_id):
+ """
+ Retrieve a list of queue stats entries.
+ """
+ req = ofp.message.queue_stats_request(port_no=port_no, queue_id=queue_id)
+ return get_stats(test, req)
+
+def verify_flow_stats(test, match, table_id=0xff,
+ initial=[],
+ pkts=None, bytes=None):
+ """
+ Verify that flow stats changed as expected.
+
+ Optionally takes an 'initial' list of stats entries, as returned by
+ get_flow_stats(). If 'initial' is not given the counters are assumed to
+ begin at 0.
+ """
+
+ def accumulate(stats):
+ pkts_acc = bytes_acc = 0
+ for stat in stats:
+ pkts_acc += stat.packet_count
+ bytes_acc += stat.byte_count
+ return (pkts_acc, bytes_acc)
+
+ pkts_before, bytes_before = accumulate(initial)
+
+ # Wait 10s for counters to update
+ pkt_diff = byte_diff = None
+ for i in range(0, 100):
+ stats = get_flow_stats(test, match, table_id=table_id)
+ pkts_after, bytes_after = accumulate(stats)
+ pkt_diff = pkts_after - pkts_before
+ byte_diff = bytes_after - bytes_before
+ if (pkts == None or pkt_diff >= pkts) and \
+ (bytes == None or byte_diff >= bytes):
+ break
+ time.sleep(0.1)
+
+ if pkts != None:
+ test.assertEquals(pkt_diff, pkts, "Flow packet counter not updated properly (expected increase of %d, got increase of %d)" % (pkts, pkt_diff))
+
+ if bytes != None:
+ test.assertTrue(byte_diff >= bytes and byte_diff <= bytes*1.1,
+ "Flow byte counter not updated properly (expected increase of %d, got increase of %d)" % (bytes, byte_diff))
+
+def verify_port_stats(test, port,
+ initial=[],
+ tx_pkts=None, rx_pkts=None,
+ tx_bytes=None, rx_bytes=None):
+ """
+ Verify that port stats changed as expected.
+
+ Optionally takes an 'initial' list of stats entries, as returned by
+ get_port_stats(). If 'initial' is not given the counters are assumed to
+ begin at 0.
+ """
+ def accumulate(stats):
+ tx_pkts_acc = rx_pkts_acc = tx_bytes_acc = rx_bytes_acc = 0
+ for stat in stats:
+ tx_pkts_acc += stat.tx_packets
+ rx_pkts_acc += stat.rx_packets
+ tx_bytes_acc += stat.tx_bytes
+ rx_bytes_acc += stat.rx_bytes
+ return (tx_pkts_acc, rx_pkts_acc, tx_bytes_acc, rx_bytes_acc)
+
+ tx_pkts_before, rx_pkts_before, \
+ tx_bytes_before, rx_bytes_before = accumulate(initial)
+
+ # Wait 10s for counters to update
+ for i in range(0, 100):
+ stats = get_port_stats(test, port)
+ tx_pkts_after, rx_pkts_after, \
+ tx_bytes_after, rx_bytes_after = accumulate(stats)
+ tx_pkts_diff = tx_pkts_after - tx_pkts_before
+ rx_pkts_diff = rx_pkts_after - rx_pkts_before
+ tx_bytes_diff = tx_bytes_after - tx_bytes_before
+ rx_bytes_diff = rx_bytes_after - rx_bytes_before
+ if (tx_pkts == None or tx_pkts <= tx_pkts_diff) and \
+ (rx_pkts == None or rx_pkts <= rx_pkts_diff) and \
+ (tx_bytes == None or tx_bytes <= tx_bytes_diff) and \
+ (rx_bytes == None or rx_bytes <= rx_bytes_diff):
+ break
+ time.sleep(0.1)
+
+ if (tx_pkts != None):
+ test.assertGreaterEqual(tx_pkts_diff, tx_pkts,
+ "Port TX packet counter is not updated correctly (expected increase of %d, got increase of %d)" % (tx_pkts, tx_pkts_diff))
+ if (rx_pkts != None):
+ test.assertGreaterEqual(rx_pkts_diff, rx_pkts,
+ "Port RX packet counter is not updated correctly (expected increase of %d, got increase of %d)" % (rx_pkts, rx_pkts_diff))
+ if (tx_bytes != None):
+ test.assertGreaterEqual(tx_bytes_diff, tx_bytes,
+ "Port TX byte counter is not updated correctly (expected increase of %d, got increase of %d)" % (tx_bytes, tx_bytes_diff))
+ if (rx_bytes != None):
+ test.assertGreaterEqual(rx_bytes_diff, rx_bytes,
+ "Port RX byte counter is not updated correctly (expected increase of %d, got increase of %d)" % (rx_bytes, rx_bytes_diff))
+
+def verify_queue_stats(test, port_no, queue_id,
+ initial=[],
+ pkts=None, bytes=None):
+ """
+ Verify that queue stats changed as expected.
+
+ Optionally takes an 'initial' list of stats entries, as returned by
+ get_queue_stats(). If 'initial' is not given the counters are assumed to
+ begin at 0.
+ """
+ def accumulate(stats):
+ pkts_acc = bytes_acc = 0
+ for stat in stats:
+ pkts_acc += stat.tx_packets
+ bytes_acc += stat.tx_bytes
+ return (pkts_acc, bytes_acc)
+
+ pkts_before, bytes_before = accumulate(initial)
+
+ # Wait 10s for counters to update
+ pkt_diff = byte_diff = None
+ for i in range(0, 100):
+ stats = get_queue_stats(test, port_no, queue_id)
+ pkts_after, bytes_after = accumulate(stats)
+ pkt_diff = pkts_after - pkts_before
+ byte_diff = bytes_after - bytes_before
+ if (pkts == None or pkt_diff >= pkts) and \
+ (bytes == None or byte_diff >= bytes):
+ break
+ time.sleep(0.1)
+
+ if pkts != None:
+ test.assertEquals(pkt_diff, pkts, "Queue packet counter not updated properly (expected increase of %d, got increase of %d)" % (pkts, pkt_diff))
+
+ if bytes != None:
+ test.assertTrue(byte_diff >= bytes and byte_diff <= bytes*1.1,
+ "Queue byte counter not updated properly (expected increase of %d, got increase of %d)" % (bytes, byte_diff))
+
+def packet_in_match(msg, data, in_port=None, reason=None):
+ """
+ Check whether the packet_in message 'msg' has fields matching 'data',
+ 'in_port', and 'reason'.
+
+ This function handles truncated packet_in data. The 'in_port' and 'reason'
+ parameters are optional.
+
+ @param msg packet_in message
+ @param data Expected packet_in data
+ @param in_port Expected packet_in in_port, or None
+ @param reason Expected packet_in reason, or None
+ """
+
+ if ofp.OFP_VERSION <= 2:
+ pkt_in_port = msg.in_port
+ else:
+ oxms = { type(oxm): oxm for oxm in msg.match.oxm_list }
+ if ofp.oxm.in_port in oxms:
+ pkt_in_port = oxms[ofp.oxm.in_port].value
+ else:
+ logging.warn("Missing in_port in packet-in message")
+ pkt_in_port = None
+
+ if in_port != None and in_port != pkt_in_port:
+ logging.debug("Incorrect packet_in in_port (expected %d, received %d)", in_port, pkt_in_port)
+ return False
+
+ if reason != None and reason != msg.reason:
+ logging.debug("Incorrect packet_in reason (expected %d, received %d)", reason, msg.reason)
+ return False
+
+ # Check that one of the packets is a prefix of the other.
+ # The received packet may be either truncated or padded, but not both.
+ # (Some of the padding may be truncated but that's irrelevant). We
+ # need to check that the smaller packet is a prefix of the larger one.
+ # Note that this check succeeds if the switch sends a zero-length
+ # packet-in.
+ compare_len = min(len(msg.data), len(data))
+ if data[:compare_len] != msg.data[:compare_len]:
+ logging.debug("Incorrect packet_in data")
+ logging.debug("Expected %s" % format_packet(data[:compare_len]))
+ logging.debug("Received %s" % format_packet(msg.data[:compare_len]))
+ return False
+
+ return True
+
+def verify_packet_in(test, data, in_port, reason, controller=None):
+ """
+ Assert that the controller receives a packet_in message matching data 'data'
+ from port 'in_port' with reason 'reason'. Does not trigger the packet_in
+ itself, that's up to the test case.
+
+ @param test Instance of base_tests.SimpleProtocol
+ @param pkt String to expect as the packet_in data
+ @param in_port OpenFlow port number to expect as the packet_in in_port
+ @param reason One of OFPR_* to expect as the packet_in reason
+ @param controller Controller instance, defaults to test.controller
+ @returns The received packet-in message
+ """
+
+ if controller == None:
+ controller = test.controller
+
+ end_time = time.time() + oftest.ofutils.default_timeout
+
+ while True:
+ msg, _ = controller.poll(ofp.OFPT_PACKET_IN, end_time - time.time())
+ if not msg:
+ # Timeout
+ break
+ elif packet_in_match(msg, data, in_port, reason):
+ # Found a matching message
+ break
+
+ test.assertTrue(msg is not None, 'Packet in message not received on port %r' % in_port)
+ return msg
+
+def verify_no_packet_in(test, data, in_port, controller=None):
+ """
+ Assert that the controller does not receive a packet_in message matching
+ data 'data' from port 'in_port'.
+
+ @param test Instance of base_tests.SimpleProtocol
+ @param pkt String to expect as the packet_in data
+ @param in_port OpenFlow port number to expect as the packet_in in_port
+ @param controller Controller instance, defaults to test.controller
+ """
+
+ if controller == None:
+ controller = test.controller
+
+ # Negative test, need to wait a short amount of time before checking we
+ # didn't receive the message.
+ time.sleep(oftest.ofutils.default_negative_timeout)
+
+ # Check every packet_in queued in the controller
+ while True:
+ msg, _ = controller.poll(ofp.OFPT_PACKET_IN, timeout=0)
+ if msg == None:
+ # No more queued packet_in messages
+ break
+ elif packet_in_match(msg, data, in_port, None):
+ # Found a matching message
+ break
+
+ if in_port == None:
+ test.assertTrue(msg == None, "Did not expect a packet-in message on any port")
+ else:
+ test.assertTrue(msg == None, "Did not expect a packet-in message on port %d" % in_port)
+
+def openflow_ports(num=None):
+ """
+ Return a list of 'num' OpenFlow port numbers
+
+ If 'num' is None, return all available ports. Otherwise, limit the length
+ of the result to 'num' and raise an exception if not enough ports are
+ available.
+ """
+ ports = sorted(oftest.config["port_map"].keys())
+ if num != None and len(ports) < num:
+ raise Exception("test requires %d ports but only %d are available" % (num, len(ports)))
+ return ports[:num]
+
+def verify_packet(test, pkt, ofport):
+ """
+ Check that an expected packet is received
+ """
+ logging.debug("Checking for pkt on port %r", ofport)
+ (rcv_port, rcv_pkt, pkt_time) = test.dataplane.poll(port_number=ofport, exp_pkt=str(pkt))
+ test.assertTrue(rcv_pkt != None, "Did not receive pkt on %r" % ofport)
+ return (rcv_port, rcv_pkt, pkt_time)
+
+def verify_no_packet(test, pkt, ofport):
+ """
+ Check that a particular packet is not received
+ """
+ logging.debug("Negative check for pkt on port %r", ofport)
+ (rcv_port, rcv_pkt, pkt_time) = \
+ test.dataplane.poll(
+ port_number=ofport, exp_pkt=str(pkt),
+ timeout=oftest.ofutils.default_negative_timeout)
+ test.assertTrue(rcv_pkt == None, "Received packet on %r" % ofport)
+
+def verify_no_other_packets(test):
+ """
+ Check that no unexpected packets are received
+
+ This is a no-op if the --relax option is in effect.
+ """
+ if oftest.config["relax"]:
+ return
+ logging.debug("Checking for unexpected packets on all ports")
+ (rcv_port, rcv_pkt, pkt_time) = test.dataplane.poll(timeout=oftest.ofutils.default_negative_timeout)
+ if rcv_pkt != None:
+ logging.debug("Received unexpected packet on port %r: %s", rcv_port, format_packet(rcv_pkt))
+ test.assertTrue(rcv_pkt == None, "Unexpected packet on port %r" % rcv_port)
+
+def verify_packets(test, pkt, ofports):
+ """
+ Check that a packet is received on certain ports
+
+ Also verifies that the packet is not received on any other ports, and that no
+ other packets are received (unless --relax is in effect).
+
+ This covers the common and simplest cases for checking dataplane outputs.
+ For more complex usage, like multiple different packets being output, or
+ multiple packets on the same port, use the primitive verify_packet,
+ verify_no_packet, and verify_no_other_packets functions directly.
+ """
+ pkt = str(pkt)
+ for ofport in openflow_ports():
+ if ofport in ofports:
+ verify_packet(test, pkt, ofport)
+ else:
+ verify_no_packet(test, pkt, ofport)
+ verify_no_other_packets(test)
+
+def verify_no_errors(ctrl):
+ error, _ = ctrl.poll(ofp.OFPT_ERROR, 0)
+ if error:
+ raise AssertionError("unexpected error type=%d code=%d" % (error.err_type, error.code))
+
+def verify_capability(test, capability):
+ """
+ Return True if DUT supports the specified capability.
+
+ @param test Instance of base_tests.SimpleProtocol
+ @param capability One of ofp_capabilities.
+ """
+ logging.info("Verifing that capability code is valid.")
+ test.assertIn(capability, ofp.const.ofp_capabilities_map,
+ "Capability code %d does not exist." % capability)
+ capability_str = ofp.const.ofp_capabilities_map[capability]
+
+ logging.info(("Sending features_request to test if capability "
+ "%s is supported."), capability_str)
+ req = ofp.message.features_request()
+ res, raw = test.controller.transact(req)
+ test.assertIsNotNone(res, "Did not receive a response from the DUT.")
+ test.assertEqual(res.type, ofp.OFPT_FEATURES_REPLY,
+ ("Unexpected packet type %d received in response to "
+ "OFPT_FEATURES_REQUEST") % res.type)
+ logging.info("Received features_reply.")
+
+ if (res.capabilities & capability) > 0:
+ logging.info("Switch capabilities bitmask claims to support %s",
+ capability_str)
+ return True, res.capabilities
+ else:
+ logging.info("Capabilities bitmask does not support %s.",
+ capability_str)
+ return False, res.capabilities
+
+def verify_configuration_flag(test, flag):
+ """
+ Return True if DUT supports specified configuration flag.
+
+ @param test Instance of base_tests.SimpleProtocol
+ @param flag One of ofp_config_flags.
+ @returns (supported, flags) Bool if flag is set and flag values.
+ """
+ logging.info("Verifing that flag is valid.")
+ test.assertIn(flag, ofp.const.ofp_config_flags_map,
+ "flag %s does not exist." % flag)
+ flag_str = ofp.const.ofp_config_flags_map[flag]
+
+ logging.info("Sending OFPT_GET_CONFIG_REQUEST.")
+ req = ofp.message.get_config_request()
+ rv = test.controller.message_send(req)
+ test.assertNotEqual(rv, -1, "Not able to send get_config_request.")
+
+ logging.info("Expecting OFPT_GET_CONFIG_REPLY ")
+ (res, pkt) = test.controller.poll(exp_msg=ofp.OFPT_GET_CONFIG_REPLY,
+ timeout=2)
+ test.assertIsNotNone(res, "Did not receive OFPT_GET_CONFIG_REPLY")
+ logging.info("Received OFPT_GET_CONFIG_REPLY ")
+
+ if res.flags == flag:
+ logging.info("%s flag is set.", flag_str)
+ return True, res.flags
+ else:
+ logging.info("%s flag is not set.", flag_str)
+ return False, res.flags
+
+__all__ = list(set(locals()) - _import_blacklist)