Merge pull request #58 from rlane/of13
OpenFlow 1.3 matching tests
diff --git a/oft b/oft
index 4ca4d64..28de9f7 100755
--- a/oft
+++ b/oft
@@ -270,7 +270,8 @@
# Find all testcases defined in the module
tests = dict((k, v) for (k, v) in mod.__dict__.items() if type(v) == type and
- issubclass(v, unittest.TestCase))
+ issubclass(v, unittest.TestCase) and
+ hasattr(v, "runTest"))
if tests:
for (testname, test) in tests.items():
# Set default annotation values
diff --git a/src/python/oftest/base_tests.py b/src/python/oftest/base_tests.py
index 9d905f9..a1eec43 100644
--- a/src/python/oftest/base_tests.py
+++ b/src/python/oftest/base_tests.py
@@ -78,12 +78,6 @@
self.controller.join()
del self.controller
- def runTest(self):
- # Just a simple sanity check as illustration
- logging.info("Running simple proto test")
- self.assertTrue(self.controller.switch_socket is not None,
- str(self) + 'No connection to switch')
-
def assertTrue(self, cond, msg):
if not cond:
logging.error("** FAILED ASSERTION: " + msg)
@@ -112,12 +106,6 @@
SimpleProtocol.tearDown(self)
logging.info("Teardown done")
- def runTest(self):
- self.assertTrue(self.controller.switch_socket is not None,
- str(self) + 'No connection to switch')
- # self.dataplane.show()
- # Would like an assert that checks the data plane
-
class DataPlaneOnly(unittest.TestCase):
"""
Root class that sets up only the dataplane
@@ -131,8 +119,3 @@
def tearDown(self):
logging.info("Teardown for simple dataplane test")
logging.info("Teardown done")
-
- def runTest(self):
- logging.info("DataPlaneOnly")
- # self.dataplane.show()
- # Would like an assert that checks the data plane
diff --git a/src/python/oftest/testutils.py b/src/python/oftest/testutils.py
index 7e1b2c8..33a4cc1 100644
--- a/src/python/oftest/testutils.py
+++ b/src/python/oftest/testutils.py
@@ -122,6 +122,53 @@
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):
+ """
+ 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
+
+ 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)
+ 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',
@@ -182,6 +229,53 @@
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',
@@ -234,6 +328,53 @@
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',
diff --git a/tests-1.3/basic.py b/tests-1.3/basic.py
index c8e6758..611aacd 100644
--- a/tests-1.3/basic.py
+++ b/tests-1.3/basic.py
@@ -104,6 +104,45 @@
self.dataplane.send(in_port, pkt)
receive_pkt_verify(self, [out_port], pkt, in_port)
+class OutputWildcard(base_tests.SimpleDataPlane):
+ """
+ Test output function for a match-all (but not table-miss) flow
+
+ For each port A, adds a flow directing all packets to that port.
+ Then, for all other ports B != A, verifies that sending a packet
+ to B results in an output to A.
+ """
+ def runTest(self):
+ ports = sorted(config["port_map"].keys())
+
+ delete_all_flows(self.controller)
+
+ pkt = str(simple_tcp_packet())
+
+ for out_port in ports:
+ request = ofp.message.flow_add(
+ table_id=0,
+ cookie=42,
+ instructions=[
+ ofp.instruction.apply_actions(
+ actions=[
+ ofp.action.output(
+ port=out_port,
+ max_len=ofp.OFPCML_NO_BUFFER)])],
+ buffer_id=ofp.OFP_NO_BUFFER,
+ priority=1000)
+
+ logging.info("Inserting flow sending all packets to port %d", out_port)
+ self.controller.message_send(request)
+ do_barrier(self.controller)
+
+ for in_port in ports:
+ if in_port == out_port:
+ continue
+ logging.info("OutputWildcard test, ports %d to %d", in_port, out_port)
+ self.dataplane.send(in_port, pkt)
+ receive_pkt_verify(self, [out_port], pkt, in_port)
+
class PacketInExact(base_tests.SimpleDataPlane):
"""
Test packet in function for an exact-match flow
@@ -140,6 +179,39 @@
self.dataplane.send(of_port, pkt)
verify_packet_in(self, pkt, of_port, ofp.OFPR_ACTION)
+class PacketInWildcard(base_tests.SimpleDataPlane):
+ """
+ Test packet in function for a match-all flow
+
+ Send a packet to each dataplane port and verify that a packet
+ in message is received from the controller for each
+ """
+ def runTest(self):
+ delete_all_flows(self.controller)
+
+ pkt = str(simple_tcp_packet())
+
+ request = ofp.message.flow_add(
+ table_id=0,
+ cookie=42,
+ instructions=[
+ ofp.instruction.apply_actions(
+ actions=[
+ ofp.action.output(
+ port=ofp.OFPP_CONTROLLER,
+ max_len=ofp.OFPCML_NO_BUFFER)])],
+ buffer_id=ofp.OFP_NO_BUFFER,
+ priority=1000)
+
+ logging.info("Inserting flow sending all packets to controller")
+ self.controller.message_send(request)
+ do_barrier(self.controller)
+
+ for of_port in config["port_map"].keys():
+ logging.info("PacketInWildcard test, port %d", of_port)
+ self.dataplane.send(of_port, pkt)
+ verify_packet_in(self, pkt, of_port, ofp.OFPR_ACTION)
+
class PacketInMiss(base_tests.SimpleDataPlane):
"""
Test packet in function for a table-miss flow
diff --git a/tests-1.3/match.py b/tests-1.3/match.py
new file mode 100644
index 0000000..c8bf51d
--- /dev/null
+++ b/tests-1.3/match.py
@@ -0,0 +1,1651 @@
+# Distributed under the OpenFlow Software License (see LICENSE)
+# Copyright (c) 2010 The Board of Trustees of The Leland Stanford Junior University
+# Copyright (c) 2012, 2013 Big Switch Networks, Inc.
+"""
+Flow match test cases
+
+These tests check the behavior of each match field. The only action used is a
+single output.
+"""
+
+import logging
+
+from oftest import config
+import oftest.base_tests as base_tests
+import ofp
+import scapy.all as scapy
+
+from oftest.testutils import *
+from oftest.parse import parse_ipv6
+
+class MatchTest(base_tests.SimpleDataPlane):
+ """
+ Base class for match tests
+ """
+
+ def verify_match(self, match, matching, nonmatching):
+ """
+ Verify matching behavior
+
+ Checks that all the packets in 'matching' match 'match', and that
+ the packets in 'nonmatching' do not.
+
+ 'match' is a LOXI match object. 'matching' and 'nonmatching' are
+ dicts mapping from string names (used in log messages) to string
+ packet data.
+ """
+ ports = sorted(config["port_map"].keys())
+ in_port = ports[0]
+ out_port = ports[1]
+
+ logging.info("Running match test for %s", match.show())
+
+ delete_all_flows(self.controller)
+
+ logging.info("Inserting flow sending matching packets to port %d", out_port)
+ request = ofp.message.flow_add(
+ table_id=0,
+ match=match,
+ instructions=[
+ ofp.instruction.apply_actions(
+ actions=[
+ ofp.action.output(
+ port=out_port,
+ max_len=ofp.OFPCML_NO_BUFFER)])],
+ buffer_id=ofp.OFP_NO_BUFFER,
+ priority=1000)
+ self.controller.message_send(request)
+
+ logging.info("Inserting match-all flow sending packets to controller")
+ request = ofp.message.flow_add(
+ table_id=0,
+ instructions=[
+ ofp.instruction.apply_actions(
+ actions=[
+ ofp.action.output(
+ port=ofp.OFPP_CONTROLLER,
+ max_len=ofp.OFPCML_NO_BUFFER)])],
+ buffer_id=ofp.OFP_NO_BUFFER,
+ priority=1)
+ self.controller.message_send(request)
+
+ do_barrier(self.controller)
+
+ for name, pkt in matching.items():
+ logging.info("Sending matching packet %s, expecting output to port %d", repr(name), out_port)
+ pktstr = str(pkt)
+ self.dataplane.send(in_port, pktstr)
+ receive_pkt_verify(self, [out_port], pktstr, in_port)
+
+ for name, pkt in nonmatching.items():
+ logging.info("Sending non-matching packet %s, expecting packet-in", repr(name))
+ pktstr = str(pkt)
+ self.dataplane.send(in_port, pktstr)
+ verify_packet_in(self, pktstr, in_port, ofp.OFPR_ACTION)
+
+# Does not use MatchTest because the ingress port is not a packet field
+class InPort(base_tests.SimpleDataPlane):
+ """
+ Match on ingress port
+ """
+ def runTest(self):
+ ports = sorted(config["port_map"].keys())
+ in_port = ports[0]
+ out_port = ports[1]
+ bad_port = ports[2]
+
+ match = ofp.match([
+ ofp.oxm.in_port(in_port)
+ ])
+
+ pkt = simple_tcp_packet()
+
+ logging.info("Running match test for %s", match.show())
+
+ delete_all_flows(self.controller)
+
+ logging.info("Inserting flow sending matching packets to port %d", out_port)
+ request = ofp.message.flow_add(
+ table_id=0,
+ match=match,
+ instructions=[
+ ofp.instruction.apply_actions(
+ actions=[
+ ofp.action.output(
+ port=out_port,
+ max_len=ofp.OFPCML_NO_BUFFER)])],
+ buffer_id=ofp.OFP_NO_BUFFER,
+ priority=1000)
+ self.controller.message_send(request)
+
+ logging.info("Inserting match-all flow sending packets to controller")
+ request = ofp.message.flow_add(
+ table_id=0,
+ instructions=[
+ ofp.instruction.apply_actions(
+ actions=[
+ ofp.action.output(
+ port=ofp.OFPP_CONTROLLER,
+ max_len=ofp.OFPCML_NO_BUFFER)])],
+ buffer_id=ofp.OFP_NO_BUFFER,
+ priority=1)
+ self.controller.message_send(request)
+
+ do_barrier(self.controller)
+
+ pktstr = str(pkt)
+
+ logging.info("Sending packet on matching ingress port, expecting output to port %d", out_port)
+ self.dataplane.send(in_port, pktstr)
+ receive_pkt_verify(self, [out_port], pktstr, in_port)
+
+ logging.info("Sending packet on non-matching ingress port, expecting packet-in")
+ self.dataplane.send(bad_port, pktstr)
+ verify_packet_in(self, pktstr, bad_port, ofp.OFPR_ACTION)
+
+class EthDst(MatchTest):
+ """
+ Match on ethernet destination
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_dst([0x00, 0x01, 0x02, 0x03, 0x04, 0x05])
+ ])
+
+ matching = {
+ "correct": simple_tcp_packet(eth_dst='00:01:02:03:04:05'),
+ }
+
+ nonmatching = {
+ "incorrect": simple_tcp_packet(eth_dst='00:01:02:03:04:06'),
+ "multicast": simple_tcp_packet(eth_dst='01:01:02:03:04:05'),
+ "local": simple_tcp_packet(eth_dst='02:01:02:03:04:05'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class EthDstBroadcast(MatchTest):
+ """
+ Match on ethernet destination (broadcast)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_dst([0xff, 0xff, 0xff, 0xff, 0xff, 0xff])
+ ])
+
+ matching = {
+ "ff:ff:ff:ff:ff:ff": simple_tcp_packet(eth_dst='ff:ff:ff:ff:ff:ff'),
+ }
+
+ nonmatching = {
+ "fd:ff:ff:ff:ff:ff": simple_tcp_packet(eth_dst='fd:ff:ff:ff:ff:ff'),
+ "fe:ff:ff:ff:ff:ff": simple_tcp_packet(eth_dst='fe:ff:ff:ff:ff:ff'),
+ "ff:fe:ff:ff:ff:ff": simple_tcp_packet(eth_dst='ff:fe:ff:ff:ff:ff'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class EthDstMulticast(MatchTest):
+ """
+ Match on ethernet destination (IPv4 multicast)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_dst([0x01, 0x00, 0x5e, 0xed, 0x99, 0x02])
+ ])
+
+ matching = {
+ "correct": simple_tcp_packet(eth_dst='01:00:5e:ed:99:02'),
+ }
+
+ nonmatching = {
+ "incorrect": simple_tcp_packet(eth_dst='01:00:5e:ed:99:03'),
+ "unicast": simple_tcp_packet(eth_dst='00:00:5e:ed:99:02'),
+ "broadcast": simple_tcp_packet(eth_dst='ff:ff:ff:ff:ff:ff'),
+ "local": simple_tcp_packet(eth_dst='03:00:5e:ed:99:02'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class EthDstMasked(MatchTest):
+ """
+ Match on ethernet destination (masked)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_dst_masked([0x00, 0x01, 0x02, 0x03, 0x04, 0x05],
+ [0x00, 0xff, 0xff, 0x0f, 0xff, 0xff])
+ ])
+
+ matching = {
+ "00:01:02:03:04:05": simple_tcp_packet(eth_dst='00:01:02:03:04:05'),
+ "ff:01:02:f3:04:05": simple_tcp_packet(eth_dst='ff:01:02:f3:04:05'),
+ }
+
+ nonmatching = {
+ "00:02:02:03:04:05": simple_tcp_packet(eth_dst='00:02:02:03:04:05'),
+ "00:01:02:07:04:05": simple_tcp_packet(eth_dst='00:01:02:07:04:05'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class EthSrc(MatchTest):
+ """
+ Match on ethernet source
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_src([0,1,2,3,4,5])
+ ])
+
+ matching = {
+ "correct": simple_tcp_packet(eth_src='00:01:02:03:04:05'),
+ }
+
+ nonmatching = {
+ "incorrect": simple_tcp_packet(eth_src='00:01:02:03:04:06'),
+ "multicast": simple_tcp_packet(eth_src='01:01:02:03:04:05'),
+ "local": simple_tcp_packet(eth_src='02:01:02:03:04:05'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class EthSrcBroadcast(MatchTest):
+ """
+ Match on ethernet source (broadcast)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_src([0xff, 0xff, 0xff, 0xff, 0xff, 0xff])
+ ])
+
+ matching = {
+ "ff:ff:ff:ff:ff:ff": simple_tcp_packet(eth_src='ff:ff:ff:ff:ff:ff'),
+ }
+
+ nonmatching = {
+ "fd:ff:ff:ff:ff:ff": simple_tcp_packet(eth_src='fd:ff:ff:ff:ff:ff'),
+ "fe:ff:ff:ff:ff:ff": simple_tcp_packet(eth_src='fe:ff:ff:ff:ff:ff'),
+ "ff:fe:ff:ff:ff:ff": simple_tcp_packet(eth_src='ff:fe:ff:ff:ff:ff'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class EthSrcMulticast(MatchTest):
+ """
+ Match on ethernet source (IPv4 multicast)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_src([0x01, 0x00, 0x5e, 0xed, 0x99, 0x02])
+ ])
+
+ matching = {
+ "correct": simple_tcp_packet(eth_src='01:00:5e:ed:99:02'),
+ }
+
+ nonmatching = {
+ "incorrect": simple_tcp_packet(eth_src='01:00:5e:ed:99:03'),
+ "unicast": simple_tcp_packet(eth_src='00:00:5e:ed:99:02'),
+ "broadcast": simple_tcp_packet(eth_src='ff:ff:ff:ff:ff:ff'),
+ "local": simple_tcp_packet(eth_src='03:00:5e:ed:99:02'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class EthSrcMasked(MatchTest):
+ """
+ Match on ethernet source (masked)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_src_masked([0x00, 0x01, 0x02, 0x03, 0x04, 0x05],
+ [0x00, 0xff, 0xff, 0x0f, 0xff, 0xff])
+ ])
+
+ matching = {
+ "00:01:02:03:04:05": simple_tcp_packet(eth_src='00:01:02:03:04:05'),
+ "ff:01:02:f3:04:05": simple_tcp_packet(eth_src='ff:01:02:f3:04:05'),
+ }
+
+ nonmatching = {
+ "00:02:02:03:04:05": simple_tcp_packet(eth_src='00:02:02:03:04:05'),
+ "00:01:02:07:04:05": simple_tcp_packet(eth_src='00:01:02:07:04:05'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class EthTypeIPv4(MatchTest):
+ """
+ Match on ethertype (IPv4)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800)
+ ])
+
+ snap_pkt = \
+ scapy.Ether(dst='00:01:02:03:04:05', src='00:06:07:08:09:0a', type=48)/ \
+ scapy.LLC(dsap=0xaa, ssap=0xaa, ctrl=0x03)/ \
+ scapy.SNAP(OUI=0x000000, code=0x0800)/ \
+ scapy.IP(src='192.168.0.1', dst='192.168.0.2', proto=6)/ \
+ scapy.TCP(sport=1234, dport=80)
+
+ llc_pkt = \
+ scapy.Ether(dst='00:01:02:03:04:05', src='00:06:07:08:09:0a', type=17)/ \
+ scapy.LLC(dsap=0xaa, ssap=0xab, ctrl=0x03)
+
+ matching = {
+ "ipv4/tcp": simple_tcp_packet(),
+ "ipv4/udp": simple_udp_packet(),
+ "ipv4/icmp": simple_icmp_packet(),
+ "vlan tagged": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=3),
+ "llc/snap": snap_pkt,
+ }
+
+ nonmatching = {
+ "arp": simple_arp_packet(),
+ "llc": llc_pkt,
+ "ipv6/tcp": simple_tcpv6_packet(),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class EthTypeIPv6(MatchTest):
+ """
+ Match on ethertype (IPv6)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd)
+ ])
+
+ matching = {
+ "ipv6/tcp": simple_tcpv6_packet(),
+ "ipv6/udp": simple_udpv6_packet(),
+ "ipv6/icmp": simple_icmpv6_packet(),
+ "vlan tagged": simple_tcpv6_packet(vlan_vid=2, vlan_pcp=3),
+ }
+
+ nonmatching = {
+ "ipv4/tcp": simple_tcp_packet(),
+ "arp": simple_arp_packet(),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class EthTypeARP(MatchTest):
+ """
+ Match on ethertype (ARP)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0806)
+ ])
+
+ matching = {
+ "arp": simple_arp_packet(),
+ # TODO vlan tagged
+ }
+
+ nonmatching = {
+ "ipv4/tcp": simple_tcp_packet(),
+ "ipv6/tcp": simple_tcpv6_packet(),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class EthTypeNone(MatchTest):
+ """
+ Match on no ethertype (IEEE 802.3 without SNAP header)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x05ff)
+ ])
+
+ snap_pkt = \
+ scapy.Ether(dst='00:01:02:03:04:05', src='00:06:07:08:09:0a', type=48)/ \
+ scapy.LLC(dsap=0xaa, ssap=0xaa, ctrl=0x03)/ \
+ scapy.SNAP(OUI=0x000000, code=0x0800)/ \
+ scapy.IP(src='192.168.0.1', dst='192.168.0.2', proto=6)/ \
+ scapy.TCP(sport=1234, dport=80)
+
+ llc_pkt = \
+ scapy.Ether(dst='00:01:02:03:04:05', src='00:06:07:08:09:0a', type=17)/ \
+ scapy.LLC(dsap=0xaa, ssap=0xab, ctrl=0x03)
+
+ matching = {
+ "llc": llc_pkt,
+ }
+
+ nonmatching = {
+ "ipv4/tcp": simple_tcp_packet(),
+ "ipv6/tcp": simple_tcpv6_packet(),
+ "llc/snap": snap_pkt,
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class VlanExact(MatchTest):
+ """
+ Match on VLAN VID and PCP
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.vlan_vid(ofp.OFPVID_PRESENT|2),
+ ofp.oxm.vlan_pcp(3),
+ ])
+
+ matching = {
+ "vid=2 pcp=3": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=3),
+ }
+
+ nonmatching = {
+ "vid=4 pcp=2": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=4, vlan_pcp=2),
+ "vid=4 pcp=3": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=4, vlan_pcp=3),
+ "vid=2 pcp=2": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=2),
+ "vid=0 pcp=3": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=0, vlan_pcp=3),
+ "vid=2 pcp=0": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=0),
+ "no vlan tag": simple_tcp_packet(),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class VlanVID(MatchTest):
+ """
+ Match on VLAN VID
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.vlan_vid(ofp.OFPVID_PRESENT|2),
+ ])
+
+ matching = {
+ "vid=2 pcp=3": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=3),
+ "vid=2 pcp=7": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=7),
+ }
+
+ nonmatching = {
+ "vid=4 pcp=2": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=4, vlan_pcp=2),
+ "no vlan tag": simple_tcp_packet(),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class VlanVIDMasked(MatchTest):
+ """
+ Match on VLAN VID (masked)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.vlan_vid_masked(ofp.OFPVID_PRESENT|3, ofp.OFPVID_PRESENT|3),
+ ])
+
+ matching = {
+ "vid=3 pcp=2": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=3, vlan_pcp=2),
+ "vid=7 pcp=2": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=7, vlan_pcp=2),
+ "vid=11 pcp=2": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=11, vlan_pcp=2),
+ }
+
+ nonmatching = {
+ "vid=0 pcp=2": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=0, vlan_pcp=2),
+ "vid=1 pcp=2": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=1, vlan_pcp=2),
+ "vid=2 pcp=2": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=2),
+ "vid=4 pcp=2": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=4, vlan_pcp=2),
+ "no vlan tag": simple_tcp_packet(),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class VlanPCP(MatchTest):
+ """
+ Match on VLAN PCP (VID matched)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.vlan_vid(ofp.OFPVID_PRESENT|2),
+ ofp.oxm.vlan_pcp(3),
+ ])
+
+ matching = {
+ "vid=2 pcp=3": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=3),
+ }
+
+ nonmatching = {
+ "vid=2 pcp=4": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=4),
+ "no vlan tag": simple_tcp_packet(),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+@nonstandard
+class VlanPCPMasked(MatchTest):
+ """
+ Match on VLAN PCP (masked, VID matched)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.vlan_vid(ofp.OFPVID_PRESENT|2),
+ ofp.oxm.vlan_pcp_masked(3, 3),
+ ])
+
+ matching = {
+ "vid=2 pcp=3": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=3),
+ "vid=2 pcp=7": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=7),
+ }
+
+ nonmatching = {
+ "vid=2 pcp=1": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=1),
+ "vid=2 pcp=2": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=2),
+ "vid=2 pcp=4": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=4),
+ "vid=2 pcp=5": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=5),
+ "vid=2 pcp=6": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=6),
+ "no vlan tag": simple_tcp_packet(),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class VlanPCPAnyVID(MatchTest):
+ """
+ Match on VLAN PCP (VID present)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.vlan_vid_masked(ofp.OFPVID_PRESENT, ofp.OFPVID_PRESENT),
+ ofp.oxm.vlan_pcp(3),
+ ])
+
+ matching = {
+ "vid=2 pcp=3": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=3),
+ "vid=0 pcp=3": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=0, vlan_pcp=3),
+ }
+
+ nonmatching = {
+ "vid=2 pcp=4": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=4),
+ "no vlan tag": simple_tcp_packet(),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class VlanPresent(MatchTest):
+ """
+ Match on any VLAN tag (but must be present)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.vlan_vid_masked(ofp.OFPVID_PRESENT, ofp.OFPVID_PRESENT),
+ ])
+
+ matching = {
+ "vid=2 pcp=3": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=3),
+ "vid=0 pcp=7": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=0, vlan_pcp=7),
+ "vid=2 pcp=0": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=0),
+ }
+
+ nonmatching = {
+ "no vlan tag": simple_tcp_packet()
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class VlanAbsent(MatchTest):
+ """
+ Match on absent VLAN tag
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.vlan_vid(ofp.OFPVID_NONE),
+ ])
+
+ matching = {
+ "no vlan tag": simple_tcp_packet()
+ }
+
+ nonmatching = {
+ "vid=2 pcp=3": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=3),
+ "vid=0 pcp=7": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=0, vlan_pcp=7),
+ "vid=2 pcp=0": simple_tcp_packet(dl_vlan_enable=True, vlan_vid=2, vlan_pcp=0),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4Dscp(MatchTest):
+ """
+ Match on ipv4 dscp
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ip_dscp(4),
+ ])
+
+ matching = {
+ "dscp=4 ecn=0": simple_tcp_packet(ip_tos=0x10),
+ "dscp=4 ecn=3": simple_tcp_packet(ip_tos=0x13),
+ }
+
+ nonmatching = {
+ "dscp=5 ecn=0": simple_tcp_packet(ip_tos=0x14),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6Dscp(MatchTest):
+ """
+ Match on ipv6 dscp
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ip_dscp(4),
+ ])
+
+ matching = {
+ "dscp=4 ecn=0": simple_tcpv6_packet(ipv6_tc=0x10),
+ "dscp=4 ecn=3": simple_tcpv6_packet(ipv6_tc=0x13),
+ }
+
+ nonmatching = {
+ "dscp=5 ecn=0": simple_tcpv6_packet(ipv6_tc=0x14),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4Ecn(MatchTest):
+ """
+ Match on ipv4 ecn
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ip_ecn(2),
+ ])
+
+ matching = {
+ "dscp=4 ecn=2": simple_tcp_packet(ip_tos=0x12),
+ "dscp=6 ecn=2": simple_tcp_packet(ip_tos=0x1a),
+ }
+
+ nonmatching = {
+ "dscp=4 ecn=0": simple_tcp_packet(ip_tos=0x10),
+ "dscp=4 ecn=3": simple_tcp_packet(ip_tos=0x13),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6Ecn(MatchTest):
+ """
+ Match on ipv6 ecn
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ip_ecn(2),
+ ])
+
+ matching = {
+ "dscp=4 ecn=2": simple_tcpv6_packet(ipv6_tc=0x12),
+ "dscp=6 ecn=2": simple_tcpv6_packet(ipv6_tc=0x1a),
+ }
+
+ nonmatching = {
+ "dscp=4 ecn=0": simple_tcpv6_packet(ipv6_tc=0x10),
+ "dscp=4 ecn=3": simple_tcpv6_packet(ipv6_tc=0x13),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4ProtoTCP(MatchTest):
+ """
+ Match on ipv4 protocol field (TCP)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ip_proto(6),
+ ])
+
+ matching = {
+ "tcp": simple_tcp_packet(),
+ }
+
+ nonmatching = {
+ "udp": simple_udp_packet(),
+ "icmp": simple_icmp_packet(),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6ProtoTCP(MatchTest):
+ """
+ Match on ipv6 protocol field (TCP)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ip_proto(6),
+ ])
+
+ matching = {
+ "tcp": simple_tcpv6_packet(),
+ }
+
+ nonmatching = {
+ "udp": simple_udpv6_packet(),
+ "icmp": simple_icmpv6_packet(),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4ProtoUDP(MatchTest):
+ """
+ Match on ipv4 protocol field (UDP)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ip_proto(17),
+ ])
+
+ matching = {
+ "udp": simple_udp_packet(),
+ }
+
+ nonmatching = {
+ "tcp": simple_tcp_packet(),
+ "icmp": simple_icmp_packet(),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6ProtoUDP(MatchTest):
+ """
+ Match on ipv6 protocol field (UDP)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ip_proto(17),
+ ])
+
+ matching = {
+ "udp": simple_udpv6_packet(),
+ }
+
+ nonmatching = {
+ "tcp": simple_tcpv6_packet(),
+ "icmp": simple_icmpv6_packet(),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4ProtoICMP(MatchTest):
+ """
+ Match on ipv4 protocol field (ICMP)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ip_proto(1),
+ ])
+
+ matching = {
+ "icmp": simple_icmp_packet(),
+ }
+
+ nonmatching = {
+ "tcp": simple_tcp_packet(),
+ "udp": simple_udp_packet(),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6ProtoICMP(MatchTest):
+ """
+ Match on ipv6 protocol field (ICMP)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ip_proto(58),
+ ])
+
+ matching = {
+ "icmp": simple_icmpv6_packet(),
+ }
+
+ nonmatching = {
+ "tcp": simple_tcpv6_packet(),
+ "udp": simple_udpv6_packet(),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4Src(MatchTest):
+ """
+ Match on ipv4 source address
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ipv4_src(0xc0a80001), # 192.168.0.1
+ ])
+
+ matching = {
+ "192.168.0.1": simple_tcp_packet(ip_src='192.168.0.1'),
+ }
+
+ nonmatching = {
+ "192.168.0.2": simple_tcp_packet(ip_src='192.168.0.2'),
+ "255.255.255.255": simple_tcp_packet(ip_src='255.255.255.255'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4SrcSubnetMasked(MatchTest):
+ """
+ Match on ipv4 source address (subnet masked)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ # 192.168.0.0/20 (255.255.240.0)
+ ofp.oxm.ipv4_src_masked(0xc0a80000, 0xfffff000),
+ ])
+
+ matching = {
+ "192.168.0.1": simple_tcp_packet(ip_src='192.168.0.1'),
+ "192.168.0.2": simple_tcp_packet(ip_src='192.168.0.2'),
+ "192.168.4.2": simple_tcp_packet(ip_src='192.168.4.2'),
+ "192.168.0.0": simple_tcp_packet(ip_src='192.168.0.0'),
+ "192.168.15.255": simple_tcp_packet(ip_src='192.168.15.255'),
+ }
+
+ nonmatching = {
+ "192.168.16.0": simple_tcp_packet(ip_src='192.168.16.0'),
+ "192.167.255.255": simple_tcp_packet(ip_src='192.167.255.255'),
+ "192.168.31.1": simple_tcp_packet(ip_src='192.168.31.1'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4SrcMasked(MatchTest):
+ """
+ Match on ipv4 source address (arbitrarily masked)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ # 192.168.0.1 255.254.255.255
+ ofp.oxm.ipv4_src_masked(0xc0a80001, 0xfffeffff),
+ ])
+
+ matching = {
+ "192.168.0.1": simple_tcp_packet(ip_src='192.168.0.1'),
+ "192.169.0.1": simple_tcp_packet(ip_src='192.169.0.1'),
+ }
+
+ nonmatching = {
+ "192.168.0.2": simple_tcp_packet(ip_src='192.168.0.2'),
+ "192.167.0.1": simple_tcp_packet(ip_src='192.167.0.1'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4Dst(MatchTest):
+ """
+ Match on ipv4 destination address
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ipv4_dst(0xc0a80001), # 192.168.0.1
+ ])
+
+ matching = {
+ "192.168.0.1": simple_tcp_packet(ip_dst='192.168.0.1'),
+ }
+
+ nonmatching = {
+ "192.168.0.2": simple_tcp_packet(ip_dst='192.168.0.2'),
+ "255.255.255.255": simple_tcp_packet(ip_dst='255.255.255.255'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4DstSubnetMasked(MatchTest):
+ """
+ Match on ipv4 destination address (subnet masked)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ # 192.168.0.0/20 (255.255.240.0)
+ ofp.oxm.ipv4_dst_masked(0xc0a80000, 0xfffff000),
+ ])
+
+ matching = {
+ "192.168.0.1": simple_tcp_packet(ip_dst='192.168.0.1'),
+ "192.168.0.2": simple_tcp_packet(ip_dst='192.168.0.2'),
+ "192.168.4.2": simple_tcp_packet(ip_dst='192.168.4.2'),
+ "192.168.0.0": simple_tcp_packet(ip_dst='192.168.0.0'),
+ "192.168.15.255": simple_tcp_packet(ip_dst='192.168.15.255'),
+ }
+
+ nonmatching = {
+ "192.168.16.0": simple_tcp_packet(ip_dst='192.168.16.0'),
+ "192.167.255.255": simple_tcp_packet(ip_dst='192.167.255.255'),
+ "192.168.31.1": simple_tcp_packet(ip_dst='192.168.31.1'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4DstMasked(MatchTest):
+ """
+ Match on ipv4 destination address (arbitrarily masked)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ # 192.168.0.1 255.254.255.255
+ ofp.oxm.ipv4_dst_masked(0xc0a80001, 0xfffeffff),
+ ])
+
+ matching = {
+ "192.168.0.1": simple_tcp_packet(ip_dst='192.168.0.1'),
+ "192.169.0.1": simple_tcp_packet(ip_dst='192.169.0.1'),
+ }
+
+ nonmatching = {
+ "192.168.0.2": simple_tcp_packet(ip_dst='192.168.0.2'),
+ "192.167.0.1": simple_tcp_packet(ip_dst='192.167.0.1'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6Src(MatchTest):
+ """
+ Match on ipv6 source address
+ """
+ def runTest(self):
+ correct = "2001:db8:85a3::8a2e:370:7334"
+ incorrect = "2001:db8:85a3::8a2e:370:7324"
+ unspecified = "::"
+
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ipv6_src(parse_ipv6(correct)),
+ ])
+
+ matching = {
+ "correct": simple_tcpv6_packet(ipv6_src=correct),
+ }
+
+ nonmatching = {
+ "incorrect": simple_tcpv6_packet(ipv6_src=incorrect),
+ "unspecified": simple_tcpv6_packet(ipv6_src=unspecified),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6SrcSubnetMasked(MatchTest):
+ """
+ Match on ipv6 source address (subnet masked)
+ """
+ def runTest(self):
+ flow = "2001:0db8:85a3::"
+ mask = "ffff:ffff:ffff::"
+ correct1 = "2001:0db8:85a3::8a2e:0370:7331"
+ correct2 = "2001:0db8:85a3::ffff:ffff:ffff"
+ incorrect1 = "2001:0db8:85a2::"
+
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ipv6_src_masked(parse_ipv6(flow), parse_ipv6(mask)),
+ ])
+
+ matching = {
+ "flow": simple_tcpv6_packet(ipv6_src=flow),
+ "correct1": simple_tcpv6_packet(ipv6_src=correct1),
+ "correct2": simple_tcpv6_packet(ipv6_src=correct2),
+ }
+
+ nonmatching = {
+ "incorrect1": simple_tcpv6_packet(ipv6_src=incorrect1),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6SrcMasked(MatchTest):
+ """
+ Match on ipv6 source address (arbitrarily masked)
+ """
+ def runTest(self):
+ flow = "2001:0db8:85a3::0001"
+ mask = "ffff:ffff:ffff::000f"
+ correct1 = "2001:0db8:85a3::8a2e:0370:7331"
+ correct2 = "2001:0db8:85a3::ffff:ffff:fff1"
+ incorrect1 = "2001:0db8:85a2::0001"
+ incorrect2 = "2001:0db8:85a3::0000"
+
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ipv6_src_masked(parse_ipv6(flow), parse_ipv6(mask)),
+ ])
+
+ matching = {
+ "flow": simple_tcpv6_packet(ipv6_src=flow),
+ "correct1": simple_tcpv6_packet(ipv6_src=correct1),
+ "correct2": simple_tcpv6_packet(ipv6_src=correct2),
+ }
+
+ nonmatching = {
+ "incorrect1": simple_tcpv6_packet(ipv6_src=incorrect1),
+ "incorrect2": simple_tcpv6_packet(ipv6_src=incorrect2),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6Dst(MatchTest):
+ """
+ Match on ipv6 destination address
+ """
+ def runTest(self):
+ correct = "2001:db8:85a3::8a2e:370:7334"
+ incorrect = "2001:db8:85a3::8a2e:370:7324"
+ unspecified = "::"
+
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ipv6_dst(parse_ipv6(correct)),
+ ])
+
+ matching = {
+ "correct": simple_tcpv6_packet(ipv6_dst=correct),
+ }
+
+ nonmatching = {
+ "incorrect": simple_tcpv6_packet(ipv6_dst=incorrect),
+ "unspecified": simple_tcpv6_packet(ipv6_dst=unspecified),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6DstSubnetMasked(MatchTest):
+ """
+ Match on ipv6 destination address (subnet masked)
+ """
+ def runTest(self):
+ flow = "2001:0db8:85a3::"
+ mask = "ffff:ffff:ffff::"
+ correct1 = "2001:0db8:85a3::8a2e:0370:7331"
+ correct2 = "2001:0db8:85a3::ffff:ffff:ffff"
+ incorrect1 = "2001:0db8:85a2::"
+
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ipv6_dst_masked(parse_ipv6(flow), parse_ipv6(mask)),
+ ])
+
+ matching = {
+ "flow": simple_tcpv6_packet(ipv6_dst=flow),
+ "correct1": simple_tcpv6_packet(ipv6_dst=correct1),
+ "correct2": simple_tcpv6_packet(ipv6_dst=correct2),
+ }
+
+ nonmatching = {
+ "incorrect1": simple_tcpv6_packet(ipv6_dst=incorrect1),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6DstMasked(MatchTest):
+ """
+ Match on ipv6 destination address (arbitrarily masked)
+ """
+ def runTest(self):
+ flow = "2001:0db8:85a3::0001"
+ mask = "ffff:ffff:ffff::000f"
+ correct1 = "2001:0db8:85a3::8a2e:0370:7331"
+ correct2 = "2001:0db8:85a3::ffff:ffff:fff1"
+ incorrect1 = "2001:0db8:85a2::0001"
+ incorrect2 = "2001:0db8:85a3::0000"
+
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ipv6_dst_masked(parse_ipv6(flow), parse_ipv6(mask)),
+ ])
+
+ matching = {
+ "flow": simple_tcpv6_packet(ipv6_dst=flow),
+ "correct1": simple_tcpv6_packet(ipv6_dst=correct1),
+ "correct2": simple_tcpv6_packet(ipv6_dst=correct2),
+ }
+
+ nonmatching = {
+ "incorrect1": simple_tcpv6_packet(ipv6_dst=incorrect1),
+ "incorrect2": simple_tcpv6_packet(ipv6_dst=incorrect2),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4TCPSrc(MatchTest):
+ """
+ Match on ipv4 tcp source port
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ip_proto(6),
+ ofp.oxm.tcp_src(53),
+ ])
+
+ matching = {
+ "tcp sport=53": simple_tcp_packet(tcp_sport=53),
+ }
+
+ nonmatching = {
+ "tcp sport=52": simple_tcp_packet(tcp_sport=52),
+ "udp sport=53": simple_udp_packet(udp_sport=53),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+@nonstandard
+class IPv4TCPSrcMasked(MatchTest):
+ """
+ Match on ipv4 tcp source port (masked)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ip_proto(6),
+ ofp.oxm.tcp_src_masked(52, 0xFE),
+ ])
+
+ matching = {
+ "tcp sport=53": simple_tcp_packet(tcp_sport=53),
+ "tcp sport=52": simple_tcp_packet(tcp_sport=52),
+ }
+
+ nonmatching = {
+ "tcp sport=54": simple_tcp_packet(tcp_sport=54),
+ "tcp sport=51": simple_tcp_packet(tcp_sport=51),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6TCPSrc(MatchTest):
+ """
+ Match on ipv4 tcp source port
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ip_proto(6),
+ ofp.oxm.tcp_src(53),
+ ])
+
+ matching = {
+ "tcp sport=53": simple_tcpv6_packet(tcp_sport=53),
+ }
+
+ nonmatching = {
+ "tcp sport=52": simple_tcpv6_packet(tcp_sport=52),
+ "udp sport=53": simple_udpv6_packet(udp_sport=53),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4TCPDst(MatchTest):
+ """
+ Match on ipv4 tcp destination port
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ip_proto(6),
+ ofp.oxm.tcp_dst(53),
+ ])
+
+ matching = {
+ "tcp dport=53": simple_tcp_packet(tcp_dport=53),
+ }
+
+ nonmatching = {
+ "tcp dport=52": simple_tcp_packet(tcp_dport=52),
+ "udp dport=53": simple_udp_packet(udp_dport=53),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+@nonstandard
+class IPv4TCPDstMasked(MatchTest):
+ """
+ Match on ipv4 tcp destination port (masked)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ip_proto(6),
+ ofp.oxm.tcp_dst_masked(52, 0xFE),
+ ])
+
+ matching = {
+ "tcp dport=53": simple_tcp_packet(tcp_dport=53),
+ "tcp dport=52": simple_tcp_packet(tcp_dport=52),
+ }
+
+ nonmatching = {
+ "tcp dport=54": simple_tcp_packet(tcp_dport=54),
+ "tcp dport=51": simple_tcp_packet(tcp_dport=51),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6TCPDst(MatchTest):
+ """
+ Match on ipv6 tcp destination port
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ip_proto(6),
+ ofp.oxm.tcp_dst(53),
+ ])
+
+ matching = {
+ "tcp dport=53": simple_tcpv6_packet(tcp_dport=53),
+ }
+
+ nonmatching = {
+ "tcp dport=52": simple_tcpv6_packet(tcp_dport=52),
+ "udp dport=53": simple_udpv6_packet(udp_dport=53),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4UDPSrc(MatchTest):
+ """
+ Match on ipv4 udp source port
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ip_proto(17),
+ ofp.oxm.udp_src(53),
+ ])
+
+ matching = {
+ "udp sport=53": simple_udp_packet(udp_sport=53),
+ }
+
+ nonmatching = {
+ "udp sport=52": simple_udp_packet(udp_sport=52),
+ "tcp sport=53": simple_tcp_packet(tcp_sport=53),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+@nonstandard
+class IPv4UDPSrcMasked(MatchTest):
+ """
+ Match on ipv4 udp source port (masked)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ip_proto(17),
+ ofp.oxm.udp_src_masked(52, 0xFE),
+ ])
+
+ matching = {
+ "udp sport=53": simple_udp_packet(udp_sport=53),
+ "udp sport=52": simple_udp_packet(udp_sport=52),
+ }
+
+ nonmatching = {
+ "udp sport=54": simple_udp_packet(udp_sport=54),
+ "udp sport=51": simple_udp_packet(udp_sport=51),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6UDPSrc(MatchTest):
+ """
+ Match on ipv4 udp source port
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ip_proto(17),
+ ofp.oxm.udp_src(53),
+ ])
+
+ matching = {
+ "udp sport=53": simple_udpv6_packet(udp_sport=53),
+ }
+
+ nonmatching = {
+ "udp sport=52": simple_udpv6_packet(udp_sport=52),
+ "tcp sport=53": simple_tcpv6_packet(tcp_sport=53),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4UDPDst(MatchTest):
+ """
+ Match on ipv4 udp destination port
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ip_proto(17),
+ ofp.oxm.udp_dst(53),
+ ])
+
+ matching = {
+ "udp dport=53": simple_udp_packet(udp_dport=53),
+ }
+
+ nonmatching = {
+ "udp dport=52": simple_udp_packet(udp_dport=52),
+ "tcp dport=53": simple_tcp_packet(tcp_dport=53),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+@nonstandard
+class IPv4UDPDstMasked(MatchTest):
+ """
+ Match on ipv4 udp destination port (masked)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ip_proto(17),
+ ofp.oxm.udp_dst_masked(52, 0xFE),
+ ])
+
+ matching = {
+ "udp dport=53": simple_udp_packet(udp_dport=53),
+ "udp dport=52": simple_udp_packet(udp_dport=52),
+ }
+
+ nonmatching = {
+ "udp dport=54": simple_udp_packet(udp_dport=54),
+ "udp dport=51": simple_udp_packet(udp_dport=51),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6UDPDst(MatchTest):
+ """
+ Match on ipv4 udp destination port
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ip_proto(17),
+ ofp.oxm.udp_dst(53),
+ ])
+
+ matching = {
+ "udp dport=53": simple_udpv6_packet(udp_dport=53),
+ }
+
+ nonmatching = {
+ "udp dport=52": simple_udpv6_packet(udp_dport=52),
+ "tcp dport=53": simple_tcpv6_packet(tcp_dport=53),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4ICMPType(MatchTest):
+ """
+ Match on ipv4 icmp type
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ip_proto(1),
+ ofp.oxm.icmpv4_type(3),
+ ])
+
+ matching = {
+ "type=3 code=1": simple_icmp_packet(icmp_type=3, icmp_code=1),
+ "type=3 code=2": simple_icmp_packet(icmp_type=3, icmp_code=2),
+ }
+
+ nonmatching = {
+ "type=2 code=1": simple_icmp_packet(icmp_type=2, icmp_code=1),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv4ICMPCode(MatchTest):
+ """
+ Match on ipv4 icmp code
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0800),
+ ofp.oxm.ip_proto(1),
+ ofp.oxm.icmpv4_code(2),
+ ])
+
+ matching = {
+ "type=3 code=2": simple_icmp_packet(icmp_type=3, icmp_code=2),
+ "type=5 code=2": simple_icmp_packet(icmp_type=5, icmp_code=2),
+ }
+
+ nonmatching = {
+ "type=2 code=1": simple_icmp_packet(icmp_type=2, icmp_code=1),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6ICMPType(MatchTest):
+ """
+ Match on ipv6 icmp type
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ip_proto(58),
+ ofp.oxm.icmpv6_type(3),
+ ])
+
+ matching = {
+ "type=3 code=1": simple_icmpv6_packet(icmp_type=3, icmp_code=1),
+ "type=3 code=2": simple_icmpv6_packet(icmp_type=3, icmp_code=2),
+ }
+
+ nonmatching = {
+ "type=2 code=1": simple_icmpv6_packet(icmp_type=2, icmp_code=1),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class IPv6ICMPCode(MatchTest):
+ """
+ Match on ipv6 icmp code
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x86dd),
+ ofp.oxm.ip_proto(58),
+ ofp.oxm.icmpv6_code(2),
+ ])
+
+ matching = {
+ "type=3 code=2": simple_icmpv6_packet(icmp_type=3, icmp_code=2),
+ "type=5 code=2": simple_icmpv6_packet(icmp_type=5, icmp_code=2),
+ }
+
+ nonmatching = {
+ "type=2 code=1": simple_icmpv6_packet(icmp_type=2, icmp_code=1),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class ArpOp(MatchTest):
+ """
+ Match on ARP operation
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0806),
+ ofp.oxm.arp_op(3),
+ ])
+
+ matching = {
+ "op=3": simple_arp_packet(arp_op=3),
+ }
+
+ nonmatching = {
+ "op=4": simple_arp_packet(arp_op=4),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class ArpSPA(MatchTest):
+ """
+ Match on ARP sender IP
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0806),
+ ofp.oxm.arp_spa(0xc0a80001), # 192.168.0.1
+ ])
+
+ matching = {
+ "192.168.0.1": simple_arp_packet(ip_snd="192.168.0.1"),
+ }
+
+ nonmatching = {
+ "192.168.0.2": simple_arp_packet(ip_snd="192.168.0.2"),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class ArpSPASubnetMasked(MatchTest):
+ """
+ Match on ARP sender IP (subnet mask)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0806),
+ # 192.168.0.0/20 (255.255.240.0)
+ ofp.oxm.arp_spa_masked(0xc0a80000, 0xfffff000),
+ ])
+
+ matching = {
+ "192.168.0.1": simple_arp_packet(ip_snd='192.168.0.1'),
+ "192.168.0.2": simple_arp_packet(ip_snd='192.168.0.2'),
+ "192.168.4.2": simple_arp_packet(ip_snd='192.168.4.2'),
+ "192.168.0.0": simple_arp_packet(ip_snd='192.168.0.0'),
+ "192.168.15.255": simple_arp_packet(ip_snd='192.168.15.255'),
+ }
+
+ nonmatching = {
+ "192.168.16.0": simple_arp_packet(ip_snd='192.168.16.0'),
+ "192.167.255.255": simple_arp_packet(ip_snd='192.167.255.255'),
+ "192.168.31.1": simple_arp_packet(ip_snd='192.168.31.1'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class ArpSPAMasked(MatchTest):
+ """
+ Match on ARP sender IP (arbitrarily masked)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0806),
+ # 192.168.0.1 255.254.255.255
+ ofp.oxm.arp_spa_masked(0xc0a80001, 0xfffeffff),
+ ])
+
+ matching = {
+ "192.168.0.1": simple_arp_packet(ip_snd='192.168.0.1'),
+ "192.169.0.1": simple_arp_packet(ip_snd='192.169.0.1'),
+ }
+
+ nonmatching = {
+ "192.168.0.2": simple_arp_packet(ip_snd='192.168.0.2'),
+ "192.167.0.1": simple_arp_packet(ip_snd='192.167.0.1'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class ArpTPA(MatchTest):
+ """
+ Match on ARP target IP
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0806),
+ ofp.oxm.arp_tpa(0xc0a80001), # 192.168.0.1
+ ])
+
+ matching = {
+ "192.168.0.1": simple_arp_packet(ip_tgt="192.168.0.1"),
+ }
+
+ nonmatching = {
+ "192.168.0.2": simple_arp_packet(ip_tgt="192.168.0.2"),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class ArpTPASubnetMasked(MatchTest):
+ """
+ Match on ARP target IP (subnet mask)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0806),
+ # 192.168.0.0/20 (255.255.240.0)
+ ofp.oxm.arp_tpa_masked(0xc0a80000, 0xfffff000),
+ ])
+
+ matching = {
+ "192.168.0.1": simple_arp_packet(ip_tgt='192.168.0.1'),
+ "192.168.0.2": simple_arp_packet(ip_tgt='192.168.0.2'),
+ "192.168.4.2": simple_arp_packet(ip_tgt='192.168.4.2'),
+ "192.168.0.0": simple_arp_packet(ip_tgt='192.168.0.0'),
+ "192.168.15.255": simple_arp_packet(ip_tgt='192.168.15.255'),
+ }
+
+ nonmatching = {
+ "192.168.16.0": simple_arp_packet(ip_tgt='192.168.16.0'),
+ "192.167.255.255": simple_arp_packet(ip_tgt='192.167.255.255'),
+ "192.168.31.1": simple_arp_packet(ip_tgt='192.168.31.1'),
+ }
+
+ self.verify_match(match, matching, nonmatching)
+
+class ArpTPAMasked(MatchTest):
+ """
+ Match on ARP target IP (arbitrarily masked)
+ """
+ def runTest(self):
+ match = ofp.match([
+ ofp.oxm.eth_type(0x0806),
+ # 192.168.0.1 255.254.255.255
+ ofp.oxm.arp_tpa_masked(0xc0a80001, 0xfffeffff),
+ ])
+
+ matching = {
+ "192.168.0.1": simple_arp_packet(ip_tgt='192.168.0.1'),
+ "192.169.0.1": simple_arp_packet(ip_tgt='192.169.0.1'),
+ }
+
+ nonmatching = {
+ "192.168.0.2": simple_arp_packet(ip_tgt='192.168.0.2'),
+ "192.167.0.1": simple_arp_packet(ip_tgt='192.167.0.1'),
+ }
+
+ self.verify_match(match, matching, nonmatching)