aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorselias <samelias@cisco.com>2016-08-11 10:02:33 +0200
committerJan Gelety <jgelety@cisco.com>2016-08-25 21:34:48 +0000
commitf2711a847cd8de8dadce1049747e78f96bcae031 (patch)
treee6f0fb27e22c6d32fb4c0241e54a253eaad0ecbe
parent53ce7d17816de6516f3c21bd01835b8c1c474c79 (diff)
CSIT-233 IPv4 IPFIX - baseline tests
- add scapy classes for parsing IPFIX packets - add vat scripts and keywords for settings up IPFIX - add IPv4 IPFIX test suite Change-Id: I80ab76ca361c7920a01a46ad720b1c04acd0d147 Signed-off-by: selias <samelias@cisco.com>
-rw-r--r--resources/libraries/python/Classify.py38
-rw-r--r--resources/libraries/python/IPFIXSetup.py129
-rw-r--r--resources/libraries/python/IPFIXUtil.py102
-rw-r--r--resources/libraries/python/PacketVerifier.py22
-rw-r--r--resources/libraries/robot/ipfix.robot67
-rw-r--r--resources/templates/vat/classify_add_session_generic.vat1
-rw-r--r--resources/templates/vat/ipfix_exporter_set.vat1
-rw-r--r--resources/templates/vat/ipfix_interface_enable.vat1
-rw-r--r--resources/templates/vat/ipfix_stream_set.vat1
-rw-r--r--resources/templates/vat/ipfix_table_add.vat1
-rwxr-xr-xresources/traffic_scripts/ipfix_check.py198
-rw-r--r--tests/func/ipfix/ipfix_ipv4.robot205
12 files changed, 758 insertions, 8 deletions
diff --git a/resources/libraries/python/Classify.py b/resources/libraries/python/Classify.py
index dfa5c3377d..8dbe3fb25f 100644
--- a/resources/libraries/python/Classify.py
+++ b/resources/libraries/python/Classify.py
@@ -37,6 +37,7 @@ class Classify(object):
:rtype: tuple(int, int, int)
:raises RuntimeError: If VPP can't create table.
"""
+
output = VatExecutor.cmd_from_template(node, "classify_add_table.vat",
ip_version=ip_version,
direction=direction)
@@ -205,6 +206,43 @@ class Classify(object):
hex_value=hex_value)
@staticmethod
+ def vpp_configures_classify_session_generic(node, session_type, table_index,
+ skip_n, match_n, match,
+ match2=''):
+ """Configuration of classify session.
+
+ :param node: VPP node to setup classify session.
+ :param session_type: Session type - hit-next, l2-hit-next, acl-hit-next
+ or policer-hit-next, and their respective parameters.
+ :param table_index: Classify table index.
+ :param skip_n: Number of skip vectors based on mask.
+ :param match_n: Number of match vectors based on mask.
+ :param match: Match value - l2, l3, l4 or hex, and their
+ respective parameters.
+ :param match2: Additional match values, to avoid using overly long
+ variables in RobotFramework.
+ :type node: dict
+ :type session_type: str
+ :type table_index: int
+ :type skip_n: int
+ :type match_n: int
+ :type match: str
+ :type match2: str
+ """
+
+ match = ' '.join((match, match2))
+
+ with VatTerminal(node) as vat:
+ vat.vat_terminal_exec_cmd_from_template(
+ "classify_add_session_generic.vat",
+ type=session_type,
+ table_index=table_index,
+ skip_n=skip_n,
+ match_n=match_n,
+ match=match,
+ )
+
+ @staticmethod
def compute_classify_hex_mask(ip_version, protocol, direction):
"""Compute classify hex mask for TCP or UDP packet matching.
diff --git a/resources/libraries/python/IPFIXSetup.py b/resources/libraries/python/IPFIXSetup.py
new file mode 100644
index 0000000000..f0f35f3bc6
--- /dev/null
+++ b/resources/libraries/python/IPFIXSetup.py
@@ -0,0 +1,129 @@
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# 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.
+
+"""IPFIX setup library"""
+
+from resources.libraries.python.topology import Topology
+from resources.libraries.python.VatExecutor import VatTerminal
+
+
+class IPFIXSetup(object):
+ """Class contains methods for seting up IPFIX reporting on DUTs."""
+
+ def __init__(self):
+ """Initializer."""
+ pass
+
+ @staticmethod
+ def setup_ipfix_exporter(node, collector, source, fib=None, mtu=None,
+ interval=None):
+ """Setup an IPFIX exporter on node to export collected flow data.
+
+ :param node: DUT node.
+ :param collector: IP address of flow data collector.
+ :param source: IP address of local interface to send flow data from.
+ :param fib: fib table ID.
+ :param mtu: Maximum transfer unit of path to collector.
+ :param interval: Frequency of sending template packets, in seconds.
+ :type node: dict
+ :type collector: str
+ :type source: str
+ :type fib: int
+ :type mtu: int
+ :type interval: int
+ """
+
+ fib = "vrf_id {0}".format(fib) if fib else ''
+ mtu = "path_mtu {0}".format(mtu) if mtu else ''
+ interval = "template_interval {0}".format(interval) if interval else ''
+
+ with VatTerminal(node, json_param=False) as vat:
+ vat.vat_terminal_exec_cmd_from_template('ipfix_exporter_set.vat',
+ collector=collector,
+ source=source,
+ fib=fib,
+ mtu=mtu,
+ interval=interval)
+
+ @staticmethod
+ def assign_interface_to_flow_table(node, interface, table_id,
+ ip_version='ip4'):
+ """Assigns a VPP interface to the specified classify table for IPFIX
+ flow data collection.
+
+ :param node: DUT node.
+ :param interface: An interface on the DUT node.
+ :param table_id: ID of a classify table.
+ :param ip_version: Version of IP protocol. Valid options are ip4, ip6.
+ :type node: dict
+ :type interface: str or int
+ :type table_id: int
+ :type ip_version: str
+ """
+
+ if isinstance(interface, basestring):
+ sw_if_index = Topology.get_interface_sw_index(node, interface)
+ elif isinstance(interface, int):
+ sw_if_index = interface
+ else:
+ raise TypeError
+
+ table = "{0}-table {1}".format(ip_version, table_id)
+
+ with VatTerminal(node, json_param=False) as vat:
+ vat.vat_terminal_exec_cmd_from_template(
+ "ipfix_interface_enable.vat",
+ interface=sw_if_index,
+ table=table,
+ delete='')
+
+ @staticmethod
+ def set_ipfix_stream(node, domain=None, src_port=None):
+ """Set an IPFIX export stream. Can be used to break up IPFIX reports
+ into separate reporting domains.
+
+ :param node: DUT node.
+ :param domain: Desired index number of exporting domain.
+ :param src_port: Source port to use when sending IPFIX packets. Default
+ is the standard IPFIX port 4739.
+ :type node: dict
+ :type domain: int
+ :type src_port: int
+ """
+
+ domain = "domain {0}".format(domain) if domain else ''
+ src_port = "src_port {0}".format(src_port) if src_port else ''
+
+ with VatTerminal(node, json_param=False) as vat:
+ vat.vat_terminal_exec_cmd_from_template("ipfix_stream_set.vat",
+ domain=domain,
+ src_port=src_port)
+
+ @staticmethod
+ def assign_classify_table_to_exporter(node, table_id, ip_version='ip4'):
+ """Assign a classify table to an IPFIX exporter. Classified packets will
+ be included in the IPFIX flow report.
+
+ :param node: DUT node.
+ :param table_id: ID of a classify table.
+ :param ip_version: Version of IP protocol. Valid options are ip4, ip6.
+ :type node: dict
+ :type table_id: int
+ :type ip_version: str
+ """
+
+ with VatTerminal(node, json_param=False) as vat:
+ vat.vat_terminal_exec_cmd_from_template("ipfix_table_add.vat",
+ table=table_id,
+ ip_version=ip_version,
+ add_del='add')
diff --git a/resources/libraries/python/IPFIXUtil.py b/resources/libraries/python/IPFIXUtil.py
new file mode 100644
index 0000000000..f3247a8982
--- /dev/null
+++ b/resources/libraries/python/IPFIXUtil.py
@@ -0,0 +1,102 @@
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# 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.
+
+"""IPFIX utilities library. Provides classes that allow scapy to work
+with IPFIX packets.
+
+ Note:
+ Template and data sets in one packet are not supported.
+ Option template sets (Set_ID = 3) are not supported.
+ """
+
+
+from scapy.all import Packet, bind_layers
+from scapy.fields import *
+from scapy.layers.inet import UDP
+from scapy.contrib.ppi_geotag import UTCTimeField
+
+
+class IPFIXHandler(object):
+ """Class for handling IPFIX packets. To use, create instance of class before
+ dissecting IPFIX packets with scapy, then run update_template every time
+ an IPFIX template packet is received."""
+
+ template_elements = {
+ 4: ByteField("Protocol_ID", 0x00),
+ 7: ShortField("src_port", 0),
+ 8: IPField("IPv4_src", ""),
+ 11: ShortField("dst_port", 0),
+ 12: IPField("IPv4_dst", ""),
+ 86: LongField("packetTotalCount", 0),
+ 180: ShortField("udp_src_port", 0),
+ 181: ShortField("udp_dst_port", 0),
+ 182: ShortField("tcp_src_port", 0),
+ 183: ShortField("tcp_dst_port", 0),
+ }
+
+ def __init__(self):
+ """Initializer, registers IPFIX header and template layers with scapy.
+ """
+ bind_layers(UDP, IPFIXHeader, dport=4739)
+ bind_layers(IPFIXHeader, IPFIXTemplate, Set_ID=2)
+
+ def update_template(self, packet):
+ """Updates IPFIXData class with new data template. Registers IPFIX data
+ layer with scapy using the new template.
+
+ :param packet: Packet containing an IPFIX template.
+ :type packet: scapy.Ether
+ """
+ template_list = packet['IPFIX template'].Template
+ template_id = packet['IPFIX template'].Template_ID
+
+ IPFIXData.fields_desc = []
+ for item in template_list[::2]:
+ try:
+ IPFIXData.fields_desc.append(self.template_elements[item])
+ except KeyError:
+ raise KeyError(
+ "Unknown IPFIX template element with ID {0}".format(item))
+ bind_layers(IPFIXHeader, IPFIXData, Set_ID=template_id)
+ # if the packet doesn't end here, assume it contains more data sets
+ bind_layers(IPFIXData, IPFIXData)
+
+
+class IPFIXHeader(Packet):
+ """Class for IPFIX header."""
+ name = "IPFIX header"
+ fields_desc = [StrFixedLenField("Version", 0x000a, length=2),
+ ShortField("Message Length", 0),
+ UTCTimeField("Timestamp(UTC)", ""),
+ IntField("Sequence Number", 0),
+ IntField("Observation Domain ID", 0),
+ ShortField("Set_ID", 0),
+ ShortField("Set_Length", 0)
+ ]
+
+
+class IPFIXTemplate(Packet):
+ """Class for IPFIX template layer."""
+ name = "IPFIX template"
+ fields_desc = [ShortField("Template_ID", 256),
+ ShortField("nFields", 2),
+ FieldListField("Template", [], ShortField("type_len", ""),
+ count_from=lambda p: p.nFields*2)
+ ]
+
+
+class IPFIXData(Packet):
+ """Class for IPFIX data layer. Needs to be updated with
+ a template before use."""
+ name = "IPFIX flow data"
+ fields_desc = []
diff --git a/resources/libraries/python/PacketVerifier.py b/resources/libraries/python/PacketVerifier.py
index 78c3670135..59ea2db5a3 100644
--- a/resources/libraries/python/PacketVerifier.py
+++ b/resources/libraries/python/PacketVerifier.py
@@ -204,7 +204,7 @@ class RxQueue(PacketVerifier):
def __init__(self, interface_name):
PacketVerifier.__init__(self, interface_name)
- def recv(self, timeout=3, ignore=None):
+ def recv(self, timeout=3, ignore=None, verbose=True):
"""Read next received packet.
Returns scapy's Ether() object created from next packet in the queue.
@@ -212,9 +212,11 @@ class RxQueue(PacketVerifier):
arrives in given timeout queue.Empty exception will be risen.
:param timeout: How many seconds to wait for next packet.
- :param ignore: Packet list that should be ignored.
+ :param ignore: List of packets that should be ignored.
+ :param verbose: Used to suppress detailed logging of received packets.
:type timeout: int
:type ignore: list
+ :type verbose: bool
:return: Ether() initialized object from packet data.
:rtype: scapy.Ether
@@ -226,8 +228,9 @@ class RxQueue(PacketVerifier):
pkt = self._sock.recv(0x7fff)
pkt_pad = auto_pad(pkt)
print 'Received packet on {0} of len {1}'.format(self._ifname, len(pkt))
- Ether(pkt).show2()
- print
+ if verbose:
+ Ether(pkt).show2()
+ print
if ignore is not None:
for i, ig_pkt in enumerate(ignore):
@@ -238,7 +241,7 @@ class RxQueue(PacketVerifier):
# Found the packet in ignore list, get another one
# TODO: subtract timeout - time_spent in here
ignore.remove(ig_pkt)
- return self.recv(timeout, ignore)
+ return self.recv(timeout, ignore, verbose)
return Ether(pkt)
@@ -254,16 +257,19 @@ class TxQueue(PacketVerifier):
def __init__(self, interface_name):
PacketVerifier.__init__(self, interface_name)
- def send(self, pkt):
+ def send(self, pkt, verbose=True):
"""Send packet out of the bound interface.
:param pkt: Packet to send.
+ :param verbose: Used to supress detailed logging of sent packets.
:type pkt: string or scapy Packet derivative.
+ :type verbose: bool
"""
print 'Sending packet out of {0} of len {1}'.format(self._ifname,
len(pkt))
- Ether(str(pkt)).show2()
- print
+ if verbose:
+ Ether(str(pkt)).show2()
+ print
pkt = auto_pad(str(pkt))
self._sock.send(pkt)
diff --git a/resources/libraries/robot/ipfix.robot b/resources/libraries/robot/ipfix.robot
new file mode 100644
index 0000000000..a5adacfc0f
--- /dev/null
+++ b/resources/libraries/robot/ipfix.robot
@@ -0,0 +1,67 @@
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# 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.
+
+"""Traffic keywords"""
+
+*** Settings ***
+| Library | resources.libraries.python.TrafficScriptExecutor
+| Library | resources.libraries.python.InterfaceUtil
+| Resource | resources/libraries/robot/default.robot
+| Documentation | Traffic keywords
+
+*** Keywords ***
+| Send packets and verify IPFIX
+| | [Documentation] | Send simple TCP or UDP packets from source interface\
+| | ... | to destination interface. Listen for IPFIX flow report on source\
+| | ... | interface and verify received report against number of packets sent.
+| | ...
+| | ... | *Arguments:*
+| | ...
+| | ... | - tg_node - TG node. Type: dictionary
+| | ... | - dst_node - Destination node. Type: dictionary
+| | ... | - src_int - Source interface. Type: string
+| | ... | - dst_int - Destination interface. Type: string
+| | ... | - src_ip - Source IP address. Type: string
+| | ... | - dst_ip - Destination IP address. Type: string
+| | ... | - protocol - TCP or UDP (Optional, default is TCP). Type: string
+| | ... | - port - Source and destination ports to use
+| | ... | (Optional, default is port 20). Type: integer
+| | ... | - count - Number of packets to send
+| | ... | (Optional, default is one packet). Type: integer
+| | ... | - timeout - Timeout value in seconds (Optional, default is 10 sec).
+| | ... | Should be at least twice the configured IPFIX flow report interval.
+| | ... | Type: integer
+| | ...
+| | ... | *Return:*
+| | ...
+| | ... | - No value returned
+| | ...
+| | ... | *Example:*
+| | ...
+| | ... | \| Send packets and verify IPFIX \| ${nodes['TG']} | ${nodes['DUT1']}\
+| | ... | \| eth1 \| GigabitEthernet0/8/0 \| 16.0.0.1 \| 192.168.0.2 \| UDP \
+| | ... | \| ${20} \| ${5} \| ${10} \|
+| | ... |
+| | [Arguments] | ${tg_node} | ${dst_node} | ${src_int} | ${dst_int} |
+| | ... | ${src_ip} | ${dst_ip} | ${protocol}=tcp | ${port}=20 | ${count}=1
+| | ... | ${timeout}=${10}
+| | ${src_mac}= | Get Interface Mac | ${tg_node} | ${src_int}
+| | ${dst_mac}= | Get Interface Mac | ${dst_node} | ${dst_int}
+| | ${src_int_name}= | Get interface name | ${tg_node} | ${src_int}
+| | ${dst_int_name}= | Get interface name | ${dst_node} | ${dst_int}
+| | ${args}= | Traffic Script Gen Arg | ${dst_int_name} | ${src_int_name}
+| | ... | ${src_mac} | ${dst_mac} | ${src_ip} | ${dst_ip}
+| | ${args}= | Set Variable
+| | ... | ${args} --protocol ${protocol} --port ${port} --count ${count}
+| | Run Traffic Script On Node | ipfix_check.py | ${tg_node} | ${args}
+| | ... | ${timeout}
diff --git a/resources/templates/vat/classify_add_session_generic.vat b/resources/templates/vat/classify_add_session_generic.vat
new file mode 100644
index 0000000000..b100629c4e
--- /dev/null
+++ b/resources/templates/vat/classify_add_session_generic.vat
@@ -0,0 +1 @@
+classify_add_del_session {type} table-index {table_index} skip_n {skip_n} match_n {match_n} match {match} \ No newline at end of file
diff --git a/resources/templates/vat/ipfix_exporter_set.vat b/resources/templates/vat/ipfix_exporter_set.vat
new file mode 100644
index 0000000000..9217375e01
--- /dev/null
+++ b/resources/templates/vat/ipfix_exporter_set.vat
@@ -0,0 +1 @@
+set_ipfix_exporter collector_address {collector} src_address {source} {fib} {mtu} {interval} \ No newline at end of file
diff --git a/resources/templates/vat/ipfix_interface_enable.vat b/resources/templates/vat/ipfix_interface_enable.vat
new file mode 100644
index 0000000000..5432a6916a
--- /dev/null
+++ b/resources/templates/vat/ipfix_interface_enable.vat
@@ -0,0 +1 @@
+flow_classify_set_interface sw_if_index {interface} {table} {delete} \ No newline at end of file
diff --git a/resources/templates/vat/ipfix_stream_set.vat b/resources/templates/vat/ipfix_stream_set.vat
new file mode 100644
index 0000000000..97d84beb1c
--- /dev/null
+++ b/resources/templates/vat/ipfix_stream_set.vat
@@ -0,0 +1 @@
+set_ipfix_classify_stream {domain} {src_port} \ No newline at end of file
diff --git a/resources/templates/vat/ipfix_table_add.vat b/resources/templates/vat/ipfix_table_add.vat
new file mode 100644
index 0000000000..87b0cc169d
--- /dev/null
+++ b/resources/templates/vat/ipfix_table_add.vat
@@ -0,0 +1 @@
+ipfix_classify_table_add_del table {table} {ip_version} {add_del} \ No newline at end of file
diff --git a/resources/traffic_scripts/ipfix_check.py b/resources/traffic_scripts/ipfix_check.py
new file mode 100755
index 0000000000..14b5a074db
--- /dev/null
+++ b/resources/traffic_scripts/ipfix_check.py
@@ -0,0 +1,198 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# 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.
+
+"""Traffic script - IPFIX listener."""
+
+import sys
+from ipaddress import IPv4Address, IPv6Address, AddressValueError
+
+from scapy.layers.inet import IP, TCP, UDP
+from scapy.layers.inet6 import IPv6
+from scapy.layers.l2 import Ether
+
+from resources.libraries.python.IPFIXUtil import IPFIXHandler, IPFIXData
+from resources.libraries.python.PacketVerifier import RxQueue, TxQueue, auto_pad
+from resources.libraries.python.TrafficScriptArg import TrafficScriptArg
+
+
+def valid_ipv4(ip):
+ """Check if IP address has the correct IPv4 address format.
+
+ :param ip: IP address.
+ :type ip: str
+ :return: True in case of correct IPv4 address format,
+ otherwise return false.
+ :rtype: bool
+ """
+ try:
+ IPv4Address(unicode(ip))
+ return True
+ except (AttributeError, AddressValueError):
+ return False
+
+
+def valid_ipv6(ip):
+ """Check if IP address has the correct IPv6 address format.
+
+ :param ip: IP address.
+ :type ip: str
+ :return: True in case of correct IPv6 address format,
+ otherwise return false.
+ :rtype: bool
+ """
+ try:
+ IPv6Address(unicode(ip))
+ return True
+ except (AttributeError, AddressValueError):
+ return False
+
+
+def main():
+ """Send packets to VPP, then listen for IPFIX flow report. Verify that
+ the correct packet count was reported."""
+ args = TrafficScriptArg(
+ ['src_mac', 'dst_mac', 'src_ip', 'dst_ip', 'protocol', 'port', 'count']
+ )
+
+ dst_mac = args.get_arg('dst_mac')
+ src_mac = args.get_arg('src_mac')
+ src_ip = args.get_arg('src_ip')
+ dst_ip = args.get_arg('dst_ip')
+ tx_if = args.get_arg('tx_if')
+
+ protocol = args.get_arg('protocol')
+ source_port = int(args.get_arg('port'))
+ destination_port = int(args.get_arg('port'))
+ count = int(args.get_arg('count'))
+
+ txq = TxQueue(tx_if)
+ rxq = RxQueue(tx_if)
+
+ # generate simple packet based on arguments
+ ip_version = None
+ if valid_ipv4(src_ip) and valid_ipv4(dst_ip):
+ ip_version = IP
+ elif valid_ipv6(src_ip) and valid_ipv6(dst_ip):
+ ip_version = IPv6
+ else:
+ raise ValueError("Invalid IP version!")
+
+ if protocol.upper() == 'TCP':
+ protocol = TCP
+ elif protocol.upper() == 'UDP':
+ protocol = UDP
+ else:
+ raise ValueError("Invalid type of protocol!")
+
+ pkt_raw = (Ether(src=src_mac, dst=dst_mac) /
+ ip_version(src=src_ip, dst=dst_ip) /
+ protocol(sport=int(source_port),
+ dport=int(destination_port)))
+
+ # do not print details for sent packets when sending more than one
+ if count > 1:
+ verbose = False
+ print("Sending more than one packet. Details will be filtered for "
+ "all packets sent.")
+ else:
+ verbose = True
+
+ pkt_pad = auto_pad(pkt_raw)
+ ignore = []
+ for _ in range(count):
+ txq.send(pkt_pad, verbose=verbose)
+ ignore.append(pkt_pad)
+
+ # allow scapy to recognize IPFIX headers and templates
+ ipfix = IPFIXHandler()
+
+ # clear receive buffer
+ while True:
+ pkt = rxq.recv(1, ignore=ignore, verbose=verbose)
+ if pkt is None:
+ break
+
+ data = None
+ # get IPFIX template and data
+ while True:
+ pkt = rxq.recv(5)
+ if pkt is None:
+ raise RuntimeError("RX timeout")
+ if pkt.haslayer("IPFIXHeader"):
+ if pkt.haslayer("IPFIXTemplate"):
+ # create or update template for IPFIX data packets
+ ipfix.update_template(pkt)
+ elif pkt.haslayer("IPFIXData"):
+ data = pkt.getlayer(IPFIXData).fields
+ break
+ else:
+ raise RuntimeError("Unable to parse IPFIX set after header.")
+ else:
+ raise RuntimeError("Received non-IPFIX packet or IPFIX header "
+ "not recognized.")
+
+ # verify packet count
+ if data["packetTotalCount"] != count:
+ raise RuntimeError(
+ "IPFIX reported wrong packet count. Count was {0},"
+ " but should be {1}".format(data["packetTotalCount"], count))
+ # verify IP addresses
+ keys = data.keys()
+ err = "{0} mismatch. Packets used {1}, but were classified as {2}."
+ if ip_version == IP:
+ if "IPv4_src" in keys:
+ if data["IPv4_src"] != src_ip:
+ raise RuntimeError(
+ err.format("Source IP", src_ip, data["IPv4_src"]))
+ if "IPv4_dst" in keys:
+ if data["IPv4_dst"] != dst_ip:
+ raise RuntimeError(
+ err.format("Destination IP", dst_ip, data["IPv4_dst"]))
+ else:
+ if "IPv6_src" in keys:
+ if data["IPv6_src"] != src_ip:
+ raise RuntimeError(
+ err.format("Source IP", src_ip, data["IPv6_src"]))
+ if "IPv6_dst" in keys:
+ if data["IPv6_dst"] != dst_ip:
+ raise RuntimeError(
+ err.format("Source IP", src_ip, data["IPv6_dst"]))
+ # verify port numbers
+ for item in ("src_port", "tcp_src_port", "udp_src_port"):
+ try:
+ if int(data[item]) != source_port:
+ raise RuntimeError(
+ err.format("Source port", source_port, data[item]))
+ except KeyError:
+ pass
+ for item in ("dst_port", "tcp_dst_port", "udp_dst_port"):
+ try:
+ if int(data[item]) != destination_port:
+ raise RuntimeError(
+ err.format("Source port", destination_port, data[item]))
+ except KeyError:
+ pass
+ # verify protocol ID
+ if "Protocol_ID" in keys:
+ if protocol == TCP and int(data["Protocol_ID"]) != 6:
+ raise RuntimeError("TCP Packets were classified as not TCP.")
+ if protocol == UDP and int(data["Protocol_ID"]) != 17:
+ raise RuntimeError("UDP Packets were classified as not UDP.")
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+
+ main()
diff --git a/tests/func/ipfix/ipfix_ipv4.robot b/tests/func/ipfix/ipfix_ipv4.robot
new file mode 100644
index 0000000000..3f2d753301
--- /dev/null
+++ b/tests/func/ipfix/ipfix_ipv4.robot
@@ -0,0 +1,205 @@
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# 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.
+
+*** Settings ***
+| Resource | resources/libraries/robot/default.robot
+| Resource | resources/libraries/robot/interfaces.robot
+| Resource | resources/libraries/robot/testing_path.robot
+| Resource | resources/libraries/robot/ipv4.robot
+| Resource | resources/libraries/robot/ipfix.robot
+| Library | resources.libraries.python.Classify.Classify
+| Library | resources.libraries.python.IPFIXSetup
+| Library | resources.libraries.python.Trace
+
+| Force Tags | HW_ENV | VM_ENV | 3_NODE_SINGLE_LINK_TOPO | EXPECTED_FAILING
+# TODO: Remove EXPECTED_FAILING tag once functionality is implemented (VPP-204)
+| Suite Setup | Run Keywords | Setup all TGs before traffic script
+| ... | AND | Update All Interface Data On All Nodes | ${nodes}
+| Test Setup | Setup all DUTs before test
+| Test Teardown | Run Keywords | Show packet trace on all DUTs | ${nodes}
+| ... | AND | Vpp Show Errors | ${nodes['DUT1']}
+| ... | AND | Show vpp trace dump on all DUTs
+| Documentation | *IPFIX ipv4 test cases*
+| ...
+| ... | IPFIX tests use 3-node topology TG - DUT1 - DUT2 - TG with
+| ... | one link between the nodes. DUT1 is configured with IPv4
+| ... | routing and static routes. IPFIX is configured on DUT1 with
+| ... | DUT1->TG interface as collector. Test packets are
+| ... | sent from TG to DUT1. TG listens for flow report packets
+| ... | and verifies that they contains flow record of test packets sent.
+
+*** Variables ***
+| ${dut1_to_tg_ip}= | 192.168.1.1
+| ${dut2_to_dut1_ip}= | 192.168.2.1
+| ${tg_to_dut1_ip}= | 16.0.0.1
+| ${prefix_length}= | 24
+| ${ip_version}= | ip4
+| ${port}= | 80
+
+*** Test Cases ***
+| TC01: DUT sends IPFIX template and data packets
+| | [Documentation]
+| | ... | [Top] TG-DUT1-DUT2-TG. [Cfg] On DUT1 configure IPFIX with TG interface
+| | ... | address as collector and a basic classify session.
+| | ... | [Ver] Make TG listen for IPFIX template and data packets, verify
+| | ... | that packet is received and correct. No packets are sent from TG.
+| | ... | [Ref] RFC 7011
+| | Given Path for 3-node testing is set
+| | ... | ${nodes['TG']} | ${nodes['DUT1']} | ${nodes['DUT2']} | ${nodes['TG']}
+| | And Interfaces in 3-node path are up
+| | And Set Interface Address | ${dut1_node}
+| | ... | ${dut1_to_tg} | ${dut1_to_tg_ip} | ${prefix_length}
+| | And Add ARP on DUT | ${dut1_node} | ${dut1_to_tg} | ${tg_to_dut1_ip}
+| | ... | ${tg_to_dut1_mac}
+| | ${table_index} | ${skip_n} | ${match_n}=
+| | ... | And VPP creates classify table L3 | ${dut1_node} | ${ip_version} | src
+| | And VPP configures classify session L3 | ${dut1_node} | permit
+| | ... | ${table_index} | ${skip_n} | ${match_n} | ${ip_version} | src
+| | ... | ${tg_to_dut1_ip}
+| | When Assign interface to flow table | ${dut1_node} | ${dut1_to_tg}
+| | ... | ${table_index} | ip_version=${ip_version}
+| | And Setup IPFIX exporter | ${dut1_node} | ${tg_to_dut1_ip}
+| | ... | ${dut1_to_tg_ip} | interval=5
+| | And Set IPFIX stream | ${dut1_node} | ${1}
+| | And Assign classify table to exporter | ${dut1_node} | ${table_index}
+| | ... | ${ip_version}
+| | Then Send packets and verify IPFIX | ${tg_node} | ${dut1_node}
+| | ... | ${tg_to_dut1} | ${dut1_to_tg} | ${tg_to_dut1_ip} | ${dut1_to_tg_ip}
+| | ... | count=0
+
+| TC02: DUT reports packet flow for traffic by source address
+| | [Documentation]
+| | ... | [Top] TG-DUT1-DUT2-TG. [Cfg] On DUT1 configure IPFIX with TG interface
+| | ... | address as collector and add classify session with TG source address.
+| | ... | [Ver] Make TG send a packet to DUT1, then listen for IPFIX template
+| | ... | and data packets, verify that IPFIX reported the received packet.
+| | ... | [Ref] RFC 7011
+| | Given Path for 3-node testing is set
+| | ... | ${nodes['TG']} | ${nodes['DUT1']} | ${nodes['DUT2']} | ${nodes['TG']}
+| | And Interfaces in 3-node path are up
+| | And Set Interface Address | ${dut1_node}
+| | ... | ${dut1_to_tg} | ${dut1_to_tg_ip} | ${prefix_length}
+| | And Add ARP on DUT | ${dut1_node} | ${dut1_to_tg} | ${tg_to_dut1_ip}
+| | ... | ${tg_to_dut1_mac}
+| | ${table_index} | ${skip_n} | ${match_n}=
+| | ... | And VPP creates classify table L3 | ${dut1_node} | ${ip_version} | src
+| | And VPP configures classify session L3 | ${dut1_node} | permit
+| | ... | ${table_index} | ${skip_n} | ${match_n} | ${ip_version} | src
+| | ... | ${tg_to_dut1_ip}
+| | When Assign interface to flow table | ${dut1_node} | ${dut1_to_tg}
+| | ... | ${table_index} | ip_version=${ip_version}
+| | And Setup IPFIX exporter | ${dut1_node} | ${tg_to_dut1_ip}
+| | ... | ${dut1_to_tg_ip} | interval=5
+| | And Set IPFIX stream | ${dut1_node} | ${1}
+| | And Assign classify table to exporter | ${dut1_node} | ${table_index}
+| | ... | ${ip_version}
+| | Then Send packets and verify IPFIX | ${tg_node} | ${dut1_node}
+| | ... | ${tg_to_dut1} | ${dut1_to_tg} | ${tg_to_dut1_ip} | ${dut1_to_tg_ip}
+
+| TC03: DUT reports packet flow for traffic with local destination address
+| | [Documentation]
+| | ... | [Top] TG-DUT1-DUT2-TG. [Cfg] On DUT1 configure IPFIX with TG interface
+| | ... | address as collector and add classify session with destination
+| | ... | address of DUT1.
+| | ... | [Ver] Make TG send a packet to DUT1, then listen for IPFIX template
+| | ... | and data packets, verify that IPFIX reported the received packet.
+| | ... | [Ref] RFC 7011
+| | Given Path for 3-node testing is set
+| | ... | ${nodes['TG']} | ${nodes['DUT1']} | ${nodes['DUT2']} | ${nodes['TG']}
+| | And Interfaces in 3-node path are up
+| | And Set Interface Address | ${dut1_node}
+| | ... | ${dut1_to_tg} | ${dut1_to_tg_ip} | ${prefix_length}
+| | And Add ARP on DUT | ${dut1_node} | ${dut1_to_tg} | ${tg_to_dut1_ip}
+| | ... | ${tg_to_dut1_mac}
+| | ${table_index} | ${skip_n} | ${match_n}=
+| | ... | And VPP creates classify table L3 | ${dut1_node} | ${ip_version} | dst
+| | And VPP configures classify session L3 | ${dut1_node} | permit
+| | ... | ${table_index} | ${skip_n} | ${match_n} | ${ip_version} | dst
+| | ... | ${dut1_to_tg_ip}
+| | When Assign interface to flow table | ${dut1_node} | ${dut1_to_tg}
+| | ... | ${table_index} | ip_version=${ip_version}
+| | And Setup IPFIX exporter | ${dut1_node} | ${tg_to_dut1_ip}
+| | ... | ${dut1_to_tg_ip} | interval=5
+| | And Set IPFIX stream | ${dut1_node} | ${1}
+| | And Assign classify table to exporter | ${dut1_node} | ${table_index}
+| | ... | ${ip_version}
+| | Then Send packets and verify IPFIX | ${tg_node} | ${dut1_node}
+| | ... | ${tg_to_dut1} | ${dut1_to_tg} | ${tg_to_dut1_ip} | ${dut1_to_tg_ip}
+
+| TC04: DUT reports packet flow for traffic with remote destination address
+| | [Documentation]
+| | ... | [Top] TG-DUT1-DUT2-TG. [Cfg] On DUT1 configure IPFIX with TG interface
+| | ... | address as collector and add classify session with destination
+| | ... | address of DUT2.
+| | ... | [Ver] Make TG send a packet to DUT2 through DUT1, then listen
+| | ... | for IPFIX template and data packets, verify that IPFIX reported
+| | ... | the received packet.
+| | ... | [Ref] RFC 7011
+| | Given Path for 3-node testing is set
+| | ... | ${nodes['TG']} | ${nodes['DUT1']} | ${nodes['DUT2']} | ${nodes['TG']}
+| | And Interfaces in 3-node path are up
+| | And Set Interface Address | ${dut1_node}
+| | ... | ${dut1_to_tg} | ${dut1_to_tg_ip} | ${prefix_length}
+| | And Add ARP on DUT | ${dut1_node} | ${dut1_to_tg} | ${tg_to_dut1_ip}
+| | ... | ${tg_to_dut1_mac}
+| | And Add ARP on DUT | ${dut1_node} | ${dut1_to_dut2} | ${dut2_to_dut1_ip}
+| | ... | ${dut2_to_dut1_mac}
+| | ${table_index} | ${skip_n} | ${match_n}=
+| | ... | And VPP creates classify table L3 | ${dut1_node} | ${ip_version} | dst
+| | And VPP configures classify session L3 | ${dut1_node} | permit
+| | ... | ${table_index} | ${skip_n} | ${match_n} | ${ip_version} | dst
+| | ... | ${dut2_to_dut1_ip}
+| | When Assign interface to flow table | ${dut1_node} | ${dut1_to_tg}
+| | ... | ${table_index} | ip_version=${ip_version}
+| | And Setup IPFIX exporter | ${dut1_node} | ${tg_to_dut1_ip}
+| | ... | ${dut1_to_tg_ip} | interval=5
+| | And Set IPFIX stream | ${dut1_node} | ${1}
+| | And Assign classify table to exporter | ${dut1_node} | ${table_index}
+| | ... | ${ip_version}
+| | Then Send packets and verify IPFIX | ${tg_node} | ${dut1_node}
+| | ... | ${tg_to_dut1} | ${dut1_to_tg} | ${tg_to_dut1_ip} | ${dut2_to_dut1_ip}
+
+| TC05: DUT reports packet flow for traffic by source and destination port
+| | [Documentation]
+| | ... | [Top] TG-DUT1-DUT2-TG. [Cfg] On DUT1 configure IPFIX with TG interface
+| | ... | address as collector and add classify session with TG source address
+| | ... | and source and destination ports.
+| | ... | [Ver] Make TG send a packet to DUT1, then listen for IPFIX template
+| | ... | and data packets, verify that IPFIX reported the received packet.
+| | ... | [Ref] RFC 7011
+| | Given Path for 3-node testing is set
+| | ... | ${nodes['TG']} | ${nodes['DUT1']} | ${nodes['DUT2']} | ${nodes['TG']}
+| | And Interfaces in 3-node path are up
+| | And Set Interface Address | ${dut1_node}
+| | ... | ${dut1_to_tg} | ${dut1_to_tg_ip} | ${prefix_length}
+| | And Add ARP on DUT | ${dut1_node} | ${dut1_to_tg} | ${tg_to_dut1_ip}
+| | ... | ${tg_to_dut1_mac}
+| | ${table_index} | ${skip_n} | ${match_n}=
+| | ... | And VPP creates classify table L3 | ${dut1_node} | ${ip_version}
+| | ... | src proto l4 src_port dst_port
+| | And VPP configures classify session generic | ${dut1_node}
+| | ... | acl-hit-next permit | ${table_index} | ${skip_n} | ${match_n}
+| | ... | l3 ${ip_version} src ${tg_to_dut1_ip}
+| | ... | proto 6 l4 src_port ${port} dst_port ${port}
+| | When Assign interface to flow table | ${dut1_node} | ${dut1_to_tg}
+| | ... | ${table_index} | ip_version=${ip_version}
+| | And Setup IPFIX exporter | ${dut1_node} | ${tg_to_dut1_ip}
+| | ... | ${dut1_to_tg_ip} | interval=5
+| | And Set IPFIX stream | ${dut1_node} | ${1}
+| | And Assign classify table to exporter | ${dut1_node} | ${table_index}
+| | ... | ${ip_version}
+| | Then Send packets and verify IPFIX | ${tg_node} | ${dut1_node}
+| | ... | ${tg_to_dut1} | ${dut1_to_tg} | ${tg_to_dut1_ip} | ${dut1_to_tg_ip}
+| | ... | port=${port}
+
+# TODO: DUT reports packet flow when ACL is configured with wildcards