Debugging P4 Programs with Vera Radu Stoenescu, Dragos Dumitrescu, Matei Popovici, Lorina Negreanu, Costin Raiciu University Politehnica of Bucharest
Vera: static verification of functional correctness for P414 programs
Vera: static verification of functional correctness for P414 programs No concrete test traffic is generated Runs before deployment
Vera: static verification of functional correctness for P414 programs Does the program have bugs? Does the program correctly implement the policy?
P4 by example: route and encapsulate Ingress Pipeline TTL>0 Buffers Egress Pipeline encap * 1.2.3.4 5.6.7.8 IN OUT ipv4_lpm 10.0.0.0/8 Ge0 DEPARSER PARSER Control program – what sequence of M/A tables is applied to a packet Match-action (M/A) tables - how a P4 switch can tranform packets What packets the switch accepts and outputs
P4 by example: route and encapsulate Ingress Pipeline TTL>0 Buffers Egress Pipeline encap * 1.2.3.4 5.6.7.8 IN OUT ipv4_lpm 10.0.0.0/8 Ge0 DEPARSER PARSER parser start { extract(eth); return select(eth.type){ 0x800 : parse_ipv4; default: ingress; }} parser parse_ipv4 { extract(ipv4); return ingress; } Deparsing uses the same parser specification. Common problem: forget to parse output packets.
P4 by example: route and encapsulate Ingress Pipeline TTL>0 Buffers Egress Pipeline encap * 1.2.3.4 5.6.7.8 IN OUT ipv4_lpm 10.0.0.0/8 Ge0 DEPARSER PARSER parser start { extract(eth); return select(eth.type){ 0x800 : parse_ipv4; default: ingress; }} parser parse_ipv4 { extract(ipv4); return select(ipv4.protocol){ 0x5E : parse_inner_ipv4; parser parse_inner_ipv4 { extract(inner_ipv4); return ingress; }}
P4 by example: route and encapsulate Ingress Pipeline TTL>0 Buffers Egress Pipeline encap * 1.2.3.4 5.6.7.8 IN OUT ipv4_lpm 10.0.0.0/8 Ge0 DEPARSER PARSER parser start { extract(eth); return select(eth.type){ 0x800 : parse_ipv4; default: ingress; }} parser parse_ipv4 { extract(ipv4); return select(ipv4.protocol){ 0x5E : parse_inner_ipv4; parser parse_inner_ipv4 { extract(inner_ipv4); return ingress; }} control ingress(){ if (ipv4.TTL>0) apply(ipv4_lpm); } Reading unparsed header fields will return undefined values
P4 by example: route and encapsulate Ingress Pipeline valid(ipv4) &&TTL>0 Buffers Egress Pipeline encap * 1.2.3.4 5.6.7.8 IN OUT ipv4_lpm 10.0.0.0/8 Ge0 DEPARSER PARSER control ingress(){ if (valid(ipv4) and ipv4.TTL>0) apply(ipv4_lpm); } Non IPv4 packets will be sent directly to buffering resulting in undefined behavior because the egress_spec is not set
P4 by example: route and encapsulate Ingress Pipeline valid(ipv4) &&TTL>0 Buffers Egress Pipeline encap * 1.2.3.4 5.6.7.8 IN OUT ipv4_lpm 10.0.0.0/8 Ge0 DEPARSER PARSER Re-adding existing header fields: undefined behavior
Vera design goals Easy to use General Scalable high bug coverage with no to little intervention General policy verification for more complex properties Scalable comparable to compilation times
Vera at work
How Vera works P4 program P4 to SEFL Compiler Implicit bug checks P4 table entries P4 to SEFL Compiler Implicit bug checks Symnet verification engine[Sigcomm’16] Network Policy For each possible path: full instruction trace, header field values, constraints and, if needed, bug type
Vera at work: route and encapsulate Ingress Pipeline valid(ipv4) &&TTL>0 Buffers Egress Pipeline encap * 1.2.3.4 5.6.7.8 IN OUT ipv4_lpm 10.0.0.0/8 Ge0 DEPARSER PARSER parser start { extract(eth); return select(eth.type){ 0x800 : parse_ipv4; default: ingress; }} parser parse_ipv4 { extract(ipv4); return select(ipv4.protocol){ 0x5E : parse_inner_ipv4; parser parse_inner_ipv4 { extract(inner_ipv4); return ingress; }}
Generating all header layouts parser start { extract(eth); return select(eth.type){ 0x800 : parse_ipv4; default: ingress; }} parser parse_ipv4 { extract(ipv4); return select(ipv4.protocol){ 0x5E : parse_inner_ipv4; parser parse_inner_ipv4 { extract(inner_ipv4); return ingress; }}
Generating all header layouts parser start { extract(eth); return select(eth.type){ 0x800 : parse_ipv4; default: ingress; }} parser parse_ipv4 { extract(ipv4); return select(ipv4.protocol){ 0x5E : parse_inner_ipv4; parser parse_inner_ipv4 { extract(inner_ipv4); return ingress; }} Eth IPv4 Inner_IPv4
Generating all header layouts Eth Eth IPv4 Inner_IPv4
Generating all header layouts Eth Eth Eth IPv4 IPv4 Inner_IPv4
Generating all header layouts Eth Eth Eth IPv4 IPv4 Eth Inner_IPv4 IPv4 Inner_IPv4
Symbolic execution of P4 models Eth Eth IPv4 Eth Inner_IPv4 IPv4
Symbolic execution of P4 models Ethernet IPv4 Eth Dst Src EtherType * 0x800 Src Dst TTL ... * IPv4
Symbolic execution of P4 models Ethernet IPv4 Eth Dst Src EtherType * 0x800 Src Dst TTL ... * IPv4 control ingress(){ if (valid(ipv4) && ipv4.TTL>0) apply(lpm); }
Symbolic execution of P4 models control ingress(){ if (valid(ipv4) && ipv4.TTL>0) apply(lpm); }
Symbolic execution of P4 models SEFL control ingress(){ if (valid(ipv4) && ipv4.TTL>0) apply(lpm); } ‘ingress’: If(Constrain(‘ipv4.TTL’>0)) Forward(‘table.lpm’) else Forward(‘buffer.in’)
Symbolic execution of P4 models SEFL ‘ingress’: If(Constrain(‘ipv4.TTL’>0)) Forward(‘table.lpm’) else Forward(‘buffer.in’) Ethernet IPv4 Dst Src EtherType * 0x800 Src Dst TTL ... *
Symbolic execution of P4 models SEFL ‘ingress’: If(Constrain(‘ipv4.TTL’>0)) Forward(‘table.lpm’) else Forward(‘buffer.in’) Ethernet IPv4 Dst Src EtherType * 0x800 Src Dst TTL ... * Dst Src EtherType * 0x800 Src Dst TTL ... * >0 Dst Src EtherType * 0x800 Src Dst TTL ... * <=0
Symbolic execution of P4 models SEFL ‘ingress’: If(Constrain(‘ipv4.TTL’>0)) Forward(‘table.lpm’) else Forward(‘buffer.in’) Ethernet IPv4 Dst Src EtherType * 0x800 Src Dst TTL ... * Dst Src EtherType * 0x800 Src Dst TTL ... * >0 Dst Src EtherType * 0x800 Src Dst TTL ... * <=0 call ‘table.lpm’
Symbolic execution of P4 models SEFL ‘ingress’: If(Constrain(‘ipv4.TTL’>0)) Forward(‘table.lpm’) else Forward(‘buffer.in’) Ethernet IPv4 Dst Src EtherType * 0x800 Src Dst TTL ... * Dst Src EtherType * 0x800 Src Dst TTL ... * >0 Dst Src EtherType * 0x800 Src Dst TTL ... * <=0 call ‘buffer.in’
Symbolic execution of a P4 pipeline parser.start *
Symbolic execution of a P4 pipeline parser.start control.ingress * Eth: type=0x800 IPv4: proto != 0x5E
Symbolic execution of a P4 pipeline parser.start control.ingress table.lpm * Eth: type=0x800 IPv4: ttl > 0 proto != 0x5E Non-IPv4 Packets or TTL <= 0
Symbolic execution of a P4 pipeline parser.start control.ingress table.lpm buffer.in table.encap deparser * Eth: type=0x800 IPv4: ipDst ∈10.0.0.0/8 ttl > 0 proto != 0x5E Default table action
Symbolic execution of a P4 pipeline parser.start control.ingress table.lpm buffer.in table.encap deparser * Eth: type=0x800 ipv4: ipSrc = 1.2.3.4 ipDst = 5.6.7.8 proto = 0x5E inner: ipDst ∈10.0.0.0/8 ttl > 0 proto != 0x5E Default table action
Symbolic execution of a P4 pipeline parser.start control.ingress table.lpm buffer.in table.encap deparser * Non-IPv4 Packets or TTL <= 0
Symbolic execution of a P4 pipeline parser.start control.ingress table.lpm buffer.in table.encap deparser * Non-IPv4 Packets or TTL <= 0
Symbolic execution of a P4 pipeline parser.start control.ingress table.lpm buffer.in table.encap deparser * Non-IPv4 Packets or TTL <= 0 Error reported (no egress spec)
Symbolic execution of a P4 pipeline parser.start control.ingress table.lpm buffer.in table.encap deparser * Default table action No egress spec set Default table action Successfully routed and encapsulated
Vera in depth Scalable modeling of match-action tables Symbolic table entries Policy language
Vera in depth Scalable modeling of match-action tables Symbolic table entries Policy language
Match-action table modeling table ipv4_lpm { reads { ipv4.dstAddr : lpm; } actions { set_nhop; _drop; size: 1024; simple_router.p4
Match-action table modeling table ipv4_lpm { reads { ipv4.dstAddr : lpm; } actions { set_nhop; _drop; size: 1024; Match Action 1.0.0.0/24 set_nhop(h1) 2.0.0.0/24 set_nhop(h2) 1.0.0.0/16 set_nhop(h3) 0.0.0.0/0 set_nhop(h4) simple_router.p4
Match-action table modeling If(IPDst ∈1.0.0.0/24) else set_nhop(h1) If(IPDst ∈2.0.0.0/24) else set_nhop(h2) If(IPDst ∈1.0.0.0/16) else set_nhop(h3) If(IPDst ∈0.0.0.0/0) else set_nhop(h4) Match Action 1.0.0.0/24 set_nhop(h1) 2.0.0.0/24 set_nhop(h2) 1.0.0.0/16 set_nhop(h3) 0.0.0.0/0 set_nhop(h4) straw man SEFL model
Match-action table modeling If(IPDst ∈1.0.0.0/24) else set_nhop(h1) If(IPDst ∈2.0.0.0/24) else set_nhop(h2) If(IPDst ∈1.0.0.0/16) else set_nhop(h3) If(IPDst ∈0.0.0.0/0) else set_nhop(h4) Match Action 1.0.0.0/24 set_nhop(h1) 2.0.0.0/24 set_nhop(h2) 1.0.0.0/16 set_nhop(h3) 0.0.0.0/0 set_nhop(h4) straw man SEFL model
Match-action table modeling Path condition: IPDst ∈0.0.0.0/0 If(IPDst ∈1.0.0.0/24) else set_nhop(h1) If(IPDst ∈2.0.0.0/24) else set_nhop(h2) If(IPDst ∈1.0.0.0/16) else set_nhop(h3) If(IPDst ∈0.0.0.0/0) else set_nhop(h4) straw man SEFL model
Match-action table modeling Path condition: IPDst ∈0.0.0.0/0 && IPDst ∉1.0.0.0/16 && IPDst ∉2.0.0.0/24 && IPDst ∉1.0.0.0/24 If(IPDst ∈1.0.0.0/24) else set_nhop(h1) If(IPDst ∈2.0.0.0/24) else set_nhop(h2) If(IPDst ∈1.0.0.0/16) else set_nhop(h3) If(IPDst ∈0.0.0.0/0) else set_nhop(h4) straw man SEFL model
Match-action table modeling Path condition: IPDst ∈0.0.0.0/0 && IPDst ∉1.0.0.0/16 && IPDst ∉2.0.0.0/24 && IPDst ∉1.0.0.0/24 If(IPDst ∈1.0.0.0/24) else set_nhop(h1) If(IPDst ∈2.0.0.0/24) else set_nhop(h2) If(IPDst ∈1.0.0.0/16) else set_nhop(h3) If(IPDst ∈0.0.0.0/0) else set_nhop(h4) straw man SEFL model
Match-action table modeling Path condition: IPDst ∈0.0.0.0/0 && IPDst ∉1.0.0.0/16 && IPDst ∉2.0.0.0/24 && IPDst ∉1.0.0.0/24 If(IPDst ∈1.0.0.0/24) else set_nhop(h1) If(IPDst ∈2.0.0.0/24) else set_nhop(h2) If(IPDst ∈1.0.0.0/16) else set_nhop(h3) If(IPDst ∈0.0.0.0/0) else set_nhop(h4) 3 out of 10 constraints are redundant straw man SEFL model
Tree data structure for fast optimal M/A model compilation
Tree data structure for fast optimal M/A model compilation 1.0.0.0/24 2.0.0.0/24
Tree data structure for fast optimal M/A model compilation 1.0.0.0/16 1.0.0.0/24 2.0.0.0/24
Tree data structure for fast optimal M/A model compilation 0.0.0.0/0 1.0.0.0/16 1.0.0.0/24 2.0.0.0/24
Tree data structure for fast optimal M/A model compilation 0.0.0.0/0 1.0.0.0/16 1.0.0.0/24 2.0.0.0/24
Tree data structure for fast optimal M/A model compilation 0.0.0.0/0 1.0.0.0/16 1.0.0.0/24 2.0.0.0/24
Tree data structure for fast optimal M/A model compilation 0.0.0.0/0 1.0.0.0/16 1.0.0.0/24 2.0.0.0/24
Tree data structure for fast optimal M/A model compilation 0.0.0.0/0 1.0.0.0/16 1.0.0.0/24 2.0.0.0/24
Tree data structure for fast optimal M/A model compilation 0.0.0.0/0 Optimal number of constraints without losing precision Fast model compilation and update Works for other matching strategies (except from ternary) and multiple match fields 1.0.0.0/16 1.0.0.0/24 2.0.0.0/24
M/A table model comparison Modeling procedure Action 1K 10K 100K Naive Compile Model 2ms 5ms - Symbolic Execution 34s 1h lpm-heuristics 23ms 1.5s 218s 220ms 2.7s 12s Vera 36ms 83s 230ms
M/A table model comparison Modeling procedure Action 1K 10K 100K Naive Compile Model 2ms 5ms - Symbolic Execution 34s 1h lpm-heuristics 23ms 1.5s 218s 220ms 2.7s 12s Vera 36ms 83s 230ms
Related work p4v requires P4 programs manually annotated with control plane assertions finer-grained control over verification process, fast. higher burden on programmer p4pktgen – dynamic testing framework concrete bug examples weaker definition of correct behavior (P4 behavioral model) P4Nod can handle large P4 deployments (multiple switches) smaller bug coverage
Conclusions P4 programs contain surprisingly many bugs. Vera automatically finds bugs in P414 program snapshots in seconds, with no spec required. Symbolic table entries help explore many (or all) possible P4 data plane snapshots. Soon: github.com/nets-cs-pub-ro/Vera
Future work Support P416 Further evaluate symbolic table entries as an aid for developing correct controllers Develop correct by construction P4 controllers Improve scalability for fully symbolic M/A table contents