P4-utils: Simple Controller to populate
routing tables for any topology
Before
running the examples, please refer to p4-utils. This lab also refers to Simple
Routing.
[Topology]
In the previous examples, we need to set the routing rules in all switches. So in this example, we will run a controller with help of p4-utils. The controller can understand the network topology and set all the routing rules in all switches for any topology. If there are many shortest paths from sender to receiver. The controller only chooses the first path to send the packet.
[ip_forward.p4]
#include <core.p4> #include <v1model.p4> typedef bit<48> macAddr_t; typedef bit<9> egressSpec_t; header arp_t { bit<16> htype; bit<16> ptype; bit<8> hlen; bit<8> plen; bit<16> opcode; bit<48> hwSrcAddr; bit<32> protoSrcAddr; bit<48> hwDstAddr; bit<32> protoDstAddr; } header ethernet_t
{ bit<48> dstAddr; bit<48> srcAddr; bit<16> etherType; } header ipv4_t { bit<4> version; bit<4> ihl; bit<8> diffserv; bit<16> totalLen; bit<16>
identification; bit<3> flags; bit<13> fragOffset; bit<8> ttl; bit<8> protocol; bit<16> hdrChecksum; bit<32> srcAddr; bit<32> dstAddr; } struct metadata { } struct headers { @name(".arp") arp_t arp; @name(".ethernet")
ethernet_t
ethernet; @name(".ipv4")
ipv4_t ipv4; } parser ParserImpl(packet_in packet, out headers hdr,
inout metadata meta, inout
standard_metadata_t standard_metadata)
{ @name(".parse_arp") state parse_arp { packet.extract(hdr.arp);
transition accept; } @name(".parse_ethernet")
state parse_ethernet { packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType)
{
16w0x800: parse_ipv4;
16w0x806: parse_arp;
default: accept; } } @name(".parse_ipv4")
state parse_ipv4 { packet.extract(hdr.ipv4);
transition accept; } @name(".start")
state start {
transition parse_ethernet; } } control egress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata)
{ apply { } } control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata)
{ @name(".set_nhop") action set_nhop(macAddr_t dstAddr, egressSpec_t port) {
//set the
src mac address as the previous dst,
this is not correct right? hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
//set the destination mac address that we got from the match in the
table hdr.ethernet.dstAddr = dstAddr;
//set the output port that we also get from the table standard_metadata.egress_spec =
port;
//decrease ttl by 1
hdr.ipv4.ttl = hdr.ipv4.ttl - 1; } @name("._drop")
action _drop() { mark_to_drop(standard_metadata); } @name(".ipv4_lpm")
table ipv4_lpm {
actions = {
set_nhop;
_drop; }
key = {
hdr.ipv4.dstAddr: lpm; }
size = 512;
const default_action = _drop(); } apply { if (hdr.ipv4.isValid()){
ipv4_lpm.apply(); } } } control DeparserImpl(packet_out packet, in headers hdr)
{ apply { packet.emit(hdr.ethernet); packet.emit(hdr.arp); packet.emit(hdr.ipv4); } } control verifyChecksum(inout headers hdr, inout metadata meta) { apply { verify_checksum(true, { hdr.ipv4.version, hdr.ipv4.ihl,
hdr.ipv4.diffserv, hdr.ipv4.totalLen, hdr.ipv4.identification,
hdr.ipv4.flags, hdr.ipv4.fragOffset, hdr.ipv4.ttl, hdr.ipv4.protocol,
hdr.ipv4.srcAddr, hdr.ipv4.dstAddr }, hdr.ipv4.hdrChecksum,
HashAlgorithm.csum16); } } control computeChecksum(inout headers hdr, inout metadata meta) { apply { update_checksum(true, { hdr.ipv4.version, hdr.ipv4.ihl,
hdr.ipv4.diffserv, hdr.ipv4.totalLen, hdr.ipv4.identification,
hdr.ipv4.flags, hdr.ipv4.fragOffset, hdr.ipv4.ttl, hdr.ipv4.protocol,
hdr.ipv4.srcAddr, hdr.ipv4.dstAddr }, hdr.ipv4.hdrChecksum,
HashAlgorithm.csum16); } } V1Switch(ParserImpl(), verifyChecksum(), ingress(), egress(), computeChecksum(), DeparserImpl()) main; |
[p4app.json]
{
"program": "ip_forward.p4",
"switch": "simple_switch",
"compiler": "p4c",
"options": "--target bmv2 --arch v1model --std
p4-16",
"switch_cli": "simple_switch_CLI",
"cli": true,
"pcap_dump": true,
"enable_log": true,
"topo_module": { "file_path":
"", "module_name":
"p4utils.mininetlib.apptopo", "object_name":
"AppTopoStrategies"
},
"controller_module": null,
"topodb_module": { "file_path":
"", "module_name":
"p4utils.utils.topology", "object_name":
"Topology"
},
"mininet_module": { "file_path":
"", "module_name":
"p4utils.mininetlib.p4net", "object_name":
"P4Mininet"
},
"topology": { "assignment_strategy":
"mixed", "auto_arp_tables":
"true", "auto_gw_arp":
"true", "links":
[["h1", "s1"], ["s1", "s2"],
["s1", "s3"], ["s2", "s4"],
["s4", "s5"], ["s3", "s5"],
["s5", "h2"]], "hosts": {
"h1": { },
"h2": { } }, "switches": {
"s1": { },
"s2": { },
"s3": { },
"s4": { },
"s5": { } }
} } |
[routing-controller.p4] Note: we don’t have cmd-s1.txt, cmd-s2.txt … for all switches.
from p4utils.utils.topology
import Topology from p4utils.utils.sswitch_API
import SimpleSwitchAPI class RoutingController(object): def __init__(self): self.topo = Topology(db="topology.db") self.controllers = {} self.init() def init(self): self.connect_to_switches() self.reset_states() self.set_table_defaults() def reset_states(self): [controller.reset_state() for
controller in self.controllers.values()] def connect_to_switches(self):
for p4switch in self.topo.get_p4switches():
thrift_port = self.topo.get_thrift_port(p4switch)
self.controllers[p4switch]
= SimpleSwitchAPI(thrift_port) def set_table_defaults(self):
for controller in self.controllers.values():
controller.table_set_default("ipv4_lpm",
"_drop", []) def route(self):
switches = {sw_name:{} for sw_name in
self.topo.get_p4switches().keys()} print
"switches:", switches print
"==============================================================================="
print "self.controllers:",
self.controllers
print
"==============================================================================="
for sw_name, controller in self.controllers.items():
for sw_dst in self.topo.get_p4switches():
#if its ourselves we create direct connections
if sw_name == sw_dst:
for host in self.topo.get_hosts_connected_to(sw_name):
sw_port = self.topo.node_to_node_port_num(sw_name,
host)
host_ip = self.topo.get_host_ip(host)
+ "/32"
host_mac = self.topo.get_host_mac(host) print
host, "(", host_ip, host_mac,
")", "-->", sw_name,
"with port:", sw_port
#add rule
print "table_add at {}:".format(sw_name)
self.controllers[sw_name].table_add("ipv4_lpm",
"set_nhop", [str(host_ip)],
[str(host_mac), str(sw_port)])
#check if there are directly connected hosts
else:
if self.topo.get_hosts_connected_to(sw_dst):
paths = self.topo.get_shortest_paths_between_nodes(sw_name,
sw_dst) print
sw_name,"->",sw_dst,":",paths
for host in self.topo.get_hosts_connected_to(sw_dst): next_hop
= paths[0][1] #if there are more than one path,
choose the first path
host_ip = self.topo.get_host_ip(host)
+ "/24"
sw_port = self.topo.node_to_node_port_num(sw_name,
next_hop)
dst_sw_mac = self.topo.node_to_node_mac(next_hop,
sw_name)
#add rule
print "table_add at {}:".format(sw_name)
self.controllers[sw_name].table_add("ipv4_lpm",
"set_nhop", [str(host_ip)],
[str(dst_sw_mac), str(sw_port)]) def main(self): self.route() if __name__ == "__main__": controller = RoutingController().main() |
[execution]
h1 can not ping h2 now.
Open another terminal
Add the rules in s3. If matched key is equal to 10.0.1.1/24 [LPM-0a:00:01:01/24], action:set_nhop, and runtime data: 7a:a0:78:fe:b1:5d 00:01 (change the mac address and send the packet to port 1 [00:01])
Add the rules for s2.
Add the rules for s1. Note: You can also see that the shortest path from s1 to s5 is via s1-s3-s5.
Add the rules for s5
Add the rules for s4
After populating the rules. The ping can work.
Dr. Chih-Heng Ke
Department of Computer Science and
Information Engineering, National Quemoy University, Kinmen, Taiwan
Email: smallko@gmail.com