diff --git a/README.md b/README.md index d6388f4..67d02a1 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ The following software is required to run PTF: 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: +The default packet manipulation module for `ptf` is `Scapy`. To install it use: ```text pip install scapy==2.5.0 ``` @@ -72,6 +72,94 @@ yum install tcpdump apt-get install tcpdump ``` +### Using `bf_pktpy` as an alternate packet manipulation module + +The Python module `bf_pktpy` is included as part of the +`open-p4studio` repository: + ++ https://github.com/p4lang/open-p4studio/tree/main/pkgsrc/ptf-modules/bf-pktpy + +It was developed as an alternative to `scapy`. The tradeoffs of using +`bf_pktpy` vs. `scapy` are: + ++ `scapy` implements more functionality, but is licensed under the + copyleft GNU General Public License v2.0 (see + https://github.com/secdev/scapy/blob/master/LICENSE), so may be + undesirable in use cases where you wish your tests to be released + under a different license. ++ `bf_pktpy` implements only a small subset of the functionality of + `scapy`, but it does include support for very commonly-used packet + headers. It is released under an Apache 2.0 license (see + https://github.com/p4lang/open-p4studio/blob/main/pkgsrc/ptf-modules/bf-pktpy/LICENSE). + +The package `bf_pktpy` is not currently available from PyPI. To +install `bf_pktpy` from source code, choose one of these methods: + +```bash +pip install "bf-pktpy@git+https://github.com/p4lang/open-p4studio#subdirectory=pkgsrc/ptf-utils/bf-pktpy" +``` + +```bash +git clone https://github.com/p4lang/open-p4studio +cd open-p4studio/pkgsrc/ptf-utils/bf-pktpy +pip install . +``` + +To make effective use of `bf_pktpy` you must also install these +additional Python packages. All are released under an MIT or +BSD-3-Clause license, which are compatible for releasing with +`bf_pktpy`, or for importing in a project with most licenses, +including `Apache-2.0`, `BSD-3-Clause`, `GPL-2.0-only`, or many +others. + +```bash +pip install six getmac scapy_helper psutil +sudo apt-get install python3-dev +pip install netifaces +``` + +If you want to use `bf_pktpy` when running the command `ptf` from the +command line, provide the `-pmm` option as shown below. + +```bash +ptf -pmm bf_pktpy.ptf.packet_pktpy +``` + +If you want to write a Python program that imports `ptf` and causes it +to use `bf_pktpy` instead of the default `scapy`, you can do so as +follows in your Python code: + +```python +import ptf +ptf.config["packet_manipulation_module"] = "bf_pktpy.ptf.packet_pktpy" +import ptf.packet +``` + +The above methods are the highest precedence way of choosing the +packet manipulation module used by `ptf`. If you do not use those +methods, another way is to assign the packet manipulation module name +to the environment variable `PTF_PACKET_MANIPULATION_MODULE`, e.g. in +Bash: + +```bash +export PTF_PACKET_MANIPULATION_MODULE="bf_pktpy.ptf.packet_pktpy" +``` + +When running such a program, you should see the following line printed +to standard output confirming that it is using `bf_pktpy` instead of +`scapy`: + +```text +Using packet manipulation module: bf_pktpy.ptf.packet_pktpy +``` + +If instead you see this line of output, `ptf` is using `scapy`: + +```text +Using packet manipulation module: ptf.packet_scapy +``` + + ## Run PTF Once you have written tests and your switch is running, you can run 'ptf'. Use diff --git a/ptf b/ptf index d7f1262..b129450 100755 --- a/ptf +++ b/ptf @@ -97,8 +97,6 @@ config_default = { "test_case_timeout": None, # Socket options "socket_recv_size": 4096, - # Packet manipulation provider module - "packet_manipulation_module": "ptf.packet_scapy", # Other configuration "port_map": None, } @@ -435,6 +433,21 @@ be subtracted from the result by prefixing them with the '^' character. """ config = config_default.copy() for key in config.keys(): config[key] = getattr(args, key) + # For selecting the packet manipulation module when running the + # `ptf` command, the order of precedence is: + # (1) If the `--packet-manipulation-module` command line option is + # present, use its value. + # (2) Otherwise, if the environment variable + # PTF_PACKET_MANIPULATION_MODULE is defined, use its value. + # (3) Otherwise, use "ptf.packet_scapy" + pmm_key = "packet_manipulation_module" + if getattr(args, pmm_key): + # Then use the value from the command line option. + config[pmm_key] = getattr(args, pmm_key) + elif os.getenv("PTF_PACKET_MANIPULATION_MODULE"): + config[pmm_key] = os.getenv("PTF_PACKET_MANIPULATION_MODULE") + else: + config[pmm_key] = "ptf.packet_scapy" return (config, args) diff --git a/src/ptf/packet.py b/src/ptf/packet.py index a1c1a27..19cf9b9 100644 --- a/src/ptf/packet.py +++ b/src/ptf/packet.py @@ -1,20 +1,44 @@ """A pluggable packet module This module dynamically imports definitions from packet manipulation module, -specified in config or provided as an agrument. +specified in config or provided as an argument. The default one is Scapy, but one can develop its own packet manipulation framework and then, create an implementation of packet module for it (for Scapy it is packet_scapy.py) """ +import os as _os from ptf import config -__module = __import__( - config.get("packet_manipulation_module", "ptf.packet_scapy"), fromlist=["*"] -) +# When module ptf.packet is imported, this is the order of precedence for +# deciding which packet manipulation module is used: +# (1) If the key "packet_manipulation_module" is present in dict +# 'config', then its value should be a string, and it will be used +# as the name of the module to use. +# (2) Otherwise, if the environment variable +# PTF_PACKET_MANIPULATION_MODULE is defined, use its value. +# (3) Otherwise, the default module name "ptf.packet_scapy" is used. + +# Note: Applications using ptf.packet can control the choice of packet +# manipulation module in any way they wish by doing the following: +# +# import ptf +# ptf.config["packet_manipulation_module"] = my_app_choice_of_pmm +# import ptf.packet + +if "packet_manipulation_module" in config: + _packet_manipulation_module = config["packet_manipulation_module"] +else: + _env_val = _os.getenv("PTF_PACKET_MANIPULATION_MODULE") + if _env_val: + _packet_manipulation_module = _env_val + else: + _packet_manipulation_module = "ptf.packet_scapy" + +__module = __import__(_packet_manipulation_module, fromlist=["*"]) __keys = [] -# import logic - everything from __all__ if provided, otherwise everything not starting -# with underscore +# import logic - everything from __all__ if provided, otherwise +# everything not starting with underscore. print("Using packet manipulation module: %s" % __module.__name__) if "__all__" in __module.__dict__: __keys = __module.__dict__["__all__"] diff --git a/src/ptf/packet_scapy.py b/src/ptf/packet_scapy.py index a8a43b9..37c0b4a 100644 --- a/src/ptf/packet_scapy.py +++ b/src/ptf/packet_scapy.py @@ -19,6 +19,7 @@ import scapy.packet import scapy.main import scapy.fields + import scapy.sendrecv import scapy.utils if not config.get("disable_ipv6", False): @@ -151,3 +152,31 @@ def mysummary(self): # Scapy has its own hexdump hexdump = scapy.utils.hexdump ls = scapy.packet.ls + +# The names below are assigned here so that, like the other names +# above, they can be used by importers of the ptf.packet module as if +# they were defined inside of ptf.packet, and they are commonly +# available as ptf.packet. regardless whether you use scapy or +# bf-pktpy as the packet manipulation module. +# +# Commented-out lines are so because they have no equivalent function +# implemented in bf-pktpy yet. + +# sndrcv = scapy.sendrecv.sndrcv +send = scapy.sendrecv.send +sendp = scapy.sendrecv.sendp +# sendpfast = scapy.sendrecv.sendpfast +sr = scapy.sendrecv.sr +sr1 = scapy.sendrecv.sr1 +srp = scapy.sendrecv.srp +srp1 = scapy.sendrecv.srp1 +srloop = scapy.sendrecv.srloop +srploop = scapy.sendrecv.srploop +# sndrcvflood = scapy.sendrecv.sndrcvflood +# srflood = scapy.sendrecv.srflood +# sr1flood = scapy.sendrecv.sr1flood +# srpflood = scapy.sendrecv.srpflood +# srp1flood = scapy.sendrecv.srp1flood +sniff = scapy.sendrecv.sniff +bridge_and_sniff = scapy.sendrecv.bridge_and_sniff +# tshark = scapy.sendrecv.tshark