Using mininet to build p4 data plane

preface

Last P4, into network data plane programming This paper briefly introduces P4 and builds P4 network topology through the command line. The building process requires a lot of commands, which is cumbersome. In this article, we will build P4 through python script.
The code involved in this article is: https://github.com/cykun/p4-mininet

Related introduction

Mininet


Mininet is a network simulator, or more accurately, a network simulation scheduling system. It runs a set of terminal hosts, switches, routers, and links on a single Linux kernel. It uses lightweight virtualization to make a single system look like a complete network, running the same kernel, system and user code.
Through Mininet, we can write python scripts to build network topology, but Mininet itself does not support bmv2 switches, so we need to rewrite the Switch and Host classes under Mininet. Fortunately, p4lang has provided these scripts, which can be used as long as they are extracted: https://github.com/p4lang/tutorials/tree/master/utils


p4_mininet.py: inherit the Switch and Host classes of mininet to support bmv2.
p4runtime_switch.py: inherits P4Switch class and supports grpc calls on its basis.

Experimental topology


The topology is relatively simple. Two virtual hosts are connected to the same bmv2 switch.

Write a topology script named run_exercise.py

from mininet.net import Mininet
from mininet.topo import Topo
from mininet.log import setLogLevel, info
from mininet.cli import CLI

from p4_mininet import P4Switch, P4Host
from p4runtime_switch import P4RuntimeSwitch

import argparse
from time import sleep

parser = argparse.ArgumentParser(description='Mininet demo')
parser.add_argument('--behavioral-exe', help='Path to behavioral executable',
                    type=str, action="store", required=True)
parser.add_argument('--thrift-port', help='Thrift server port for table updates',
                    type=int, action="store", default=9090)
parser.add_argument('--num-hosts', help='Number of hosts to connect to switch',
                    type=int, action="store", default=2)
parser.add_argument('--mode', choices=['l2', 'l3'], type=str, default='l3')
parser.add_argument('--json', help='Path to JSON config file',
                    type=str, action="store", required=True)
parser.add_argument('--pcap-dump', help='Dump packets on interfaces to pcap files',
                    type=str, action="store", required=False, default=False)

args = parser.parse_args()

class SingleSwitchTopo(Topo):
    def __init__(self, sw_path, json_path, thrift_port, pcap_dump, n, **opts):
        # Initialize topology and default options
        Topo.__init__(self, **opts)
        switch = self.addSwitch('s1',
                                sw_path = sw_path,
                                json_path = json_path,
                                thrift_port = thrift_port,
                                pcap_dump = pcap_dump)
        for h in range(n):
            host = self.addHost('h%d' % (h + 1),
                                ip = "10.0.0.%d/24" % (h + 1),
                                mac = '00:04:00:00:00:%02x' %h)
            self.addLink(host, switch)
def main():
    num_hosts = args.num_hosts
    mode = args.mode
    topo = SingleSwitchTopo(args.behavioral_exe,
                            args.json,
                            args.thrift_port,
                            args.pcap_dump,
                            num_hosts)
    net = Mininet(topo = topo,
                  host = P4Host,
                  switch = P4RuntimeSwitch,
                  controller = None)
    net.start()
    sw_mac = ["00:aa:bb:00:00:%02x" % n for n in range(num_hosts)]
    sw_addr = ["10.0.%d.1" % n for n in range(num_hosts)]
    for n in range(num_hosts):
        h = net.get('h%d' % (n + 1))
        if mode == "l2":
            h.setDefaultRoute("dev eth0")
        else:
            h.setARP(sw_addr[n], sw_mac[n])
            h.setDefaultRoute("dev eth0 via %s" % sw_addr[n])
    for n in range(num_hosts):
        h = net.get('h%d' % (n + 1))
        h.describe()
    sleep(1)
    print("Ready !")
    CLI( net )
    net.stop()
if __name__ == '__main__':
    setLogLevel( 'info' )

The switch of Mininet is changed to P4RuntimeSwitch, which essentially calls bmv2 switch.
SingleSwitchTopo inherits the Topo and star topology. A bmv2 switch is added internally. The number of hosts is self-determined. The default is 2, and the ip prefix is 10.0.0.0/24
Those who have written Mininet scripts should be familiar with this.

Write an execution script named run.sh

BMV2_SWITCH_EXE="simple_switch_grpc"

p4c --target bmv2 --arch v1model --p4runtime-files demo.p4info.txt --std p4-16 -o build p4src/demo.p4
sudo ./run_exercise.py --behavioral-exe simple_switch_grpc --json build/demo.json

The demo.p4 here follows the previous one P4, into network data plane programming P4 code. First, compile the demo.p4 source code under p4src with p4c, put the compiled file in the build folder, and then run run_exercise.py script.

Execute run script

cyquen@cyquen-virtual-machine:~/P4/p4_mininet$ ./run.sh 
[sudo] password for cyquen: 
*** Creating network
*** Adding hosts:
h1 h2 
*** Adding switches:
s1 
*** Adding links:
(h1, s1) (h2, s1) 
*** Configuring hosts
h1 h2 
*** Starting controller

*** Starting 1 switches
s1 Starting P4 switch s1.
simple_switch_grpc -i 1@s1-eth1 -i 2@s1-eth2 --nanolog ipc:///tmp/bm-0-log.ipc --device-id 0 build/demo.json --thrift-port 9090 -- --grpc-server-addr 0.0.0.0:50051
P4 switch s1 has been started.

**********
h1
default interface: h1-eth0	10.0.0.1	00:04:00:00:00:00
**********
**********
h2
default interface: h2-eth0	10.0.0.2	00:04:00:00:00:01
**********
Ready !
*** Starting CLI:
mininet> 

See the familiar interface 😁, However, the switch here is no longer Openflow. It is observed that it executes this command

simple_switch_grpc -i 1@s1-eth1 -i 2@s1-eth2 --nanolog ipc:///tmp/bm-0-log.ipc --device-id 0 build/demo.json --thrift-port 9090 -- --grpc-server-addr 0.0.0.0:50051

It's similar to us tapping the command line manually to build bmv2, but it automatically helps us execute it.

Issue a route to the routing table of the switch

Here, we first write a commands.txt file to save the commands to be issued. The topology consists of two virtual hosts, one with ip of 10.0.0.1 and the other with ip of 10.0.0.2, which are connected to port 1 and port 2 of the switch respectively.

table_add ipv4_lpm ipv4_forward 10.0.0.1/32 => 1
table_add ipv4_lpm ipv4_forward 10.0.0.2/32 => 2

It can then be provided through p4lang runtime_CLI.py Script, enter with the following command:

cyquen@cyquen-virtual-machine:~/P4/p4_mininet$ ./runtime_CLI.py < commands.txt 
Obtaining JSON from switch...
Done
Control utility for runtime P4 table manipulation
RuntimeCmd: Adding entry to lpm match table ipv4_lpm
match key:           LPM-0a:00:00:01/32
action:              ipv4_forward
runtime data:        00:01
Entry has been added with handle 0
RuntimeCmd: Adding entry to lpm match table ipv4_lpm
match key:           LPM-0a:00:00:02/32
action:              ipv4_forward
runtime data:        00:02
Entry has been added with handle 1
RuntimeCmd: 

This completes the routing operation.

Layer 3 forwarding of test switch

Execute under the Mininet console

mininet> xterm h1 h2

Call up the h1 and h2 console

Test the connectivity of h1 and h2: h2 executes. / receive.py, h1 executes. / send.py 10.0.0.2 hello

end!

Keywords: Programming network P4

Added by nthomp on Mon, 04 Oct 2021 05:13:43 +0300