Code Monkey home page Code Monkey logo

ptf's Introduction

PTF Packet Testing Framework


Introduction

PTF is a Python based dataplane test framework. It is based on unittest, which is included in the standard Python distribution.

This document is meant to provide an introduction to the framework, discuss the basics of running tests and to provide examples of how to add tests.

Most of the code was taken from the OFTest framework. However, PTF focuses on the dataplane and is independent of OpenFlow. We also added several new features.


Contributing

Before you start pushing new changes to this repository, you should notice that the entire src/ code is automatically formatted with Black. Our GitHub Action pipeline will verify that code is correctly formatted and fail if not.

Two separate targets in makefile were prepared to make our work easier. If you want to run a check, type make format-check, but if you want to reformat your code, please use make format.

Black is listed in the requirements-dev.txt. To install it locally, you can use make set-dev or pip install -r requirements-dev.txt. More information about Black, you find at Black's GitHub Page


Longer Start

Dependencies

The following software is required to run PTF:

  • Python 3.x
  • Scapy 2.4.5 (unless you provide another packet manipulation module)
  • pypcap (optional - VLAN tests will fail without this)
  • tcpdump (optional - Scapy will complain if it's missing)

Root/sudo privilege is required on the host, in order to run ptf.

The default packet manipulator tool for ptf is Scapy. To install it use:

pip install scapy==2.4.5

To enable VLAN tests, you need to install pypcap:

pip install pypcap

For developer purpose, you should install requirements-dev.txt with:

pip install -r requirements-dev.txt

The tcpdump is optional, but to install it use:

# on CentOS
yum install tcpdump

# on Debian base
apt-get install tcpdump

Run PTF

Once you have written tests and your switch is running, you can run 'ptf'. Use --help to see command line switches.

For example:

sudo ./ptf --test-dir mytests/ --pypath $PWD \
	 --interface 0@veth1 --interface 1@veth3 --interface 2@veth5 \
	 --interface 3@veth7 --interface 4@veth9 --interface 5@veth11 \
	 --interface 6@veth13 --interface 7@veth15 --interface 8@veth17

This will run all the tests included in the mytests directory. The --pypath option can be used to easily add directories to the Python PATH. This is useful if you use a Python interface to configure your data plane (as part of your tests). The --interface option (or -i) can be used to specify the interfaces on which to inject packets (along with the corresponding port number).

Install PTF

PTF can be installed with pip:

# Install the latest version
pip install ptf
# Install specific version
pip install ptf==0.9.1

You can also install a local copy of PTF with pip install ..


Writing tests for your switch

Take a look at the example directory. This is not a working example as it is (the switch is not included), but it will show you how to write tests. This directory contains the following:

  • run_client.sh: a wrapper around ptf
  • switch_sai_thrift: empty directory, this is where the Python bindings to program the switch's control plane would be copied
  • mytests/sai_base_test.py: a wrapper Python class around PTF's BaseTest class. It is the base class for all the tests we added to mytests/switch.py
  • mytests/switch.py: some example tests

Running the example

If you want to run the example, you will need to obtain p4factory. For the following, I will assume that you cloned the repository and installed the dependencies. I will assume that environment variable P4FACTORY contains the path to the cloned repository.

First, you need to create the required veths:

cd $P4FACTORY/tools/
sudo ./veth_setup.sh

The next step is to compile the target switch and to run it:

cd $P4FACTORY/targets/switch/
make bm-switchsai
sudo ./behavioral-model

Finally, you can run the example tests:

cd <ptf-dir>/example/
sudo ../ptf --test-dir mytests/ \
	 --pypath $P4FACTORY/targets/switch/tests/pd_thrift/
	 --interface 0@veth1 --interface 1@veth3 --interface 2@veth5 \
	 --interface 3@veth7 --interface 4@veth9 --interface 5@veth11 \
	 --interface 6@veth13 --interface 7@veth15 --interface 8@veth17

New features

We added the following features to the base OFTest framework:

Filters

They can be used to discard some of the packets received from the switch. Take a look at sai_base_test.py for an example. You will see the following code testutils.add_filter(testutils.not_ipv6_filter) which tells PTF to discard received IPv6 packets. You can add your own filters (they have to be callable Python objects which take a Scapy packet as input).

Ternary matching

A PTF test -just like an OFTest test- matches the received packets against expected packets. This is an exact match. However, sometimes one does not care about all the fields in the packets. PTF introduces the Mask class which lets you specified some field you do not care about when performing the match. For example:

import mask
m = mask.Mask(expected_pkt)
m.set_do_not_care_scapy(IP, 'ttl')
verify_packets(<test>, m, <port list>)

Test timeout

A timeout for test cases can be specified using the --test-case-timeout command line option. This timeout must be expressed in seconds. A timeout of 0 is the same as no timeout (the default). If the timeout expires before the test is done executing, an exception will be raised and the test counts as an error. A timeout can also be specified for each individual test case, using the @testtimeout decorator, which needs to be imported from ptf.testutils. This timeout takes precedence over the global timeout passed on the command line.

Pluggable packet manipulation module

By default, ptf uses Scapy as the packet manipulation module, but it can also operate on a different one.

Such module must define/implement the same symbols, as defined in Scapy implementation of packet. Most of them are just names of most common frame headers (Ether, IP, TCP, UDP, ...).

The default implementation can be found in file /src/ptf/packet_scapy.py. It can be used as a reference when implementing your own version.

To use another packet manipulation module, one needs to provide it as argument -pmm or --packet-manipulation-module when running the ptf binary.

sudo ./ptf <other parameters> -pmm foo.packet_foo 

Please make sure that this module is loaded into the runtime before running any tests.

Sharding

You can achieve parallelization by splitting tests into N groups and running them with separate PTF processes. Each PTF instance will run disjoint subset of all selected tests.

For example to run specific set of tests across 3 PTF instances:

$ ssh mynode0 sudo ./ptf --test-dir mytests --num-shards 3 --shard-id 0 all ^other &
$ ssh mynode1 sudo ./ptf --test-dir mytests --num-shards 3 --shard-id 1 all ^other &
$ ssh mynode2 sudo ./ptf --test-dir mytests --num-shards 3 --shard-id 2 all ^other &

Configuring PTF

Platforms

The "platform" is a configuration file (written in Python) that tells PTF how to send packets to and receive packets from the dataplane of the switch.

eth

The default platform, eth, uses Linux Ethernet interfaces and is configured with the -i option (or --interface). Pass the option as -i ofport@interface, for example -i 1@eth1. If no -i options are given the the default configuration uses vEths.

remote

Another common platform, remote, provides support for testing of switches on a different host. This can be useful for cases where interfaces are not available on one host (i.e. they're not bound to a Linux interface driver) or where PTF cannot run on the same host (unsupported OS, missing software, etc.).

This can be enable by modifying the platforms/remote.py file to point to 4 NICs on the host running PTF, like so:

remote_port_map = {
    (0, 23) : "eth2", # port 23 of device 0 is connected to physical port on the server eth2
    (0, 24) : "eth3", # port 24 of device 0 is connected to physical port on the server eth3
    (0, 25) : "eth4",
    (0, 26) : "eth5"
}

nn

We introduce a new platform, nn, which uses [nanomsg] (http://nanomsg.org/) to send and receive packet to the switch. We support IPC and TCP nanomsg sockets. When using this platform, you need to make sure that the Python package [nnpy] (https://github.com/nanomsg/nnpy) is installed. With nn, do not use --interface, instead use --device-socket. For each device, you need to provide a list of enabled ports and a nanomsg socket address. For example:

--device-socket 0-{1,2,5-8}@ipc:///tmp/bmv2_packets_1.ipc

This command will enable ports 1, 2, 5, 6, 7, 8 on device 0. Packets for device 0 will be captured and send on IPC socket ipc:///tmp/bmv2_packets_1.ipc.

Passing Parameters to Tests

There is a facility for passing test-specific parameters into tests that works as follows. On the command line, give the parameter

--test-params="key1=17;key2=True"

You can then access these parameters in your tests' Pyhton code using the following code:

import ptf.testutils as testutils
# Returns a dictionary which includes all your parameters
test_params = testutils.test_params_get()
# Returns the value of the parameter "param", or None if not found
param_value = testutils.test_param_get("param")

Take a look at sai_base_test.py for an example.

Grouping Tests together

It is very easy to create groups of tests, using the provided group Python decorator. Simply decorate your test with @group(<name of group>).

Take a look at switch.py for an example.

One given test can belong to several groups. You can choose to run only the tests belonging to a given group using a command like this one:

sudo ./ptf --test-dir mytests/ --pypath $PWD <name of group>

We also provide a convenient disabled decorator for tests.

Support for multidevice tests

The original OFTest was meant to unit test a single OF-compliant switch. With PTF, we tried to add support for testing a network of several devices. If you do not intend to use this multi-device feature, you do not need to worry about it, it should not impact you. If you want to leverage this feature, here is what you need to do:

  • when adding interfaces, instead of writing <port_number>@<interface_name>, you need to write <device_number>-<port_number>@<interface_name>
  • when sending a packet, the port number becomes a tuple (device, port): send_packet(self, (<device_number>, <port_number>), pkt)
  • the verify_* functions where also updated to include device information. For example: verify_packets(self, pkt, device_number=<device_number>, ports=<port_list>). For more information, you can take a look at these functions in src/ptf/dataplane.py.

ptf's People

Contributors

antoninbas avatar chestercheng avatar cruzj avatar dependabot[bot] avatar dileephr avatar fruffy avatar jafingerhut avatar jklr avatar jleveque avatar karolrom-intel avatar lguohan avatar linarnan avatar masoudmj avatar mbrar avatar mellanox-zhichenr avatar nexsabre avatar paulnice avatar pavel-shirshov avatar pjhsieh avatar rohanmahajan avatar saynb avatar sborkows avatar sethfowler avatar slicking avatar theojepsen avatar vkochan-mlnx avatar vmytnykx avatar vshuper avatar w1nda avatar yi-tseng avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ptf's Issues

[ptf_nn_agent] Unable to capture packets with VLAN tag

Description
When running traffic test using ptf_nn_agent infrastructure, any test involving vlan tagging will fail.

Issue is observed due to RAW socket used by ptf_nn_agent module for sending/receiving packets. It happens because Linux kernel does VLAN offloading on receiving packets and stores the tag in a different place. In order to fetch VLAN tag additional logic is required or PCAP library can be used. ptf already uses such approach for ptf dataplane implementation and looks like the same should be done for ptf_nn_agent.

Link to the ptf_nn_agent implementation with RAW socket used:
https://github.com/p4lang/ptf/blob/master/ptf_nn/ptf_nn_agent.py#L252

Implementation of fetching VLAN tag is here:
https://github.com/p4lang/ptf/blob/master/src/ptf/afpacket.py#L112

Below is example of using above approach:
https://github.com/p4lang/ptf/blob/master/src/ptf/dataplane.py#L163

Steps to reproduce

  • Configure VLAN
  • Send tagged packet
  • Expect tagged packet to be received on ptf port, but observe that packet is untagged

Observed behavior
If send tagged packet using ptf_nn_agent then it is received as untagged

Expected behavior
If send tagged packet using ptf_nn_agent then it must be received as tagged since otherwise any VLAN test fails

Additional information
This issue was found in SONiC test framework while using ptf_nn_agent for VLAN test: sonic-net/sonic-mgmt#1343

"TypeError: ord() expected string of length 1, but int found" thrown in get_mac function in netutils.py

Install ptf 0.9.3, my python version is 3.7.
In my script, just want to get mac address based on device number and port number, such as self.dataplane.get_mac(0, 2).
It throws TypeError error. I think it shouldn't use ord(char) in get_mac function for python3.
After remove ord, it returns correct mac address.
How to resolve this compatible issue for python3? Thanks.

Traceback (most recent call last):
  File "ptftests/dhcp_relay_test.py", line 114, in setUp
    self.server_iface_mac = self.dataplane.get_mac(0, 2)
  File "/env-python3/lib/python3.7/site-packages/ptf/dataplane.py", line 990, in get_mac
    return self.ports[(device_number, port_number)].mac()
  File "/env-python3/lib/python3.7/site-packages/ptf/dataplane.py", line 198, in mac
    return netutils.get_mac(self.interface_name)
  File "/env-python3/lib/python3.7/site-packages/ptf/netutils.py", line 57, in get_mac
    return ":".join(["%02x" % ord(char) for char in get_if(iff, SIOCGIFHWADDR)[18:24]])
  File "/env-python3/lib/python3.7/site-packages/ptf/netutils.py", line 57, in <listcomp>
    return ":".join(["%02x" % ord(char) for char in get_if(iff, SIOCGIFHWADDR)[18:24]])
TypeError: ord() expected string of length 1, but int found
>>> import socket
>>> from fcntl import ioctl
>>> import struct
>>> SIOCGIFHWADDR = 0x8927
>>> s = socket.socket()
>>> ifreq = ioctl(s, SIOCGIFHWADDR, struct.pack("16s16x", 'eth2'.encode("utf-8")))
>>> ifreq
b'eth2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\xfeT\x00\xba~\x02\x00\x00\x00\x00\x00\x00\x00\x00'
>>> ":".join(["%02x" % ord(char) for char in ifreq[18:24]])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <listcomp>
TypeError: ord() expected string of length 1, but int found
>>> ":".join(["%02x" % char for char in ifreq[18:24]])
'fe:54:00:ba:7e:02'
>>> exit()
(env-python3) root@14d1d69ee82f:~# ifconfig eth2
eth2: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 9216
        inet6 fe80::fc54:ff:feba:7e02  prefixlen 64  scopeid 0x20<link>
        ether fe:54:00:ba:7e:02  txqueuelen 1000  (Ethernet)
        RX packets 14360  bytes 1683464 (1.6 MiB)
        RX errors 0  dropped 9  overruns 0  frame 0
        TX packets 703  bytes 69618 (67.9 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

PTF creates double log entries

I have been using PTF with the default logging settings. This configuration has all testcases write to the same log file (ptf.log).

I have noticed that from the second testcase onwards, all log entries are duplicated. For example:

20:05:24.469  root      : INFO    : ** START TEST CASE session_hello.HelloWhenSendRefreshShouldSucceed
20:05:24.469  root      : INFO    : ** START TEST CASE session_hello.HelloWhenSendRefreshShouldSucceed

After debugging, I determined that src/ptf/init.py - open_logfile is not cleaning up the handlers from the previous test case properly.

This function iterates through the existing handlers and removes them:

    # Remove any existing handlers
    for handler in logger.handlers:
        logger.removeHandler(handler)
        handler.close()

Unfortunately, Python does not support mutating a list while iterating through it with a for loop. The result was that some of the handlers were not deleted.

Instead, we need to create a copy of the list before modifying. The easiest way is with a slice as described by:

https://stackoverflow.com/questions/41443336/python-2-7-remove-handler-object-or-logger

The code would then become:

    # Remove any existing handlers
    for handler in logger.handlers[:]:
        logger.removeHandler(handler)
        handler.close()

I am using Python 2.7 in my development environment.

`ptf --version` should return the correct value

abasYLVDQ:ptf abas$ python3 -m pip install --upgrade ptf
Requirement already satisfied: ptf in /usr/local/lib/python3.11/site-packages (0.9.4)
Requirement already satisfied: six==1.16.0 in /usr/local/lib/python3.11/site-packages/six-1.16.0-py3.11.egg (from ptf) (1.16.0)

[notice] A new release of pip is available: 23.0.1 -> 23.1.2
[notice] To update, run: python3.11 -m pip install --upgrade pip
abasYLVDQ:ptf abas$ ptf --version
/usr/local/bin/ptf:19: DeprecationWarning: the imp module is deprecated in favour of importlib and slated for removal in Python 3.12; see the module's documentation for alternative uses
  import imp
ptf 0.1

The displayed version here should be 0.9.4 or v0.9.4.
0.1 was a placeholder, and is no longer valid.

scapy is GPL but PTF is not

PTF is licensed under Apache 2.0, but links against scapy, which is GPL 2+. This licensing is incompatible. PTF needs to either find an Apache 2.0-compatible replacement for scapy or be released under a GPL-compatible licence.

Is scapy-vxlan still recommended?

The README states:

We recommend that you install our extension of Scapy, which you can obtain here. It adds support for additional header types: VXLAN, ERSPAN, GENEVE, MPLS and NVGRE.

But it doesn't look like scapy-vxlan has been updated for many years.

Also upstream Scapy has added Python 3 support and the following modules:

Python module imp is deprecated in favor of importlib

For at least the last month or so I have gotten this warning message when running ptf:

$ ptf --version
/usr/local/bin/ptf:19: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
  import imp
ptf 0.1

Renew Travis licencse

Travis CI doesn't work anymore. This repo runs into risk of breaking existing code with every new commit.

how can i solve this problem

./runptf.sh

Started simple_switch_grpc. Waiting 2 seconds before starting PTF test ...
[sudo] password for liuwei:
Calling target program-options parser
Adding interface veth0 as port 0
Adding interface veth2 as port 1
Adding interface veth4 as port 2
Adding interface veth6 as port 3
Adding interface veth8 as port 4
/usr/local/lib/python3.10/dist-packages/ptf-0.9.3-py3.10.egg/EGG-INFO/scripts/ptf:19: DeprecationWarning: the imp module is deprecated in favour of importlib and slated for removal in Python 3.12; see the module's documentation for alternative uses
import imp
Adding interface veth10 as port 5
Adding interface veth12 as port 6
Adding interface veth14 as port 7
Server listening on 0.0.0.0:9559
Thrift: Wed Apr 26 21:19:16 2023 TServerSocket::listen() BIND 9090
Thrift returned an exception when trying to bind to port 9090
The exception is: Could not bind: Address already in use
You may have another process already using this port, maybe another instance of bmv2.
Using packet manipulation module: ptf.packet_scapy
2023-04-26 21:19:16,632 - root - INFO - Importing platform: eth
2023-04-26 21:19:16,633 - root - INFO - port map: {(0, 0): 'veth1', (0, 1): 'veth3', (0, 2): 'veth5', (0, 3): 'veth7', (0, 4): 'veth9', (0, 5): 'veth11', (0, 6): 'veth13', (0, 7): 'veth15'}
2023-04-26 21:19:16,633 - root - INFO - Autogen random seed: 42669981
Traceback (most recent call last):
File "/usr/local/bin/ptf", line 4, in
import('pkg_resources').run_script('ptf==0.9.3', 'ptf')
File "/usr/lib/python3/dist-packages/pkg_resources/init.py", line 656, in run_script
self.require(requires)[0].run_script(script_name, ns)
File "/usr/lib/python3/dist-packages/pkg_resources/init.py", line 1453, in run_script
exec(code, namespace, namespace)
File "/usr/local/lib/python3.10/dist-packages/ptf-0.9.3-py3.10.egg/EGG-INFO/scripts/ptf", line 859, in
ptf.dataplane_instance.port_add(ifname, device, port)
File "/usr/local/lib/python3.10/dist-packages/ptf-0.9.3-py3.10.egg/ptf/dataplane.py", line 682, in port_add
self.ports[port_id] = self.dppclass(
File "/usr/local/lib/python3.10/dist-packages/ptf-0.9.3-py3.10.egg/ptf/dataplane.py", line 145, in init
self.socket.bind((interface_name, self.ETH_P_ALL))
OSError: [Errno 19] No such device

Can PTF be distributed by pypi.org (pip)?

Can PTF be distributed by pypi.org (pip)?

Distribution by the PIP will be easier pip install ptf, than

git clone https://github.com/p4lang/ptf && cd ptf &&  python setup.py install

# or even

pip install git+https://github.com/p4lang/ptf.git

Mask has problem with headers with conditional fields (like VXLAN)

Example (truncated paths to files):

> from ptf.mask import Mask
Using packet manipulation module: ptf.packet_scapy
> from scapy.all import VXLAN, hexdump
> pkt = VXLAN(flags=0x80, gpflags=0x23, gpid=0x1234, vni=0x1337)
> hexdump(pkt)
0000  80 23 12 34 00 13 37 00                          .#.4..7.
> pkt2 = VXLAN(flags=0x80, gpflags=0x23, gpid=0x5678, vni=0x1337)
> hexdump(pkt2)
0000  80 23 56 78 00 13 37 00                          .#Vx..7.
> m = Mask(pkt)
> m.set_do_not_care_packet(VXLAN, "gpid")
Traceback (most recent call last):
  File "...\Python36_64\lib\site-packages\IPython\core\interactiveshell.py", line 2878, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-17-3b392d872fb8>", line 1, in <module>
    m.set_do_not_care_packet(VXLAN, "gpid")
  File "...\ptf\src\ptf\mask.py", line 47, in set_do_not_care_packet
    self.set_do_not_care(hdr_offset * 8 + offset, bitwidth)
  File "...\ptf\src\ptf\mask.py", line 22, in set_do_not_care
    self.mask[offsetB] = self.mask[offsetB] & (~(1 << (7 - offsetb)))
IndexError: list index out of range

This happens, because I am setting here flags field in a way, that a few first conditional fields will not be included in a header.
The definition of VXLAN from Scapy: https://github.com/secdev/scapy/blob/dcd54d59c94b83632b74e268e8b14026cbcd67c8/scapy/layers/vxlan.py#L28

As the flag NextProtocol is not set, first 3 fields won't be included, only gpflags and gpid will be.

Now, in set_do_not_care_packet method of Mask we are looking into fields_desc definitions and there are present all conditional fields, despite their conditions and whether they are met. Thus, we are getting in this example "out of range" problem.

Please, add the installer

Please, make sure that there is an installer (be it setup.py or "make install") that would allow the user to install PTF into any location of their choice as long with all the modules as appropriate.

PI nanomsg hangs if the corresponding switch crashes

I have been successfully using nanomsg instead of virtual interfaces to run simple switch. An example is this PR:
p4lang/p4c#3951
However, there is one problem. If simple_switch crashes, the nn framework seems to get stuck at this point:

self.packet_inject.port_remove(self.port_number)

It tries to remove the port but does not get a response back, presumably because the socket is blocking indefinitely? Is there a straightforward way to fix this?

PTF does not support wildcard byte akin to STF

STF supports wildcard byte in expected packet using * so that the byte can be ignored. PTF does not. I think, this code change to PTF should allow wildcard byte akin to STF.

diff --git a/src/ptf/mask.py b/src/ptf/mask.py
index 3b45d9a..3987e96 100644
--- a/src/ptf/mask.py
+++ b/src/ptf/mask.py
@@ -61,6 +61,8 @@ class Mask:
             return False
         exp_pkt = str(self.exp_pkt)
         for i in xrange(self.size):
+            if exp_pkt[i] == "*":
+                continue;
             if (ord(exp_pkt[i]) & self.mask[i]) != (ord(pkt[i]) & self.mask[i]):
                 return False
         return True

running SAI case error : socket.error :[Errno 19] No such device

run cmd [root@sai saithrift]ptf -- test-dir tests saiacl.IPAclTest -- interface '0@eth0' --interface '1@eth1' --interface '2@eth2' -t server='10.0.0.1' ;port_map_file='default_interface_to_front_map.ini'"

File "/opt/Python-2.7.13/lib/python2.7/socket.py",kine 228 ,in meth
return getattr(self._sock,name)(*args)
socket.error :[Errno 19] No such device

Usage of default_timeout vs default_negative_timeout

Currently there are 2 options which ptf takes in - --default-timeout and --default-negative-timeout
default_negative_timeout is used by all negative verify functions like verify_no_packet here

But default_timeout is not being used by verify_packet here

But instead it is being used directly here
which will eventually be called in dataplane poll() here

So it seems the only way one can use the value passed in via --default-timeout is by explicitly mentioning timeout as -1 in verify_packet which seems wrong.

Field mrtime in IGMP packet has different name in Scapy: mrcode

In testutils.simple_igmp_packet we are calling IGMP constructor:

packet.IGMP(type=igmp_type, gaddr=igmp_gaddr, mrtime=igmp_mrtime)

However, if we look into Scapy definition:
https://github.com/secdev/scapy/blob/dcd54d59c94b83632b74e268e8b14026cbcd67c8/scapy/contrib/igmp.py#L62
they name this field "mrcode".

What is more, according to RFC: https://datatracker.ietf.org/doc/html/rfc2236
field is named Max Response Time.

It means, that this code won't work, failing with AttributeError: mrtime .

Now, my question is: should we change it here to "mrcode" or ask Scapy to change name of this field?

FCS support in PTF

Hello, I m testing packet flow on my DUT, the packet is correctly sent and received by the PTF host. But the verification is failing at the PTF host, as the DUT is inserting FCS(frame check sequence) in the packet.
Is there a way to add FCS to packet generated in PTF ?

Documentation for PTF?

I am new to PTF. Where is documentation for PTF to show how to insert table entries, and add packets and expected packets to a PTF test? Also, how does one run a PTF test using p4c simple_switch?

I have only worked with STF which lets me use the following cmd to add a table entry:

table_add <table name> <action name> <match fields> ## => <action parameters> [priority]

and <packet port> and expect <port> to test packets with simple_switch.

Suggestions for eventually updating where Python3 packages are installed via pip?

I have been doing some early experiments with testing my install script that installs a bunch of P4 open source development tools with Ubuntu 23.04, and found that when you attempt to run a command like sudo pip install some-python-package, it gives the following error message:

$ sudo pip install ply
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.
    
    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.
    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
    sure you have python3-full installed.
    
    If you wish to install a non-Debian packaged Python application,
    it may be easiest to use pipx install xyz, which will manage a
    virtual environment for you. Make sure you have pipx installed.
    
    See /usr/share/doc/python3.11/README.venv for more information.

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

I believe this is because Ubuntu 23.04 has a newer version of pip installed by default than earlier Ubuntu versions, and this newer version of pip gives an error in this situation, not merely a warning as previous versions did.

Potentially the least-effort way to hack around this would be to add --break-system-packages to all sudo pip install ... commands, across all p4lang projects, and any dependencies they install.

The way recommended by Python developers appears to be to use a Python virtual environment and use pip to install packages into that. That also seems like it might require updates to all sudo pip install ... commands across p4lang projects to remove the sudo, as well as a common convention for assuming that a virtual env has been set up before their install scripts are run? Not sure.

Remove debian packaging rules

This is a proposal to remove https://github.com/p4lang/ptf/tree/main/debian
There is no one to maintain it and it has not been updated for 7 years. As it stands today, it is probably not useful to anyone.

Note that we now distribute PTF has a PyPI package, with the package being automatically updated when a new Git tag is created.

I plan to leave this issue open for a couple of weeks. After that delay, if no one has complained (or volunteered to maintain the python-ptf debian package for some current distributions), we will just remove the debian/ folder.

Documentation regading Python 3

According to Issue #106, support for python 3 is already merged into master. However, the documentation still states the requirement of python 2.7. Should be updated to match the solved issue.

`format_failure` throws an error because of unexpanded tuple

Culprit:

failure_report = "\n".join([format_failure(f) for f in failures])

The format_failure function here always expects two arguments. However, the failure list which is used to store arguments for format_failure only ever has tuples appended to it, and the unexpected tuples are passed to format_failure as a single argument leading to the below error:

test = <tests.common.plugins.ptfadapter.ptfadapter.PtfTestAdapter testMethod=runTest>
pkt = <Ether  dst=5a:08:8c:93:f8:02 src=5e:43:11:39:4c:38 type=0x800 |<IP  tos=0x0 i...|<Raw  load='0000000000000000000000000000000000000000000000000000000000' |>>>>>, ports = [2]
device_number = 0

    def verify_packets_any(test, pkt, ports=[], device_number=0):
        """
        Check that a packet is received on _any_ of the specified ports belonging to
        the given device (default device_number is 0).

        Also verifies that the packet is not received on any other ports for this
        device, and that no other packets are received on the device (unless --relax
        is in effect).
        """
        received = False
        failures = []
        for device, port in ptf_ports():
            if device != device_number:
                continue
            if port in ports:
                logging.debug("Checking for pkt on device %d, port %d", device_number, port)
                result = dp_poll(test, device_number=device, port_number=port, exp_pkt=pkt)
                if isinstance(result, test.dataplane.PollSuccess):
                    received = True
                else:
                    failures.append((port, result))
            else:
                verify_no_packet(test, pkt, (device, port))
        verify_no_other_packets(test)

        if not received:
            def format_failure(port, failure):
                return "On port %d:\n%s" % (port, failure.format())
>           failure_report = "\n".join([format_failure(f) for f in failures])
E           TypeError: format_failure() takes exactly 2 arguments (1 given)

device     = 0
device_number = 0
f          = (2, PollResult(device=None, port=None, packet=None, time=None))
failures   = [(2, PollResult(device=None, port=None, packet=None, time=None))]
format_failure = <function format_failure at 0x7fa25903f450>
pkt        = <Ether  dst=5a:08:8c:93:f8:02 src=5e:43:11:39:4c:38 type=0x800 |<IP  tos=0x0 i...|<Raw  load='0000000000000000000000000000000000000000000000000000000000' |>>>>>
port       = 71
ports      = [2]
received   = False
result     = PollResult(device=None, port=None, packet=None, time=None)
test       = <tests.common.plugins.ptfadapter.ptfadapter.PtfTestAdapter testMethod=runTest>

/usr/lib/python2.7/dist-packages/ptf/testutils.py:2506: TypeError

setup.py still ok?

Before this commit: ff79b8c

I had a bash script that was installing ptf this way on an Ubuntu 20.04 system:

git clone https://github.com/p4lang/ptf
cd ptf
sudo python3 setup.py install

After that commit, there is an exception thrown when I attempt that setup.py install command.

Should that command still work?

Is it anti-recommended to install ptf that way from source code?

Perhaps I should be using sudo pip install . instead?

Fix DeprecationWarning and replace `imp` with `importlib`

abasYLVDQ:ptf abas$ ptf --version
/usr/local/bin/ptf:19: DeprecationWarning: the imp module is deprecated in favour of importlib and slated for removal in Python 3.12; see the module's documentation for alternative uses
  import imp
ptf 0.1

I haven't checked the potential impact on the list of supported Python versions.

Drop support for Python 2.7

ptf still supports (or claims to support) Python 2.7 (as per the README), even though Python 2.x has been sunsetted several years ago
since we no longer test with Python 2.7, and it is very likely that Python 2.7 support is broken or will be broken in the future, we should just remove references to Python 2.7

[ptf]: EventDescriptor destructor fails with exception

Summary
EventDescriptor class has a bug in destructor implementation.
It tries to access nonexistent system library and fails with the exception:

Exception AttributeError: "'NoneType' object has no attribute 'close'" in <bound method EventDescriptor.__del__ of <ptf.ptfutils.EventDescriptor instance at 0x7f18966645a8>> ignored

line: https://github.com/p4lang/ptf/blob/master/src/ptf/ptfutils.py#L52

Steps to reproduce

  • Run PTF

Observed behavior
EventDescriptor destructor throws an exception

Expected behavior
EventDescriptor destructor should do a clean up gracefully

Unify test packet payload and allow for manual control

PTF provides a lot of very convenient functions to form test packets (simple_*_packet())

One thing that they do not allow is specifying the actual payload. Not only that, different functions use different payload for whatever reason.

For example, simple_tcp_packet uses:

pkt = pkt / codecs.decode("".join(["%02x" % (x % 256) for x in range(pktlen - len(pkt))]), "hex")

whereas simple_tcpv6_packet uses:

pkt /= "D" * (pktlen - len(pkt))

This makes direct comparisons a little difficult.

Here are two things which can changed:

  • Use the same payload across all functions (if applicable)
  • Add "payload" parameter so that the user can specify it manually

dhcp_discover_packet fills incorrect client mac address in Discover packet

PTF version: 0.9.3
Python:3.5

issue:
Run testutils.dhcp_discover_packet to send DHCP discover packet with testutils.dhcp_discover_packet(eth_client="fe:54:00:1c:7b:01", set_broadcast_bit=True), the client mac addr turns out to be c3be54001c7b
I found it fills incorrect client mac address in Discover packet.

The root cause is that it still uses str for chaddr not bytes type.
Can someone have a took for this issue? I provide the fix in last, please correct me if I am wrong. Thanks.

>>> import ptf.testutils as testutils
/env-python3/lib/python3.5/site-packages/scapy/config.py:520: CryptographyDeprecationWarning: Python 3.5 support will be dropped in the next release of cryptography. Please upgrade your Python.
  import cryptography
Using packet manipulation module: ptf.packet_scapy
>>> testutils.dhcp_discover_packet(eth_client="fe:54:00:1c:7b:01", set_broadcast_bit=True) 
<Ether  dst=ff:ff:ff:ff:ff:ff src=fe:54:00:1c:7b:01 type=IPv4 |<IP  frag=0 proto=udp src=0.0.0.0 dst=255.255.255.255 |<UDP  sport=bootpc dport=bootps |<BOOTP  op=BOOTREQUEST htype=1 hlen=6 hops=0 xid=0 secs=0 flags=B ciaddr=0.0.0.0 yiaddr=0.0.0.0 siaddr=0.0.0.0 giaddr=0.0.0.0 chaddr='þT\x00\x1c{\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' options='c\\x82Sc' |<DHCP  options=[message-type='discover' end] |>>>>>
>>> chaddr='þT\x00\x1c{\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>> chaddr.encode('utf-8').hex()
'c3be54001c7b0100000000000000000000'
>>> 

image

solution:
Change __dhcp_mac_to_chaddr as below to fix this issue.

def __dhcp_mac_to_chaddr(mac_addr="00:01:02:03:04:05"):
    """
    Private helper function to convert a 6-byte MAC address of form:
      '00:01:02:03:04:05'
    into a 16-byte chaddr byte string of form:
      '\x00\x01\x02\x03\x04\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

    """
    # chaddr = "".join([chr(int(octet, 16)) for octet in mac_addr.split(":")])
    # chaddr += "\x00" * 10
    import binascii
    chaddr = binascii.unhexlify(mac_addr.replace(':', ''))
    chaddr += b'\x00\x00\x00\x00\x00\x00'
    return chaddr

Then it prints out the correct client mac address:

>>> import ptf.testutils as testutils
/env-python3/lib/python3.5/site-packages/scapy/config.py:520: CryptographyDeprecationWarning: Python 3.5 support will be dropped in the next release of cryptography. Please upgrade your Python.
  import cryptography
Using packet manipulation module: ptf.packet_scapy
>>> testutils.dhcp_discover_packet(eth_client="fe:54:00:1c:7b:01", set_broadcast_bit=True)
<Ether  dst=ff:ff:ff:ff:ff:ff src=fe:54:00:1c:7b:01 type=IPv4 |<IP  frag=0 proto=udp src=0.0.0.0 dst=255.255.255.255 |<UDP  sport=bootpc dport=bootps |<BOOTP  op=BOOTREQUEST htype=1 hlen=6 hops=0 xid=0 secs=0 flags=B ciaddr=0.0.0.0 yiaddr=0.0.0.0 siaddr=0.0.0.0 giaddr=0.0.0.0 chaddr=b'\xfeT\x00\x1c{\x01\x00\x00\x00\x00\x00\x00' options='c\\x82Sc' |<DHCP  options=[message-type='discover' end] |>>>>>
>>> chaddr=b'\xfeT\x00\x1c{\x01\x00\x00\x00\x00\x00\x00'
>>> chaddr.hex()
'fe54001c7b01000000000000'

Setting up ptf isn't working for me

I'm pretty new to p4 and not very good with code, so I apologize ahead of time if this is a simple question. I am trying to run ptf and I'm not sure what the command sudo python setup.py install is doing, so it's hard for me to figure out what is going wrong. I am getting the error error: error in 'egg_base' option: 'src' does not exist or is not a directory when I run this command in the same docker that has the environment for the switch, and if I try to run this command outside of the docker, I get the error error: error in 'egg_base' option: 'src' does not exist or is not a directory. Is someone willing to help me figure out how to fix this issue and start using ptf? Also I cannot view the setuptools.html for some reason.

Thanks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.