As part of my new OpenShift-flavoured responsibilities, I’ve been trying to get DPDK-based applications running successfully on OpenShift-on-OpenStack. A pre-requisite step to this was getting DPDK configured on the host. With this done, I figured I’d investigate using an actual DPDK application in the guests. I’d done this before, though it’s been a while, and I did eventually get there. There were a few wrong turns along the way though, so I’ve documented the steps I finally settled on here in case they’re helpful to anyone else.
Cloud configuration
We’re going to be using OpenStack to provision our guests, so we obviously need
an OpenStack cloud to point to. Given that we’re using DPDK in the guests, we
also need to ensure that OVS-DPDK is used on the host(s). You can use a
pre-existing cloud if you’re sure it provides the latter, but I decided to use
dev-install
along with a modified version of the sample OVS-DPDK
configuration file to install a small, TripleO Standalone-based OpenStack
deployment on a borrowed server.
Once you have you cloud selected, you need to ensure you have appropriate
networks and an appropriate image and flavor. dev-install
provides most of
these for you, but can create them manually using the below commands.
First, the image. We’re going to use CentOS 8 Stream:
[stack@host]$ wget https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20210210.0.x86_64.qcow2
[stack@host]$ openstack image create \
--disk-format qcow2 \
--public \
--file CentOS-Stream-GenericCloud-8-20210210.0.x86_64.qcow2 \
centos8-stream
Next, the flavor. This is a pretty standard flavor except that we need to use pinned CPUs and enable hugepages:
[stack@host]$ openstack flavor create \
--vcpu 4 \
--ram 8192 \
--property hw:cpu_policy='dedicated' \
--property hw:mem_page_size='large' \
m1.large.nfv
You might also need to create a suitable network. dev-install
configures a
hostonly
provider network. If using another method, you need to create your
own provider network. Adding this is an exercise left to the reader. Once done,
we need to create a tenant network that we’ll use to configure additional
ports. Create this, along with a router:
[stack@host]$ openstack network create internal_net
[stack@host]$ openstack subnet create \
--network internal_net \
--subnet-range 192.168.200.0/24 \
internal_subnet
[stack@host]$ openstack router create router_a
[stack@host]$ openstack router set --external-gateway hostonly router_a
[stack@host]$ openstack router add subnet router_a internal_subnet
Finally, you need some way to access the guest. Create and upload a keypair:
[stack@host]$ ssh-keygen
[stack@host]$ openstack keypair create --public ~/.ssh/id_rsa.pub my-key
Initial server setup
We’re going to create two servers: guest-tx
and guest-rx
. These are
effectively identical save for the configuration we’ll eventually pass to
Pktgen-DPDK. Create the servers:
[stack@host]$ openstack server create \
--image centos8-stream \
--flavor m1.large.nfv \
--network internal_net \
--key-name my-key \
--wait \
guest-tx
[stack@host]$ openstack server create \
--image centos8-stream \
--flavor m1.large.nfv \
--network internal_net \
--key-name my-key \
--wait \
guest-rx
Create two floating IPs and attach them to the server so we can SSH into the machine:
[stack@host]$ tx_fip=$(openstack floating ip create hostonly-dpdk -f value -c name | tr -d '\n')
[stack@host]$ rx_fip=$(openstack floating ip create hostonly-dpdk -f value -c name | tr -d '\n')
[stack@host]$ openstack server add floating ip guest-tx $tx_fip
[stack@host]$ openstack server add floating ip guest-rx $rx_fip
You’ll also need to add security groups to enable SSH and potentially ICMP
(for ping) access. If you deployed with dev-install
like I did, then these
will already be present. If you use another mechanism then you can create
these security groups like so:
[stack@host]$ openstack security group create --description allow_ssh allow_ssh
[stack@host]$ openstack security group rule create --protocol tcp --dst-port 22 allow_ssh
[stack@host]$ openstack security group create --description allow_ping allow_ping
[stack@host]$ openstack security group rule create --protocol icmp allow_ping
You can then add these security groups to the servers:
[stack@host]$ openstack server add security group guest-tx allow_ssh
[stack@host]$ openstack server add security group guest-tx allow_ping
[stack@host]$ openstack server add security group guest-rx allow_ssh
[stack@host]$ openstack server add security group guest-rx allow_ping
You should now be able to SSH into the instances:
[stack@host]$ openstack server ssh --login centos guest-tx
Attach DPDK interfaces
With the initial server configuration out of the way, we can move onto
configuring the interfaces we will use for our DPDK application. It’s necessary
to use secondary interfaces since we’re going to be binding these interfaces
to the vfio-pci
driver. The second we do this, the devices will no longer
be usable by the kernel network stack which means, among other things, we
cannot use SSH on this interface. With that said, there’s not going to be
anything special about these interfaces: every interface attached to the
instance will be vhostuser
interface. We can confirm this by inspecting
the single interface currently attached to the instance:
[stack@host]$ sudo podman exec -it nova_libvirt virsh dumpxml 14 | xmllint --xpath '/domain/devices/interface' -
This will yield something like the following:
<interface type="vhostuser">
<mac address="fa:16:3e:97:3d:11"/>
<source type="unix" path="/var/lib/vhost_sockets/vhub20e827c-4a" mode="server"/>
<target dev="vhub20e827c-4a "/>
<model type="virtio"/>
<driver rx_queue_size="1024" tx_queue_size="1024"/>
<alias name="net0"/>
<address type="pci" domain="0x0000" bus="0x00" slot="0x03" function="0x0"/>
</interface>
With that bit out of the way, we can now add our additional network interface to each instance:
[stack@host]$ openstack port create --network internal_net dpdk-port-tx
[stack@host]$ openstack port create --network internal_net dpdk-port-rx
We’ll save the IP and MAC addresses for these interfaces: these will be useful later:
[stack@host]$ tx_dpdk_ip=$(openstack port show -f json dpdk-port-tx | jq -r '.fixed_ips[0].ip_address')
[stack@host]$ rx_dpdk_ip=$(openstack port show -f json dpdk-port-rx | jq -r '.fixed_ips[0].ip_address')
[stack@host]$ tx_dpdk_mac=$(openstack port show -f json dpdk-port-tx | jq -r '.mac_address')
[stack@host]$ rx_dpdk_mac=$(openstack port show -f json dpdk-port-rx | jq -r '.mac_address')
Now add them to the servers:
[stack@host]$ openstack server add port guest-tx dpdk-port-tx
[stack@host]$ openstack server add port guest-rx dpdk-port-rx
You can verify that these interfaces are of type vhostuser
again, if you like.
Compile Pktgen-DPDK
We now have our servers effectively configured from an “infrastructure” perspective. Going forward, everything will be done inside the guests. The first of these steps is to compile Pktgen-DPDK. This is necessary because while DPDK itself is packaged for CentOS, Pktgen-DPDK is not. We’re going to demonstrate the steps to run for one intance. You should then repeat these on the second instance.
First, let’s SSH into the machine:
[stack@host]$ openstack server ssh guest-tx --login centos
[centos@guest-tx]$
Install the dependencies. These are numerous:
[centos@guest-tx]$ sudo dnf config-manager --set-enabled powertools
[centos@guest-tx]$ sudo dnf groupinstall -y 'Development Tools'
[centos@guest-tx]$ sudo dnf install -y numactl-devel libpcap-devel meson driverctl
[centos@guest-tx]$ python3 -m pip install --user pyelftools # avoid requiring EPEL
Now, let’s clone and build DPDK. We could use the dpdk
and dpdk-devel
packages provided by CentOS but these are pretty old. If you decide to go
this route, don’t forget to checkout a tag in the Pktgen-DPDK repo
corresponding to the correct DPDK release in the environment.
You can build DPDK like so:
[centos@guest-tx]$ git clone https://github.com/dpdk/dpdk
[centos@guest-tx]$ cd dpdk
[centos@guest-tx]$ git checkout v21.11rc3 # use v21.11 if it's been released
[centos@guest-tx]$ meson build
[centos@guest-tx]$ ninja -C build
[centos@guest-tx]$ sudo ninja -C build install
With DPDK build, let’s do the same for Pktgen-DPDK:
[centos@guest-tx]$ git clone https://github.com/pktgen/Pktgen-DPDK
[centos@guest-tx]$ cd Pktgen-DPDK
[centos@guest-tx]$ git checkout pktgen-21.11.0
[centos@guest-tx]$ PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig meson build
[centos@guest-tx]$ ninja -C build
[centos@guest-tx]$ sudo ninja -C build install
Finally, restart the instance to propogate all these changes:
[centos@guest-tx]$ sudo reboot
Configure environment
Now that our applications are ready to go, we need to configure our
environment. There are a two parts to this: enabling hugepages and configuring
our annointed interfaces to use the vfio-pci
driver. Once again, these steps
should be done on both instances.
We’re going to use root for most of these commands since it’s easier than working with pipes and sudo. Become root and configure the hugepages:
[centos@guest-tx]$ sudo su
[root@guest-tx]# echo 1024 > /proc/sys/vm/nr_hugepages
Now the trickier job of rebinding our interfaces to use the vfio-pci
driver.
We’ve got two interfaces in both instances: the primary interface that we’re
SSHing through, and the second interface that we’re going to use for our DPDK
application. We can verify this using a combination of ip
and lspci
:
[root@guest-tx]# ip link
[root@guest-tx]# lspci | grep Ethernet
This should return something akin to the following:
[root@guest-tx]# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1442 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether fa:16:3e:97:3d:11 brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1442 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether fa:16:3e:5b:af:eb brd ff:ff:ff:ff:ff:ff
[root@guest-tx]# lspci | grep Ethernet
00:03.0 Ethernet controller: Red Hat, Inc. Virtio network device
00:07.0 Ethernet controller: Red Hat, Inc. Virtio network device
In this instance, our device has been assigned the address 00:07.0
. We can
inspect the driver currently in use using lspci
:
[root@guest-tx]# lspci -s 00:07.0 -v
00:07.0 Ethernet controller: Red Hat, Inc. Virtio network device
Subsystem: Red Hat, Inc. Device 0001
Physical Slot: 7
Flags: bus master, fast devsel, latency 0, IRQ 10
I/O ports at 1000 [size=32]
Memory at c0040000 (32-bit, non-prefetchable) [size=4K]
Memory at 240000000 (64-bit, prefetchable) [size=16K]
Expansion ROM at c0000000 [virtual] [disabled] [size=256K]
Capabilities: [98] MSI-X: Enable+ Count=3 Masked-
Capabilities: [84] Vendor Specific Information: VirtIO: <unknown>
Capabilities: [70] Vendor Specific Information: VirtIO: Notify
Capabilities: [60] Vendor Specific Information: VirtIO: DeviceCfg
Capabilities: [50] Vendor Specific Information: VirtIO: ISR
Capabilities: [40] Vendor Specific Information: VirtIO: CommonCfg
Kernel driver in use: virtio-pci
We’re using virtio-pci
, but we need to be using vfio-pci
. Let’s
load that driver and then rebind the interface to the driver using the
driverctl
utility we used earlier:
[root@guest-tx]# modprobe vfio enable_unsafe_noiommu_mode=1
[root@guest-tx]# cat /sys/module/vfio/parameters/enable_unsafe_noiommu_mode
Y
[root@guest-tx]# modprobe vfio-pci
[root@guest-tx]# driverctl -v set-override 0000:00:07.0 vfio-pci
If you inspect the device again, you should see that it’s now bound to the
vfio-pci
driver.
Run applications
The final step: running the applications we built and installed earlier.
The pktgen
application requires a small bit of configuration. Forunately
everything we already have everything we need. Back on the host, run the
following, which will create a configuration file using the MAC and IP
address information we stored earlier:
$ cat << EOF > pktgen.pkt
stop 0
set 0 rate 0.1
set 0 ttl 10
set 0 proto udp
set 0 dport 8000
set 0 src mac ${tx_dpdk_mac}
set 0 dst mac ${rx_dpdk_mac}
set 0 src ip ${tx_dpdk_ip}/32
set 0 dst ip ${rx_dpdk_ip}
set 0 size 64
EOF
You can now copy this to the guest-tx
instance:
$ scp pktgen.pkt centos@${tx_fip}:/home/centos
On the guest-rx
instance, simply run:
LD_LIBRARY_PATH=/usr/local/lib64 /usr/local/bin/pktgen -l 1-3 -n 2 -- -P -m '2.0' -T
Then on the guest-tx
instance, run:
LD_LIBRARY_PATH=/usr/local/lib64 /usr/local/bin/pktgen -l 1-3 -n 2 -- -P -m '2.0' -T -f pktgen.pkt
In the interactive prompt that appears, type start
. You should see packets
start flowing and being received by the pktgen
instance running on
guest-rx
. You can stop this process by typing stop
or simply quit
.
References
- https://www.redhat.com/en/blog/hands-vhost-user-warm-welcome-dpdk
- https://github.com/cloud-hypervisor/cloud-hypervisor/blob/main/docs/vhost-user-net-testing.md
- https://blog.scottlowe.org/2017/02/16/correlating-ovs-guest-domain-interfaces/
- https://toonk.io/building-a-high-performance-linux-based-traffic-generator-with-dpdk/index.html
- https://access.redhat.com/solutions/3243661