Dear snappi, please meet Scapy!
As Scapy Project puts it:
Scapy is a powerful interactive packet manipulation program. It is able to forge or decode packets of a wide number of protocols, send them on the wire, capture them, match requests and replies, and much more.
In other words, Scapy allows you to craft any packet you want, including L2-4 headers as well as L7 payload. It can also send these packets into a network, as is.
Meanwhile, the Open Traffic Generator API with its Python client library snappi, is really great with scaling up the task of putting the packets onto a wire by leveraging OTG-compliant traffic generators, like Ixia-c. The OTG supports the notion of flows, with precise capabilities to schedule packet transmission - rate, interval, duration. It also has rich capabilities to iterate over ranges of MAC and IP addresses, TCP/UDP ports and other parameters.
Wouldn't it be nice if these two could meet and work as a team?
How would it work?
Let's assume you want to stress-test a network device with a large number of specific packets. For example, DNS requests & replies. With Scapy, it is easy to craft such payload. Note, how Scapy allows you to create a subset of packet layers. In this case, we're skipping Ethernet, IP and UDP, as OTG would take care of them.
from scapy.all import * # create custom DNS request payloads with Scapy requests = [DNS(id=0, rd=1, qr=0, qd=DNSQR(qtype="A", qname="example.com")), DNS(id=1, rd=1, qr=0, qd=DNSQR(qtype="AAAA", qname="example.com"))]
Now, with snappi, we can use these payloads to create a dedicated flow for each Scapy packet, with duration and rate we need.
import snappi api = snappi.api(location=OTG_API, verify=False) cfg = api.config() packet_count = 10 # send 10 packets per each flow # flows for requests for i in range(len(requests)): n = "request" + str(i) f = cfg.flows.flow(name=n)[-1] # will use UDP with custom payload eth, ip, udp, payload = f.packet.ethernet().ipv4().udp().custom() eth.src.value, eth.dst.value = "02:00:00:00:01:AA", "02:00:00:00:02:AA" ip.src.value, ip.dst.value = "192.0.2.1", "192.0.2.2" # increment UDP source port number for each packet udp.src_port.increment.start = 1024 udp.src_port.increment.step = 1 udp.src_port.increment.count = requests_count udp.dst_port.value = 53 # copy a payload from Scapy packet into a snappi flow payload.bytes = requests[i].build().hex() # number of packets to transmit f.duration.fixed_packets.packets = requests_count # delay between flows to simulate a sequence of packets: 1ms f.duration.fixed_packets.delay.microseconds = 1000 * i
Some details above are omitted, see scapy2otg.py for more.
As a result, the produced OTG configuration of the first flow of the DNS requests will have a custom payload after the UDP layer (see the very end of the YAML below):
flows: - duration: choice: fixed_packets fixed_packets: delay: choice: microseconds microseconds: 0 gap: 12 packets: 10 metrics: enable: true name: request0 packet: - choice: ethernet ethernet: dst: choice: value value: 02:00:00:00:02:AA src: choice: value value: 02:00:00:00:01:AA - choice: ipv4 ipv4: dst: choice: value value: 192.0.2.2 src: choice: value value: 192.0.2.1 - choice: udp udp: dst_port: choice: value value: 53 src_port: choice: increment increment: count: 10 start: 1024 step: 1 - choice: custom custom: bytes: 000001000001000000000000076578616d706c6503636f6d0000010001
Captured & Framed
When captured, the packet frames generated by Ixia-c look as DNS queries in Wireshark, with the exception of additional data signature Ixia-c adds at the end of each packet. The signature is needed to identify each packet at the receiving side, and measure latency, packet loss and other metrics. If you look into scapy2otg.py, the following line instructs Ixia-c to add the signature:
f.metrics.enable = True.
An alternative implementation that uses port-level metrics instead of packet signatures can be found in scapy2otg-port.py
Giving it a try
- Linux host or VM with sudo permissions and Docker support. See some ready-to-use options
git- how to install depends on your Linux distro.
Clone of the repository
git clone --recurse-submodules https://github.com/open-traffic-generator/otg-examples.git cd otg-examples/clab/ixia-c-b2b
p2.pcap to inspect captured packets.
Otherwise, follow a step-by-step guide:
snappi container image
Run the following only once, to build a container image where
snappi program will execute:
Deploy a lab
Run scapy2otg test
sudo docker exec -it clab-ixcb2b-snappi bash -c "OTG_API='https://clab-ixcb2b-ixia-c:8443' OTG_LOCATION_P1=eth1 OTG_LOCATION_P2=eth2 python scapy2otg.py"