Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

After having followed P4Lang P4 for dummies [ #002 ] article, you should have now a working P4 development environment.

Requirement

  • Basic Linux/Unix knowledge
  • Service provider networking knowledge

image2020-6-29_13-54-48.pngImage Modified

Overview

Let's start writing. compiling and running our first P4 program.

...

Expand
titlemy_program.p4 verification


Code Block
languagecpp
themeMidnight
titleCompilation of my_program.p4 artefact in ./build
floui@ubi16:~/my_program$ ls -l build/
total 44
-rw-rw-r-- 1 floui floui  7532 Jul 24 14:23 my_program.json  <------ output used when launching bmv2
-rw-rw-r-- 1 floui floui 35462 Jul 24 14:23 my_program.p4ip  <------ other usage (not taken into account by the examplr)


Warning
titleCreate veth pair before ...

Before launching our BMv2 virtual switch we need to create the veth pair that will be bound the P4 switch.

for that we will reuse bash scripts from Andy Fingerhut public GitHub Repository:


Code Block
languagecpp
themeMidnight
titleveth pairs setup
cd ~/my_program/bin
wget https://raw.githubusercontent.com/jafingerhut/p4-guide/master/bin/veth_setup.sh
wget https://raw.githubusercontent.com/jafingerhut/p4-guide/master/bin/veth_teardown.sh
chmod u+x ./veth_setup.sh
chmod u+x ./veth_teardown.sh
sudo ./veth_setup.sh

ip link | grep veth
4: veth1@veth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
5: veth0@veth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
6: veth3@veth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
7: veth2@veth3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
8: veth5@veth4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
9: veth4@veth5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
10: veth7@veth6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
11: veth6@veth7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
12: veth9@veth8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
13: veth8@veth9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
14: veth11@veth10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
15: veth10@veth11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
16: veth13@veth12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
17: veth12@veth13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
18: veth15@veth14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
19: veth14@veth15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
20: veth17@veth16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
21: veth16@veth17: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP mode DEFAULT group default qlen 1000

we can now launch BMv2 simple_switch and bind the 8 veth pairs we just configured

Code Block
languagecpp
themeMidnight
titlestart bmv2 simple_switch (load my_program.json)
sudo simple_switch --log-console -i 1@veth2 -i 2@veth4 -i 3@veth6 -i 4@veth8 -i 5@veth10 -i 6@veth12 -i 7@veth14 -i 8@veth16 ./build/my_program.json
Calling target program-options parser
[14:28:41.364] [bmv2] [D] [thread 15917] Set default default entry for table 'tbl_my_program76': my_program76 - 
Adding interface veth2 as port 1
[14:28:41.364] [bmv2] [D] [thread 15917] Adding interface veth2 as port 1
Adding interface veth4 as port 2
[14:28:41.415] [bmv2] [D] [thread 15917] Adding interface veth4 as port 2
Adding interface veth6 as port 3
[14:28:41.455] [bmv2] [D] [thread 15917] Adding interface veth6 as port 3
Adding interface veth8 as port 4
[14:28:41.503] [bmv2] [D] [thread 15917] Adding interface veth8 as port 4
Adding interface veth10 as port 5
[14:28:41.547] [bmv2] [D] [thread 15917] Adding interface veth10 as port 5
Adding interface veth12 as port 6
[14:28:41.587] [bmv2] [D] [thread 15917] Adding interface veth12 as port 6
Adding interface veth14 as port 7
[14:28:41.635] [bmv2] [D] [thread 15917] Adding interface veth14 as port 7
Adding interface veth16 as port 8
[14:28:41.683] [bmv2] [D] [thread 15917] Adding interface veth16 as port 8
[14:28:41.727] [bmv2] [I] [thread 15917] Starting Thrift server on port 9090
[14:28:41.728] [bmv2] [I] [thread 15917] Thrift server was started
...


Code Block
languagecpp
themeMidnight
titletcpdump veth8 (port 4)
sudo tcpdump -i veth8
...


Code Block
languagecpp
themeMidnight
titletcpdump veth8 (port 8)
sudo tcpdump -i veth16
...

Now you need to find a way to:

  • send a packet to simple_switch@PORT_4 (veth8)
  • send another packet to simple_switch@PORT_1 (veth2)

We will use scapy for that:

Code Block
languagecpp
themeMidnight
titlescapy installation as root
pip3 install --pre scapy[complete]

Run scapy with sufficient privileges to send packets on specific interface

Code Block
languagecpp
themeMidnight
titlesudo scapy3
/usr/lib/python3/dist-packages/IPython/utils/module_paths.py:29: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
  import imp
                                      
                     aSPY//YASa       
             apyyyyCY//////////YCa       |
            sY//////YSpcs  scpCY//Pp     | Welcome to Scapy
 ayp ayyyyyyySCP//Pp           syY//C    | Version 2.4.3~bionic
 AYAsAYYYYYYYY///Ps              cY//S   |
         pCCCCY//p          cSSps y//Y   | https://github.com/secdev/scapy
         SPPPP///a          pP///AC//Y   |
              A//A            cyP////C   | Have fun!
              p///Ac            sC///a   |
              P////YCpc           A//A   | Craft packets like I craft my beer.
       scccccp///pSP///p          p//Y   |               -- Jean De Clerck
      sY/////////y  caa           S//P   |
       cayCyayP//Ya              pY/Ya
        sY/PsY////YCc          aC//Yp 
         sc  sccaCY//PCypaapyCP//YSs  
                  spCPY//////YPSps    
                       ccaacs         
                                       using IPython 5.5.0
>>> 


Code Block
languagecpp
themeMidnight
titleFrom scapy prompt, send a packet to PORT_4 (veth8)
>>> sendp(IP(dst="1.2.3.4")/ICMP(),iface="veth8")
.
Sent 1 packets.
>>> 


Code Block
languagecpp
themeMidnight
titleCheck tcpdump on veth8 (PORT_4)
floui@ubi16:~$ sudo tcpdump -i veth8
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth8, link-type EN10MB (Ethernet), capture size 262144 bytes
14:58:23.404299 00:00:40:01:9d:d2 (oui Unknown) > 45:00:00:1c:00:01 (oui Unknown), ethertype Unknown (0xc1e0), length 28: 
        0x0000:  1728 0102 0304 0800 f7ff 0000 0000       .(............ 


Code Block
languagecpp
themeMidnight
titleCheck tcpdump on veth16 (PORT_8)
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth16, link-type EN10MB (Ethernet), capture size 262144 bytes
14:58:23.406042 00:00:40:01:9d:d2 (oui Unknown) > 45:00:00:1c:00:01 (oui Unknown), ethertype Unknown (0xc1e0), length 28: 
        0x0000:  1728 0102 0304 0800 f7ff 0000 0000       .(............ Conclusion


Warning
titleCongratulations !

You have successfully written, compiled, load your program P4Lang P4 virtual switch ! In addition, you also checked that the logic of your program is implemented correctly by sending a packet to PORT_4 using scapy python3 tool. You then checked with tcpdump that your packet ingressed the P4 switch via PORT_4 and egressed via PORT_8 as it was expected.


Note
titleWhat's happening to other packets arriving on a port that is different from PORT_4 ?

Let's try to find out. In that situation, let's send an ingress packet to PORT_1 (veth2) of the switch and see what's happening.


Code Block
languagecpp
themeMidnight
titleFrom scapy prompt, send a packet to PORT_4 (veth8)
>>> sendp(IP(dst="1.2.3.4")/ICMP(),iface="veth2")
.
Sent 1 packets.
>>> 

In that case we don't know what is the egress port so let's look at simple_switch console.

Code Block
languagecpp
themeMidnight
titlesimple_switch console
floui@ubi16:~/my_program$ sudo simple_switch --log-console -i 1@veth2 -i 2@veth4 -i 3@veth6 -i 4@veth8 -i 5@veth10 -i 6@veth12 -i 7@veth14 -i 8@veth16 ./build/my_program.json
Calling target program-options parser
[15:10:55.525] [bmv2] [D] [thread 16129] Set default default entry for table 'tbl_my_program76': my_program76 - 
Adding interface veth2 as port 1
[15:10:55.525] [bmv2] [D] [thread 16129] Adding interface veth2 as port 1
Adding interface veth4 as port 2
[15:10:55.555] [bmv2] [D] [thread 16129] Adding interface veth4 as port 2
Adding interface veth6 as port 3
[15:10:55.603] [bmv2] [D] [thread 16129] Adding interface veth6 as port 3
Adding interface veth8 as port 4
[15:10:55.651] [bmv2] [D] [thread 16129] Adding interface veth8 as port 4
Adding interface veth10 as port 5
[15:10:55.691] [bmv2] [D] [thread 16129] Adding interface veth10 as port 5
Adding interface veth12 as port 6
[15:10:55.739] [bmv2] [D] [thread 16129] Adding interface veth12 as port 6
Adding interface veth14 as port 7
[15:10:55.791] [bmv2] [D] [thread 16129] Adding interface veth14 as port 7
Adding interface veth16 as port 8
[15:10:55.839] [bmv2] [D] [thread 16129] Adding interface veth16 as port 8
[15:10:55.879] [bmv2] [I] [thread 16129] Starting Thrift server on port 9090
[15:10:55.880] [bmv2] [I] [thread 16129] Thrift server was started
[15:11:00.449] [bmv2] [D] [thread 16135] [0.0] [cxt 0] Processing packet received on port 1
[15:11:00.449] [bmv2] [D] [thread 16135] [0.0] [cxt 0] Parser 'parser': start
[15:11:00.449] [bmv2] [D] [thread 16135] [0.0] [cxt 0] Parser 'parser' entering state 'start'
[15:11:00.449] [bmv2] [D] [thread 16135] [0.0] [cxt 0] Parser state 'start': key is 0001
[15:11:00.449] [bmv2] [T] [thread 16135] [0.0] [cxt 0] Bytes parsed: 0
[15:11:00.449] [bmv2] [D] [thread 16135] [0.0] [cxt 0] Parser 'parser': end
[15:11:00.449] [bmv2] [D] [thread 16135] [0.0] [cxt 0] Pipeline 'ingress': start
[15:11:00.450] [bmv2] [T] [thread 16135] [0.0] [cxt 0] ./p4src/my_program.p4(75) Condition "std_md.ingress_port == 4" (node_2) is false
[15:11:00.450] [bmv2] [D] [thread 16135] [0.0] [cxt 0] Pipeline 'ingress': end
[15:11:00.450] [bmv2] [D] [thread 16135] [0.0] [cxt 0] Egress port is 0
[15:11:00.450] [bmv2] [D] [thread 16136] [0.0] [cxt 0] Pipeline 'egress': start
[15:11:00.450] [bmv2] [D] [thread 16136] [0.0] [cxt 0] Pipeline 'egress': end
[15:11:00.450] [bmv2] [D] [thread 16136] [0.0] [cxt 0] Deparser 'deparser': start
[15:11:00.450] [bmv2] [D] [thread 16136] [0.0] [cxt 0] Deparser 'deparser': end
[15:11:00.450] [bmv2] [D] [thread 16140] [0.0] [cxt 0] Transmitting packet of size 28 out of port 0

So in that case we see that ingress line: "Egress port is 0", which is a special port number that designate the null0 interace. (packet dropped)

Let's now resent a packet to PORT_4 and observe simple_switch console log.

Code Block
languagecpp
themeMidnight
titlesimple_switch console
sudo simple_switch --log-console -i 1@veth2 -i 2@veth4 -i 3@veth6 -i 4@veth8 -i 5@veth10 -i 6@veth12 -i 7@veth14 -i 8@veth16 ./build/my_program.json
Calling target program-options parser
[15:14:51.047] [bmv2] [D] [thread 16151] Set default default entry for table 'tbl_my_program76': my_program76 - 
Adding interface veth2 as port 1
[15:14:51.048] [bmv2] [D] [thread 16151] Adding interface veth2 as port 1
Adding interface veth4 as port 2
[15:14:51.099] [bmv2] [D] [thread 16151] Adding interface veth4 as port 2
Adding interface veth6 as port 3
[15:14:51.139] [bmv2] [D] [thread 16151] Adding interface veth6 as port 3
Adding interface veth8 as port 4
[15:14:51.175] [bmv2] [D] [thread 16151] Adding interface veth8 as port 4
Adding interface veth10 as port 5
[15:14:51.207] [bmv2] [D] [thread 16151] Adding interface veth10 as port 5
Adding interface veth12 as port 6
[15:14:51.239] [bmv2] [D] [thread 16151] Adding interface veth12 as port 6
Adding interface veth14 as port 7
[15:14:51.271] [bmv2] [D] [thread 16151] Adding interface veth14 as port 7
Adding interface veth16 as port 8
[15:14:51.319] [bmv2] [D] [thread 16151] Adding interface veth16 as port 8
[15:14:51.347] [bmv2] [I] [thread 16151] Starting Thrift server on port 9090
[15:14:51.348] [bmv2] [I] [thread 16151] Thrift server was started
[15:14:58.053] [bmv2] [D] [thread 16158] [0.0] [cxt 0] Processing packet received on port 4
[15:14:58.053] [bmv2] [D] [thread 16158] [0.0] [cxt 0] Parser 'parser': start
[15:14:58.053] [bmv2] [D] [thread 16158] [0.0] [cxt 0] Parser 'parser' entering state 'start'
[15:14:58.053] [bmv2] [D] [thread 16158] [0.0] [cxt 0] Parser state 'start': key is 0004
[15:14:58.053] [bmv2] [T] [thread 16158] [0.0] [cxt 0] Bytes parsed: 0
[15:14:58.053] [bmv2] [D] [thread 16158] [0.0] [cxt 0] Parser 'parser' entering state 'prs_port_4'
[15:14:58.053] [bmv2] [D] [thread 16158] [0.0] [cxt 0] Parser set: setting field 'scalars.userMetadata.nexthop_id' to 8
[15:14:58.053] [bmv2] [D] [thread 16158] [0.0] [cxt 0] Parser state 'prs_port_4' has no switch, going to default next state
[15:14:58.053] [bmv2] [T] [thread 16158] [0.0] [cxt 0] Bytes parsed: 0
[15:14:58.053] [bmv2] [D] [thread 16158] [0.0] [cxt 0] Parser 'parser': end
[15:14:58.053] [bmv2] [D] [thread 16158] [0.0] [cxt 0] Pipeline 'ingress': start
[15:14:58.054] [bmv2] [T] [thread 16158] [0.0] [cxt 0] ./p4src/my_program.p4(75) Condition "std_md.ingress_port == 4" (node_2) is true
[15:14:58.054] [bmv2] [T] [thread 16158] [0.0] [cxt 0] Applying table 'tbl_my_program76'
[15:14:58.054] [bmv2] [D] [thread 16158] [0.0] [cxt 0] Looking up key:

[15:14:58.054] [bmv2] [D] [thread 16158] [0.0] [cxt 0] Table 'tbl_my_program76': miss
[15:14:58.054] [bmv2] [D] [thread 16158] [0.0] [cxt 0] Action entry is my_program76 - 
[15:14:58.054] [bmv2] [T] [thread 16158] [0.0] [cxt 0] Action my_program76
[15:14:58.054] [bmv2] [T] [thread 16158] [0.0] [cxt 0] ./p4src/my_program.p4(76) Primitive std_md.egress_spec = md.nexthop_id
[15:14:58.054] [bmv2] [D] [thread 16158] [0.0] [cxt 0] Pipeline 'ingress': end
[15:14:58.054] [bmv2] [D] [thread 16158] [0.0] [cxt 0] Egress port is 8
[15:14:58.054] [bmv2] [D] [thread 16159] [0.0] [cxt 0] Pipeline 'egress': start
[15:14:58.054] [bmv2] [D] [thread 16159] [0.0] [cxt 0] Pipeline 'egress': end
[15:14:58.054] [bmv2] [D] [thread 16159] [0.0] [cxt 0] Deparser 'deparser': start
[15:14:58.054] [bmv2] [D] [thread 16159] [0.0] [cxt 0] Deparser 'deparser': end
[15:14:58.054] [bmv2] [D] [thread 16163] [0.0] [cxt 0] Transmitting packet of size 28 out of port 8

We clearly confirmed what tcpdump what putting in evidence: ingress PORT_4 leads to a packet switched to PORT_8

Conclusion

In this article you:

  • wrote your first P4 program
  • use p4c in order to compile it
  • learned how to instantiate virtual ethernet pair in order to bind them with simple_switch
  • launch simple_switch and load your program on it
  • set up a test environment using scapy
  • and verify your program using a combination a scapy and tcpdump

...