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!