diff --git a/exercises/basic/ptf/basic_fwd.py b/exercises/basic/ptf/basic_fwd.py index 54e52a6a..c35fb488 100644 --- a/exercises/basic/ptf/basic_fwd.py +++ b/exercises/basic/ptf/basic_fwd.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # SPDX-FileCopyrightText: 2026 Andrew Nguyen # # SPDX-License-Identifier: Apache-2.0 @@ -14,18 +12,17 @@ # Import p4runtime_lib from the tutorials repo utils directory sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), - '../../../utils/')) + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../utils/") +) import p4runtime_lib.bmv2 import p4runtime_lib.helper from p4runtime_lib.switch import ShutdownAllSwitchConnections - # Configure logging logger = logging.getLogger(None) ch = logging.StreamHandler() ch.setLevel(logging.INFO) -formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) logger.addHandler(ch) @@ -35,12 +32,12 @@ def setUp(self): self.dataplane = ptf.dataplane_instance self.dataplane.flush() - logging.debug("BasicFwdTest.setUp()") + logger.debug("BasicFwdTest.setUp()") # Get test parameters grpc_addr = tu.test_param_get("grpcaddr") if grpc_addr is None: - grpc_addr = 'localhost:9559' + grpc_addr = "localhost:9559" p4info_txt_fname = tu.test_param_get("p4info") p4prog_binary_fname = tu.test_param_get("config") @@ -49,145 +46,172 @@ def setUp(self): # Connect to the switch via gRPC self.sw = p4runtime_lib.bmv2.Bmv2SwitchConnection( - name='s1', + name="s1", address=grpc_addr, device_id=0, - proto_dump_file='logs/s1-p4runtime-requests.txt') + proto_dump_file="logs/s1-p4runtime-requests.txt", + ) # Establish as master controller self.sw.MasterArbitrationUpdate() # Load the P4 program onto the switch self.sw.SetForwardingPipelineConfig( - p4info=self.p4info_helper.p4info, - bmv2_json_file_path=p4prog_binary_fname) + p4info=self.p4info_helper.p4info, bmv2_json_file_path=p4prog_binary_fname + ) def tearDown(self): - logging.debug("BasicFwdTest.tearDown()") + logger.debug("BasicFwdTest.tearDown()") ShutdownAllSwitchConnections() - -###################################################################### -# Helper function to add entries to ipv4_lpm table -###################################################################### + ###################################################################### + # Helper function to add entries to ipv4_lpm table + ###################################################################### def add_ipv4_lpm_entry(self, ipv4_addr_str, prefix_len, dst_mac_str, port): table_entry = self.p4info_helper.buildTableEntry( - table_name='MyIngress.ipv4_lpm', - match_fields={ - 'hdr.ipv4.dstAddr': (ipv4_addr_str, prefix_len) - }, - action_name='MyIngress.ipv4_forward', - action_params={ - 'dstAddr': dst_mac_str, - 'port': port - }) + table_name="MyIngress.ipv4_lpm", + match_fields={"hdr.ipv4.dstAddr": (ipv4_addr_str, prefix_len)}, + action_name="MyIngress.ipv4_forward", + action_params={"dstAddr": dst_mac_str, "port": port}, + ) self.sw.WriteTableEntry(table_entry) class DropTest(BasicFwdTest): """Test that packets are dropped when no table entries are installed.""" + def runTest(self): - in_dmac = 'ee:30:ca:9d:1e:00' - in_smac = 'ee:cd:00:7e:70:00' - ip_dst = '10.0.1.1' + in_dmac = "ee:30:ca:9d:1e:00" + in_smac = "ee:cd:00:7e:70:00" + ip_dst = "10.0.1.1" ig_port = 1 - pkt = tu.simple_tcp_packet(eth_src=in_smac, eth_dst=in_dmac, - ip_dst=ip_dst, ip_ttl=64) + pkt = tu.simple_tcp_packet( + eth_src=in_smac, eth_dst=in_dmac, ip_dst=ip_dst, ip_ttl=64 + ) tu.send_packet(self, ig_port, pkt) tu.verify_no_other_packets(self) class FwdTest(BasicFwdTest): """Test that a packet is forwarded correctly with one table entry.""" + def runTest(self): - in_dmac = 'ee:30:ca:9d:1e:00' - in_smac = 'ee:cd:00:7e:70:00' - ip_dst = '10.0.1.1' + in_dmac = "ee:30:ca:9d:1e:00" + in_smac = "ee:cd:00:7e:70:00" + ip_dst = "10.0.1.1" ig_port = 1 eg_port = 2 - out_dmac = '08:00:00:00:02:22' + out_dmac = "08:00:00:00:02:22" # Add a forwarding entry self.add_ipv4_lpm_entry(ip_dst, 32, out_dmac, eg_port) # Send packet - pkt = tu.simple_tcp_packet(eth_src=in_smac, eth_dst=in_dmac, - ip_dst=ip_dst, ip_ttl=64) + pkt = tu.simple_tcp_packet( + eth_src=in_smac, eth_dst=in_dmac, ip_dst=ip_dst, ip_ttl=64 + ) # Expected: srcAddr = old dstAddr, dstAddr = new MAC, TTL decremented - exp_pkt = tu.simple_tcp_packet(eth_src=in_dmac, eth_dst=out_dmac, - ip_dst=ip_dst, ip_ttl=63) + exp_pkt = tu.simple_tcp_packet( + eth_src=in_dmac, eth_dst=out_dmac, ip_dst=ip_dst, ip_ttl=63 + ) tu.send_packet(self, ig_port, pkt) tu.verify_packets(self, exp_pkt, [eg_port]) class MultiEntryTest(BasicFwdTest): """Test multiple LPM entries route to different ports correctly.""" + def runTest(self): - in_dmac = 'ee:30:ca:9d:1e:00' - in_smac = 'ee:cd:00:7e:70:00' + in_dmac = "ee:30:ca:9d:1e:00" + in_smac = "ee:cd:00:7e:70:00" ig_port = 0 entries = [] - entries.append({'ip_dst': '10.0.1.1', - 'prefix_len': 32, - 'pkt_dst': '10.0.1.1', - 'eg_port': 1, - 'out_dmac': '08:00:00:00:01:11'}) - entries.append({'ip_dst': '10.0.2.0', - 'prefix_len': 24, - 'pkt_dst': '10.0.2.99', - 'eg_port': 2, - 'out_dmac': '08:00:00:00:02:22'}) - entries.append({'ip_dst': '10.0.3.0', - 'prefix_len': 24, - 'pkt_dst': '10.0.3.1', - 'eg_port': 3, - 'out_dmac': '08:00:00:00:03:33'}) + entries.append( + { + "ip_dst": "10.0.1.1", + "prefix_len": 32, + "pkt_dst": "10.0.1.1", + "eg_port": 1, + "out_dmac": "08:00:00:00:01:11", + } + ) + entries.append( + { + "ip_dst": "10.0.2.0", + "prefix_len": 24, + "pkt_dst": "10.0.2.99", + "eg_port": 2, + "out_dmac": "08:00:00:00:02:22", + } + ) + entries.append( + { + "ip_dst": "10.0.3.0", + "prefix_len": 24, + "pkt_dst": "10.0.3.1", + "eg_port": 3, + "out_dmac": "08:00:00:00:03:33", + } + ) # Add all entries for e in entries: - self.add_ipv4_lpm_entry(e['ip_dst'], e['prefix_len'], - e['out_dmac'], e['eg_port']) + self.add_ipv4_lpm_entry( + e["ip_dst"], e["prefix_len"], e["out_dmac"], e["eg_port"] + ) # Test each entry ttl_in = 64 for e in entries: - pkt = tu.simple_tcp_packet(eth_src=in_smac, eth_dst=in_dmac, - ip_dst=e['pkt_dst'], ip_ttl=ttl_in) - exp_pkt = tu.simple_tcp_packet(eth_src=in_dmac, eth_dst=e['out_dmac'], - ip_dst=e['pkt_dst'], - ip_ttl=ttl_in - 1) + pkt = tu.simple_tcp_packet( + eth_src=in_smac, eth_dst=in_dmac, ip_dst=e["pkt_dst"], ip_ttl=ttl_in + ) + exp_pkt = tu.simple_tcp_packet( + eth_src=in_dmac, + eth_dst=e["out_dmac"], + ip_dst=e["pkt_dst"], + ip_ttl=ttl_in - 1, + ) tu.send_packet(self, ig_port, pkt) - tu.verify_packets(self, exp_pkt, [e['eg_port']]) + tu.verify_packets(self, exp_pkt, [e["eg_port"]]) ttl_in -= 10 class LpmTiebreakerTest(BasicFwdTest): """Test that longest-prefix match wins for overlapping routes.""" + def runTest(self): - in_dmac = 'ee:30:ca:9d:1e:00' - in_smac = 'ee:cd:00:7e:70:00' + in_dmac = "ee:30:ca:9d:1e:00" + in_smac = "ee:cd:00:7e:70:00" ig_port = 0 - less_specific_out_dmac = '08:00:00:00:11:11' + less_specific_out_dmac = "08:00:00:00:11:11" less_specific_eg_port = 1 - more_specific_out_dmac = '08:00:00:00:22:22' + more_specific_out_dmac = "08:00:00:00:22:22" more_specific_eg_port = 2 # Two overlapping routes: /16 and /24. Packet should match /24. - self.add_ipv4_lpm_entry('10.0.0.0', 16, - less_specific_out_dmac, less_specific_eg_port) - self.add_ipv4_lpm_entry('10.0.1.0', 24, - more_specific_out_dmac, more_specific_eg_port) - - pkt = tu.simple_tcp_packet(eth_src=in_smac, eth_dst=in_dmac, - ip_dst='10.0.1.99', ip_ttl=64) - exp_pkt = tu.simple_tcp_packet(eth_src=in_dmac, eth_dst=more_specific_out_dmac, - ip_dst='10.0.1.99', ip_ttl=63) + self.add_ipv4_lpm_entry( + "10.0.0.0", 16, less_specific_out_dmac, less_specific_eg_port + ) + self.add_ipv4_lpm_entry( + "10.0.1.0", 24, more_specific_out_dmac, more_specific_eg_port + ) + + pkt = tu.simple_tcp_packet( + eth_src=in_smac, eth_dst=in_dmac, ip_dst="10.0.1.99", ip_ttl=64 + ) + exp_pkt = tu.simple_tcp_packet( + eth_src=in_dmac, + eth_dst=more_specific_out_dmac, + ip_dst="10.0.1.99", + ip_ttl=63, + ) tu.send_packet(self, ig_port, pkt) tu.verify_packets(self, exp_pkt, [more_specific_eg_port]) @@ -195,21 +219,24 @@ def runTest(self): class TtlBoundaryTest(BasicFwdTest): """Test forwarding behavior when input IPv4 TTL is at boundary value 1.""" + def runTest(self): - in_dmac = 'ee:30:ca:9d:1e:00' - in_smac = 'ee:cd:00:7e:70:00' - ip_dst = '10.0.9.9' + in_dmac = "ee:30:ca:9d:1e:00" + in_smac = "ee:cd:00:7e:70:00" + ip_dst = "10.0.9.9" ig_port = 1 eg_port = 3 - out_dmac = '08:00:00:00:09:99' + out_dmac = "08:00:00:00:09:99" self.add_ipv4_lpm_entry(ip_dst, 32, out_dmac, eg_port) - pkt = tu.simple_tcp_packet(eth_src=in_smac, eth_dst=in_dmac, - ip_dst=ip_dst, ip_ttl=1) - exp_pkt = tu.simple_tcp_packet(eth_src=in_dmac, eth_dst=out_dmac, - ip_dst=ip_dst, ip_ttl=0) + pkt = tu.simple_tcp_packet( + eth_src=in_smac, eth_dst=in_dmac, ip_dst=ip_dst, ip_ttl=1 + ) + exp_pkt = tu.simple_tcp_packet( + eth_src=in_dmac, eth_dst=out_dmac, ip_dst=ip_dst, ip_ttl=0 + ) tu.send_packet(self, ig_port, pkt) tu.verify_packets(self, exp_pkt, [eg_port]) @@ -217,16 +244,18 @@ def runTest(self): class NonIpv4DropTest(BasicFwdTest): """Test non-IPv4 traffic bypasses IPv4 LPM forwarding logic.""" + def runTest(self): ig_port = 1 pkt = tu.simple_arp_packet( - eth_dst='ff:ff:ff:ff:ff:ff', - eth_src='00:de:ad:be:ef:01', + eth_dst="ff:ff:ff:ff:ff:ff", + eth_src="00:de:ad:be:ef:01", arp_op=1, - ip_snd='10.0.1.10', - ip_tgt='10.0.1.1', - hw_snd='00:de:ad:be:ef:01', - hw_tgt='00:00:00:00:00:00') + ip_snd="10.0.1.10", + ip_tgt="10.0.1.1", + hw_snd="00:de:ad:be:ef:01", + hw_tgt="00:00:00:00:00:00", + ) tu.send_packet(self, ig_port, pkt) tu.verify_packets(self, pkt, [0]) diff --git a/exercises/basic/receive.py b/exercises/basic/receive.py index 2e10e6ee..77ef4433 100755 --- a/exercises/basic/receive.py +++ b/exercises/basic/receive.py @@ -5,6 +5,7 @@ # SPDX-License-Identifier: GPL-2.0-only import os import sys +from typing import Any, ClassVar from scapy.all import ( TCP, @@ -14,50 +15,53 @@ IPOption, ShortField, get_if_list, - sniff + sniff, ) from scapy.layers.inet import _IPOption_HDR def get_if(): - ifs=get_if_list() - iface=None + iface = None for i in get_if_list(): if "eth0" in i: - iface=i - break; + iface = i + break if not iface: print("Cannot find eth0 interface") - exit(1) + sys.exit(1) return iface + class IPOption_MRI(IPOption): name = "MRI" option = 31 - fields_desc = [ _IPOption_HDR, - FieldLenField("length", None, fmt="B", - length_of="swids", - adjust=lambda pkt,l:l+4), - ShortField("count", 0), - FieldListField("swids", - [], - IntField("", 0), - length_from=lambda pkt:pkt.count*4) ] + fields_desc: ClassVar[list[Any]] = [ + _IPOption_HDR, + FieldLenField( + "length", None, fmt="B", length_of="swids", adjust=lambda pkt, x: x + 4 + ), + ShortField("count", 0), + FieldListField( + "swids", [], IntField("", 0), length_from=lambda pkt: pkt.count * 4 + ), + ] + + def handle_pkt(pkt): if TCP in pkt and pkt[TCP].dport == 1234: print("got a packet") pkt.show2() - # hexdump(pkt) + # hexdump(pkt) sys.stdout.flush() def main(): - ifaces = [i for i in os.listdir('/sys/class/net/') if 'eth' in i] + ifaces = [i for i in os.listdir("/sys/class/net/") if "eth" in i] iface = ifaces[0] - print("sniffing on %s" % iface) + print(f"sniffing on {iface}") sys.stdout.flush() - sniff(iface = iface, - prn = lambda x: handle_pkt(x)) + sniff(iface=iface, prn=lambda x: handle_pkt(x)) + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/exercises/basic/send.py b/exercises/basic/send.py index ee98ff47..4646de38 100755 --- a/exercises/basic/send.py +++ b/exercises/basic/send.py @@ -11,32 +11,37 @@ def get_if(): - ifs=get_if_list() - iface=None # "h1-eth0" + iface = None # "h1-eth0" for i in get_if_list(): if "eth0" in i: - iface=i - break; + iface = i + break if not iface: print("Cannot find eth0 interface") - exit(1) + sys.exit(1) return iface + def main(): - if len(sys.argv)<3: + if len(sys.argv) < 3: print('pass 2 arguments: ""') - exit(1) + sys.exit(1) addr = socket.gethostbyname(sys.argv[1]) iface = get_if() - print("sending on interface %s to %s" % (iface, str(addr))) - pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') - pkt = pkt /IP(dst=addr) / TCP(dport=1234, sport=random.randint(49152,65535)) / sys.argv[2] + print(f"sending on interface {iface} to {addr!s}") + pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") + pkt = ( + pkt + / IP(dst=addr) + / TCP(dport=1234, sport=random.randint(49152, 65535)) + / sys.argv[2] + ) pkt.show2() sendp(pkt, iface=iface, verbose=False) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/exercises/basic_tunnel/myTunnel_header.py b/exercises/basic_tunnel/myTunnel_header.py index 30fdedf9..01e9b7b4 100644 --- a/exercises/basic_tunnel/myTunnel_header.py +++ b/exercises/basic_tunnel/myTunnel_header.py @@ -3,21 +3,21 @@ # SPDX-License-Identifier: GPL-2.0-only -from scapy.all import * +from typing import Any, ClassVar + +from scapy.all import IP, Ether, Packet, ShortField, bind_layers TYPE_MYTUNNEL = 0x1212 TYPE_IPV4 = 0x0800 + class MyTunnel(Packet): name = "MyTunnel" - fields_desc = [ - ShortField("pid", 0), - ShortField("dst_id", 0) - ] + fields_desc: ClassVar[list[Any]] = [ShortField("pid", 0), ShortField("dst_id", 0)] + def mysummary(self): return self.sprintf("pid=%pid%, dst_id=%dst_id%") bind_layers(Ether, MyTunnel, type=TYPE_MYTUNNEL) bind_layers(MyTunnel, IP, pid=TYPE_IPV4) - diff --git a/exercises/basic_tunnel/ptf/basic_tunnel.py b/exercises/basic_tunnel/ptf/basic_tunnel.py index adbe6ab8..48bfb235 100644 --- a/exercises/basic_tunnel/ptf/basic_tunnel.py +++ b/exercises/basic_tunnel/ptf/basic_tunnel.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # Copyright 2026 Andrew Nguyen # SPDX-License-Identifier: GPL-2.0-only # Reason-GPL: import-scapy @@ -7,6 +5,7 @@ import logging import os import sys +from typing import Any, ClassVar import ptf import ptf.testutils as tu @@ -20,7 +19,7 @@ class MyTunnel(Packet): name = "MyTunnel" - fields_desc = [ShortField("proto_id", TYPE_IPV4), ShortField("dst_id", 0)] + fields_desc: ClassVar[list[Any]] = [ShortField("proto_id", TYPE_IPV4), ShortField("dst_id", 0)] bind_layers(Ether, MyTunnel, type=TYPE_MYTUNNEL) @@ -35,7 +34,6 @@ class MyTunnel(Packet): import p4runtime_lib.helper from p4runtime_lib.switch import ShutdownAllSwitchConnections - # Configure Logging logger = logging.getLogger(None) handler = logging.StreamHandler() @@ -51,7 +49,7 @@ def setUp(self): self.dataplane = ptf.dataplane_instance self.dataplane.flush() - logging.debug("BasicTunnelTest.setUp()") + logger.debug("BasicTunnelTest.setUp()") # Get test parameters grpc_addr = tu.test_param_get("grpcaddr") or "localhost:9559" @@ -60,29 +58,30 @@ def setUp(self): # Create P4Info helper for building the table entries self.p4info_helper = p4runtime_lib.helper.P4InfoHelper(p4info_txt_fname) - + # Connect to the switch via gRPC self.sw = p4runtime_lib.bmv2.Bmv2SwitchConnection( name="s1", address=grpc_addr, device_id=0, - proto_dump_file="logs/s1-p4runtime-requests.txt") + proto_dump_file="logs/s1-p4runtime-requests.txt", + ) # Establish as master controller self.sw.MasterArbitrationUpdate() # Load the P4 Program onto the switch self.sw.SetForwardingPipelineConfig( - p4info=self.p4info_helper.p4info, bmv2_json_file_path=p4prog_binary_fname) + p4info=self.p4info_helper.p4info, bmv2_json_file_path=p4prog_binary_fname + ) def tearDown(self): - logging.debug("BasicTunnelTest.tearDown()") + logger.debug("BasicTunnelTest.tearDown()") ShutdownAllSwitchConnections() - -###################################################################### -# Helper function to add entries to ipv4_lpm table -###################################################################### + ###################################################################### + # Helper function to add entries to ipv4_lpm table + ###################################################################### def add_ipv4_lpm_entry(self, ipv4_addr_str, prefix_len, dst_mac_str, port): table_entry = self.p4info_helper.buildTableEntry( @@ -104,7 +103,9 @@ def add_tunnel_entry(self, dst_id, port): class Ipv4DropOnMissTest(BasicTunnelTest): - """Verify that a plain IPv4 packet is dropped when no LPM table entry exists.""" + """Verify that a plain IPv4 packet is dropped when no LPM table + entry exists.""" + def runTest(self): pkt = tu.simple_tcp_packet( eth_src="ee:cd:00:7e:70:00", @@ -117,7 +118,9 @@ def runTest(self): class Ipv4ForwardTest(BasicTunnelTest): - """Verify that a plain IPv4 packet is forwarded correctly with one table entry.""" + """Verify that a plain IPv4 packet is forwarded correctly with one + table entry.""" + def runTest(self): in_dmac = "ee:30:ca:9d:1e:00" in_smac = "ee:cd:00:7e:70:00" @@ -138,7 +141,9 @@ def runTest(self): class TunnelForwardTest(BasicTunnelTest): - """Verify that a tunneled packet is forwarded correctly when a valid table entry exists.""" + """Verify that a tunneled packet is forwarded correctly when a + valid table entry exists.""" + def runTest(self): in_pkt = ( Ether(src="00:11:22:33:44:55", dst="ff:ff:ff:ff:ff:ff", type=TYPE_MYTUNNEL) @@ -153,7 +158,9 @@ def runTest(self): class TunnelDropOnMissTest(BasicTunnelTest): - """Verify that a tunneled packet is dropped when no matching table entry exists.""" + """Verify that a tunneled packet is dropped when no matching table + entry exists.""" + def runTest(self): in_pkt = ( Ether(src="00:11:22:33:44:66", dst="ff:ff:ff:ff:ff:ff", type=TYPE_MYTUNNEL) @@ -168,6 +175,7 @@ def runTest(self): class TtlBoundaryTest(BasicTunnelTest): """Verify IPv4 TTL is decremented to 0 correctly when input TTL is 1.""" + def runTest(self): in_dmac = "ee:30:ca:9d:1e:00" in_smac = "ee:cd:00:7e:70:00" @@ -179,19 +187,19 @@ def runTest(self): self.add_ipv4_lpm_entry(ip_dst, 32, out_dmac, eg_port) pkt = tu.simple_tcp_packet( - eth_src=in_smac, eth_dst=in_dmac, - ip_dst=ip_dst, ip_ttl=1 + eth_src=in_smac, eth_dst=in_dmac, ip_dst=ip_dst, ip_ttl=1 ) exp_pkt = tu.simple_tcp_packet( - eth_src=in_dmac, eth_dst=out_dmac, - ip_dst=ip_dst, ip_ttl=0 + eth_src=in_dmac, eth_dst=out_dmac, ip_dst=ip_dst, ip_ttl=0 ) tu.send_packet(self, ig_port, pkt) tu.verify_packets(self, exp_pkt, [eg_port]) class TunnelUnknownProtoTest(BasicTunnelTest): - """Verify tunnel packet with non-IPv4 proto_id is still forwarded by dst_id.""" + """Verify tunnel packet with non-IPv4 proto_id is still forwarded + by dst_id.""" + def runTest(self): self.add_tunnel_entry(dst_id=5, port=2) @@ -205,7 +213,9 @@ def runTest(self): class MixedTrafficTest(BasicTunnelTest): - """Verify IPv4 and tunnel traffic are handled independently correctly via separate tables.""" + """Verify IPv4 and tunnel traffic are handled independently + correctly via separate tables.""" + def runTest(self): in_dmac = "ee:30:ca:9d:1e:00" in_smac = "ee:cd:00:7e:70:00" @@ -220,12 +230,10 @@ def runTest(self): # test plain IPv4 which should hit ipv4_lpm table ipv4_pkt = tu.simple_tcp_packet( - eth_src=in_smac, eth_dst=in_dmac, - ip_dst=ip_dst, ip_ttl=64 + eth_src=in_smac, eth_dst=in_dmac, ip_dst=ip_dst, ip_ttl=64 ) exp_ipv4_pkt = tu.simple_tcp_packet( - eth_src=in_dmac, eth_dst=out_dmac, - ip_dst=ip_dst, ip_ttl=63 + eth_src=in_dmac, eth_dst=out_dmac, ip_dst=ip_dst, ip_ttl=63 ) tu.send_packet(self, 1, ipv4_pkt) tu.verify_packets(self, exp_ipv4_pkt, [ipv4_port]) diff --git a/exercises/basic_tunnel/receive.py b/exercises/basic_tunnel/receive.py index 6a6cf210..ae13704f 100755 --- a/exercises/basic_tunnel/receive.py +++ b/exercises/basic_tunnel/receive.py @@ -11,33 +11,33 @@ def get_if(): - ifs=get_if_list() - iface=None + iface = None for i in get_if_list(): if "eth0" in i: - iface=i - break; + iface = i + break if not iface: print("Cannot find eth0 interface") - exit(1) + sys.exit(1) return iface + def handle_pkt(pkt): if MyTunnel in pkt or (TCP in pkt and pkt[TCP].dport == 1234): print("got a packet") pkt.show2() -# hexdump(pkt) -# print "len(pkt) = ", len(pkt) + # hexdump(pkt) + # print "len(pkt) = ", len(pkt) sys.stdout.flush() def main(): - ifaces = [i for i in os.listdir('/sys/class/net/') if 'eth' in i] + ifaces = [i for i in os.listdir("/sys/class/net/") if "eth" in i] iface = ifaces[0] - print("sniffing on %s" % iface) + print(f"sniffing on {iface}") sys.stdout.flush() - sniff(iface = iface, - prn = lambda x: handle_pkt(x)) + sniff(iface=iface, prn=lambda x: handle_pkt(x)) + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/exercises/basic_tunnel/send.py b/exercises/basic_tunnel/send.py index e06bd955..709995f3 100755 --- a/exercises/basic_tunnel/send.py +++ b/exercises/basic_tunnel/send.py @@ -6,48 +6,66 @@ import argparse import random import socket +import sys from myTunnel_header import MyTunnel from scapy.all import IP, TCP, Ether, get_if_hwaddr, get_if_list, sendp def get_if(): - ifs=get_if_list() - iface=None # "h1-eth0" + iface = None # "h1-eth0" for i in get_if_list(): if "eth0" in i: - iface=i - break; + iface = i + break if not iface: print("Cannot find eth0 interface") - exit(1) + sys.exit(1) return iface + def main(): parser = argparse.ArgumentParser() - parser.add_argument('ip_addr', type=str, help="The destination IP address to use") - parser.add_argument('message', type=str, help="The message to include in packet") - parser.add_argument('--dst_id', type=int, default=None, help='The myTunnel dst_id to use, if unspecified then myTunnel header will not be included in packet') + parser.add_argument("ip_addr", type=str, help="The destination IP address to use") + parser.add_argument("message", type=str, help="The message to include in packet") + parser.add_argument( + "--dst_id", + type=int, + default=None, + help="""The myTunnel dst_id to use, if unspecified then + myTunnel header will not be included in packet""", + ) args = parser.parse_args() addr = socket.gethostbyname(args.ip_addr) dst_id = args.dst_id iface = get_if() - if (dst_id is not None): - print("sending on interface {} to dst_id {}".format(iface, str(dst_id))) - pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') - pkt = pkt / MyTunnel(dst_id=dst_id) / IP(dst=addr) / TCP(dport=1234, sport=random.randint(49152,65535)) / args.message + if dst_id is not None: + print(f"sending on interface {iface} to dst_id {dst_id!s}") + pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") + pkt = ( + pkt + / MyTunnel(dst_id=dst_id) + / IP(dst=addr) + / TCP(dport=1234, sport=random.randint(49152, 65535)) + / args.message + ) else: - print("sending on interface {} to IP addr {}".format(iface, str(addr))) - pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') - pkt = pkt / IP(dst=addr) / TCP(dport=1234, sport=random.randint(49152,65535)) / args.message + print(f"sending on interface {iface} to IP addr {addr!s}") + pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") + pkt = ( + pkt + / IP(dst=addr) + / TCP(dport=1234, sport=random.randint(49152, 65535)) + / args.message + ) pkt.show2() -# hexdump(pkt) -# print "len(pkt) = ", len(pkt) + # hexdump(pkt) + # print "len(pkt) = ", len(pkt) sendp(pkt, iface=iface, verbose=False) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/exercises/calc/calc.py b/exercises/calc/calc.py index a3c93035..1b8193a2 100644 --- a/exercises/calc/calc.py +++ b/exercises/calc/calc.py @@ -1,10 +1,9 @@ -#!/usr/bin/env python3 - # SPDX-FileCopyrightText: 2018 Nate Foster # # SPDX-License-Identifier: GPL-2.0-only import re +from typing import Any, ClassVar from scapy.all import ( Ether, @@ -13,95 +12,111 @@ StrFixedLenField, XByteField, bind_layers, - srp1 + srp1, ) + # Define custom packet class for P4calc class P4calc(Packet): name = "P4calc" # Define fields for the P4calc packet - fields_desc = [ StrFixedLenField("P", "P", length=1), - StrFixedLenField("Four", "4", length=1), - XByteField("version", 0x01), - StrFixedLenField("op", "+", length=1), - IntField("operand_a", 0), - IntField("operand_b", 0), - IntField("result", 0xDEADBABE)] + fields_desc: ClassVar[list[Any]] = [ + StrFixedLenField("P", "P", length=1), + StrFixedLenField("Four", "4", length=1), + XByteField("version", 0x01), + StrFixedLenField("op", "+", length=1), + IntField("operand_a", 0), + IntField("operand_b", 0), + IntField("result", 0xDEADBABE), + ] + # Bind custom packet class to Ethernet type 0x1234 bind_layers(Ether, P4calc, type=0x1234) + # Custom exception for number parsing error class NumParseError(Exception): pass + # Custom exception for operator parsing error class OpParseError(Exception): pass + # Token class for representing parsed tokens class Token: def __init__(self, type, value=None): self.type = type self.value = value + # Parser function for parsing number literals def num_parser(s, i, ts): - pattern = "^\s*([0-9]+)\s*" - match = re.match(pattern,s[i:]) + pattern = r"^\s*([0-9]+)\s*" + match = re.match(pattern, s[i:]) if match: - ts.append(Token('num', match.group(1))) + ts.append(Token("num", match.group(1))) return i + match.end(), ts - raise NumParseError('Expected number literal.') + raise NumParseError("Expected number literal.") + # Parser function for parsing binary operators def op_parser(s, i, ts): - pattern = "^\s*([-+&|^])\s*" - match = re.match(pattern,s[i:]) + pattern = r"^\s*([-+&|^])\s*" + match = re.match(pattern, s[i:]) if match: - ts.append(Token('op', match.group(1))) + ts.append(Token("op", match.group(1))) return i + match.end(), ts raise OpParseError("Expected binary operator '-', '+', '&', '|', or '^'.") + # Function to create a sequence of parsers def make_seq(p1, p2): def parse(s, i, ts): - i,ts2 = p1(s,i,ts) - return p2(s,i,ts2) + i, ts2 = p1(s, i, ts) + return p2(s, i, ts2) + return parse def main(): - p = make_seq(num_parser, make_seq(op_parser,num_parser)) # Create parser for number and operator sequence - s = '' - iface = 'eth0' + # Create parser for number and operator sequence + p = make_seq(num_parser, make_seq(op_parser, num_parser)) + s = "" + iface = "eth0" while True: - s = input('> ') + s = input("> ") if s == "quit": break print(s) try: - i,ts = p(s,0,[]) + _i, ts = p(s, 0, []) # Construct packet using parsed tokens - pkt = Ether(dst='00:04:00:00:00:00', type=0x1234) / P4calc(op=ts[1].value, - operand_a=int(ts[0].value), - operand_b=int(ts[2].value)) - pkt = pkt/' ' + pkt = Ether(dst="00:04:00:00:00:00", type=0x1234) / P4calc( + op=ts[1].value, operand_a=int(ts[0].value), operand_b=int(ts[2].value) + ) + pkt = pkt / " " - resp = srp1(pkt, iface=iface, timeout=1, verbose=False) # Send packet and receive response + # Send packet and receive response + resp = srp1(pkt, iface=iface, timeout=1, verbose=False) if resp: - p4calc=resp[P4calc] + p4calc = resp[P4calc] if p4calc: - print(p4calc.result) # Print the result from the response packet + # Print the result from the response packet + print(p4calc.result) else: print("cannot find P4calc header in the packet") else: print("Didn't receive response") except Exception as error: - print(error) # Print any exceptions that occur during parsing or packet handling + # Print any exceptions that occur during parsing or packet + # handling + print(error) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/exercises/ecn/receive.py b/exercises/ecn/receive.py index 85b3b6d0..ddae25d5 100755 --- a/exercises/ecn/receive.py +++ b/exercises/ecn/receive.py @@ -9,30 +9,30 @@ def get_if(): - ifs=get_if_list() - iface=None + iface = None for i in get_if_list(): if "eth0" in i: - iface=i - break; + iface = i + break if not iface: print("Cannot find eth0 interface") - exit(1) + sys.exit(1) return iface + def handle_pkt(pkt): print("got a packet") pkt.show2() -# hexdump(pkt) + # hexdump(pkt) sys.stdout.flush() def main(): - iface = 'eth0' - print("sniffing on %s" % iface) + iface = "eth0" + print(f"sniffing on {iface}") sys.stdout.flush() - sniff(filter="udp and port 4321", iface = iface, - prn = lambda x: handle_pkt(x)) + sniff(filter="udp and port 4321", iface=iface, prn=lambda x: handle_pkt(x)) + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/exercises/ecn/send.py b/exercises/ecn/send.py index 298f1496..9c6e8067 100755 --- a/exercises/ecn/send.py +++ b/exercises/ecn/send.py @@ -12,36 +12,38 @@ def get_if(): - ifs=get_if_list() - iface=None # "h1-eth0" + iface = None # "h1-eth0" for i in get_if_list(): if "eth0" in i: - iface=i - break; + iface = i + break if not iface: print("Cannot find eth0 interface") - exit(1) + sys.exit(1) return iface + def main(): - if len(sys.argv)<4: + if len(sys.argv) < 4: print('pass 2 arguments: "" ') - exit(1) + sys.exit(1) addr = socket.gethostbyname(sys.argv[1]) iface = get_if() - pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") / IP(dst=addr, tos=1) / UDP(dport=4321, sport=1234) / sys.argv[2] + pkt = ( + Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") + / IP(dst=addr, tos=1) + / UDP(dport=4321, sport=1234) + / sys.argv[2] + ) pkt.show2() - #hexdump(pkt) - try: - for i in range(int(sys.argv[3])): + # hexdump(pkt) + for i in range(int(sys.argv[3])): sendp(pkt, iface=iface) sleep(1) - except KeyboardInterrupt: - raise -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/exercises/flowcache/mycontroller.py b/exercises/flowcache/mycontroller.py index 4d2a76dd..b0d4d39b 100755 --- a/exercises/flowcache/mycontroller.py +++ b/exercises/flowcache/mycontroller.py @@ -4,43 +4,43 @@ # # SPDX-License-Identifier: GPL-2.0-only import argparse -import os -import sys import asyncio -import traceback -import time +import datetime import ipaddress +import os import pprint - +import sys +import time +import traceback from collections import Counter -from datetime import datetime, timedelta -from scapy.all import * import grpc +from scapy.all import IP, Ether # Import P4Runtime lib from parent utils dir # Probably there's a better way of doing this. sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), - '../../utils/')) + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../utils/") +) import p4runtime_lib.bmv2 import p4runtime_lib.helper -from p4runtime_lib.switch import ShutdownAllSwitchConnections import p4runtime_sh.p4runtime as shp4rt +from p4runtime_lib.switch import ShutdownAllSwitchConnections NSEC_PER_SEC = 1000 * 1000 * 1000 global_data = {} -global_data['CPU_PORT'] = 510 -global_data['CPU_PORT_CLONE_SESSION_ID'] = 57 -global_data['NUM_PORTS'] = 3 -global_data['index'] = 0 +global_data["CPU_PORT"] = 510 +global_data["CPU_PORT_CLONE_SESSION_ID"] = 57 +global_data["NUM_PORTS"] = 3 +global_data["index"] = 0 global_data["10.0.1.1"] = "08:00:00:00:01:11" global_data["10.0.2.2"] = "08:00:00:00:02:22" global_data["10.0.3.3"] = "08:00:00:00:03:33" -## The notification database keeps track of the received idle notifications and triggers the deletion of stale flow rules. +# The notification database keeps track of the received idle +# notifications and triggers the deletion of stale flow rules. notif_db = {} # The lookup table is defined to simplify reachability and provide connectivity @@ -48,109 +48,103 @@ # to solve this problem more effectively. lookup_table = { - "s1": { - "10.0.1.1": 1, - "10.0.2.2": 2, - "10.0.3.3": 3 - }, - "s2": { - "10.0.2.2": 1, - "10.0.1.1": 2, - "10.0.3.3": 3 - }, - "s3": { - "10.0.3.3": 1, - "10.0.1.1": 2, - "10.0.2.2": 3 - } + "s1": {"10.0.1.1": 1, "10.0.2.2": 2, "10.0.3.3": 3}, + "s2": {"10.0.2.2": 1, "10.0.1.1": 2, "10.0.3.3": 3}, + "s3": {"10.0.3.3": 1, "10.0.1.1": 2, "10.0.2.2": 3}, } + def ipv4ToInt(addr): """Take an argument 'addr' containing an IPv4 address written as a string in dotted decimal notation, e.g. '10.1.2.3', and convert it to an integer.""" - bytes_ = [int(b, 10) for b in addr.split('.')] + bytes_ = [int(b, 10) for b in addr.split(".")] assert len(bytes_) == 4 # Note: The bytes() call below will throw exception if any # elements of bytes_ is outside of the range [0, 255]], so no need # to add a separate check for that here. - return int.from_bytes(bytes(bytes_), byteorder='big') + return int.from_bytes(bytes(bytes_), byteorder="big") + def intToIpv4(n): """Take an argument 'n' containing a 32-bit IPv4 address as an integer in the range [0, 2^32-1], and return a string in dotted decimal notation.""" - return "%d.%d.%d.%d" % ((n >> 24) & 0xff, - (n >> 16) & 0xff, - (n >> 8) & 0xff, - n & 0xff) + return "{}.{}.{}.{}".format( + (n >> 24) & 0xFF, + (n >> 16) & 0xFF, + (n >> 8) & 0xFF, + n & 0xFF, + ) + def flowCacheEntryToDebugStr(table_entry, include_action=False): - # TODO: The match fields are hardcoded to specific indices to retrieve specific parameters, such as hdr.ipv4.srcAddr and its value. - src_ip = intToIpv4(int.from_bytes(table_entry.match[1].exact.value, byteorder='big')) - dst_ip = intToIpv4(int.from_bytes(table_entry.match[2].exact.value, byteorder='big')) - proto = int.from_bytes(table_entry.match[0].exact.value, byteorder='big') - return ("(SA=%s, DA=%s, proto=%d)" - "" % (src_ip, dst_ip, proto)) + # TODO: The match fields are hardcoded to specific indices to + # retrieve specific parameters, such as hdr.ipv4.srcAddr and its + # value. + src_ip = intToIpv4( + int.from_bytes(table_entry.match[1].exact.value, byteorder="big") + ) + dst_ip = intToIpv4( + int.from_bytes(table_entry.match[2].exact.value, byteorder="big") + ) + proto = int.from_bytes(table_entry.match[0].exact.value, byteorder="big") + return f"(SA={src_ip}, DA={dst_ip}, proto={proto})" + def decodePacketInMetadata(pktin_info, packet): pktin_field_to_val = {} for md in packet.metadata: md_id_int = md.metadata_id - md_val_int = int.from_bytes(md.value, byteorder='big') + md_val_int = int.from_bytes(md.value, byteorder="big") assert md_id_int in pktin_info md_field_info = pktin_info[md_id_int] - pktin_field_to_val[md_field_info['name']] = md_val_int - ret = {'metadata': pktin_field_to_val, - 'payload': packet.payload} - print("decodePacketInMetadata: ret=%s" % (ret)) + pktin_field_to_val[md_field_info["name"]] = md_val_int + ret = {"metadata": pktin_field_to_val, "payload": packet.payload} + print(f"decodePacketInMetadata: ret={ret}") return ret + def serializableEnumDict(p4info_data, name): type_info = p4info_data.type_info name_to_int = {} int_to_name = {} for member in type_info.serializable_enums[name].members: name = member.name - int_val = int.from_bytes(member.value, byteorder='big') + int_val = int.from_bytes(member.value, byteorder="big") name_to_int[name] = int_val int_to_name[int_val] = name - print("serializableEnumDict: name='%s' name_to_int=%s int_to_name=%s" - "" % (name, name_to_int, int_to_name)) + print( + f"serializableEnumDict: name='{name}' name_to_int={name_to_int} int_to_name={int_to_name}" + ) return name_to_int, int_to_name -def decodePacketInMetadata(pktin_info, packet): - pktin_field_to_val = {} - for md in packet.metadata: - md_id_int = md.metadata_id - md_val_int = int.from_bytes(md.value, byteorder='big') - assert md_id_int in pktin_info - md_field_info = pktin_info[md_id_int] - pktin_field_to_val[md_field_info['name']] = md_val_int - ret = {'metadata': pktin_field_to_val, - 'payload': packet.payload} - print("decodePacketInMetadata: ret=%s" % (ret)) - return ret def getObj(p4info_obj_map, obj_type, name): key = (obj_type, name) return p4info_obj_map.get(key, None) + def controllerPacketMetadataDictKeyId(p4info_obj_map, name): cpm_info = getObj(p4info_obj_map, "controller_packet_metadata", name) - assert cpm_info != None + assert cpm_info is not None ret = {} for md in cpm_info.metadata: - id = md.id - ret[md.id] = {'id': md.id, 'name': md.name, 'bitwidth': md.bitwidth} + ret[md.id] = {"id": md.id, "name": md.name, "bitwidth": md.bitwidth} return ret def makeP4infoObjMap(p4info_data): p4info_obj_map = {} suffix_count = Counter() - for obj_type in ["tables", "action_profiles", "actions", "counters", - "direct_counters", "controller_packet_metadata"]: + for obj_type in [ + "tables", + "action_profiles", + "actions", + "counters", + "direct_counters", + "controller_packet_metadata", + ]: for obj in getattr(p4info_data, obj_type): pre = obj.preamble suffix = None @@ -164,12 +158,26 @@ def makeP4infoObjMap(p4info_data): del p4info_obj_map[key] return p4info_obj_map + def writeCloneSession(sw, clone_session_id, replicas): - # Size 0 bmv2 does not support truncation for clones, issue behavioral-model #996 - clone_entry = global_data['p4info_helper'].buildCloneSessionEntry(clone_session_id, replicas, 0) + # Size 0 bmv2 does not support truncation for clones, issue + # behavioral-model #996 + clone_entry = global_data["p4info_helper"].buildCloneSessionEntry( + clone_session_id, replicas, 0 + ) sw.WritePREEntry(clone_entry) -def addFlowRule( ingress_sw, src_ip_addr, dst_ip_addr, protocol, port, new_dscp, decrement_ttl_bool, dst_eth_addr): + +def addFlowRule( + ingress_sw, + src_ip_addr, + dst_ip_addr, + protocol, + port, + new_dscp, + decrement_ttl_bool, + dst_eth_addr, +): """ Install flow rule in flow cache table @@ -188,51 +196,61 @@ def addFlowRule( ingress_sw, src_ip_addr, dst_ip_addr, protocol, port, new_dscp, else: x = 0 - table_entry = global_data['p4info_helper'].buildTableEntry( + table_entry = global_data["p4info_helper"].buildTableEntry( table_name="MyIngress.flow_cache", match_fields={ "hdr.ipv4.protocol": protocol, "hdr.ipv4.srcAddr": src_ip_addr, - "hdr.ipv4.dstAddr": dst_ip_addr + "hdr.ipv4.dstAddr": dst_ip_addr, }, action_name="MyIngress.cached_action", action_params={ - "port": port, - "decrement_ttl": x, - "new_dscp": new_dscp, - "dst_eth_addr": dst_eth_addr + "port": port, + "decrement_ttl": x, + "new_dscp": new_dscp, + "dst_eth_addr": dst_eth_addr, }, # TODO: Add idle timeout - ) + ) ingress_sw.WriteTableEntry(table_entry) + def createFlowRule(notif): - # TODO: This function generates a flow entry to trigger deletion. - # The match fields are populated using values retrieved from the IDLE notification. - # Hardcoded values are configured to extract specific parameters, such as hdr.ipv4.protocol, from the IDLE notification. - table_entry = global_data['p4info_helper'].buildTableEntry( + # TODO: This function generates a flow entry to trigger deletion. + # The match fields are populated using values retrieved from the + # IDLE notification. Hardcoded values are configured to extract + # specific parameters, such as hdr.ipv4.protocol, from the IDLE + # notification. + te = notif["idle"].table_entry[0] + table_entry = global_data["p4info_helper"].buildTableEntry( table_name="MyIngress.flow_cache", match_fields={ - "hdr.ipv4.protocol": int.from_bytes(notif["idle"].table_entry[0].match[0].exact.value,byteorder='big'), - "hdr.ipv4.srcAddr": int(ipaddress.IPv4Address(notif["idle"].table_entry[0].match[1].exact.value)), - "hdr.ipv4.dstAddr": int(ipaddress.IPv4Address(notif["idle"].table_entry[0].match[2].exact.value)) + "hdr.ipv4.protocol": int.from_bytes( + te.match[0].exact.value, byteorder="big" + ), + "hdr.ipv4.srcAddr": int(ipaddress.IPv4Address(te.match[1].exact.value)), + "hdr.ipv4.dstAddr": int(ipaddress.IPv4Address(te.match[2].exact.value)), }, ) return table_entry + def deleteFlowRule(sw, table_entry): sw.DeleteTableEntry(table_entry) - print("Deleted flow_cache entry on %s. %s" - "" % (sw.name, flowCacheEntryToDebugStr(table_entry))) + print( + f"Deleted flow_cache entry on {sw.name}. {flowCacheEntryToDebugStr(table_entry)}" + ) + def addNotification(sw_name, flow_rule): # Add notification to notification DB notification = { - "timestamp": datetime.now(), + "timestamp": datetime.datetime.now(datetime.timezone.utc), "flow_rule": flow_rule, } notif_db[sw_name].append(notification) + def checkFlowRule(sw_name, flow_rule): # Checks if a flow rule is already in the notification DB # to avoid storing multiple notifications for the same flow rule @@ -242,34 +260,43 @@ def checkFlowRule(sw_name, flow_rule): for notif in notif_db[sw_name]: if notif["flow_rule"] == flow_rule: return True - + return False + def isExpired(timestamp, timeout): - return datetime.now() - timestamp > timedelta(seconds=timeout) + return datetime.datetime.now(datetime.timezone.utc) - timestamp > datetime.timedelta(seconds=timeout) + def cleanExpiredNotifiction(sw_name, timeout=5): - # Removes expired notifications + # Removes expired notifications if sw_name not in notif_db: return False # Filter the notifications to remove expired ones notif_db[sw_name] = [ - notif for notif in notif_db[sw_name] + notif + for notif in notif_db[sw_name] if not isExpired(notif["timestamp"], timeout) ] return True + def packetOutMetadataList(opcode, reserved1, operand0): # This function does not use the generated contents of the P4Info # file to map PacketOut metadata fields to indices. If you change # the PacketOut metadata format in the P4 program, this code must # be manually updated to match. - return [{"value": opcode, "bitwidth": 8}, - {"value": reserved1, "bitwidth": 8}, - {"value": operand0, "bitwidth": 32}] + return [ + {"value": opcode, "bitwidth": 8}, + {"value": reserved1, "bitwidth": 8}, + {"value": operand0, "bitwidth": 32}, + ] + -def sendPacketOut(sw ,payload, metadatas): +def sendPacketOut(sw, payload, metadatas): # TODO: Implement the function logic to send a packet-out message + pass + def readTableRules(p4info_helper, sw): """ @@ -278,19 +305,22 @@ def readTableRules(p4info_helper, sw): :param p4info_helper: the P4Info helper :param sw: the switch connection """ - print('\n----- Reading tables rules for %s -----' % sw.name) + print(f"\n----- Reading tables rules for {sw.name} -----") for response in sw.ReadTableEntries(): for entity in response.entities: entry = entity.table_entry # TODO For extra credit, you can use the p4info_helper to translate # the IDs in the entry to names print(entry) - print('-----') + print("-----") + def printCounter(p4info_helper, sw, counter_name, index): """ - Reads the specified counter at the given index from the switch. In our - program, the index is derived from the first 6 bits of the IP destination address. + Reads the specified counter at the given index from the switch. In + our program, the index is derived from the first 6 bits of the IP + destination address. + If the index is 0, it will return all values from the counter. :param p4info_helper: the P4Info helper @@ -299,122 +329,151 @@ def printCounter(p4info_helper, sw, counter_name, index): :param index: the counter index (in our case, first 6 bits of the IP) """ try: - for response in sw.ReadCounters(p4info_helper.get_counters_id(counter_name), index): + for response in sw.ReadCounters( + p4info_helper.get_counters_id(counter_name), index + ): for entity in response.entities: counter = entity.counter_entry - print("%s %s %d: %d packets (%d bytes)" % ( - sw.name, counter_name, index, - counter.data.packet_count, counter.data.byte_count - )) + print( + f"{sw.name} {counter_name} {index}: {counter.data.packet_count} packets ({counter.data.byte_count} bytes)" + ) except grpc.RpcError as e: - print(f"[gRPC Error in printCounter for {sw.name}]") - printGrpcError(e) + print(f"[gRPC Error in printCounter for {sw.name}]") + printGrpcError(e) - if e.code() == grpc.StatusCode.UNKNOWN: + if e.code() == grpc.StatusCode.UNKNOWN: print(f"Unknown gRPC error from {sw.name}. Retrying...") time.sleep(2) except Exception as e: - print(f"[Unexpected Error in printCounter for {sw.name}]: {e}") - traceback.print_exc() - time.sleep(2) + print(f"[Unexpected Error in printCounter for {sw.name}]: {e}") + traceback.print_exc() + time.sleep(2) + def processPacket(message): - payload = message["packet-in"].payload - packet = message["packet-in"] - print("Received PacketIn message of length %d bytes from switch %s" - "" % (len(payload), message["sw"].name)) - if len(payload) > 0: - i = 0 - pkt = Ether(payload) - ip_proto = pkt[IP].proto - ip_sa_str = pkt[IP].src - src_ip_addr = ipv4ToInt(ip_sa_str) - ip_da_str = pkt[IP].dst - dst_ip_addr = ipv4ToInt(ip_da_str) - pktinfo = decodePacketInMetadata(global_data['cpm_packetin_id2data'], packet) - debug_packetin = False - if debug_packetin: - i += 1 - print("") - print("pktin %d of %d" % (i, len(payload))) - print("type(pktin.packet.payload)=%s" - "" % (type(payload))) - print(payload) - print(pktinfo) - print("Scapy decode:") - print(pkt) - print("IPv4 proto %d (type %s)" - "" % (ip_proto, type(ip_proto))) - print("IPv4 SA %08x (type %s)" - "" % (src_ip_addr, type(src_ip_addr))) - print("IPv4 DA %08x (type %s)" - "" % (dst_ip_addr, type(dst_ip_addr))) - if pktinfo['metadata']['punt_reason'] == global_data['punt_reason_name2int']['FLOW_UNKNOWN']: - dest_port_int = lookup_table[message["sw"].name][ip_da_str] - decrement_ttl_bool = True - new_dscp_int = 5 - global_data['index'] = int(pkt[IP].dst.split('.')[3]) - dst_eth_addr = global_data[ip_da_str] - metadatas = packetOutMetadataList( - global_data['controller_opcode_name2int']['SEND_TO_PORT_IN_OPERAND0'], - 0, dest_port_int) - sendPacketOut(message["sw"], payload, metadatas) - addFlowRule(message["sw"], - src_ip_addr, - dst_ip_addr, - ip_proto, - dest_port_int, - new_dscp_int, - decrement_ttl_bool, - dst_eth_addr) - - print("For switch %s flow (SA=%s, DA=%s, proto=%d)" - " added table entry to send packets" - " to port %d with new DSCP %d" - "" % (message["sw"].name, ip_sa_str, ip_da_str, - ip_proto, dest_port_int, new_dscp_int)) + payload = message["packet-in"].payload + packet = message["packet-in"] + print("Received PacketIn message of length {} bytes from switch {}".format( + len(payload), message["sw"].name + )) + if len(payload) > 0: + i = 0 + pkt = Ether(payload) + ip_proto = pkt[IP].proto + ip_sa_str = pkt[IP].src + src_ip_addr = ipv4ToInt(ip_sa_str) + ip_da_str = pkt[IP].dst + dst_ip_addr = ipv4ToInt(ip_da_str) + pktinfo = decodePacketInMetadata(global_data["cpm_packetin_id2data"], packet) + debug_packetin = False + if debug_packetin: + i += 1 + print() + print(f"pktin {i} of {len(payload)}") + print(f"type(pktin.packet.payload)={type(payload)}") + print(payload) + print(pktinfo) + print("Scapy decode:") + print(pkt) + print(f"IPv4 proto {ip_proto} (type {type(ip_proto)})") + print(f"IPv4 SA {src_ip_addr:08x} (type {type(src_ip_addr)})") + print(f"IPv4 DA {dst_ip_addr:08x} (type {type(dst_ip_addr)})") + if ( + pktinfo["metadata"]["punt_reason"] + == global_data["punt_reason_name2int"]["FLOW_UNKNOWN"] + ): + dest_port_int = lookup_table[message["sw"].name][ip_da_str] + decrement_ttl_bool = True + new_dscp_int = 5 + global_data["index"] = int(pkt[IP].dst.split(".")[3]) + dst_eth_addr = global_data[ip_da_str] + metadatas = packetOutMetadataList( + global_data["controller_opcode_name2int"]["SEND_TO_PORT_IN_OPERAND0"], + 0, + dest_port_int, + ) + sendPacketOut(message["sw"], payload, metadatas) + addFlowRule( + message["sw"], + src_ip_addr, + dst_ip_addr, + ip_proto, + dest_port_int, + new_dscp_int, + decrement_ttl_bool, + dst_eth_addr, + ) + + print( + "For switch {} flow (SA={}, DA={}, proto={})" + " added table entry to send packets" + " to port {} with new DSCP {}".format( + message["sw"].name, + ip_sa_str, + ip_da_str, + ip_proto, + dest_port_int, + new_dscp_int, + ) + ) + async def processNotif(notif_queue): - while True: - notif = await notif_queue.get() - debug_notif = False + while True: + notif = await notif_queue.get() + debug_notif = False + if debug_notif: + print(notif) + pprint.pprint(notif_db) + if notif["type"] == "packet-in": + processPacket(notif) + printCounter( + global_data["p4info_helper"], + notif["sw"], + "MyIngress.ingressPktOutCounter", + global_data["index"], + ) + printCounter( + global_data["p4info_helper"], + notif["sw"], + "MyEgress.egressPktInCounter", + global_data["index"], + ) if debug_notif: - print(notif) - pprint.pprint(notif_db) - if notif["type"] == "packet-in": - processPacket(notif) - printCounter(global_data ['p4info_helper'], notif["sw"], 'MyIngress.ingressPktOutCounter', global_data ['index']) - printCounter(global_data ['p4info_helper'], notif["sw"], 'MyEgress.egressPktInCounter', global_data ['index']) - if debug_notif: - readTableRules(global_data ['p4info_helper'], notif["sw"]) - elif notif["type"] == "idle-notif": - # TODO: For extra credit, you can experiment with adjusting the stale time for notifications (e.g., 10 seconds) - # and optimize the behavior of the notification database (notif_db). - if notif["sw"].name not in notif_db: - notif_db[notif["sw"].name] = [] - else: - # Check if a notification is older than 10 seconds - cleanExpiredNotifiction(notif["sw"].name, 10) - - table_entry = createFlowRule(notif) - - if not checkFlowRule(notif["sw"].name, table_entry): - addNotification(notif["sw"].name, table_entry) - deleteFlowRule(notif["sw"], table_entry) - else: - print("Received idle timeout notification for switch=%s %s" - " It is duplicate of recently processed notification, so ignoring it." - "" % (notif["sw"].name, - flowCacheEntryToDebugStr(table_entry))) - notif_queue.task_done() - -async def packetInHandler(notif_queue,sw): + readTableRules(global_data["p4info_helper"], notif["sw"]) + elif notif["type"] == "idle-notif": + # TODO: For extra credit, you can experiment with + # adjusting the stale time for notifications (e.g., 10 + # seconds) and optimize the behavior of the notification + # database (notif_db). + if notif["sw"].name not in notif_db: + notif_db[notif["sw"].name] = [] + else: + # Check if a notification is older than 10 seconds + cleanExpiredNotifiction(notif["sw"].name, 10) + + table_entry = createFlowRule(notif) + + if not checkFlowRule(notif["sw"].name, table_entry): + addNotification(notif["sw"].name, table_entry) + deleteFlowRule(notif["sw"], table_entry) + else: + print( + "Received idle timeout notification for switch={} {}" + " It is duplicate of recently processed notification," + " so ignoring it." + "".format(notif["sw"].name, flowCacheEntryToDebugStr(table_entry)) + ) + notif_queue.task_done() + + +async def packetInHandler(notif_queue, sw): # TODO: Implement the function logic to handle a packet-in message while True: try: packet_in = await asyncio.to_thread(sw.PacketIn) - #print(f"Received packet: {packet_in}") + # print(f"Received packet: {packet_in}") message = {"type": "packet-in", "sw": sw, "packet-in": packet_in} await notif_queue.put(message) @@ -431,7 +490,8 @@ async def packetInHandler(notif_queue,sw): traceback.print_exc() await asyncio.sleep(2) -async def idleTimeHandler(notif_queue,sw): + +async def idleTimeHandler(notif_queue, sw): # TODO: Implement the function logic to handle idle timeout notification while True: idle_notif = await asyncio.to_thread(sw.IdleTimeoutNotification) @@ -439,76 +499,90 @@ async def idleTimeHandler(notif_queue,sw): await notif_queue.put(message) await asyncio.sleep(5) + def printGrpcError(e): - print("gRPC Error:", e.details(), end=' ') + print("gRPC Error:", e.details(), end=" ") status_code = e.code() - print("(%s)" % status_code.name, end=' ') + print(f"({status_code.name})", end=" ") traceback = sys.exc_info()[2] - print("[%s:%d]" % (traceback.tb_frame.f_code.co_filename, traceback.tb_lineno)) + print(f"[{traceback.tb_frame.f_code.co_filename}:{traceback.tb_lineno}]") + async def main(p4info_file_path, bmv2_file_path): # Instantiate a P4Runtime helper from the p4info file - global_data ['p4info_helper'] = p4runtime_lib.helper.P4InfoHelper(p4info_file_path) - p4info_helper = global_data ['p4info_helper'] + global_data["p4info_helper"] = p4runtime_lib.helper.P4InfoHelper(p4info_file_path) + p4info_helper = global_data["p4info_helper"] try: # Create a switch connection object for s1,s2,s3; # this is backed by a P4Runtime gRPC connection. # Also, dump all P4Runtime messages sent to switch to given txt files. s1 = p4runtime_lib.bmv2.Bmv2SwitchConnection( - name='s1', - address='127.0.0.1:50051', + name="s1", + address="127.0.0.1:50051", device_id=0, - proto_dump_file='logs/s1-p4runtime-requests.txt') + proto_dump_file="logs/s1-p4runtime-requests.txt", + ) s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection( - name='s2', - address='127.0.0.1:50052', + name="s2", + address="127.0.0.1:50052", device_id=1, - proto_dump_file='logs/s2-p4runtime-requests.txt') + proto_dump_file="logs/s2-p4runtime-requests.txt", + ) s3 = p4runtime_lib.bmv2.Bmv2SwitchConnection( - name='s3', - address='127.0.0.1:50053', + name="s3", + address="127.0.0.1:50053", device_id=2, - proto_dump_file='logs/s3-p4runtime-requests.txt') + proto_dump_file="logs/s3-p4runtime-requests.txt", + ) - # Send master arbitration update message to establish this controller as - # master (required by P4Runtime before performing any other write operation) + # Send master arbitration update message to establish this + # controller as master (required by P4Runtime before + # performing any other write operation) s1.MasterArbitrationUpdate() s2.MasterArbitrationUpdate() s3.MasterArbitrationUpdate() # Install the P4 program on the switches - s1.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, - bmv2_json_file_path=bmv2_file_path) + s1.SetForwardingPipelineConfig( + p4info=p4info_helper.p4info, bmv2_json_file_path=bmv2_file_path + ) print("Installed P4 Program using SetForwardingPipelineConfig on s1") - s2.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, - bmv2_json_file_path=bmv2_file_path) + s2.SetForwardingPipelineConfig( + p4info=p4info_helper.p4info, bmv2_json_file_path=bmv2_file_path + ) print("Installed P4 Program using SetForwardingPipelineConfig on s2") - s3.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, - bmv2_json_file_path=bmv2_file_path) + s3.SetForwardingPipelineConfig( + p4info=p4info_helper.p4info, bmv2_json_file_path=bmv2_file_path + ) print("Installed P4 Program using SetForwardingPipelineConfig on s3") - global_data['p4info_obj_map'] = makeP4infoObjMap(p4info_helper.p4info) - global_data['cpm_packetin_id2data'] = \ - controllerPacketMetadataDictKeyId(global_data['p4info_obj_map'], - "packet_in") + global_data["p4info_obj_map"] = makeP4infoObjMap(p4info_helper.p4info) + global_data["cpm_packetin_id2data"] = controllerPacketMetadataDictKeyId( + global_data["p4info_obj_map"], "packet_in" + ) - global_data['punt_reason_name2int'], global_data['punt_reason_int2name'] = \ - serializableEnumDict(p4info_helper.p4info, 'PuntReason_t') - global_data['controller_opcode_name2int'], global_data['controller_opcode_int2name'] = \ - serializableEnumDict(p4info_helper.p4info, 'ControllerOpcode_t') + global_data["punt_reason_name2int"], global_data["punt_reason_int2name"] = ( + serializableEnumDict(p4info_helper.p4info, "PuntReason_t") + ) + ( + global_data["controller_opcode_name2int"], + global_data["controller_opcode_int2name"], + ) = serializableEnumDict(p4info_helper.p4info, "ControllerOpcode_t") try: - replicas = [{ "egress_port": global_data['CPU_PORT'], "instance": 1 }] - writeCloneSession(s1, global_data['CPU_PORT_CLONE_SESSION_ID'], replicas) - writeCloneSession(s2, global_data['CPU_PORT_CLONE_SESSION_ID'], replicas) - writeCloneSession(s3, global_data['CPU_PORT_CLONE_SESSION_ID'], replicas) - - except shp4rt.P4RuntimeWriteException as e: - print("Got exception trying to configure clone session %d." - " Assuming it was initialized already in an earlier" - " run of the controller." - "" % (global_data['CPU_PORT_CLONE_SESSION_ID'])) + replicas = [{"egress_port": global_data["CPU_PORT"], "instance": 1}] + writeCloneSession(s1, global_data["CPU_PORT_CLONE_SESSION_ID"], replicas) + writeCloneSession(s2, global_data["CPU_PORT_CLONE_SESSION_ID"], replicas) + writeCloneSession(s3, global_data["CPU_PORT_CLONE_SESSION_ID"], replicas) + + except shp4rt.P4RuntimeWriteException: + print( + "Got exception trying to configure clone session {}." + " Assuming it was initialized already in an earlier" + " run of the controller.".format( + global_data["CPU_PORT_CLONE_SESSION_ID"]) + ) notif_queue = asyncio.Queue() @@ -522,33 +596,53 @@ async def main(p4info_file_path, bmv2_file_path): proc_notif = asyncio.create_task(processNotif(notif_queue)) - await asyncio.gather(pkt_s1,pkt_s2,pkt_s3,idle_notif_s1, idle_notif_s2, idle_notif_s3, proc_notif) + await asyncio.gather( + pkt_s1, + pkt_s2, + pkt_s3, + idle_notif_s1, + idle_notif_s2, + idle_notif_s3, + proc_notif, + ) except KeyboardInterrupt: print(" Shutting down.") except grpc.RpcError as e: print(f"gRPC error occurred: {e}") - print(f"Status code: {e.code()}") # e.g., StatusCode.UNAVAILABLE or StatusCode.INVALID_ARGUMENT + # e.g., StatusCode.UNAVAILABLE or StatusCode.INVALID_ARGUMENT + print(f"Status code: {e.code()}") print(f"Details: {e.details()}") ShutdownAllSwitchConnections() -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='P4Runtime Controller') - parser.add_argument('--p4info', help='p4info proto in text format from p4c', - type=str, action="store", required=False, - default='./build/flowcache.p4.p4info.txtpb') - parser.add_argument('--bmv2-json', help='BMv2 JSON file from p4c', - type=str, action="store", required=False, - default='./build/flowcache.json') + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="P4Runtime Controller") + parser.add_argument( + "--p4info", + help="p4info proto in text format from p4c", + type=str, + action="store", + required=False, + default="./build/flowcache.p4.p4info.txtpb", + ) + parser.add_argument( + "--bmv2-json", + help="BMv2 JSON file from p4c", + type=str, + action="store", + required=False, + default="./build/flowcache.json", + ) args = parser.parse_args() if not os.path.exists(args.p4info): parser.print_help() - print("\np4info file not found: %s\nHave you run 'make'?" % args.p4info) + print(f"\np4info file not found: {args.p4info}\nHave you run 'make'?") parser.exit(1) if not os.path.exists(args.bmv2_json): parser.print_help() - print("\nBMv2 JSON file not found: %s\nHave you run 'make'?" % args.bmv2_json) + print(f"\nBMv2 JSON file not found: {args.bmv2_json}\nHave you run 'make'?") parser.exit(1) asyncio.run(main(args.p4info, args.bmv2_json)) diff --git a/exercises/flowcache/solution/mycontroller.py b/exercises/flowcache/solution/mycontroller.py index 0e517b3d..f98e6ddd 100755 --- a/exercises/flowcache/solution/mycontroller.py +++ b/exercises/flowcache/solution/mycontroller.py @@ -4,43 +4,43 @@ # # SPDX-License-Identifier: GPL-2.0-only import argparse -import os -import sys import asyncio -import traceback -import time +import datetime import ipaddress +import os import pprint - +import sys +import time +import traceback from collections import Counter -from datetime import datetime, timedelta -from scapy.all import * import grpc +from scapy.all import IP, Ether # Import P4Runtime lib from parent utils dir # Probably there's a better way of doing this. sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), - '../../utils/')) + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../utils/") +) import p4runtime_lib.bmv2 import p4runtime_lib.helper -from p4runtime_lib.switch import ShutdownAllSwitchConnections import p4runtime_sh.p4runtime as shp4rt +from p4runtime_lib.switch import ShutdownAllSwitchConnections NSEC_PER_SEC = 1000 * 1000 * 1000 global_data = {} -global_data['CPU_PORT'] = 510 -global_data['CPU_PORT_CLONE_SESSION_ID'] = 57 -global_data['NUM_PORTS'] = 3 -global_data['index'] = 0 +global_data["CPU_PORT"] = 510 +global_data["CPU_PORT_CLONE_SESSION_ID"] = 57 +global_data["NUM_PORTS"] = 3 +global_data["index"] = 0 global_data["10.0.1.1"] = "08:00:00:00:01:11" global_data["10.0.2.2"] = "08:00:00:00:02:22" global_data["10.0.3.3"] = "08:00:00:00:03:33" -## The notification database keeps track of the received idle notifications and triggers the deletion of stale flow rules. +# The notification database keeps track of the received idle +# notifications and triggers the deletion of stale flow rules. notif_db = {} # The lookup table is defined to simplify reachability and provide connectivity @@ -48,109 +48,103 @@ # to solve this problem more effectively. lookup_table = { - "s1": { - "10.0.1.1": 1, - "10.0.2.2": 2, - "10.0.3.3": 3 - }, - "s2": { - "10.0.2.2": 1, - "10.0.1.1": 2, - "10.0.3.3": 3 - }, - "s3": { - "10.0.3.3": 1, - "10.0.1.1": 2, - "10.0.2.2": 3 - } + "s1": {"10.0.1.1": 1, "10.0.2.2": 2, "10.0.3.3": 3}, + "s2": {"10.0.2.2": 1, "10.0.1.1": 2, "10.0.3.3": 3}, + "s3": {"10.0.3.3": 1, "10.0.1.1": 2, "10.0.2.2": 3}, } + def ipv4ToInt(addr): """Take an argument 'addr' containing an IPv4 address written as a string in dotted decimal notation, e.g. '10.1.2.3', and convert it to an integer.""" - bytes_ = [int(b, 10) for b in addr.split('.')] + bytes_ = [int(b, 10) for b in addr.split(".")] assert len(bytes_) == 4 # Note: The bytes() call below will throw exception if any # elements of bytes_ is outside of the range [0, 255]], so no need # to add a separate check for that here. - return int.from_bytes(bytes(bytes_), byteorder='big') + return int.from_bytes(bytes(bytes_), byteorder="big") + def intToIpv4(n): """Take an argument 'n' containing a 32-bit IPv4 address as an integer in the range [0, 2^32-1], and return a string in dotted decimal notation.""" - return "%d.%d.%d.%d" % ((n >> 24) & 0xff, - (n >> 16) & 0xff, - (n >> 8) & 0xff, - n & 0xff) + return "{}.{}.{}.{}".format( + (n >> 24) & 0xFF, + (n >> 16) & 0xFF, + (n >> 8) & 0xFF, + n & 0xFF, + ) + def flowCacheEntryToDebugStr(table_entry, include_action=False): - # TODO: The match fields are hardcoded to specific indices to retrieve specific parameters, such as hdr.ipv4.srcAddr and its value. - src_ip = intToIpv4(int.from_bytes(table_entry.match[1].exact.value, byteorder='big')) - dst_ip = intToIpv4(int.from_bytes(table_entry.match[2].exact.value, byteorder='big')) - proto = int.from_bytes(table_entry.match[0].exact.value, byteorder='big') - return ("(SA=%s, DA=%s, proto=%d)" - "" % (src_ip, dst_ip, proto)) + # TODO: The match fields are hardcoded to specific indices to + # retrieve specific parameters, such as hdr.ipv4.srcAddr and its + # value. + src_ip = intToIpv4( + int.from_bytes(table_entry.match[1].exact.value, byteorder="big") + ) + dst_ip = intToIpv4( + int.from_bytes(table_entry.match[2].exact.value, byteorder="big") + ) + proto = int.from_bytes(table_entry.match[0].exact.value, byteorder="big") + return f"(SA={src_ip}, DA={dst_ip}, proto={proto})" + def decodePacketInMetadata(pktin_info, packet): pktin_field_to_val = {} for md in packet.metadata: md_id_int = md.metadata_id - md_val_int = int.from_bytes(md.value, byteorder='big') + md_val_int = int.from_bytes(md.value, byteorder="big") assert md_id_int in pktin_info md_field_info = pktin_info[md_id_int] - pktin_field_to_val[md_field_info['name']] = md_val_int - ret = {'metadata': pktin_field_to_val, - 'payload': packet.payload} - print("decodePacketInMetadata: ret=%s" % (ret)) + pktin_field_to_val[md_field_info["name"]] = md_val_int + ret = {"metadata": pktin_field_to_val, "payload": packet.payload} + print(f"decodePacketInMetadata: ret={ret}") return ret + def serializableEnumDict(p4info_data, name): type_info = p4info_data.type_info name_to_int = {} int_to_name = {} for member in type_info.serializable_enums[name].members: name = member.name - int_val = int.from_bytes(member.value, byteorder='big') + int_val = int.from_bytes(member.value, byteorder="big") name_to_int[name] = int_val int_to_name[int_val] = name - print("serializableEnumDict: name='%s' name_to_int=%s int_to_name=%s" - "" % (name, name_to_int, int_to_name)) + print( + f"serializableEnumDict: name='{name}' name_to_int={name_to_int} int_to_name={int_to_name}" + ) return name_to_int, int_to_name -def decodePacketInMetadata(pktin_info, packet): - pktin_field_to_val = {} - for md in packet.metadata: - md_id_int = md.metadata_id - md_val_int = int.from_bytes(md.value, byteorder='big') - assert md_id_int in pktin_info - md_field_info = pktin_info[md_id_int] - pktin_field_to_val[md_field_info['name']] = md_val_int - ret = {'metadata': pktin_field_to_val, - 'payload': packet.payload} - print("decodePacketInMetadata: ret=%s" % (ret)) - return ret def getObj(p4info_obj_map, obj_type, name): key = (obj_type, name) return p4info_obj_map.get(key, None) + def controllerPacketMetadataDictKeyId(p4info_obj_map, name): cpm_info = getObj(p4info_obj_map, "controller_packet_metadata", name) - assert cpm_info != None + assert cpm_info is not None ret = {} for md in cpm_info.metadata: - id = md.id - ret[md.id] = {'id': md.id, 'name': md.name, 'bitwidth': md.bitwidth} + ret[md.id] = {"id": md.id, "name": md.name, "bitwidth": md.bitwidth} return ret def makeP4infoObjMap(p4info_data): p4info_obj_map = {} suffix_count = Counter() - for obj_type in ["tables", "action_profiles", "actions", "counters", - "direct_counters", "controller_packet_metadata"]: + for obj_type in [ + "tables", + "action_profiles", + "actions", + "counters", + "direct_counters", + "controller_packet_metadata", + ]: for obj in getattr(p4info_data, obj_type): pre = obj.preamble suffix = None @@ -164,12 +158,26 @@ def makeP4infoObjMap(p4info_data): del p4info_obj_map[key] return p4info_obj_map + def writeCloneSession(sw, clone_session_id, replicas): - # Size 0 bmv2 does not support truncation for clones, issue behavioral-model #996 - clone_entry = global_data['p4info_helper'].buildCloneSessionEntry(clone_session_id, replicas, 0) + # Size 0 bmv2 does not support truncation for clones, issue + # behavioral-model #996 + clone_entry = global_data["p4info_helper"].buildCloneSessionEntry( + clone_session_id, replicas, 0 + ) sw.WritePREEntry(clone_entry) -def addFlowRule( ingress_sw, src_ip_addr, dst_ip_addr, protocol, port, new_dscp, decrement_ttl_bool, dst_eth_addr): + +def addFlowRule( + ingress_sw, + src_ip_addr, + dst_ip_addr, + protocol, + port, + new_dscp, + decrement_ttl_bool, + dst_eth_addr, +): """ Install flow rule in flow cache table @@ -188,52 +196,62 @@ def addFlowRule( ingress_sw, src_ip_addr, dst_ip_addr, protocol, port, new_dscp, else: x = 0 - table_entry = global_data['p4info_helper'].buildTableEntry( + table_entry = global_data["p4info_helper"].buildTableEntry( table_name="MyIngress.flow_cache", match_fields={ "hdr.ipv4.protocol": protocol, "hdr.ipv4.srcAddr": src_ip_addr, - "hdr.ipv4.dstAddr": dst_ip_addr + "hdr.ipv4.dstAddr": dst_ip_addr, }, action_name="MyIngress.cached_action", action_params={ - "port": port, - "decrement_ttl": x, - "new_dscp": new_dscp, - "dst_eth_addr": dst_eth_addr + "port": port, + "decrement_ttl": x, + "new_dscp": new_dscp, + "dst_eth_addr": dst_eth_addr, }, # TODO: Add idle timeout - idle_timeout_ns = 3 * NSEC_PER_SEC - ) + idle_timeout_ns=3 * NSEC_PER_SEC, + ) ingress_sw.WriteTableEntry(table_entry) + def createFlowRule(notif): - # TODO: This function generates a flow entry to trigger deletion. - # The match fields are populated using values retrieved from the IDLE notification. - # Hardcoded values are configured to extract specific parameters, such as hdr.ipv4.protocol, from the IDLE notification. - table_entry = global_data['p4info_helper'].buildTableEntry( + # TODO: This function generates a flow entry to trigger deletion. + # The match fields are populated using values retrieved from the + # IDLE notification. Hardcoded values are configured to extract + # specific parameters, such as hdr.ipv4.protocol, from the IDLE + # notification. + te = notif["idle"].table_entry[0] + table_entry = global_data["p4info_helper"].buildTableEntry( table_name="MyIngress.flow_cache", match_fields={ - "hdr.ipv4.protocol": int.from_bytes(notif["idle"].table_entry[0].match[0].exact.value,byteorder='big'), - "hdr.ipv4.srcAddr": int(ipaddress.IPv4Address(notif["idle"].table_entry[0].match[1].exact.value)), - "hdr.ipv4.dstAddr": int(ipaddress.IPv4Address(notif["idle"].table_entry[0].match[2].exact.value)) + "hdr.ipv4.protocol": int.from_bytes( + te.match[0].exact.value, byteorder="big" + ), + "hdr.ipv4.srcAddr": int(ipaddress.IPv4Address(te.match[1].exact.value)), + "hdr.ipv4.dstAddr": int(ipaddress.IPv4Address(te.match[2].exact.value)), }, ) return table_entry + def deleteFlowRule(sw, table_entry): sw.DeleteTableEntry(table_entry) - print("Deleted flow_cache entry on %s. %s" - "" % (sw.name, flowCacheEntryToDebugStr(table_entry))) + print( + f"Deleted flow_cache entry on {sw.name}. {flowCacheEntryToDebugStr(table_entry)}" + ) + def addNotification(sw_name, flow_rule): # Add notification to notification DB notification = { - "timestamp": datetime.now(), + "timestamp": datetime.datetime.now(datetime.timezone.utc), "flow_rule": flow_rule, } notif_db[sw_name].append(notification) + def checkFlowRule(sw_name, flow_rule): # Checks if a flow rule is already in the notification DB # to avoid storing multiple notifications for the same flow rule @@ -243,36 +261,44 @@ def checkFlowRule(sw_name, flow_rule): for notif in notif_db[sw_name]: if notif["flow_rule"] == flow_rule: return True - + return False + def isExpired(timestamp, timeout): - return datetime.now() - timestamp > timedelta(seconds=timeout) + return datetime.datetime.now(datetime.timezone.utc) - timestamp > datetime.timedelta(seconds=timeout) + def cleanExpiredNotifiction(sw_name, timeout=5): - # Removes expired notifications + # Removes expired notifications if sw_name not in notif_db: return False # Filter the notifications to remove expired ones notif_db[sw_name] = [ - notif for notif in notif_db[sw_name] + notif + for notif in notif_db[sw_name] if not isExpired(notif["timestamp"], timeout) ] return True + def packetOutMetadataList(opcode, reserved1, operand0): # This function does not use the generated contents of the P4Info # file to map PacketOut metadata fields to indices. If you change # the PacketOut metadata format in the P4 program, this code must # be manually updated to match. - return [{"value": opcode, "bitwidth": 8}, - {"value": reserved1, "bitwidth": 8}, - {"value": operand0, "bitwidth": 32}] + return [ + {"value": opcode, "bitwidth": 8}, + {"value": reserved1, "bitwidth": 8}, + {"value": operand0, "bitwidth": 32}, + ] + -def sendPacketOut(sw ,payload, metadatas): +def sendPacketOut(sw, payload, metadatas): # TODO: Implement the function logic to send a packet-out message sw.PacketOut(payload, metadatas) + def readTableRules(p4info_helper, sw): """ Reads the table entries from all tables on the switch. @@ -280,19 +306,22 @@ def readTableRules(p4info_helper, sw): :param p4info_helper: the P4Info helper :param sw: the switch connection """ - print('\n----- Reading tables rules for %s -----' % sw.name) + print(f"\n----- Reading tables rules for {sw.name} -----") for response in sw.ReadTableEntries(): for entity in response.entities: entry = entity.table_entry # TODO For extra credit, you can use the p4info_helper to translate # the IDs in the entry to names print(entry) - print('-----') + print("-----") + def printCounter(p4info_helper, sw, counter_name, index): """ - Reads the specified counter at the given index from the switch. In our - program, the index is derived from the first 6 bits of the IP destination address. + Reads the specified counter at the given index from the switch. In + our program, the index is derived from the first 6 bits of the IP + destination address. + If the index is 0, it will return all values from the counter. :param p4info_helper: the P4Info helper @@ -301,122 +330,151 @@ def printCounter(p4info_helper, sw, counter_name, index): :param index: the counter index (in our case, first 6 bits of the IP) """ try: - for response in sw.ReadCounters(p4info_helper.get_counters_id(counter_name), index): + for response in sw.ReadCounters( + p4info_helper.get_counters_id(counter_name), index + ): for entity in response.entities: counter = entity.counter_entry - print("%s %s %d: %d packets (%d bytes)" % ( - sw.name, counter_name, index, - counter.data.packet_count, counter.data.byte_count - )) + print( + f"{sw.name} {counter_name} {index}: {counter.data.packet_count} packets ({counter.data.byte_count} bytes)" + ) except grpc.RpcError as e: - print(f"[gRPC Error in printCounter for {sw.name}]") - printGrpcError(e) + print(f"[gRPC Error in printCounter for {sw.name}]") + printGrpcError(e) - if e.code() == grpc.StatusCode.UNKNOWN: + if e.code() == grpc.StatusCode.UNKNOWN: print(f"Unknown gRPC error from {sw.name}. Retrying...") time.sleep(2) except Exception as e: - print(f"[Unexpected Error in printCounter for {sw.name}]: {e}") - traceback.print_exc() - time.sleep(2) + print(f"[Unexpected Error in printCounter for {sw.name}]: {e}") + traceback.print_exc() + time.sleep(2) + def processPacket(message): - payload = message["packet-in"].payload - packet = message["packet-in"] - print("Received PacketIn message of length %d bytes from switch %s" - "" % (len(payload), message["sw"].name)) - if len(payload) > 0: - i = 0 - pkt = Ether(payload) - ip_proto = pkt[IP].proto - ip_sa_str = pkt[IP].src - src_ip_addr = ipv4ToInt(ip_sa_str) - ip_da_str = pkt[IP].dst - dst_ip_addr = ipv4ToInt(ip_da_str) - pktinfo = decodePacketInMetadata(global_data['cpm_packetin_id2data'], packet) - debug_packetin = False - if debug_packetin: - i += 1 - print("") - print("pktin %d of %d" % (i, len(payload))) - print("type(pktin.packet.payload)=%s" - "" % (type(payload))) - print(payload) - print(pktinfo) - print("Scapy decode:") - print(pkt) - print("IPv4 proto %d (type %s)" - "" % (ip_proto, type(ip_proto))) - print("IPv4 SA %08x (type %s)" - "" % (src_ip_addr, type(src_ip_addr))) - print("IPv4 DA %08x (type %s)" - "" % (dst_ip_addr, type(dst_ip_addr))) - if pktinfo['metadata']['punt_reason'] == global_data['punt_reason_name2int']['FLOW_UNKNOWN']: - dest_port_int = lookup_table[message["sw"].name][ip_da_str] - decrement_ttl_bool = True - new_dscp_int = 5 - global_data['index'] = int(pkt[IP].dst.split('.')[3]) - dst_eth_addr = global_data[ip_da_str] - metadatas = packetOutMetadataList( - global_data['controller_opcode_name2int']['SEND_TO_PORT_IN_OPERAND0'], - 0, dest_port_int) - sendPacketOut(message["sw"], payload, metadatas) - addFlowRule(message["sw"], - src_ip_addr, - dst_ip_addr, - ip_proto, - dest_port_int, - new_dscp_int, - decrement_ttl_bool, - dst_eth_addr) - - print("For switch %s flow (SA=%s, DA=%s, proto=%d)" - " added table entry to send packets" - " to port %d with new DSCP %d" - "" % (message["sw"].name, ip_sa_str, ip_da_str, - ip_proto, dest_port_int, new_dscp_int)) + payload = message["packet-in"].payload + packet = message["packet-in"] + print("Received PacketIn message of length {} bytes from switch {}".format( + len(payload), message["sw"].name + )) + if len(payload) > 0: + i = 0 + pkt = Ether(payload) + ip_proto = pkt[IP].proto + ip_sa_str = pkt[IP].src + src_ip_addr = ipv4ToInt(ip_sa_str) + ip_da_str = pkt[IP].dst + dst_ip_addr = ipv4ToInt(ip_da_str) + pktinfo = decodePacketInMetadata(global_data["cpm_packetin_id2data"], packet) + debug_packetin = False + if debug_packetin: + i += 1 + print() + print(f"pktin {i} of {len(payload)}") + print(f"type(pktin.packet.payload)={type(payload)}") + print(payload) + print(pktinfo) + print("Scapy decode:") + print(pkt) + print(f"IPv4 proto {ip_proto} (type {type(ip_proto)})") + print(f"IPv4 SA {src_ip_addr:08x} (type {type(src_ip_addr)})") + print(f"IPv4 DA {dst_ip_addr:08x} (type {type(dst_ip_addr)})") + if ( + pktinfo["metadata"]["punt_reason"] + == global_data["punt_reason_name2int"]["FLOW_UNKNOWN"] + ): + dest_port_int = lookup_table[message["sw"].name][ip_da_str] + decrement_ttl_bool = True + new_dscp_int = 5 + global_data["index"] = int(pkt[IP].dst.split(".")[3]) + dst_eth_addr = global_data[ip_da_str] + metadatas = packetOutMetadataList( + global_data["controller_opcode_name2int"]["SEND_TO_PORT_IN_OPERAND0"], + 0, + dest_port_int, + ) + sendPacketOut(message["sw"], payload, metadatas) + addFlowRule( + message["sw"], + src_ip_addr, + dst_ip_addr, + ip_proto, + dest_port_int, + new_dscp_int, + decrement_ttl_bool, + dst_eth_addr, + ) + + print( + "For switch {} flow (SA={}, DA={}, proto={})" + " added table entry to send packets" + " to port {} with new DSCP {}".format( + message["sw"].name, + ip_sa_str, + ip_da_str, + ip_proto, + dest_port_int, + new_dscp_int, + ) + ) + async def processNotif(notif_queue): - while True: - notif = await notif_queue.get() - debug_notif = False + while True: + notif = await notif_queue.get() + debug_notif = False + if debug_notif: + print(notif) + pprint.pprint(notif_db) + if notif["type"] == "packet-in": + processPacket(notif) + printCounter( + global_data["p4info_helper"], + notif["sw"], + "MyIngress.ingressPktOutCounter", + global_data["index"], + ) + printCounter( + global_data["p4info_helper"], + notif["sw"], + "MyEgress.egressPktInCounter", + global_data["index"], + ) if debug_notif: - print(notif) - pprint.pprint(notif_db) - if notif["type"] == "packet-in": - processPacket(notif) - printCounter(global_data ['p4info_helper'], notif["sw"], 'MyIngress.ingressPktOutCounter', global_data ['index']) - printCounter(global_data ['p4info_helper'], notif["sw"], 'MyEgress.egressPktInCounter', global_data ['index']) - if debug_notif: - readTableRules(global_data ['p4info_helper'], notif["sw"]) - elif notif["type"] == "idle-notif": - # TODO: For extra credit, you can experiment with adjusting the stale time for notifications (e.g., 10 seconds) - # and optimize the behavior of the notification database (notif_db). - if notif["sw"].name not in notif_db: - notif_db[notif["sw"].name] = [] - else: - # Check if a notification is older than 10 seconds - cleanExpiredNotifiction(notif["sw"].name, 10) - - table_entry = createFlowRule(notif) - - if not checkFlowRule(notif["sw"].name, table_entry): - addNotification(notif["sw"].name, table_entry) - deleteFlowRule(notif["sw"], table_entry) - else: - print("Received idle timeout notification for switch=%s %s" - " It is duplicate of recently processed notification, so ignoring it." - "" % (notif["sw"].name, - flowCacheEntryToDebugStr(table_entry))) - notif_queue.task_done() - -async def packetInHandler(notif_queue,sw): + readTableRules(global_data["p4info_helper"], notif["sw"]) + elif notif["type"] == "idle-notif": + # TODO: For extra credit, you can experiment with + # adjusting the stale time for notifications (e.g., 10 + # seconds) and optimize the behavior of the notification + # database (notif_db). + if notif["sw"].name not in notif_db: + notif_db[notif["sw"].name] = [] + else: + # Check if a notification is older than 10 seconds + cleanExpiredNotifiction(notif["sw"].name, 10) + + table_entry = createFlowRule(notif) + + if not checkFlowRule(notif["sw"].name, table_entry): + addNotification(notif["sw"].name, table_entry) + deleteFlowRule(notif["sw"], table_entry) + else: + print( + "Received idle timeout notification for switch={} {}" + " It is duplicate of recently processed notification," + " so ignoring it." + "".format(notif["sw"].name, flowCacheEntryToDebugStr(table_entry)) + ) + notif_queue.task_done() + + +async def packetInHandler(notif_queue, sw): # TODO: Implement the function logic to handle a packet-in message while True: try: packet_in = await asyncio.to_thread(sw.PacketIn) - #print(f"Received packet: {packet_in}") + # print(f"Received packet: {packet_in}") message = {"type": "packet-in", "sw": sw, "packet-in": packet_in} await notif_queue.put(message) @@ -433,7 +491,8 @@ async def packetInHandler(notif_queue,sw): traceback.print_exc() await asyncio.sleep(2) -async def idleTimeHandler(notif_queue,sw): + +async def idleTimeHandler(notif_queue, sw): # TODO: Implement the function logic to handle idle timeout notification while True: idle_notif = await asyncio.to_thread(sw.IdleTimeoutNotification) @@ -441,76 +500,90 @@ async def idleTimeHandler(notif_queue,sw): await notif_queue.put(message) await asyncio.sleep(5) + def printGrpcError(e): - print("gRPC Error:", e.details(), end=' ') + print("gRPC Error:", e.details(), end=" ") status_code = e.code() - print("(%s)" % status_code.name, end=' ') + print(f"({status_code.name})", end=" ") traceback = sys.exc_info()[2] - print("[%s:%d]" % (traceback.tb_frame.f_code.co_filename, traceback.tb_lineno)) + print(f"[{traceback.tb_frame.f_code.co_filename}:{traceback.tb_lineno}]") + async def main(p4info_file_path, bmv2_file_path): # Instantiate a P4Runtime helper from the p4info file - global_data ['p4info_helper'] = p4runtime_lib.helper.P4InfoHelper(p4info_file_path) - p4info_helper = global_data ['p4info_helper'] + global_data["p4info_helper"] = p4runtime_lib.helper.P4InfoHelper(p4info_file_path) + p4info_helper = global_data["p4info_helper"] try: # Create a switch connection object for s1,s2,s3; # this is backed by a P4Runtime gRPC connection. # Also, dump all P4Runtime messages sent to switch to given txt files. s1 = p4runtime_lib.bmv2.Bmv2SwitchConnection( - name='s1', - address='127.0.0.1:50051', + name="s1", + address="127.0.0.1:50051", device_id=0, - proto_dump_file='logs/s1-p4runtime-requests.txt') + proto_dump_file="logs/s1-p4runtime-requests.txt", + ) s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection( - name='s2', - address='127.0.0.1:50052', + name="s2", + address="127.0.0.1:50052", device_id=1, - proto_dump_file='logs/s2-p4runtime-requests.txt') + proto_dump_file="logs/s2-p4runtime-requests.txt", + ) s3 = p4runtime_lib.bmv2.Bmv2SwitchConnection( - name='s3', - address='127.0.0.1:50053', + name="s3", + address="127.0.0.1:50053", device_id=2, - proto_dump_file='logs/s3-p4runtime-requests.txt') + proto_dump_file="logs/s3-p4runtime-requests.txt", + ) - # Send master arbitration update message to establish this controller as - # master (required by P4Runtime before performing any other write operation) + # Send master arbitration update message to establish this + # controller as master (required by P4Runtime before + # performing any other write operation) s1.MasterArbitrationUpdate() s2.MasterArbitrationUpdate() s3.MasterArbitrationUpdate() # Install the P4 program on the switches - s1.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, - bmv2_json_file_path=bmv2_file_path) + s1.SetForwardingPipelineConfig( + p4info=p4info_helper.p4info, bmv2_json_file_path=bmv2_file_path + ) print("Installed P4 Program using SetForwardingPipelineConfig on s1") - s2.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, - bmv2_json_file_path=bmv2_file_path) + s2.SetForwardingPipelineConfig( + p4info=p4info_helper.p4info, bmv2_json_file_path=bmv2_file_path + ) print("Installed P4 Program using SetForwardingPipelineConfig on s2") - s3.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, - bmv2_json_file_path=bmv2_file_path) + s3.SetForwardingPipelineConfig( + p4info=p4info_helper.p4info, bmv2_json_file_path=bmv2_file_path + ) print("Installed P4 Program using SetForwardingPipelineConfig on s3") - global_data['p4info_obj_map'] = makeP4infoObjMap(p4info_helper.p4info) - global_data['cpm_packetin_id2data'] = \ - controllerPacketMetadataDictKeyId(global_data['p4info_obj_map'], - "packet_in") + global_data["p4info_obj_map"] = makeP4infoObjMap(p4info_helper.p4info) + global_data["cpm_packetin_id2data"] = controllerPacketMetadataDictKeyId( + global_data["p4info_obj_map"], "packet_in" + ) - global_data['punt_reason_name2int'], global_data['punt_reason_int2name'] = \ - serializableEnumDict(p4info_helper.p4info, 'PuntReason_t') - global_data['controller_opcode_name2int'], global_data['controller_opcode_int2name'] = \ - serializableEnumDict(p4info_helper.p4info, 'ControllerOpcode_t') + global_data["punt_reason_name2int"], global_data["punt_reason_int2name"] = ( + serializableEnumDict(p4info_helper.p4info, "PuntReason_t") + ) + ( + global_data["controller_opcode_name2int"], + global_data["controller_opcode_int2name"], + ) = serializableEnumDict(p4info_helper.p4info, "ControllerOpcode_t") try: - replicas = [{ "egress_port": global_data['CPU_PORT'], "instance": 1 }] - writeCloneSession(s1, global_data['CPU_PORT_CLONE_SESSION_ID'], replicas) - writeCloneSession(s2, global_data['CPU_PORT_CLONE_SESSION_ID'], replicas) - writeCloneSession(s3, global_data['CPU_PORT_CLONE_SESSION_ID'], replicas) - - except shp4rt.P4RuntimeWriteException as e: - print("Got exception trying to configure clone session %d." - " Assuming it was initialized already in an earlier" - " run of the controller." - "" % (global_data['CPU_PORT_CLONE_SESSION_ID'])) + replicas = [{"egress_port": global_data["CPU_PORT"], "instance": 1}] + writeCloneSession(s1, global_data["CPU_PORT_CLONE_SESSION_ID"], replicas) + writeCloneSession(s2, global_data["CPU_PORT_CLONE_SESSION_ID"], replicas) + writeCloneSession(s3, global_data["CPU_PORT_CLONE_SESSION_ID"], replicas) + + except shp4rt.P4RuntimeWriteException: + print( + "Got exception trying to configure clone session {}." + " Assuming it was initialized already in an earlier" + " run of the controller.".format( + global_data["CPU_PORT_CLONE_SESSION_ID"]) + ) notif_queue = asyncio.Queue() @@ -524,33 +597,53 @@ async def main(p4info_file_path, bmv2_file_path): proc_notif = asyncio.create_task(processNotif(notif_queue)) - await asyncio.gather(pkt_s1,pkt_s2,pkt_s3,idle_notif_s1, idle_notif_s2, idle_notif_s3, proc_notif) + await asyncio.gather( + pkt_s1, + pkt_s2, + pkt_s3, + idle_notif_s1, + idle_notif_s2, + idle_notif_s3, + proc_notif, + ) except KeyboardInterrupt: print(" Shutting down.") except grpc.RpcError as e: print(f"gRPC error occurred: {e}") - print(f"Status code: {e.code()}") # e.g., StatusCode.UNAVAILABLE or StatusCode.INVALID_ARGUMENT + # e.g., StatusCode.UNAVAILABLE or StatusCode.INVALID_ARGUMENT + print(f"Status code: {e.code()}") print(f"Details: {e.details()}") ShutdownAllSwitchConnections() -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='P4Runtime Controller') - parser.add_argument('--p4info', help='p4info proto in text format from p4c', - type=str, action="store", required=False, - default='./build/flowcache.p4.p4info.txtpb') - parser.add_argument('--bmv2-json', help='BMv2 JSON file from p4c', - type=str, action="store", required=False, - default='./build/flowcache.json') + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="P4Runtime Controller") + parser.add_argument( + "--p4info", + help="p4info proto in text format from p4c", + type=str, + action="store", + required=False, + default="./build/flowcache.p4.p4info.txtpb", + ) + parser.add_argument( + "--bmv2-json", + help="BMv2 JSON file from p4c", + type=str, + action="store", + required=False, + default="./build/flowcache.json", + ) args = parser.parse_args() if not os.path.exists(args.p4info): parser.print_help() - print("\np4info file not found: %s\nHave you run 'make'?" % args.p4info) + print(f"\np4info file not found: {args.p4info}\nHave you run 'make'?") parser.exit(1) if not os.path.exists(args.bmv2_json): parser.print_help() - print("\nBMv2 JSON file not found: %s\nHave you run 'make'?" % args.bmv2_json) + print(f"\nBMv2 JSON file not found: {args.bmv2_json}\nHave you run 'make'?") parser.exit(1) asyncio.run(main(args.p4info, args.bmv2_json)) diff --git a/exercises/link_monitor/probe_hdrs.py b/exercises/link_monitor/probe_hdrs.py old mode 100755 new mode 100644 index 614037fa..68ce1b34 --- a/exercises/link_monitor/probe_hdrs.py +++ b/exercises/link_monitor/probe_hdrs.py @@ -1,23 +1,31 @@ # SPDX-FileCopyrightText: 2019 Stephen Ibanez # # SPDX-License-Identifier: GPL-2.0-only -from scapy.all import * +from typing import Any, ClassVar + +from scapy.all import BitField, ByteField, Ether, IntField, Packet, bind_layers TYPE_PROBE = 0x812 + class Probe(Packet): - fields_desc = [ ByteField("hop_cnt", 0)] + fields_desc: ClassVar[list[Any]] = [ByteField("hop_cnt", 0)] + class ProbeData(Packet): - fields_desc = [ BitField("bos", 0, 1), - BitField("swid", 0, 7), - ByteField("port", 0), - IntField("byte_cnt", 0), - BitField("last_time", 0, 48), - BitField("cur_time", 0, 48)] + fields_desc: ClassVar[list[Any]] = [ + BitField("bos", 0, 1), + BitField("swid", 0, 7), + ByteField("port", 0), + IntField("byte_cnt", 0), + BitField("last_time", 0, 48), + BitField("cur_time", 0, 48), + ] + class ProbeFwd(Packet): - fields_desc = [ ByteField("egress_spec", 0)] + fields_desc: ClassVar[list[Any]] = [ByteField("egress_spec", 0)] + bind_layers(Ether, Probe, type=TYPE_PROBE) bind_layers(Probe, ProbeFwd, hop_cnt=0) @@ -25,4 +33,3 @@ class ProbeFwd(Packet): bind_layers(ProbeData, ProbeData, bos=0) bind_layers(ProbeData, ProbeFwd, bos=1) bind_layers(ProbeFwd, ProbeFwd) - diff --git a/exercises/link_monitor/receive.py b/exercises/link_monitor/receive.py index 6d363546..96b5700a 100755 --- a/exercises/link_monitor/receive.py +++ b/exercises/link_monitor/receive.py @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: GPL-2.0-only -from probe_hdrs import * +from probe_hdrs import ProbeData, sniff def expand(x): @@ -13,19 +13,25 @@ def expand(x): x = x.payload yield x + def handle_pkt(pkt): if ProbeData in pkt: - data_layers = [l for l in expand(pkt) if l.name=='ProbeData'] - print("") + data_layers = [x for x in expand(pkt) if x.name == "ProbeData"] + print() for sw in data_layers: - utilization = 0 if sw.cur_time == sw.last_time else 8.0*sw.byte_cnt/(sw.cur_time - sw.last_time) - print("Switch {} - Port {}: {} Mbps".format(sw.swid, sw.port, utilization)) + utilization = ( + 0 + if sw.cur_time == sw.last_time + else 8.0 * sw.byte_cnt / (sw.cur_time - sw.last_time) + ) + print(f"Switch {sw.swid} - Port {sw.port}: {utilization} Mbps") + def main(): - iface = 'eth0' - print("sniffing on {}".format(iface)) - sniff(iface = iface, - prn = lambda x: handle_pkt(x)) + iface = "eth0" + print(f"sniffing on {iface}") + sniff(iface=iface, prn=lambda x: handle_pkt(x)) + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/exercises/link_monitor/send.py b/exercises/link_monitor/send.py index 874e5902..17ee4c94 100755 --- a/exercises/link_monitor/send.py +++ b/exercises/link_monitor/send.py @@ -6,29 +6,33 @@ import sys import time -from probe_hdrs import * +from probe_hdrs import Probe, ProbeFwd +from scapy.all import Ether, get_if_hwaddr, sendp def main(): - probe_pkt = Ether(dst='ff:ff:ff:ff:ff:ff', src=get_if_hwaddr('eth0')) / \ - Probe(hop_cnt=0) / \ - ProbeFwd(egress_spec=4) / \ - ProbeFwd(egress_spec=1) / \ - ProbeFwd(egress_spec=4) / \ - ProbeFwd(egress_spec=1) / \ - ProbeFwd(egress_spec=3) / \ - ProbeFwd(egress_spec=2) / \ - ProbeFwd(egress_spec=3) / \ - ProbeFwd(egress_spec=2) / \ - ProbeFwd(egress_spec=1) + probe_pkt = ( + Ether(dst="ff:ff:ff:ff:ff:ff", src=get_if_hwaddr("eth0")) + / Probe(hop_cnt=0) + / ProbeFwd(egress_spec=4) + / ProbeFwd(egress_spec=1) + / ProbeFwd(egress_spec=4) + / ProbeFwd(egress_spec=1) + / ProbeFwd(egress_spec=3) + / ProbeFwd(egress_spec=2) + / ProbeFwd(egress_spec=3) + / ProbeFwd(egress_spec=2) + / ProbeFwd(egress_spec=1) + ) while True: try: - sendp(probe_pkt, iface='eth0') + sendp(probe_pkt, iface="eth0") time.sleep(1) except KeyboardInterrupt: sys.exit() -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/exercises/load_balance/receive.py b/exercises/load_balance/receive.py index 7672976c..ad752ad6 100755 --- a/exercises/load_balance/receive.py +++ b/exercises/load_balance/receive.py @@ -5,6 +5,7 @@ # SPDX-License-Identifier: GPL-2.0-only import os import sys +from typing import Any, ClassVar from scapy.all import ( FieldLenField, @@ -13,49 +14,52 @@ IPOption, ShortField, get_if_list, - sniff + sniff, ) from scapy.layers.inet import _IPOption_HDR def get_if(): - ifs=get_if_list() - iface=None + iface = None for i in get_if_list(): if "eth0" in i: - iface=i - break; + iface = i + break if not iface: print("Cannot find eth0 interface") - exit(1) + sys.exit(1) return iface + class IPOption_MRI(IPOption): name = "MRI" option = 31 - fields_desc = [ _IPOption_HDR, - FieldLenField("length", None, fmt="B", - length_of="swids", - adjust=lambda pkt,l:l+4), - ShortField("count", 0), - FieldListField("swids", - [], - IntField("", 0), - length_from=lambda pkt:pkt.count*4) ] + fields_desc: ClassVar[list[Any]] = [ + _IPOption_HDR, + FieldLenField( + "length", None, fmt="B", length_of="swids", adjust=lambda pkt, x: x + 4 + ), + ShortField("count", 0), + FieldListField( + "swids", [], IntField("", 0), length_from=lambda pkt: pkt.count * 4 + ), + ] + + def handle_pkt(pkt): print("got a packet") pkt.show2() -# hexdump(pkt) + # hexdump(pkt) sys.stdout.flush() def main(): - ifaces = [i for i in os.listdir('/sys/class/net/') if 'eth' in i] + ifaces = [i for i in os.listdir("/sys/class/net/") if "eth" in i] iface = ifaces[0] - print("sniffing on %s" % iface) + print(f"sniffing on {iface}") sys.stdout.flush() - sniff(filter="tcp", iface = iface, - prn = lambda x: handle_pkt(x)) + sniff(filter="tcp", iface=iface, prn=lambda x: handle_pkt(x)) + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/exercises/load_balance/send.py b/exercises/load_balance/send.py index ee98ff47..4646de38 100755 --- a/exercises/load_balance/send.py +++ b/exercises/load_balance/send.py @@ -11,32 +11,37 @@ def get_if(): - ifs=get_if_list() - iface=None # "h1-eth0" + iface = None # "h1-eth0" for i in get_if_list(): if "eth0" in i: - iface=i - break; + iface = i + break if not iface: print("Cannot find eth0 interface") - exit(1) + sys.exit(1) return iface + def main(): - if len(sys.argv)<3: + if len(sys.argv) < 3: print('pass 2 arguments: ""') - exit(1) + sys.exit(1) addr = socket.gethostbyname(sys.argv[1]) iface = get_if() - print("sending on interface %s to %s" % (iface, str(addr))) - pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') - pkt = pkt /IP(dst=addr) / TCP(dport=1234, sport=random.randint(49152,65535)) / sys.argv[2] + print(f"sending on interface {iface} to {addr!s}") + pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") + pkt = ( + pkt + / IP(dst=addr) + / TCP(dport=1234, sport=random.randint(49152, 65535)) + / sys.argv[2] + ) pkt.show2() sendp(pkt, iface=iface, verbose=False) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/exercises/mri/receive.py b/exercises/mri/receive.py index 71c63a09..a5d4d7f8 100755 --- a/exercises/mri/receive.py +++ b/exercises/mri/receive.py @@ -4,6 +4,7 @@ # # SPDX-License-Identifier: GPL-2.0-only import sys +from typing import Any, ClassVar from scapy.all import ( FieldLenField, @@ -13,55 +14,62 @@ PacketListField, ShortField, get_if_list, - sniff + sniff, ) from scapy.layers.inet import _IPOption_HDR def get_if(): - ifs=get_if_list() - iface=None + iface = None for i in get_if_list(): if "eth0" in i: - iface=i - break; + iface = i + break if not iface: print("Cannot find eth0 interface") - exit(1) + sys.exit(1) return iface + class SwitchTrace(Packet): - fields_desc = [ IntField("swid", 0), - IntField("qdepth", 0)] + fields_desc: ClassVar[list[Any]] = [IntField("swid", 0), IntField("qdepth", 0)] + def extract_padding(self, p): - return "", p + return "", p + class IPOption_MRI(IPOption): name = "MRI" option = 31 - fields_desc = [ _IPOption_HDR, - FieldLenField("length", None, fmt="B", - length_of="swtraces", - adjust=lambda pkt,l:l*2+4), - ShortField("count", 0), - PacketListField("swtraces", - [], - SwitchTrace, - count_from=lambda pkt:(pkt.count*1)) ] + fields_desc: ClassVar[list[Any]] = [ + _IPOption_HDR, + FieldLenField( + "length", + None, + fmt="B", + length_of="swtraces", + adjust=lambda pkt, x: x * 2 + 4, + ), + ShortField("count", 0), + PacketListField( + "swtraces", [], SwitchTrace, count_from=lambda pkt: pkt.count * 1 + ), + ] + def handle_pkt(pkt): print("got a packet") pkt.show2() -# hexdump(pkt) + # hexdump(pkt) sys.stdout.flush() def main(): - iface = 'eth0' - print("sniffing on %s" % iface) + iface = "eth0" + print(f"sniffing on {iface}") sys.stdout.flush() - sniff(filter="udp and port 4321", iface = iface, - prn = lambda x: handle_pkt(x)) + sniff(filter="udp and port 4321", iface=iface, prn=lambda x: handle_pkt(x)) + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/exercises/mri/send.py b/exercises/mri/send.py index d805efac..f42502f7 100755 --- a/exercises/mri/send.py +++ b/exercises/mri/send.py @@ -7,6 +7,7 @@ import socket import sys from time import sleep +from typing import Any, ClassVar from scapy.all import ( IP, @@ -20,70 +21,75 @@ ShortField, get_if_hwaddr, get_if_list, - sendp + sendp, ) from scapy.layers.inet import _IPOption_HDR def get_if(): - ifs=get_if_list() - iface=None # "h1-eth0" + iface = None # "h1-eth0" for i in get_if_list(): if "eth0" in i: - iface=i - break; + iface = i + break if not iface: print("Cannot find eth0 interface") - exit(1) + sys.exit(1) return iface + class SwitchTrace(Packet): - fields_desc = [ IntField("swid", 0), - IntField("qdepth", 0)] + fields_desc: ClassVar[list[Any]] = [IntField("swid", 0), IntField("qdepth", 0)] + def extract_padding(self, p): - return "", p + return "", p + class IPOption_MRI(IPOption): name = "MRI" option = 31 - fields_desc = [ _IPOption_HDR, - FieldLenField("length", None, fmt="B", - length_of="swtraces", - adjust=lambda pkt,l:l*2+4), - ShortField("count", 0), - PacketListField("swtraces", - [], - SwitchTrace, - count_from=lambda pkt:(pkt.count*1)) ] + fields_desc: ClassVar[list[Any]] = [ + _IPOption_HDR, + FieldLenField( + "length", + None, + fmt="B", + length_of="swtraces", + adjust=lambda pkt, x: x * 2 + 4, + ), + ShortField("count", 0), + PacketListField( + "swtraces", [], SwitchTrace, count_from=lambda pkt: pkt.count * 1 + ), + ] def main(): - if len(sys.argv)<3: + if len(sys.argv) < 3: print('pass 2 arguments: ""') - exit(1) + sys.exit(1) addr = socket.gethostbyname(sys.argv[1]) iface = get_if() - pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") / IP( - dst=addr, options = IPOption_MRI(count=0, - swtraces=[])) / UDP( - dport=4321, sport=1234) / sys.argv[2] + pkt = ( + Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") + / IP(dst=addr, options=IPOption_MRI(count=0, swtraces=[])) + / UDP(dport=4321, sport=1234) + / sys.argv[2] + ) - # pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") / IP( - # dst=addr, options = IPOption_MRI(count=2, - # swtraces=[SwitchTrace(swid=0,qdepth=0), SwitchTrace(swid=1,qdepth=0)])) / UDP( - # dport=4321, sport=1234) / sys.argv[2] + # pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") / IP( + # dst=addr, options = IPOption_MRI(count=2, + # swtraces=[SwitchTrace(swid=0,qdepth=0), SwitchTrace(swid=1,qdepth=0)])) / UDP( + # dport=4321, sport=1234) / sys.argv[2] pkt.show2() - #hexdump(pkt) - try: - for i in range(int(sys.argv[3])): + # hexdump(pkt) + for i in range(int(sys.argv[3])): sendp(pkt, iface=iface) sleep(1) - except KeyboardInterrupt: - raise -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/exercises/p4runtime/mycontroller.py b/exercises/p4runtime/mycontroller.py index b9e43665..2b4c238b 100755 --- a/exercises/p4runtime/mycontroller.py +++ b/exercises/p4runtime/mycontroller.py @@ -13,8 +13,8 @@ # Import P4Runtime lib from parent utils dir # Probably there's a better way of doing this. sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), - '../../utils/')) + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../utils/") +) import p4runtime_lib.bmv2 import p4runtime_lib.helper from p4runtime_lib.error_utils import printGrpcError @@ -24,8 +24,9 @@ SWITCH_TO_SWITCH_PORT = 2 -def writeTunnelRules(p4info_helper, ingress_sw, egress_sw, tunnel_id, - dst_eth_addr, dst_ip_addr): +def writeTunnelRules( + p4info_helper, ingress_sw, egress_sw, tunnel_id, dst_eth_addr, dst_ip_addr +): """ Installs three rules: 1) An tunnel ingress rule on the ingress switch in the ipv4_lpm table that @@ -46,31 +47,32 @@ def writeTunnelRules(p4info_helper, ingress_sw, egress_sw, tunnel_id, # 1) Tunnel Ingress Rule table_entry = p4info_helper.buildTableEntry( table_name="MyIngress.ipv4_lpm", - match_fields={ - "hdr.ipv4.dstAddr": (dst_ip_addr, 32) - }, + match_fields={"hdr.ipv4.dstAddr": (dst_ip_addr, 32)}, action_name="MyIngress.myTunnel_ingress", action_params={ "dst_id": tunnel_id, - }) + }, + ) ingress_sw.WriteTableEntry(table_entry) - print("Installed ingress tunnel rule on %s" % ingress_sw.name) + print(f"Installed ingress tunnel rule on {ingress_sw.name}") # 2) Tunnel Transit Rule - # The rule will need to be added to the myTunnel_exact table and match on - # the tunnel ID (hdr.myTunnel.dst_id). Traffic will need to be forwarded - # using the myTunnel_forward action on the port connected to the next switch. + # The rule will need to be added to the myTunnel_exact table and + # match on the tunnel ID (hdr.myTunnel.dst_id). Traffic will need + # to be forwarded using the myTunnel_forward action on the port + # connected to the next switch. # - # For our simple topology, switch 1 and switch 2 are connected using a - # link attached to port 2 on both switches. We have defined a variable at - # the top of the file, SWITCH_TO_SWITCH_PORT, that you can use as the output - # port for this action. + # For our simple topology, switch 1 and switch 2 are connected + # using a link attached to port 2 on both switches. We have + # defined a variable at the top of the file, + # SWITCH_TO_SWITCH_PORT, that you can use as the output port for + # this action. # - # We will only need a transit rule on the ingress switch because we are - # using a simple topology. In general, you'll need on transit rule for - # each switch in the path (except the last switch, which has the egress rule), - # and you will need to select the port dynamically for each switch based on - # your topology. + # We will only need a transit rule on the ingress switch because + # we are using a simple topology. In general, you'll need on + # transit rule for each switch in the path (except the last + # switch, which has the egress rule), and you will need to select + # the port dynamically for each switch based on your topology. # TODO build the transit rule # TODO install the transit rule on the ingress switch @@ -83,16 +85,12 @@ def writeTunnelRules(p4info_helper, ingress_sw, egress_sw, tunnel_id, # connected to. table_entry = p4info_helper.buildTableEntry( table_name="MyIngress.myTunnel_exact", - match_fields={ - "hdr.myTunnel.dst_id": tunnel_id - }, + match_fields={"hdr.myTunnel.dst_id": tunnel_id}, action_name="MyIngress.myTunnel_egress", - action_params={ - "dstAddr": dst_eth_addr, - "port": SWITCH_TO_HOST_PORT - }) + action_params={"dstAddr": dst_eth_addr, "port": SWITCH_TO_HOST_PORT}, + ) egress_sw.WriteTableEntry(table_entry) - print("Installed egress tunnel rule on %s" % egress_sw.name) + print(f"Installed egress tunnel rule on {egress_sw.name}") def readTableRules(p4info_helper, sw): @@ -102,21 +100,21 @@ def readTableRules(p4info_helper, sw): :param p4info_helper: the P4Info helper :param sw: the switch connection """ - print('\n----- Reading tables rules for %s -----' % sw.name) + print(f"\n----- Reading tables rules for {sw.name} -----") for response in sw.ReadTableEntries(): for entity in response.entities: entry = entity.table_entry # TODO For extra credit, you can use the p4info_helper to translate # the IDs in the entry to names print(entry) - print('-----') + print("-----") def printCounter(p4info_helper, sw, counter_name, index): """ - Reads the specified counter at the specified index from the switch. In our - program, the index is the tunnel ID. If the index is 0, it will return all - values from the counter. + Reads the specified counter at the specified index from the + switch. In our program, the index is the tunnel ID. If the index + is 0, it will return all values from the counter. :param p4info_helper: the P4Info helper :param sw: the switch connection @@ -126,10 +124,10 @@ def printCounter(p4info_helper, sw, counter_name, index): for response in sw.ReadCounters(p4info_helper.get_counters_id(counter_name), index): for entity in response.entities: counter = entity.counter_entry - print("%s %s %d: %d packets (%d bytes)" % ( - sw.name, counter_name, index, - counter.data.packet_count, counter.data.byte_count - )) + print(f"{sw.name} {counter_name} {index}:" + f" {counter.data.packet_count} packets" + f" ({counter.data.byte_count} bytes)") + def main(p4info_file_path, bmv2_file_path): # Instantiate a P4Runtime helper from the p4info file @@ -140,45 +138,63 @@ def main(p4info_file_path, bmv2_file_path): # this is backed by a P4Runtime gRPC connection. # Also, dump all P4Runtime messages sent to switch to given txt files. s1 = p4runtime_lib.bmv2.Bmv2SwitchConnection( - name='s1', - address='127.0.0.1:50051', + name="s1", + address="127.0.0.1:50051", device_id=0, - proto_dump_file='logs/s1-p4runtime-requests.txt') + proto_dump_file="logs/s1-p4runtime-requests.txt", + ) s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection( - name='s2', - address='127.0.0.1:50052', + name="s2", + address="127.0.0.1:50052", device_id=1, - proto_dump_file='logs/s2-p4runtime-requests.txt') + proto_dump_file="logs/s2-p4runtime-requests.txt", + ) - # Send master arbitration update message to establish this controller as - # master (required by P4Runtime before performing any other write operation) + # Send master arbitration update message to establish this + # controller as master (required by P4Runtime before + # performing any other write operation) s1.MasterArbitrationUpdate() s2.MasterArbitrationUpdate() # Install the P4 program on the switches - s1.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, - bmv2_json_file_path=bmv2_file_path) + s1.SetForwardingPipelineConfig( + p4info=p4info_helper.p4info, bmv2_json_file_path=bmv2_file_path + ) print("Installed P4 Program using SetForwardingPipelineConfig on s1") - s2.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, - bmv2_json_file_path=bmv2_file_path) + s2.SetForwardingPipelineConfig( + p4info=p4info_helper.p4info, bmv2_json_file_path=bmv2_file_path + ) print("Installed P4 Program using SetForwardingPipelineConfig on s2") # Write the rules that tunnel traffic from h1 to h2 - writeTunnelRules(p4info_helper, ingress_sw=s1, egress_sw=s2, tunnel_id=100, - dst_eth_addr="08:00:00:00:02:22", dst_ip_addr="10.0.2.2") + writeTunnelRules( + p4info_helper, + ingress_sw=s1, + egress_sw=s2, + tunnel_id=100, + dst_eth_addr="08:00:00:00:02:22", + dst_ip_addr="10.0.2.2", + ) # Write the rules that tunnel traffic from h2 to h1 - writeTunnelRules(p4info_helper, ingress_sw=s2, egress_sw=s1, tunnel_id=200, - dst_eth_addr="08:00:00:00:01:11", dst_ip_addr="10.0.1.1") - - # TODO Uncomment the following two lines to read table entries from s1 and s2 + writeTunnelRules( + p4info_helper, + ingress_sw=s2, + egress_sw=s1, + tunnel_id=200, + dst_eth_addr="08:00:00:00:01:11", + dst_ip_addr="10.0.1.1", + ) + + # TODO Uncomment the following two lines to read table entries + # from s1 and s2 # readTableRules(p4info_helper, s1) # readTableRules(p4info_helper, s2) # Print the tunnel counters every 2 seconds while True: sleep(2) - print('\n----- Reading tunnel counters -----') + print("\n----- Reading tunnel counters -----") printCounter(p4info_helper, s1, "MyIngress.ingressTunnelCounter", 100) printCounter(p4info_helper, s2, "MyIngress.egressTunnelCounter", 100) printCounter(p4info_helper, s2, "MyIngress.ingressTunnelCounter", 200) @@ -191,22 +207,33 @@ def main(p4info_file_path, bmv2_file_path): ShutdownAllSwitchConnections() -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='P4Runtime Controller') - parser.add_argument('--p4info', help='p4info proto in text format from p4c', - type=str, action="store", required=False, - default='./build/advanced_tunnel.p4.p4info.txtpb') - parser.add_argument('--bmv2-json', help='BMv2 JSON file from p4c', - type=str, action="store", required=False, - default='./build/advanced_tunnel.json') + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="P4Runtime Controller") + parser.add_argument( + "--p4info", + help="p4info proto in text format from p4c", + type=str, + action="store", + required=False, + default="./build/advanced_tunnel.p4.p4info.txtpb", + ) + parser.add_argument( + "--bmv2-json", + help="BMv2 JSON file from p4c", + type=str, + action="store", + required=False, + default="./build/advanced_tunnel.json", + ) args = parser.parse_args() if not os.path.exists(args.p4info): parser.print_help() - print("\np4info file not found: %s\nHave you run 'make'?" % args.p4info) + print(f"\np4info file not found: {args.p4info}\nHave you run 'make'?") parser.exit(1) if not os.path.exists(args.bmv2_json): parser.print_help() - print("\nBMv2 JSON file not found: %s\nHave you run 'make'?" % args.bmv2_json) + print(f"\nBMv2 JSON file not found: {args.bmv2_json}\nHave you run 'make'?") parser.exit(1) main(args.p4info, args.bmv2_json) diff --git a/exercises/p4runtime/solution/mycontroller.py b/exercises/p4runtime/solution/mycontroller.py index 05815cf0..92b049f3 100755 --- a/exercises/p4runtime/solution/mycontroller.py +++ b/exercises/p4runtime/solution/mycontroller.py @@ -13,8 +13,8 @@ # Import P4Runtime lib from parent utils dir # Probably there's a better way of doing this. sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), - '../../utils/')) + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../utils/") +) import p4runtime_lib.bmv2 import p4runtime_lib.helper from p4runtime_lib.switch import ShutdownAllSwitchConnections @@ -23,8 +23,9 @@ SWITCH_TO_SWITCH_PORT = 2 -def writeTunnelRules(p4info_helper, ingress_sw, egress_sw, tunnel_id, - dst_eth_addr, dst_ip_addr): +def writeTunnelRules( + p4info_helper, ingress_sw, egress_sw, tunnel_id, dst_eth_addr, dst_ip_addr +): """ Installs three rules: 1) An tunnel ingress rule on the ingress switch in the ipv4_lpm table that @@ -45,45 +46,43 @@ def writeTunnelRules(p4info_helper, ingress_sw, egress_sw, tunnel_id, # 1) Tunnel Ingress Rule table_entry = p4info_helper.buildTableEntry( table_name="MyIngress.ipv4_lpm", - match_fields={ - "hdr.ipv4.dstAddr": (dst_ip_addr, 32) - }, + match_fields={"hdr.ipv4.dstAddr": (dst_ip_addr, 32)}, action_name="MyIngress.myTunnel_ingress", action_params={ "dst_id": tunnel_id, - }) + }, + ) ingress_sw.WriteTableEntry(table_entry) - print("Installed ingress tunnel rule on %s" % ingress_sw.name) + print(f"Installed ingress tunnel rule on {ingress_sw.name}") # 2) Tunnel Transit Rule - # The rule will need to be added to the myTunnel_exact table and match on - # the tunnel ID (hdr.myTunnel.dst_id). Traffic will need to be forwarded - # using the myTunnel_forward action on the port connected to the next switch. + # The rule will need to be added to the myTunnel_exact table and + # match on the tunnel ID (hdr.myTunnel.dst_id). Traffic will need + # to be forwarded using the myTunnel_forward action on the port + # connected to the next switch. # - # For our simple topology, switch 1 and switch 2 are connected using a - # link attached to port 2 on both switches. We have defined a variable at - # the top of the file, SWITCH_TO_SWITCH_PORT, that you can use as the output - # port for this action. + # For our simple topology, switch 1 and switch 2 are connected + # using a link attached to port 2 on both switches. We have + # defined a variable at the top of the file, + # SWITCH_TO_SWITCH_PORT, that you can use as the output port for + # this action. # - # We will only need a transit rule on the ingress switch because we are - # using a simple topology. In general, you'll need on transit rule for - # each switch in the path (except the last switch, which has the egress rule), - # and you will need to select the port dynamically for each switch based on - # your topology. + # We will only need a transit rule on the ingress switch because + # we are using a simple topology. In general, you'll need on + # transit rule for each switch in the path (except the last + # switch, which has the egress rule), and you will need to select + # the port dynamically for each switch based on your topology. # TODO build the transit rule # TODO install the transit rule on the ingress switch table_entry = p4info_helper.buildTableEntry( table_name="MyIngress.myTunnel_exact", - match_fields={ - "hdr.myTunnel.dst_id": tunnel_id - }, + match_fields={"hdr.myTunnel.dst_id": tunnel_id}, action_name="MyIngress.myTunnel_forward", - action_params={ - "port": SWITCH_TO_SWITCH_PORT - }) + action_params={"port": SWITCH_TO_SWITCH_PORT}, + ) ingress_sw.WriteTableEntry(table_entry) - print("Installed transit tunnel rule on %s" % ingress_sw.name) + print(f"Installed transit tunnel rule on {ingress_sw.name}") # 3) Tunnel Egress Rule # For our simple topology, the host will always be located on the @@ -92,16 +91,12 @@ def writeTunnelRules(p4info_helper, ingress_sw, egress_sw, tunnel_id, # connected to. table_entry = p4info_helper.buildTableEntry( table_name="MyIngress.myTunnel_exact", - match_fields={ - "hdr.myTunnel.dst_id": tunnel_id - }, + match_fields={"hdr.myTunnel.dst_id": tunnel_id}, action_name="MyIngress.myTunnel_egress", - action_params={ - "dstAddr": dst_eth_addr, - "port": SWITCH_TO_HOST_PORT - }) + action_params={"dstAddr": dst_eth_addr, "port": SWITCH_TO_HOST_PORT}, + ) egress_sw.WriteTableEntry(table_entry) - print("Installed egress tunnel rule on %s" % egress_sw.name) + print(f"Installed egress tunnel rule on {egress_sw.name}") def readTableRules(p4info_helper, sw): @@ -111,30 +106,36 @@ def readTableRules(p4info_helper, sw): :param p4info_helper: the P4Info helper :param sw: the switch connection """ - print('\n----- Reading tables rules for %s -----' % sw.name) + print(f"\n----- Reading tables rules for {sw.name} -----") for response in sw.ReadTableEntries(): for entity in response.entities: entry = entity.table_entry # TODO For extra credit, you can use the p4info_helper to translate # the IDs in the entry to names table_name = p4info_helper.get_tables_name(entry.table_id) - print('%s: ' % table_name, end=' ') + print(f"{table_name}: ", end=" ") for m in entry.match: - print(p4info_helper.get_match_field_name(table_name, m.field_id), end=' ') - print('%r' % (p4info_helper.get_match_field_value(m),), end=' ') + print( + p4info_helper.get_match_field_name(table_name, m.field_id), end=" " + ) + print(f"{p4info_helper.get_match_field_value(m)!r}", end=" ") action = entry.action.action action_name = p4info_helper.get_actions_name(action.action_id) - print('->', action_name, end=' ') + print("->", action_name, end=" ") for p in action.params: - print(p4info_helper.get_action_param_name(action_name, p.param_id), end=' ') - print('%r' % p.value, end=' ') + print( + p4info_helper.get_action_param_name(action_name, p.param_id), + end=" ", + ) + print(f"{p.value!r}", end=" ") print() + def printCounter(p4info_helper, sw, counter_name, index): """ - Reads the specified counter at the specified index from the switch. In our - program, the index is the tunnel ID. If the index is 0, it will return all - values from the counter. + Reads the specified counter at the specified index from the + switch. In our program, the index is the tunnel ID. If the index + is 0, it will return all values from the counter. :param p4info_helper: the P4Info helper :param sw: the switch connection @@ -144,17 +145,18 @@ def printCounter(p4info_helper, sw, counter_name, index): for response in sw.ReadCounters(p4info_helper.get_counters_id(counter_name), index): for entity in response.entities: counter = entity.counter_entry - print("%s %s %d: %d packets (%d bytes)" % ( - sw.name, counter_name, index, - counter.data.packet_count, counter.data.byte_count - )) + print(f"{sw.name} {counter_name} {index}:" + f" {counter.data.packet_count} packets" + f" ({counter.data.byte_count} bytes)") + def printGrpcError(e): - print("gRPC Error:", e.details(), end=' ') + print("gRPC Error:", e.details(), end=" ") status_code = e.code() - print("(%s)" % status_code.name, end=' ') + print(f"({status_code.name})", end=" ") traceback = sys.exc_info()[2] - print("[%s:%d]" % (traceback.tb_frame.f_code.co_filename, traceback.tb_lineno)) + print(f"[{traceback.tb_frame.f_code.co_filename}:{traceback.tb_lineno}]") + def main(p4info_file_path, bmv2_file_path): # Instantiate a P4Runtime helper from the p4info file @@ -165,45 +167,63 @@ def main(p4info_file_path, bmv2_file_path): # this is backed by a P4Runtime gRPC connection. # Also, dump all P4Runtime messages sent to switch to given txt files. s1 = p4runtime_lib.bmv2.Bmv2SwitchConnection( - name='s1', - address='127.0.0.1:50051', + name="s1", + address="127.0.0.1:50051", device_id=0, - proto_dump_file='logs/s1-p4runtime-requests.txt') + proto_dump_file="logs/s1-p4runtime-requests.txt", + ) s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection( - name='s2', - address='127.0.0.1:50052', + name="s2", + address="127.0.0.1:50052", device_id=1, - proto_dump_file='logs/s2-p4runtime-requests.txt') + proto_dump_file="logs/s2-p4runtime-requests.txt", + ) - # Send master arbitration update message to establish this controller as - # master (required by P4Runtime before performing any other write operation) + # Send master arbitration update message to establish this + # controller as master (required by P4Runtime before + # performing any other write operation) s1.MasterArbitrationUpdate() s2.MasterArbitrationUpdate() # Install the P4 program on the switches - s1.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, - bmv2_json_file_path=bmv2_file_path) + s1.SetForwardingPipelineConfig( + p4info=p4info_helper.p4info, bmv2_json_file_path=bmv2_file_path + ) print("Installed P4 Program using SetForwardingPipelineConfig on s1") - s2.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, - bmv2_json_file_path=bmv2_file_path) + s2.SetForwardingPipelineConfig( + p4info=p4info_helper.p4info, bmv2_json_file_path=bmv2_file_path + ) print("Installed P4 Program using SetForwardingPipelineConfig on s2") # Write the rules that tunnel traffic from h1 to h2 - writeTunnelRules(p4info_helper, ingress_sw=s1, egress_sw=s2, tunnel_id=100, - dst_eth_addr="08:00:00:00:02:22", dst_ip_addr="10.0.2.2") + writeTunnelRules( + p4info_helper, + ingress_sw=s1, + egress_sw=s2, + tunnel_id=100, + dst_eth_addr="08:00:00:00:02:22", + dst_ip_addr="10.0.2.2", + ) # Write the rules that tunnel traffic from h2 to h1 - writeTunnelRules(p4info_helper, ingress_sw=s2, egress_sw=s1, tunnel_id=200, - dst_eth_addr="08:00:00:00:01:11", dst_ip_addr="10.0.1.1") + writeTunnelRules( + p4info_helper, + ingress_sw=s2, + egress_sw=s1, + tunnel_id=200, + dst_eth_addr="08:00:00:00:01:11", + dst_ip_addr="10.0.1.1", + ) - # TODO Uncomment the following two lines to read table entries from s1 and s2 + # TODO Uncomment the following two lines to read table entries + # from s1 and s2 readTableRules(p4info_helper, s1) readTableRules(p4info_helper, s2) # Print the tunnel counters every 2 seconds while True: sleep(2) - print('\n----- Reading tunnel counters -----') + print("\n----- Reading tunnel counters -----") printCounter(p4info_helper, s1, "MyIngress.ingressTunnelCounter", 100) printCounter(p4info_helper, s2, "MyIngress.egressTunnelCounter", 100) printCounter(p4info_helper, s2, "MyIngress.ingressTunnelCounter", 200) @@ -216,22 +236,33 @@ def main(p4info_file_path, bmv2_file_path): ShutdownAllSwitchConnections() -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='P4Runtime Controller') - parser.add_argument('--p4info', help='p4info proto in text format from p4c', - type=str, action="store", required=False, - default='./build/advanced_tunnel.p4.p4info.txtpb') - parser.add_argument('--bmv2-json', help='BMv2 JSON file from p4c', - type=str, action="store", required=False, - default='./build/advanced_tunnel.json') + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="P4Runtime Controller") + parser.add_argument( + "--p4info", + help="p4info proto in text format from p4c", + type=str, + action="store", + required=False, + default="./build/advanced_tunnel.p4.p4info.txtpb", + ) + parser.add_argument( + "--bmv2-json", + help="BMv2 JSON file from p4c", + type=str, + action="store", + required=False, + default="./build/advanced_tunnel.json", + ) args = parser.parse_args() if not os.path.exists(args.p4info): parser.print_help() - print("\np4info file not found: %s\nHave you run 'make'?" % args.p4info) + print(f"\np4info file not found: {args.p4info}\nHave you run 'make'?") parser.exit(1) if not os.path.exists(args.bmv2_json): parser.print_help() - print("\nBMv2 JSON file not found: %s\nHave you run 'make'?" % args.bmv2_json) + print(f"\nBMv2 JSON file not found: {args.bmv2_json}\nHave you run 'make'?") parser.exit(1) main(args.p4info, args.bmv2_json) diff --git a/exercises/qos/receive.py b/exercises/qos/receive.py index 6d54acfc..57527fe5 100755 --- a/exercises/qos/receive.py +++ b/exercises/qos/receive.py @@ -16,11 +16,11 @@ def handle_pkt(pkt): def main(): - iface = 'eth0' - print("sniffing on %s" % iface) + iface = "eth0" + print(f"sniffing on {iface}") sys.stdout.flush() sniff(iface=iface, prn=lambda x: handle_pkt(x)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/exercises/qos/send.py b/exercises/qos/send.py index 2bab4be3..afc4d3dd 100755 --- a/exercises/qos/send.py +++ b/exercises/qos/send.py @@ -6,6 +6,7 @@ import argparse import socket +import sys from time import sleep from scapy.all import IP, TCP, UDP, Ether, get_if_hwaddr, get_if_list, sendp @@ -19,13 +20,15 @@ def get_if(): break if not iface: print("Cannot find eth0 interface") - exit(1) + sys.exit(1) return iface def main(): parser = argparse.ArgumentParser() - parser.add_argument("--p", help="Protocol name To send TCP/UDP etc packets", type=str) + parser.add_argument( + "--p", help="Protocol name To send TCP/UDP etc packets", type=str + ) parser.add_argument("--des", help="IP address of the destination", type=str) parser.add_argument("--m", help="Raw Message", type=str) parser.add_argument("--dur", help="in seconds", type=str) @@ -34,25 +37,29 @@ def main(): if args.p and args.des and args.m and args.dur: addr = socket.gethostbyname(args.des) iface = get_if() - if args.p == 'UDP': - pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") / IP(dst=addr, tos=1) / UDP(dport=4321, sport=1234) / args.m + if args.p == "UDP": + pkt = ( + Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") + / IP(dst=addr, tos=1) + / UDP(dport=4321, sport=1234) + / args.m + ) pkt.show2() - try: - for i in range(int(args.dur)): - sendp(pkt, iface=iface) - sleep(1) - except KeyboardInterrupt: - raise - elif args.p == 'TCP': - pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") / IP(dst=addr, tos=1) / TCP() / args.m + for i in range(int(args.dur)): + sendp(pkt, iface=iface) + sleep(1) + elif args.p == "TCP": + pkt = ( + Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") + / IP(dst=addr, tos=1) + / TCP() + / args.m + ) pkt.show2() - try: - for i in range(int(args.dur)): - sendp(pkt, iface=iface) - sleep(1) - except KeyboardInterrupt: - raise + for i in range(int(args.dur)): + sendp(pkt, iface=iface) + sleep(1) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/exercises/source_routing/receive.py b/exercises/source_routing/receive.py index 8e5e6b3f..ad1ea5b8 100755 --- a/exercises/source_routing/receive.py +++ b/exercises/source_routing/receive.py @@ -4,58 +4,80 @@ # # SPDX-License-Identifier: GPL-2.0-only import sys +from typing import Any, ClassVar -from scapy.all import Ether, IPOption, Packet, bind_layers, get_if_list, sniff -from scapy.fields import * +from scapy.all import ( + Ether, + IPOption, + Packet, + bind_layers, + get_if_list, + sniff, +) +from scapy.fields import ( + BitField, + FieldLenField, + FieldListField, + IntField, + ShortField, + XShortField, +) from scapy.layers.inet import _IPOption_HDR def get_if(): - ifs=get_if_list() - iface=None + iface = None for i in get_if_list(): if "eth0" in i: - iface=i - break; + iface = i + break if not iface: print("Cannot find eth0 interface") - exit(1) + sys.exit(1) return iface + class IPOption_MRI(IPOption): name = "MRI" option = 31 - fields_desc = [ _IPOption_HDR, - FieldLenField("length", None, fmt="B", - length_of="swids", - adjust=lambda pkt,l:l+4), - ShortField("count", 0), - FieldListField("swids", - [], - IntField("", 0), - length_from=lambda pkt:pkt.count*4) ] + fields_desc: ClassVar[list[Any]] = [ + _IPOption_HDR, + FieldLenField( + "length", None, fmt="B", length_of="swids", adjust=lambda pkt, x: x + 4 + ), + ShortField("count", 0), + FieldListField( + "swids", [], IntField("", 0), length_from=lambda pkt: pkt.count * 4 + ), + ] + + def handle_pkt(pkt): print("got a packet") pkt.show2() -# hexdump(pkt) + # hexdump(pkt) sys.stdout.flush() + class SourceRoute(Packet): - fields_desc = [ BitField("bos", 0, 1), - BitField("port", 0, 15)] + fields_desc: ClassVar[list[Any]] = [BitField("bos", 0, 1), BitField("port", 0, 15)] + + class SourceRoutingTail(Packet): - fields_desc = [ XShortField("etherType", 0x800)] + fields_desc: ClassVar[list[Any]] = [XShortField("etherType", 0x800)] + bind_layers(Ether, SourceRoute, type=0x1234) bind_layers(SourceRoute, SourceRoute, bos=0) bind_layers(SourceRoute, SourceRoutingTail, bos=1) + def main(): - iface = 'eth0' - print("sniffing on %s" % iface) + iface = "eth0" + print(f"sniffing on {iface}") sys.stdout.flush() - sniff(filter="udp and port 4321", iface = iface, - prn = lambda x: handle_pkt(x)) + sniff(filter="udp and port 4321", iface=iface, prn=lambda x: handle_pkt(x)) + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/exercises/source_routing/send.py b/exercises/source_routing/send.py index 0ddd73fd..66bc5ccc 100755 --- a/exercises/source_routing/send.py +++ b/exercises/source_routing/send.py @@ -5,6 +5,7 @@ # SPDX-License-Identifier: GPL-2.0-only import socket import sys +from typing import Any, ClassVar from scapy.all import ( IP, @@ -14,55 +15,59 @@ bind_layers, get_if_hwaddr, get_if_list, - sendp + sendp, ) -from scapy.fields import * +from scapy.fields import BitField def get_if(): - ifs=get_if_list() - iface=None # "h1-eth0" + iface = None # "h1-eth0" for i in get_if_list(): if "eth0" in i: - iface=i - break; + iface = i + break if not iface: print("Cannot find eth0 interface") - exit(1) + sys.exit(1) return iface + class SourceRoute(Packet): - fields_desc = [ BitField("bos", 0, 1), - BitField("port", 0, 15)] + fields_desc: ClassVar[list[Any]] = [BitField("bos", 0, 1), BitField("port", 0, 15)] + bind_layers(Ether, SourceRoute, type=0x1234) bind_layers(SourceRoute, SourceRoute, bos=0) bind_layers(SourceRoute, IP, bos=1) + def main(): - if len(sys.argv)<2: - print('pass 2 arguments: ') - exit(1) + if len(sys.argv) < 2: + print("pass 2 arguments: ") + sys.exit(1) addr = socket.gethostbyname(sys.argv[1]) iface = get_if() - print("sending on interface %s to %s" % (iface, str(addr))) + print(f"sending on interface {iface} to {addr!s}") while True: print() - s = str(input('Type space separated port nums ' - '(example: "2 3 2 2 1") or "q" to quit: ')) + s = str( + input( + 'Type space separated port nums (example: "2 3 2 2 1") or "q" to quit: ' + ) + ) if s == "q": - break; + break print() i = 0 - pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff'); + pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") for p in s.split(" "): try: pkt = pkt / SourceRoute(bos=0, port=int(p)) - i = i+1 + i = i + 1 except ValueError: pass if pkt.haslayer(SourceRoute): @@ -72,10 +77,10 @@ def main(): pkt.show2() sendp(pkt, iface=iface, verbose=False) - #pkt = pkt / SourceRoute(bos=0, port=2) / SourceRoute(bos=0, port=3); - #pkt = pkt / SourceRoute(bos=0, port=2) / SourceRoute(bos=0, port=2); - #pkt = pkt / SourceRoute(bos=1, port=1) + # pkt = pkt / SourceRoute(bos=0, port=2) / SourceRoute(bos=0, port=3); + # pkt = pkt / SourceRoute(bos=0, port=2) / SourceRoute(bos=0, port=2); + # pkt = pkt / SourceRoute(bos=1, port=1) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/utils/README.md b/utils/README.md index 950ecdc1..ec9b800e 100644 --- a/utils/README.md +++ b/utils/README.md @@ -204,9 +204,9 @@ link_dict = {'node1':s, The class *Exercise* is created to help manage all data and manage the execution flow. The code below describes the *run_exercise()* method. Lines 4 and 5 create the network. Lines 9 and 10 program the network elements. Line 15 launches the user interface. ```python -#(... Omitted ...)# +# (... Omitted ...)# def run_exercise(self): -# Initialize mininet with the topology specified by the config + # Initialize mininet with the topology specified by the config self.create_network() self.net.start() sleep(1) @@ -221,7 +221,9 @@ def run_exercise(self): self.do_net_cli() # stop right after the CLI is exited self.net.stop() -#(... Omitted ...)# + + +# (... Omitted ...)# ``` #### Creating the Mininet Network @@ -229,17 +231,22 @@ def run_exercise(self): It is in this stage that switches, links, and hosts are added to the network. To instantiate the network object, a class, *ExerciseTopo*, is used. This class inherits from the *Topo Class* native to Mininet. The code below shows the initialization process of the *ExerciseTopo* class. ```python -#(... Omitted ...)# -self.topo = ExerciseTopo(self.hosts, self.switches, self.links, self.log_dir, self.bmv2_exe, self.pcap_dir) +# (... Omitted ...)# +self.topo = ExerciseTopo( + self.hosts, self.switches, self.links, self.log_dir, self.bmv2_exe, self.pcap_dir +) + class ExerciseTopo(Topo): - """ The mininet topology class for the P4 tutorial exercises. - """ + """The mininet topology class for the P4 tutorial exercises.""" + def __init__(self, hosts, switches, links, log_dir, bmv2_exe, pcap_dir, **opts): Topo.__init__(self, **opts) host_links = [] switch_links = [] - #(... Omitted ...)# + + +# (... Omitted ...)# ``` Below is shown how the switches are configured. Using the method *configureP4Switch* ensures the switch is created using the correct architecture, (*simple_switch* or *simple_switch_grpc*). @@ -247,36 +254,37 @@ Below is shown how the switches are configured. Using the method *configureP4Swi If no program is specified, the switch follows the default implementation. ```python -#(... Omitted ...)# +# (... Omitted ...)# for sw, params in switches.items(): if "program" in params: switchClass = configureP4Switch( - sw_path=bmv2_exe, - json_path=params["program"], - log_console=True, - pcap_dump=pcap_dir) + sw_path=bmv2_exe, + json_path=params["program"], + log_console=True, + pcap_dump=pcap_dir, + ) else: # add default switch switchClass = None - self.addSwitch(sw, log_file="%s/%s.log" %(log_dir, sw), cls=switchClass) -#(... Omitted ...) + self.addSwitch(sw, log_file="%s/%s.log" % (log_dir, sw), cls=switchClass) +# (... Omitted ...) ``` The penultimate step is to generate the hosts and the host-to-switch links. Using the information provided in the [topology file](#topology-file) and the methods *addHost* and *addLink*, configurations are directly translated to the Mininet Network. ```python -#(... Omitted ...) +# (... Omitted ...) for link in host_links: - host_name = link['node1'] - sw_name, sw_port = self.parse_switch_node(link['node2']) - host_ip = hosts[host_name]['ip'] - host_mac = hosts[host_name]['mac'] + host_name = link["node1"] + sw_name, sw_port = self.parse_switch_node(link["node2"]) + host_ip = hosts[host_name]["ip"] + host_mac = hosts[host_name]["mac"] self.addHost(host_name, ip=host_ip, mac=host_mac) - self.addLink(host_name, sw_name, - delay=link['latency'], bw=link['bandwidth'], - port2=sw_port) -#(... Omitted ...) + self.addLink( + host_name, sw_name, delay=link["latency"], bw=link["bandwidth"], port2=sw_port + ) +# (... Omitted ...) ``` @@ -284,14 +292,19 @@ Finally, the links between the switches are added. Using the data structure dict ```python -#(... Omitted ...) +# (... Omitted ...) for link in switch_links: - sw1_name, sw1_port = self.parse_switch_node(link['node1']) - sw2_name, sw2_port = self.parse_switch_node(link['node2']) - self.addLink(sw1_name, sw2_name, - port1=sw1_port, port2=sw2_port, - delay=link['latency'], bw=link['bandwidth']) -#(... Omitted ...) + sw1_name, sw1_port = self.parse_switch_node(link["node1"]) + sw2_name, sw2_port = self.parse_switch_node(link["node2"]) + self.addLink( + sw1_name, + sw2_name, + port1=sw1_port, + port2=sw2_port, + delay=link["latency"], + bw=link["bandwidth"], + ) +# (... Omitted ...) ``` #### Starting the Mininet Network @@ -301,14 +314,16 @@ Starting the Mininet network can be achieved by using the *start* method of the After starting the network, runtime commands are executed. Below it is demonstrated how console commands are applied to the hosts created in the network. First, the host is retrieved from the network (using its name as key) and then, using the method ```.cmd()```, commands are executed. ```python -#(... Omitted ...)# +# (... Omitted ...)# def program_hosts(self): for host_name, host_info in list(self.hosts.items()): h = self.net.get(host_name) if "commands" in host_info: for cmd in host_info["commands"]: h.cmd(cmd) -#(... Omitted ...)# + + +# (... Omitted ...)# ``` #### Programming the Switches @@ -319,19 +334,23 @@ def program_switch_p4runtime(self, sw_name, sw_dict): sw_obj = self.net.get(sw_name) grpc_port = sw_obj.grpc_port device_id = sw_obj.device_id - runtime_json = sw_dict['runtime_json'] - self.logger('Configuring switch %s using P4Runtime with file %s' % (sw_name, runtime_json)) - with open(runtime_json, 'r') as sw_conf_file: - outfile = '%s/%s-p4runtime-requests.txt' %(self.log_dir, sw_name) + runtime_json = sw_dict["runtime_json"] + self.logger( + "Configuring switch %s using P4Runtime with file %s" % (sw_name, runtime_json) + ) + with open(runtime_json, "r") as sw_conf_file: + outfile = "%s/%s-p4runtime-requests.txt" % (self.log_dir, sw_name) p4runtime_lib.simple_controller.program_switch( - addr='127.0.0.1:%d' % grpc_port, + addr="127.0.0.1:%d" % grpc_port, device_id=device_id, sw_conf_file=sw_conf_file, workdir=os.getcwd(), proto_dump_fpath=outfile, - runtime_json=runtime_json + runtime_json=runtime_json, ) -#(... Omitted ...)# + + +# (... Omitted ...)# ``` @@ -340,15 +359,15 @@ The final step of execution is presenting the user with a interface. This tool i ```python def do_net_cli(self): - #(... Omitted ...)# - print('===============================================') - print('Welcome to the BMV2 Mininet CLI!') - print('===============================================') - print('Your P4 program is installed into the BMV2 software switch') - print('and your initial runtime configuration is loaded. You can interact') - print('with the network using the mininet CLI below.') - print('') - #(... Omitted ...)# + # (... Omitted ...)# + print("===============================================") + print("Welcome to the BMV2 Mininet CLI!") + print("===============================================") + print("Your P4 program is installed into the BMV2 software switch") + print("and your initial runtime configuration is loaded. You can interact") + print("with the network using the mininet CLI below.") + print("") + # (... Omitted ...)# CLI(self.net) ``` @@ -372,10 +391,11 @@ As mentioned previously, if no program is provided, the default switch is used i ```python defaultSwitchClass = configureP4Switch( - sw_path=self.bmv2_exe, - json_path=self.switch_json, - log_console=True, - pcap_dump=self.pcap_dir) + sw_path=self.bmv2_exe, + json_path=self.switch_json, + log_console=True, + pcap_dump=self.pcap_dir, +) ``` ### Makefile diff --git a/utils/mininet/appcontroller.py b/utils/mininet/appcontroller.py index 03f53381..222663e1 100644 --- a/utils/mininet/appcontroller.py +++ b/utils/mininet/appcontroller.py @@ -7,37 +7,47 @@ class AppController: - def __init__(self, manifest=None, target=None, topo=None, net=None, links=None): self.manifest = manifest self.target = target - self.conf = manifest['targets'][target] + self.conf = manifest["targets"][target] self.topo = topo self.net = net self.links = links def read_entries(self, filename): entries = [] - with open(filename, 'r') as f: + with open(filename, "r") as f: for line in f: line = line.strip() - if line == '': continue + if line == "": + continue entries.append(line) return entries def add_entries(self, thrift_port=9090, sw=None, entries=None): assert entries - if sw: thrift_port = sw.thrift_port + if sw: + thrift_port = sw.thrift_port - print('\n'.join(entries)) - p = subprocess.Popen(['simple_switch_CLI', '--thrift-port', str(thrift_port)], stdin=subprocess.PIPE) - p.communicate(input='\n'.join(entries)) + print("\n".join(entries)) + p = subprocess.Popen( + ["simple_switch_CLI", "--thrift-port", str(thrift_port)], + stdin=subprocess.PIPE, + ) + p.communicate(input="\n".join(entries)) def read_register(self, register, idx, thrift_port=9090, sw=None): - if sw: thrift_port = sw.thrift_port - p = subprocess.Popen(['simple_switch_CLI', '--thrift-port', str(thrift_port)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = p.communicate(input="register_read %s %d" % (register, idx)) - reg_val = [l for l in stdout.split('\n') if ' %s[%d]' % (register, idx) in l][0].split('= ', 1)[1] + if sw: + thrift_port = sw.thrift_port + p = subprocess.Popen( + ["simple_switch_CLI", "--thrift-port", str(thrift_port)], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + stdout, _stderr = p.communicate(input=f"register_read {register} {idx}") + reg_val = next(x for x in stdout.split("\n") if f" {register}[{idx}]" in x).split("= ", 1)[1] return int(reg_val) def start(self): @@ -45,13 +55,17 @@ def start(self): entries = {} for sw in self.topo.switches(): entries[sw] = [] - if 'switches' in self.conf and sw in self.conf['switches'] and 'entries' in self.conf['switches'][sw]: - extra_entries = self.conf['switches'][sw]['entries'] - if type(extra_entries) == list: # array of entries + if ( + "switches" in self.conf + and sw in self.conf["switches"] + and "entries" in self.conf["switches"][sw] + ): + extra_entries = self.conf["switches"][sw]["entries"] + if isinstance(extra_entries, list): # array of entries entries[sw] += extra_entries - else: # path to file that contains entries + else: # path to file that contains entries entries[sw] += self.read_entries(extra_entries) - #entries[sw] += [ + # entries[sw] += [ # 'table_set_default send_frame _drop', # 'table_set_default forward _drop', # 'table_set_default ipv4_lpm _drop'] @@ -59,48 +73,51 @@ def start(self): for host_name in self.topo._host_links: h = self.net.get(host_name) for link in list(self.topo._host_links[host_name].values()): - sw = link['sw'] - #entries[sw].append('table_add send_frame rewrite_mac %d => %s' % (link['sw_port'], link['sw_mac'])) - #entries[sw].append('table_add forward set_dmac %s => %s' % (link['host_ip'], link['host_mac'])) - #entries[sw].append('table_add ipv4_lpm set_nhop %s/32 => %s %d' % (link['host_ip'], link['host_ip'], link['sw_port'])) - iface = h.intfNames()[link['idx']] + sw = link["sw"] + # entries[sw].append('table_add send_frame rewrite_mac %d => %s' % (link['sw_port'], link['sw_mac'])) + # entries[sw].append('table_add forward set_dmac %s => %s' % (link['host_ip'], link['host_mac'])) + # entries[sw].append('table_add ipv4_lpm set_nhop %s/32 => %s %d' % (link['host_ip'], link['host_ip'], link['sw_port'])) + iface = h.intfNames()[link["idx"]] # use mininet to set ip and mac to let it know the change - h.setIP(link['host_ip'], 24) - h.setMAC(link['host_mac']) - #h.cmd('ifconfig %s %s hw ether %s' % (iface, link['host_ip'], link['host_mac'])) - h.cmd('arp -i %s -s %s %s' % (iface, link['sw_ip'], link['sw_mac'])) - h.cmd('ethtool --offload %s rx off tx off' % iface) - h.cmd('ip route add %s dev %s' % (link['sw_ip'], iface)) - h.setDefaultRoute("via %s" % link['sw_ip']) + h.setIP(link["host_ip"], 24) + h.setMAC(link["host_mac"]) + # h.cmd('ifconfig %s %s hw ether %s' % (iface, link['host_ip'], link['host_mac'])) + h.cmd("arp -i {} -s {} {}".format(iface, link["sw_ip"], link["sw_mac"])) + h.cmd(f"ethtool --offload {iface} rx off tx off") + h.cmd("ip route add {} dev {}".format(link["sw_ip"], iface)) + h.setDefaultRoute("via {}".format(link["sw_ip"])) for h in self.net.hosts: - h_link = list(self.topo._host_links[h.name].values())[0] + h_link = next(iter(self.topo._host_links[h.name].values())) for sw in self.net.switches: - path = shortestpath.get(sw.name, h.name, exclude=lambda n: n[0]=='h') - if not path: continue - if not path[1][0] == 's': continue # next hop is a switch - sw_link = self.topo._sw_links[sw.name][path[1]] - #entries[sw.name].append('table_add send_frame rewrite_mac %d => %s' % (sw_link[0]['port'], sw_link[0]['mac'])) - #entries[sw.name].append('table_add forward set_dmac %s => %s' % (h_link['host_ip'], sw_link[1]['mac'])) - #entries[sw.name].append('table_add ipv4_lpm set_nhop %s/32 => %s %d' % (h_link['host_ip'], h_link['host_ip'], sw_link[0]['port'])) + path = shortestpath.get(sw.name, h.name, exclude=lambda n: n[0] == "h") + if not path: + continue + if path[1][0] != "s": + continue # next hop is a switch + # sw_link = self.topo._sw_links[sw.name][path[1]] + # entries[sw.name].append('table_add send_frame rewrite_mac %d => %s' % (sw_link[0]['port'], sw_link[0]['mac'])) + # entries[sw.name].append('table_add forward set_dmac %s => %s' % (h_link['host_ip'], sw_link[1]['mac'])) + # entries[sw.name].append('table_add ipv4_lpm set_nhop %s/32 => %s %d' % (h_link['host_ip'], h_link['host_ip'], sw_link[0]['port'])) for h2 in self.net.hosts: - if h == h2: continue - path = shortestpath.get(h.name, h2.name, exclude=lambda n: n[0]=='h') - if not path: continue + if h == h2: + continue + path = shortestpath.get(h.name, h2.name, exclude=lambda n: n[0] == "h") + if not path: + continue h_link = self.topo._host_links[h.name][path[1]] - h2_link = list(self.topo._host_links[h2.name].values())[0] - h.cmd('ip route add %s via %s' % (h2_link['host_ip'], h_link['sw_ip'])) - + h2_link = next(iter(self.topo._host_links[h2.name].values())) + h.cmd("ip route add {} via {}".format(h2_link["host_ip"], h_link["sw_ip"])) print("**********") print("Configuring entries in p4 tables") - for sw_name in entries: + for sw_name, value in entries.items(): print() - print("Configuring switch... %s" % sw_name) + print(f"Configuring switch... {sw_name}") sw = self.net.get(sw_name) - if entries[sw_name]: - self.add_entries(sw=sw, entries=entries[sw_name]) + if value: + self.add_entries(sw=sw, entries=value) print("Configuration complete.") print("**********") diff --git a/utils/mininet/apptopo.py b/utils/mininet/apptopo.py index 8d055f11..da59c29d 100644 --- a/utils/mininet/apptopo.py +++ b/utils/mininet/apptopo.py @@ -1,25 +1,39 @@ # SPDX-FileCopyrightText: 2018 Nate Foster # # SPDX-License-Identifier: Apache-2.0 +import functools +import operator + from mininet.topo import Topo class AppTopo(Topo): - - def __init__(self, links, latencies={}, manifest=None, target=None, - log_dir="/tmp", bws={}, **opts): + def __init__( + self, + links, + latencies=None, + manifest=None, + target=None, + log_dir="/tmp", + bws=None, + **opts, + ): + if bws is None: + bws = {} + if latencies is None: + latencies = {} Topo.__init__(self, **opts) - nodes = sum(list(map(list, list(zip(*links)))), []) - host_names = sorted(list(set([n for n in nodes if n[0] == 'h']))) - sw_names = sorted(list(set([n for n in nodes if n[0] == 's']))) - sw_ports = dict([(sw, []) for sw in sw_names]) + nodes = functools.reduce(operator.iadd, list(map(list, list(zip(*links)))), []) + host_names = sorted({n for n in nodes if n[0] == "h"}) + sw_names = sorted({n for n in nodes if n[0] == "s"}) + sw_ports = {sw: [] for sw in sw_names} self._host_links = {} - self._sw_links = dict([(sw, {}) for sw in sw_names]) + self._sw_links = {sw: {} for sw in sw_names} for sw_name in sw_names: - self.addSwitch(sw_name, log_file="%s/%s.log" %(log_dir, sw_name)) + self.addSwitch(sw_name, log_file=f"{log_dir}/{sw_name}.log") for host_name in host_names: host_num = int(host_name[1:]) @@ -27,48 +41,60 @@ def __init__(self, links, latencies={}, manifest=None, target=None, self.addHost(host_name) self._host_links[host_name] = {} - host_links = [l for l in links if l[0]==host_name or l[1]==host_name] + host_links = [x for x in links if x[0] == host_name or x[1] == host_name] - sw_idx = 0 - for link in host_links: + for sw_idx, link in enumerate(host_links): sw = link[0] if link[0] != host_name else link[1] sw_num = int(sw[1:]) - assert sw[0]=='s', "Hosts should be connected to switches, not " + str(sw) - host_ip = "10.0.%d.%d" % (sw_num, host_num) - host_mac = '00:00:00:00:%02x:%02x' % (sw_num, host_num) - delay_key = ''.join([host_name, sw]) - delay = latencies[delay_key] if delay_key in latencies else '0ms' - bw = bws[delay_key] if delay_key in bws else None + assert sw[0] == "s", ( + "Hosts should be connected to switches, not " + str(sw) + ) + host_ip = f"10.0.{sw_num}.{host_num}" + host_mac = f"00:00:00:00:{sw_num:02x}:{host_num:02x}" + delay_key = f"{host_name}{sw}" + delay = latencies.get(delay_key, "0ms") + bw = bws.get(delay_key, None) sw_ports[sw].append(host_name) - self._host_links[host_name][sw] = dict( - idx=sw_idx, - host_mac = host_mac, - host_ip = host_ip, - sw = sw, - sw_mac = "00:00:00:00:%02x:%02x" % (sw_num, host_num), - sw_ip = "10.0.%d.%d" % (sw_num, 254), - sw_port = sw_ports[sw].index(host_name)+1 - ) - self.addLink(host_name, sw, delay=delay, bw=bw, - addr1=host_mac, addr2=self._host_links[host_name][sw]['sw_mac']) - sw_idx += 1 - - for link in links: # only check switch-switch links + self._host_links[host_name][sw] = { + "idx": sw_idx, + "host_mac": host_mac, + "host_ip": host_ip, + "sw": sw, + "sw_mac": f"00:00:00:00:{sw_num:02x}:{host_num:02x}", + "sw_ip": f"10.0.{sw_num}.254", + "sw_port": sw_ports[sw].index(host_name) + 1, + } + self.addLink( + host_name, + sw, + delay=delay, + bw=bw, + addr1=host_mac, + addr2=self._host_links[host_name][sw]["sw_mac"], + ) + + for link in links: # only check switch-switch links sw1, sw2 = link - if sw1[0] != 's' or sw2[0] != 's': continue + if sw1[0] != "s" or sw2[0] != "s": + continue - delay_key = ''.join(sorted([sw1, sw2])) - delay = latencies[delay_key] if delay_key in latencies else '0ms' - bw = bws[delay_key] if delay_key in bws else None + delay_key = "".join(sorted([sw1, sw2])) + delay = latencies.get(delay_key, "0ms") + bw = bws.get(delay_key, None) - self.addLink(sw1, sw2, delay=delay, bw=bw)#, max_queue_size=10) + self.addLink(sw1, sw2, delay=delay, bw=bw) # , max_queue_size=10) sw_ports[sw1].append(sw2) sw_ports[sw2].append(sw1) sw1_num, sw2_num = int(sw1[1:]), int(sw2[1:]) - sw1_port = dict(mac="00:00:00:%02x:%02x:00" % (sw1_num, sw2_num), port=sw_ports[sw1].index(sw2)+1) - sw2_port = dict(mac="00:00:00:%02x:%02x:00" % (sw2_num, sw1_num), port=sw_ports[sw2].index(sw1)+1) + sw1_port = { + "mac": f"00:00:00:{sw1_num:02x}:{sw2_num:02x}:00", + "port": sw_ports[sw1].index(sw2) + 1, + } + sw2_port = { + "mac": f"00:00:00:{sw2_num:02x}:{sw1_num:02x}:00", + "port": sw_ports[sw2].index(sw1) + 1, + } self._sw_links[sw1][sw2] = [sw1_port, sw2_port] self._sw_links[sw2][sw1] = [sw2_port, sw1_port] - diff --git a/utils/mininet/multi_switch_mininet.py b/utils/mininet/multi_switch_mininet.py index 8ebd5e8f..e4f0c138 100755 --- a/utils/mininet/multi_switch_mininet.py +++ b/utils/mininet/multi_switch_mininet.py @@ -19,26 +19,68 @@ from mininet.net import Mininet from p4_mininet import P4Host, P4Switch -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('--bmv2-log', help='verbose messages in log file', action="store_true") -parser.add_argument('--cli', help="start the mininet cli", action="store_true") -parser.add_argument('--auto-control-plane', help='enable automatic control plane population', action="store_true") -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', - action="store_true") -parser.add_argument('--manifest', '-m', help='Path to manifest file', - type=str, action="store", required=True) -parser.add_argument('--target', '-t', help='Target in manifest file to run', - type=str, action="store", required=True) -parser.add_argument('--log-dir', '-l', help='Location to save output to', - type=str, action="store", required=True) -parser.add_argument('--cli-message', help='Message to print before starting CLI', - type=str, action="store", required=False, default=False) +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( + "--bmv2-log", help="verbose messages in log file", action="store_true" +) +parser.add_argument("--cli", help="start the mininet cli", action="store_true") +parser.add_argument( + "--auto-control-plane", + help="enable automatic control plane population", + action="store_true", +) +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", action="store_true" +) +parser.add_argument( + "--manifest", + "-m", + help="Path to manifest file", + type=str, + action="store", + required=True, +) +parser.add_argument( + "--target", + "-t", + help="Target in manifest file to run", + type=str, + action="store", + required=True, +) +parser.add_argument( + "--log-dir", + "-l", + help="Location to save output to", + type=str, + action="store", + required=True, +) +parser.add_argument( + "--cli-message", + help="Message to print before starting CLI", + type=str, + action="store", + required=False, + default=False, +) args = parser.parse_args() @@ -46,114 +88,125 @@ next_thrift_port = args.thrift_port + def run_command(command): return os.WEXITSTATUS(os.system(command)) + def configureP4Switch(**switch_args): class ConfiguredP4Switch(P4Switch): def __init__(self, *opts, **kwargs): global next_thrift_port kwargs.update(switch_args) - kwargs['thrift_port'] = next_thrift_port + kwargs["thrift_port"] = next_thrift_port next_thrift_port += 1 P4Switch.__init__(self, *opts, **kwargs) + return ConfiguredP4Switch def main(): - with open(args.manifest, 'r') as f: + with open(args.manifest, "r") as f: manifest = json.load(f) - conf = manifest['targets'][args.target] - params = conf['parameters'] if 'parameters' in conf else {} + conf = manifest["targets"][args.target] + params = conf.get("parameters", {}) - os.environ.update(dict([(k_v[0], str(k_v[1])) for k_v in iter(params.items())])) + os.environ.update({k_v[0]: str(k_v[1]) for k_v in iter(params.items())}) def formatParams(s): for param in params: - s = re.sub('\$'+param+'(\W|$)', str(params[param]) + r'\1', s) - s = s.replace('${'+param+'}', str(params[param])) + s = re.sub(r"\$" + param + r"(\W|$)", str(params[param]) + r"\1", s) + s = s.replace("${" + param + "}", str(params[param])) return s AppTopo = apptopo.AppTopo AppController = appcontroller.AppController - if 'topo_module' in conf: + if "topo_module" in conf: sys.path.insert(0, os.path.dirname(args.manifest)) - topo_module = importlib.import_module(conf['topo_module']) + topo_module = importlib.import_module(conf["topo_module"]) AppTopo = topo_module.CustomAppTopo - if 'controller_module' in conf: + if "controller_module" in conf: sys.path.insert(0, os.path.dirname(args.manifest)) - controller_module = importlib.import_module(conf['controller_module']) + controller_module = importlib.import_module(conf["controller_module"]) AppController = controller_module.CustomAppController if not os.path.isdir(args.log_dir): - if os.path.exists(args.log_dir): raise Exception('Log dir exists and is not a dir') + if os.path.exists(args.log_dir): + raise ValueError("Log dir exists and is not a dir") os.mkdir(args.log_dir) - os.environ['P4APP_LOGDIR'] = args.log_dir + os.environ["P4APP_LOGDIR"] = args.log_dir + links = [x[:2] for x in conf["links"]] + latencies = { + "".join(sorted(x[:2])): x[2] for x in conf["links"] if len(x) >= 3 + } + bws = {"".join(sorted(x[:2])): x[3] for x in conf["links"] if len(x) >= 4} - links = [l[:2] for l in conf['links']] - latencies = dict([(''.join(sorted(l[:2])), l[2]) for l in conf['links'] if len(l)>=3]) - bws = dict([(''.join(sorted(l[:2])), l[3]) for l in conf['links'] if len(l)>=4]) - - for host_name in sorted(conf['hosts'].keys()): - host = conf['hosts'][host_name] - if 'latency' not in host: continue + for host_name in sorted(conf["hosts"].keys()): + host = conf["hosts"][host_name] + if "latency" not in host: + continue for a, b in links: - if a != host_name and b != host_name: continue + if a != host_name and b != host_name: + continue other = a if a != host_name else b - latencies[host_name+other] = host['latency'] + latencies[host_name + other] = host["latency"] - for l in latencies: - if isinstance(latencies[l], str): - latencies[l] = formatParams(latencies[l]) + for x, latency in latencies.items(): + if isinstance(latency, str): + latencies[x] = formatParams(latency) else: - latencies[l] = str(latencies[l]) + "ms" - - bmv2_log = args.bmv2_log or ('bmv2_log' in conf and conf['bmv2_log']) - pcap_dump = args.pcap_dump or ('pcap_dump' in conf and conf['pcap_dump']) - - topo = AppTopo(links, latencies, manifest=manifest, target=args.target, - log_dir=args.log_dir, bws=bws) + latencies[x] = str(latency) + "ms" + + bmv2_log = args.bmv2_log or ("bmv2_log" in conf and conf["bmv2_log"]) + pcap_dump = args.pcap_dump or ("pcap_dump" in conf and conf["pcap_dump"]) + + topo = AppTopo( + links, + latencies, + manifest=manifest, + target=args.target, + log_dir=args.log_dir, + bws=bws, + ) switchClass = configureP4Switch( - sw_path=args.behavioral_exe, - json_path=args.json, - log_console=bmv2_log, - pcap_dump=pcap_dump) - net = Mininet(topo = topo, - link = TCLink, - host = P4Host, - switch = switchClass, - controller = None) + sw_path=args.behavioral_exe, + json_path=args.json, + log_console=bmv2_log, + pcap_dump=pcap_dump, + ) + net = Mininet( + topo=topo, link=TCLink, host=P4Host, switch=switchClass, controller=None + ) net.start() sleep(1) controller = None - if args.auto_control_plane or 'controller_module' in conf: - controller = AppController(manifest=manifest, target=args.target, - topo=topo, net=net, links=links) + if args.auto_control_plane or "controller_module" in conf: + controller = AppController( + manifest=manifest, target=args.target, topo=topo, net=net, links=links + ) controller.start() - for h in net.hosts: h.describe() if args.cli_message is not None: - with open(args.cli_message, 'r') as message_file: + with open(args.cli_message, "r") as message_file: print(message_file.read()) - if args.cli or ('cli' in conf and conf['cli']): + if args.cli or (conf.get("cli")): CLI(net) - stdout_files = dict() + stdout_files = {} return_codes = [] host_procs = [] - def formatCmd(cmd): for h in net.hosts: cmd = cmd.replace(h.name, h.defaultIntf().updateIP()) @@ -169,59 +222,71 @@ def _wait_for_exit(p, host): stdout_files[host_name].flush() stdout_files[host_name].close() - print('\n'.join(["%s: %s"%(k_v1[0],k_v1[1]) for k_v1 in iter(params.items())]) + '\n') + print( + "\n".join([f"{k_v1[0]}: {k_v1[1]}" for k_v1 in iter(params.items())]) + + "\n" + ) - for host_name in sorted(conf['hosts'].keys()): - host = conf['hosts'][host_name] - if 'cmd' not in host: continue + for host_name in sorted(conf["hosts"].keys()): + host = conf["hosts"][host_name] + if "cmd" not in host: + continue h = net.get(host_name) - stdout_filename = os.path.join(args.log_dir, h.name + '.stdout') - stdout_files[h.name] = open(stdout_filename, 'w') - cmd = formatCmd(host['cmd']) + stdout_filename = os.path.join(args.log_dir, h.name + ".stdout") + stdout_files[h.name] = open(stdout_filename, "w") # noqa: SIM115 + cmd = formatCmd(host["cmd"]) print(h.name, cmd) p = h.popen(cmd, stdout=stdout_files[h.name], shell=True, preexec_fn=os.setpgrp) - if 'startup_sleep' in host: sleep(host['startup_sleep']) + if "startup_sleep" in host: + sleep(host["startup_sleep"]) - if 'wait' in host and host['wait']: + if host.get("wait"): _wait_for_exit(p, host_name) else: host_procs.append((p, host_name)) for p, host_name in host_procs: - if 'wait' in conf['hosts'][host_name] and conf['hosts'][host_name]['wait']: + if "wait" in conf["hosts"][host_name] and conf["hosts"][host_name]["wait"]: _wait_for_exit(p, host_name) - for p, host_name in host_procs: - if 'wait' in conf['hosts'][host_name] and conf['hosts'][host_name]['wait']: + if "wait" in conf["hosts"][host_name] and conf["hosts"][host_name]["wait"]: continue if p.returncode is None: - run_command('pkill -INT -P %d' % p.pid) + run_command(f"pkill -INT -P {p.pid}") sleep(0.2) - rc = run_command('pkill -0 -P %d' % p.pid) # check if it's still running - if rc == 0: # the process group is still running, send TERM - sleep(1) # give it a little more time to exit gracefully - run_command('pkill -TERM -P %d' % p.pid) + # check if it's still running + rc = run_command(f"pkill -0 -P {p.pid}") + if rc == 0: # the process group is still running, send TERM + sleep(1) # give it a little more time to exit gracefully + run_command(f"pkill -TERM -P {p.pid}") _wait_for_exit(p, host_name) - if 'after' in conf and 'cmd' in conf['after']: - cmds = conf['after']['cmd'] if type(conf['after']['cmd']) == list else [conf['after']['cmd']] + if "after" in conf and "cmd" in conf["after"]: + cmds = ( + conf["after"]["cmd"] + if isinstance(conf["after"]["cmd"], list) + else [conf["after"]["cmd"]] + ) for cmd in cmds: os.system(cmd) - if controller: controller.stop() + if controller: + controller.stop() net.stop() -# if bmv2_log: -# os.system('bash -c "cp /tmp/p4s.s*.log \'%s\'"' % args.log_dir) -# if pcap_dump: -# os.system('bash -c "cp *.pcap \'%s\'"' % args.log_dir) + # if bmv2_log: + # os.system('bash -c "cp /tmp/p4s.s*.log \'%s\'"' % args.log_dir) + # if pcap_dump: + # os.system('bash -c "cp *.pcap \'%s\'"' % args.log_dir) bad_codes = [rc for rc in return_codes if rc != 0] - if len(bad_codes): sys.exit(1) + if len(bad_codes): + sys.exit(1) + -if __name__ == '__main__': - setLogLevel( 'info' ) +if __name__ == "__main__": + setLogLevel("info") main() diff --git a/utils/mininet/p4_mininet.py b/utils/mininet/p4_mininet.py index b6f39f5e..d3ca2bbc 100644 --- a/utils/mininet/p4_mininet.py +++ b/utils/mininet/p4_mininet.py @@ -15,10 +15,10 @@ class P4Host(Host): def config(self, **params): - r = super(P4Host, self).config(**params) + r = super().config(**params) for off in ["rx", "tx", "sg"]: - cmd = "/sbin/ethtool --offload %s %s off" % (self.defaultIntf().name, off) + cmd = f"/sbin/ethtool --offload {self.defaultIntf().name} {off} off" self.cmd(cmd) # disable IPv6 @@ -30,32 +30,37 @@ def config(self, **params): def describe(self, sw_addr=None, sw_mac=None): print("**********") - print("Network configuration for: %s" % self.name) - print("Default interface: %s\t%s\t%s" %( - self.defaultIntf().name, - self.defaultIntf().IP(), - self.defaultIntf().MAC() - )) + print(f"Network configuration for: {self.name}") + print( + f"Default interface: {self.defaultIntf().name}\t{self.defaultIntf().IP()}\t{self.defaultIntf().MAC()}" + ) if sw_addr is not None or sw_mac is not None: - print("Default route to switch: %s (%s)" % (sw_addr, sw_mac)) + print(f"Default route to switch: {sw_addr} ({sw_mac})") print("**********") + class P4Switch(Switch): """P4 virtual switch""" + device_id = 0 - def __init__(self, name, sw_path = None, json_path = None, - log_file = None, - thrift_port = None, - pcap_dump = False, - log_console = False, - verbose = False, - device_id = None, - enable_debugger = False, - **kwargs): + def __init__( + self, + name, + sw_path=None, + json_path=None, + log_file=None, + thrift_port=None, + pcap_dump=False, + log_console=False, + verbose=False, + device_id=None, + enable_debugger=False, + **kwargs, + ): Switch.__init__(self, name, **kwargs) - assert(sw_path) - assert(json_path) + assert sw_path + assert json_path # make sure that the provided sw_path is valid pathCheck(sw_path) # make sure that the provided JSON file exists @@ -67,8 +72,8 @@ def __init__(self, name, sw_path = None, json_path = None, self.verbose = verbose self.log_file = log_file if self.log_file is None: - self.log_file = "/tmp/p4s.{}.log".format(self.name) - self.output = open(self.log_file, 'w') + self.log_file = f"/tmp/p4s.{self.name}.log" + self.output = open(self.log_file, "w") # noqa: SIM115 self.thrift_port = thrift_port self.pcap_dump = pcap_dump self.enable_debugger = enable_debugger @@ -79,17 +84,18 @@ def __init__(self, name, sw_path = None, json_path = None, else: self.device_id = P4Switch.device_id P4Switch.device_id += 1 - self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) + self.nanomsg = f"ipc:///tmp/bm-{self.device_id}-log.ipc" @classmethod def setup(cls): pass def check_switch_started(self, pid): - """While the process is running (pid exists), we check if the Thrift - server has been started. If the Thrift server is ready, we assume that - the switch was started successfully. This is only reliable if the Thrift - server is started at the end of the init process""" + """While the process is running (pid exists), we check if the + Thrift server has been started. If the Thrift server is ready, + we assume that the switch was started successfully. This is + only reliable if the Thrift server is started at the end of + the init process""" while True: if not os.path.exists(os.path.join("/proc", str(pid))): return False @@ -97,55 +103,59 @@ def check_switch_started(self, pid): sock.settimeout(0.5) result = sock.connect_ex(("localhost", self.thrift_port)) if result == 0: - return True + return True def start(self, controllers): "Start up a new P4 switch" - info("Starting P4 switch {}.\n".format(self.name)) + info(f"Starting P4 switch {self.name}.\n") args = [self.sw_path] for port, intf in list(self.intfs.items()): if not intf.IP(): - args.extend(['-i', str(port) + "@" + intf.name]) + args.extend(["-i", str(port) + "@" + intf.name]) if self.pcap_dump: args.append("--pcap") # args.append("--useFiles") if self.thrift_port: - args.extend(['--thrift-port', str(self.thrift_port)]) + args.extend(["--thrift-port", str(self.thrift_port)]) if self.nanomsg: - args.extend(['--nanolog', self.nanomsg]) - args.extend(['--device-id', str(self.device_id)]) + args.extend(["--nanolog", self.nanomsg]) + args.extend(["--device-id", str(self.device_id)]) P4Switch.device_id += 1 args.append(self.json_path) if self.enable_debugger: args.append("--debugger") if self.log_console: args.append("--log-console") - info(' '.join(args) + "\n") + info(" ".join(args) + "\n") pid = None with tempfile.NamedTemporaryFile() as f: # self.cmd(' '.join(args) + ' > /dev/null 2>&1 &') - self.cmd(' '.join(args) + ' >' + self.log_file + ' 2>&1 & echo $! >> ' + f.name) + self.cmd( + " ".join(args) + " >" + self.log_file + " 2>&1 & echo $! >> " + f.name + ) pid = int(f.read()) - debug("P4 switch {} PID is {}.\n".format(self.name, pid)) + debug(f"P4 switch {self.name} PID is {pid}.\n") sleep(1) if not self.check_switch_started(pid): - error("P4 switch {} did not start correctly." - "Check the switch log file.\n".format(self.name)) + error( + f"P4 switch {self.name} did not start correctly." + "Check the switch log file.\n" + ) exit(1) - info("P4 switch {} has been started.\n".format(self.name)) + info(f"P4 switch {self.name} has been started.\n") def stop(self): "Terminate P4 switch." self.output.flush() - self.cmd('kill %' + self.sw_path) - self.cmd('wait') + self.cmd("kill %" + self.sw_path) + self.cmd("wait") self.deleteIntfs() def attach(self, intf): "Connect a data port" - assert(0) + assert 0 def detach(self, intf): "Disconnect a data port" - assert(0) + assert 0 diff --git a/utils/mininet/shortest_path.py b/utils/mininet/shortest_path.py index 07d71f50..04dd5542 100644 --- a/utils/mininet/shortest_path.py +++ b/utils/mininet/shortest_path.py @@ -2,8 +2,9 @@ # # SPDX-License-Identifier: Apache-2.0 class ShortestPath: - - def __init__(self, edges=[]): + def __init__(self, edges=None): + if edges is None: + edges = [] self.neighbors = {} for edge in edges: self.addEdge(*edge) @@ -17,43 +18,46 @@ def get(self, a, b, exclude=lambda node: False): return self._recPath(a, b, [], exclude) def _recPath(self, a, b, visited, exclude): - if a == b: return [a] + if a == b: + return [a] new_visited = visited + [a] paths = [] for neighbor in self.neighbors[a]: - if neighbor in new_visited: continue - if exclude(neighbor) and neighbor != b: continue + if neighbor in new_visited: + continue + if exclude(neighbor) and neighbor != b: + continue path = self._recPath(neighbor, b, new_visited, exclude) - if path: paths.append(path) + if path: + paths.append(path) paths.sort(key=len) return [a] + paths[0] if len(paths) else None -if __name__ == '__main__': +if __name__ == "__main__": edges = [ - (1, 2), - (1, 3), - (1, 5), - (2, 4), - (3, 4), - (3, 5), - (3, 6), - (4, 6), - (5, 6), - (7, 8) - + (1, 2), + (1, 3), + (1, 5), + (2, 4), + (3, 4), + (3, 5), + (3, 6), + (4, 6), + (5, 6), + (7, 8), ] sp = ShortestPath(edges) - assert sp.get(1, 1) == [1] # shortest path from node 1 to itself - assert sp.get(2, 2) == [2] # shortest path from node 2 to itself + assert sp.get(1, 1) == [1] # shortest path from node 1 to itself + assert sp.get(2, 2) == [2] # shortest path from node 2 to itself assert sp.get(1, 2) == [1, 2] assert sp.get(2, 1) == [2, 1] - assert sp.get(1, 3) == [1, 3] # shortest path from node 1 to node 3 - assert sp.get(3, 1) == [3, 1] # shortest path from node 3 to node 1 + assert sp.get(1, 3) == [1, 3] # shortest path from node 1 to node 3 + assert sp.get(3, 1) == [3, 1] # shortest path from node 3 to node 1 assert sp.get(4, 6) == [4, 6] assert sp.get(6, 4) == [6, 4] @@ -61,8 +65,10 @@ def _recPath(self, a, b, visited, exclude): assert sp.get(2, 6) == [2, 4, 6] assert sp.get(6, 2) == [6, 4, 2] - assert sp.get(1, 6) in [[1, 3, 6], [1, 5, 6]] # Multiple shortest paths from 1 to 6 - assert sp.get(6, 1) in [[6, 3, 1], [6, 5, 1]] # Multiple shortest paths from 6 to 1 + # Multiple shortest paths from 1 to 6 + assert sp.get(1, 6) in [[1, 3, 6], [1, 5, 6]] + # Multiple shortest paths from 6 to 1 + assert sp.get(6, 1) in [[6, 3, 1], [6, 5, 1]] assert sp.get(2, 5) == [2, 1, 5] assert sp.get(5, 2) == [5, 1, 2] @@ -73,6 +79,5 @@ def _recPath(self, a, b, visited, exclude): assert sp.get(7, 8) == [7, 8] assert sp.get(8, 7) == [8, 7] - assert sp.get(1, 7) == None # There is no path from node 1 to node 7 - assert sp.get(7, 2) == None # There is no path from node 7 to node 2 - + assert sp.get(1, 7) is None # There is no path from node 1 to node 7 + assert sp.get(7, 2) is None # There is no path from node 7 to node 2 diff --git a/utils/mininet/single_switch_mininet.py b/utils/mininet/single_switch_mininet.py index 446475ec..88f0a72d 100755 --- a/utils/mininet/single_switch_mininet.py +++ b/utils/mininet/single_switch_mininet.py @@ -13,82 +13,126 @@ from mininet.topo import Topo from p4_mininet import P4Host, P4Switch -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('--log-file', help='Path to write the switch log file', - type=str, action="store", required=False) -parser.add_argument('--pcap-dump', help='Dump packets on interfaces to pcap files', - type=str, action="store", required=False, default=False) -parser.add_argument('--switch-config', help='simple_switch_CLI script to configure switch', - type=str, action="store", required=False, default=False) -parser.add_argument('--cli-message', help='Message to print before starting CLI', - type=str, action="store", required=False, default=False) +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( + "--log-file", + help="Path to write the switch log file", + type=str, + action="store", + required=False, +) +parser.add_argument( + "--pcap-dump", + help="Dump packets on interfaces to pcap files", + type=str, + action="store", + required=False, + default=False, +) +parser.add_argument( + "--switch-config", + help="simple_switch_CLI script to configure switch", + type=str, + action="store", + required=False, + default=False, +) +parser.add_argument( + "--cli-message", + help="Message to print before starting CLI", + type=str, + action="store", + required=False, + default=False, +) args = parser.parse_args() class SingleSwitchTopo(Topo): "Single switch connected to n (< 256) hosts." - def __init__(self, sw_path, json_path, log_file, - thrift_port, pcap_dump, n, **opts): + + def __init__(self, sw_path, json_path, log_file, 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, - log_console = True, - log_file = log_file, - thrift_port = thrift_port, - enable_debugger = False, - pcap_dump = pcap_dump) + switch = self.addSwitch( + "s1", + sw_path=sw_path, + json_path=json_path, + log_console=True, + log_file=log_file, + thrift_port=thrift_port, + enable_debugger=False, + pcap_dump=pcap_dump, + ) for h in range(n): - host = self.addHost('h%d' % (h + 1), - ip = "10.0.%d.10/24" % h, - mac = '00:04:00:00:00:%02x' %h) + host = self.addHost( + f"h{h+1}", ip=f"10.0.{h}.10/24", mac=f"00:04:00:00:00:{h:02x}" + ) print("Adding host", str(host)) self.addLink(host, switch) + def main(): num_hosts = args.num_hosts mode = args.mode - topo = SingleSwitchTopo(args.behavioral_exe, - args.json, - args.log_file, - args.thrift_port, - args.pcap_dump, - num_hosts) - net = Mininet(topo = topo, - host = P4Host, - switch = P4Switch, - controller = None) + topo = SingleSwitchTopo( + args.behavioral_exe, + args.json, + args.log_file, + args.thrift_port, + args.pcap_dump, + num_hosts, + ) + net = Mininet(topo=topo, host=P4Host, switch=P4Switch, controller=None) net.start() + sw_mac = [f"00:aa:bb:00:00:{n:02x}" for n in range(num_hosts)] - 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)] + sw_addr = [f"10.0.{n}.1" for n in range(num_hosts)] for n in range(num_hosts): - h = net.get('h%d' % (n + 1)) + h = net.get(f"h{n+1}") if mode == "l2": - h.setDefaultRoute("dev %s" % h.defaultIntf().name) + h.setDefaultRoute(f"dev {h.defaultIntf().name}") else: h.setARP(sw_addr[n], sw_mac[n]) - h.setDefaultRoute("dev %s via %s" % (h.defaultIntf().name, sw_addr[n])) + h.setDefaultRoute(f"dev {h.defaultIntf().name} via {sw_addr[n]}") for n in range(num_hosts): - h = net.get('h%d' % (n + 1)) + h = net.get(f"h{n+1}") h.describe(sw_addr[n], sw_mac[n]) sleep(1) @@ -96,7 +140,7 @@ def main(): if args.switch_config is not None: print() print("Reading switch configuration script:", args.switch_config) - with open(args.switch_config, 'r') as config_file: + with open(args.switch_config, "r") as config_file: switch_config = config_file.read() print("Configuring switch...") @@ -109,12 +153,13 @@ def main(): print("Ready !") if args.cli_message is not None: - with open(args.cli_message, 'r') as message_file: + with open(args.cli_message, "r") as message_file: print(message_file.read()) - CLI( net ) + CLI(net) net.stop() -if __name__ == '__main__': - setLogLevel( 'info' ) + +if __name__ == "__main__": + setLogLevel("info") main() diff --git a/utils/netstat.py b/utils/netstat.py index 6779e956..983c48f4 100644 --- a/utils/netstat.py +++ b/utils/netstat.py @@ -6,7 +6,7 @@ def check_listening_on_port(port): - for c in psutil.net_connections(kind='inet'): - if c.status == 'LISTEN' and c.laddr[1] == port: + for c in psutil.net_connections(kind="inet"): + if c.status == "LISTEN" and c.laddr[1] == port: return True return False diff --git a/utils/p4_mininet.py b/utils/p4_mininet.py index ddb77392..703c42b1 100644 --- a/utils/p4_mininet.py +++ b/utils/p4_mininet.py @@ -12,7 +12,8 @@ from mininet.node import Host, Switch from netstat import check_listening_on_port -SWITCH_START_TIMEOUT = 10 # seconds +SWITCH_START_TIMEOUT = 10 # seconds + class P4Host(Host): def config(self, **params): @@ -21,7 +22,7 @@ def config(self, **params): self.defaultIntf().rename("eth0") for off in ["rx", "tx", "sg"]: - cmd = "/sbin/ethtool --offload eth0 %s off" % off + cmd = f"/sbin/ethtool --offload eth0 {off} off" self.cmd(cmd) # disable IPv6 @@ -34,29 +35,34 @@ def config(self, **params): def describe(self): print("**********") print(self.name) - print("default interface: %s\t%s\t%s" %( - self.defaultIntf().name, - self.defaultIntf().IP(), - self.defaultIntf().MAC() - )) + print( + f"default interface: {self.defaultIntf().name}\t{self.defaultIntf().IP()}\t{self.defaultIntf().MAC()}" + ) print("**********") + class P4Switch(Switch): """P4 virtual switch""" + device_id = 0 - def __init__(self, name, sw_path = None, json_path = None, - thrift_port = None, - pcap_dump = False, - log_console = False, - log_file = None, - verbose = False, - device_id = None, - enable_debugger = False, - **kwargs): + def __init__( + self, + name, + sw_path=None, + json_path=None, + thrift_port=None, + pcap_dump=False, + log_console=False, + log_file=None, + verbose=False, + device_id=None, + enable_debugger=False, + **kwargs, + ): Switch.__init__(self, name, **kwargs) - assert(sw_path) - assert(json_path) + assert sw_path + assert json_path # make sure that the provided sw_path is valid pathCheck(sw_path) # make sure that the provided JSON file exists @@ -66,11 +72,11 @@ def __init__(self, name, sw_path = None, json_path = None, self.sw_path = sw_path self.json_path = json_path self.verbose = verbose - logfile = "/tmp/p4s.{}.log".format(self.name) - self.output = open(logfile, 'w') + logfile = f"/tmp/p4s.{self.name}.log" + self.output = open(logfile, "w") # noqa: SIM115 self.thrift_port = thrift_port if check_listening_on_port(self.thrift_port): - error('%s cannot bind port %d because it is bound by another process\n' % (self.name, self.grpc_port)) + error(f"{self.name} cannot bind port {self.grpc_port} because it is bound by another process\n") exit(1) self.pcap_dump = pcap_dump self.enable_debugger = enable_debugger @@ -78,24 +84,25 @@ def __init__(self, name, sw_path = None, json_path = None, if log_file is not None: self.log_file = log_file else: - self.log_file = "/tmp/p4s.{}.log".format(self.name) + self.log_file = f"/tmp/p4s.{self.name}.log" if device_id is not None: self.device_id = device_id P4Switch.device_id = max(P4Switch.device_id, device_id) else: self.device_id = P4Switch.device_id P4Switch.device_id += 1 - self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) + self.nanomsg = f"ipc:///tmp/bm-{self.device_id}-log.ipc" @classmethod def setup(cls): pass def check_switch_started(self, pid): - """While the process is running (pid exists), we check if the Thrift - server has been started. If the Thrift server is ready, we assume that - the switch was started successfully. This is only reliable if the Thrift - server is started at the end of the init process""" + """While the process is running (pid exists), we check if the + Thrift server has been started. If the Thrift server is ready, + we assume that the switch was started successfully. This is + only reliable if the Thrift server is started at the end of + the init process""" while True: if not os.path.exists(os.path.join("/proc", str(pid))): return False @@ -105,48 +112,50 @@ def check_switch_started(self, pid): def start(self, controllers): "Start up a new P4 switch" - info("Starting P4 switch {}.\n".format(self.name)) + info(f"Starting P4 switch {self.name}.\n") args = [self.sw_path] for port, intf in list(self.intfs.items()): if not intf.IP(): - args.extend(['-i', str(port) + "@" + intf.name]) + args.extend(["-i", str(port) + "@" + intf.name]) if self.pcap_dump: - args.append("--pcap %s" % self.pcap_dump) + args.append(f"--pcap {self.pcap_dump}") if self.thrift_port: - args.extend(['--thrift-port', str(self.thrift_port)]) + args.extend(["--thrift-port", str(self.thrift_port)]) if self.nanomsg: - args.extend(['--nanolog', self.nanomsg]) - args.extend(['--device-id', str(self.device_id)]) + args.extend(["--nanolog", self.nanomsg]) + args.extend(["--device-id", str(self.device_id)]) P4Switch.device_id += 1 args.append(self.json_path) if self.enable_debugger: args.append("--debugger") if self.log_console: args.append("--log-console") - info(' '.join(args) + "\n") + info(" ".join(args) + "\n") pid = None with tempfile.NamedTemporaryFile() as f: # self.cmd(' '.join(args) + ' > /dev/null 2>&1 &') - self.cmd(' '.join(args) + ' >' + self.log_file + ' 2>&1 & echo $! >> ' + f.name) + self.cmd( + " ".join(args) + " >" + self.log_file + " 2>&1 & echo $! >> " + f.name + ) pid = int(f.read()) - debug("P4 switch {} PID is {}.\n".format(self.name, pid)) + debug(f"P4 switch {self.name} PID is {pid}.\n") if not self.check_switch_started(pid): - error("P4 switch {} did not start correctly.\n".format(self.name)) + error(f"P4 switch {self.name} did not start correctly.\n") exit(1) - info("P4 switch {} has been started.\n".format(self.name)) + info(f"P4 switch {self.name} has been started.\n") def stop(self): "Terminate P4 switch." self.output.flush() - self.cmd('kill %' + self.sw_path) - self.cmd('wait') + self.cmd("kill %" + self.sw_path) + self.cmd("wait") self.deleteIntfs() def attach(self, intf): "Connect a data port" - assert(0) + assert 0 def detach(self, intf): "Disconnect a data port" - assert(0) + assert 0 diff --git a/utils/p4apprunner.py b/utils/p4apprunner.py index ec14c16d..14c24118 100755 --- a/utils/p4apprunner.py +++ b/utils/p4apprunner.py @@ -5,7 +5,6 @@ # SPDX-License-Identifier: Apache-2.0 - import argparse import json import os @@ -13,31 +12,55 @@ import tarfile from collections import OrderedDict -parser = argparse.ArgumentParser(description='p4apprunner') -parser.add_argument('--build-dir', help='Directory to build in.', - type=str, action='store', required=False, default='/tmp') -parser.add_argument('--quiet', help='Suppress log messages.', - action='store_true', required=False, default=False) -parser.add_argument('--manifest', help='Path to manifest file.', - type=str, action='store', required=False, default='./p4app.json') -parser.add_argument('app', help='.p4app package to run.', type=str) -parser.add_argument('target', help=('Target to run. Defaults to the first target ' - 'in the package.'), - nargs='?', type=str) +parser = argparse.ArgumentParser(description="p4apprunner") +parser.add_argument( + "--build-dir", + help="Directory to build in.", + type=str, + action="store", + required=False, + default="/tmp", +) +parser.add_argument( + "--quiet", + help="Suppress log messages.", + action="store_true", + required=False, + default=False, +) +parser.add_argument( + "--manifest", + help="Path to manifest file.", + type=str, + action="store", + required=False, + default="./p4app.json", +) +parser.add_argument("app", help=".p4app package to run.", type=str) +parser.add_argument( + "target", + help=("Target to run. Defaults to the first target in the package."), + nargs="?", + type=str, +) args = parser.parse_args() + def log(*items): - if args.quiet != True: + if args.quiet is not True: print(*items) + def log_error(*items): print(*items, file=sys.stderr) + def run_command(command): - log('>', command) + log(">", command) return os.WEXITSTATUS(os.system(command)) + class Manifest: def __init__(self, program_file, language, target, target_config): self.program_file = program_file @@ -45,83 +68,87 @@ def __init__(self, program_file, language, target, target_config): self.target = target self.target_config = target_config + def read_manifest(manifest_file): manifest = json.load(manifest_file, object_pairs_hook=OrderedDict) - if 'program' not in manifest: - log_error('No program defined in manifest.') + if "program" not in manifest: + log_error("No program defined in manifest.") sys.exit(1) - program_file = manifest['program'] + program_file = manifest["program"] - if 'language' not in manifest: - log_error('No language defined in manifest.') + if "language" not in manifest: + log_error("No language defined in manifest.") sys.exit(1) - language = manifest['language'] + language = manifest["language"] - if 'targets' not in manifest or len(manifest['targets']) < 1: - log_error('No targets defined in manifest.') + if "targets" not in manifest or len(manifest["targets"]) < 1: + log_error("No targets defined in manifest.") sys.exit(1) if args.target is not None: chosen_target = args.target - elif 'default-target' in manifest: - chosen_target = manifest['default-target'] + elif "default-target" in manifest: + chosen_target = manifest["default-target"] else: - chosen_target = list(manifest['targets'].keys())[0] + chosen_target = next(iter(manifest["targets"].keys())) - if chosen_target not in manifest['targets']: - log_error('Target not found in manifest:', chosen_target) + if chosen_target not in manifest["targets"]: + log_error("Target not found in manifest:", chosen_target) sys.exit(1) - return Manifest(program_file, language, chosen_target, manifest['targets'][chosen_target]) + return Manifest( + program_file, language, chosen_target, manifest["targets"][chosen_target] + ) def run_compile_bmv2(manifest): - if 'run-before-compile' in manifest.target_config: - commands = manifest.target_config['run-before-compile'] + if "run-before-compile" in manifest.target_config: + commands = manifest.target_config["run-before-compile"] if not isinstance(commands, list): - log_error('run-before-compile should be a list:', commands) + log_error("run-before-compile should be a list:", commands) sys.exit(1) for command in commands: run_command(command) compiler_args = [] - if manifest.language == 'p4-14': - compiler_args.append('--p4v 14') - elif manifest.language == 'p4-16': - compiler_args.append('--p4v 16') + if manifest.language == "p4-14": + compiler_args.append("--p4v 14") + elif manifest.language == "p4-16": + compiler_args.append("--p4v 16") else: - log_error('Unknown language:', manifest.language) + log_error("Unknown language:", manifest.language) sys.exit(1) - if 'compiler-flags' in manifest.target_config: - flags = manifest.target_config['compiler-flags'] + if "compiler-flags" in manifest.target_config: + flags = manifest.target_config["compiler-flags"] if not isinstance(flags, list): - log_error('compiler-flags should be a list:', flags) + log_error("compiler-flags should be a list:", flags) sys.exit(1) compiler_args.extend(flags) # Compile the program. - output_file = manifest.program_file + '.json' - compiler_args.append('"%s"' % manifest.program_file) - compiler_args.append('-o "%s"' % output_file) - rv = run_command('p4c-bm2-ss %s' % ' '.join(compiler_args)) + output_file = manifest.program_file + ".json" + compiler_args.append(f'"{manifest.program_file}"') + compiler_args.append(f'-o "{output_file}"') + rv = run_command("p4c-bm2-ss {}".format(" ".join(compiler_args))) - if 'run-after-compile' in manifest.target_config: - commands = manifest.target_config['run-after-compile'] + if "run-after-compile" in manifest.target_config: + commands = manifest.target_config["run-after-compile"] if not isinstance(commands, list): - log_error('run-after-compile should be a list:', commands) + log_error("run-after-compile should be a list:", commands) sys.exit(1) for command in commands: run_command(command) if rv != 0: - log_error('Compile failed.') + log_error("Compile failed.") sys.exit(1) return output_file + def run_mininet(manifest): output_file = run_compile_bmv2(manifest) @@ -130,182 +157,216 @@ def run_mininet(manifest): # We'll place the switch's log file in current (build) folder. cwd = os.getcwd() - log_file = os.path.join(cwd, manifest.program_file + '.log') - print ("*** Log file %s" % log_file) - switch_args.append('--log-file "%s"' % log_file) + log_file = os.path.join(cwd, manifest.program_file + ".log") + print(f"*** Log file {log_file}") + switch_args.append(f'--log-file "{log_file}"') pcap_dir = os.path.join(cwd) - print ("*** Pcap folder %s" % pcap_dir) - switch_args.append('--pcap-dump "%s" '% pcap_dir) + print(f"*** Pcap folder {pcap_dir}") + switch_args.append(f'--pcap-dump "{pcap_dir}" ') # Generate a message that will be printed by the Mininet CLI to make # interacting with the simple switch a little easier. - message_file = 'mininet_message.txt' - with open(message_file, 'w') as message: - + message_file = "mininet_message.txt" + with open(message_file, "w") as message: print(file=message) - print('======================================================================', - file=message) - print('Welcome to the BMV2 Mininet CLI!', file=message) - print('======================================================================', - file=message) - print('Your P4 program is installed into the BMV2 software switch', file=message) - print('and your initial configuration is loaded. You can interact', file=message) - print('with the network using the mininet CLI below.', file=message) + print( + "======================================================================", + file=message, + ) + print("Welcome to the BMV2 Mininet CLI!", file=message) + print( + "======================================================================", + file=message, + ) + print( + "Your P4 program is installed into the BMV2 software switch", file=message + ) + print( + "and your initial configuration is loaded. You can interact", file=message + ) + print("with the network using the mininet CLI below.", file=message) print(file=message) - print('To inspect or change the switch configuration, connect to', file=message) - print('its CLI from your host operating system using this command:', file=message) - print(' simple_switch_CLI', file=message) + print("To inspect or change the switch configuration, connect to", file=message) + print( + "its CLI from your host operating system using this command:", file=message + ) + print(" simple_switch_CLI", file=message) print(file=message) - print('To view the switch log, run this command from your host OS:', file=message) - print(' tail -f %s' % log_file, file=message) + print( + "To view the switch log, run this command from your host OS:", file=message + ) + print(f" tail -f {log_file}", file=message) print(file=message) - print('To view the switch output pcap, check the pcap files in %s:' % pcap_dir, file=message) - print(' for example run: sudo tcpdump -xxx -r s1-eth1.pcap', file=message) + print( + f"To view the switch output pcap, check the pcap files in {pcap_dir}:", + file=message, + ) + print(" for example run: sudo tcpdump -xxx -r s1-eth1.pcap", file=message) print(file=message) -# print('To run the switch debugger, run this command from your host OS:', file=message) -# print(' bm_p4dbg' , file=message) -# print(file=message) + # print('To run the switch debugger, run this command from your host OS:', file=message) + # print(' bm_p4dbg' , file=message) + # print(file=message) - switch_args.append('--cli-message "%s"' % message_file) + switch_args.append(f'--cli-message "{message_file}"') - if 'num-hosts' in manifest.target_config: - switch_args.append('--num-hosts %s' % manifest.target_config['num-hosts']) + if "num-hosts" in manifest.target_config: + switch_args.append("--num-hosts {}".format(manifest.target_config["num-hosts"])) - if 'switch-config' in manifest.target_config: - switch_args.append('--switch-config "%s"' % manifest.target_config['switch-config']) + if "switch-config" in manifest.target_config: + switch_args.append( + '--switch-config "{}"'.format(manifest.target_config["switch-config"]) + ) - switch_args.append('--behavioral-exe "%s"' % 'simple_switch') - switch_args.append('--json "%s"' % output_file) + switch_args.append('--behavioral-exe "{}"'.format("simple_switch")) + switch_args.append(f'--json "{output_file}"') + + program = f'"{sys.path[0]}/mininet/single_switch_mininet.py"' + return run_command("python3 {} {}".format(program, " ".join(switch_args))) - program = '"%s/mininet/single_switch_mininet.py"' % sys.path[0] - return run_command('python3 %s %s' % (program, ' '.join(switch_args))) def run_multiswitch(manifest): output_file = run_compile_bmv2(manifest) script_args = [] cwd = os.getcwd() - log_dir = os.path.join(cwd, cwd + '/logs') - print ("*** Log directory %s" % log_dir) - script_args.append('--log-dir "%s"' % log_dir) + log_dir = os.path.join(cwd, cwd + "/logs") + print(f"*** Log directory {log_dir}") + script_args.append(f'--log-dir "{log_dir}"') pcap_dir = os.path.join(cwd) - print ("*** Pcap directory %s" % cwd) - script_args.append('--manifest "%s"' % args.manifest) - script_args.append('--target "%s"' % manifest.target) - if 'auto-control-plane' in manifest.target_config and manifest.target_config['auto-control-plane']: - script_args.append('--auto-control-plane' ) - script_args.append('--behavioral-exe "%s"' % 'simple_switch') - script_args.append('--json "%s"' % output_file) - #script_args.append('--cli') + print(f"*** Pcap directory {cwd}") + script_args.append(f'--manifest "{args.manifest}"') + script_args.append(f'--target "{manifest.target}"') + if ( + manifest.target_config.get("auto-control-plane") + ): + script_args.append("--auto-control-plane") + script_args.append('--behavioral-exe "{}"'.format("simple_switch")) + script_args.append(f'--json "{output_file}"') + # script_args.append('--cli') # Generate a message that will be printed by the Mininet CLI to make # interacting with the simple switch a little easier. - message_file = 'mininet_message.txt' - with open(message_file, 'w') as message: - + message_file = "mininet_message.txt" + with open(message_file, "w") as message: print(file=message) - print('======================================================================', - file=message) - print('Welcome to the BMV2 Mininet CLI!', file=message) - print('======================================================================', - file=message) - print('Your P4 program is installed into the BMV2 software switch', file=message) - print('and your initial configuration is loaded. You can interact', file=message) - print('with the network using the mininet CLI below.', file=message) + print( + "======================================================================", + file=message, + ) + print("Welcome to the BMV2 Mininet CLI!", file=message) + print( + "======================================================================", + file=message, + ) + print( + "Your P4 program is installed into the BMV2 software switch", file=message + ) + print( + "and your initial configuration is loaded. You can interact", file=message + ) + print("with the network using the mininet CLI below.", file=message) print(file=message) - print('To inspect or change the switch configuration, connect to', file=message) - print('its CLI from your host operating system using this command:', file=message) - print(' simple_switch_CLI --thrift-port ', file=message) + print("To inspect or change the switch configuration, connect to", file=message) + print( + "its CLI from your host operating system using this command:", file=message + ) + print(" simple_switch_CLI --thrift-port ", file=message) print(file=message) - print('To view a switch log, run this command from your host OS:', file=message) - print(' tail -f %s/.log' % log_dir, file=message) + print("To view a switch log, run this command from your host OS:", file=message) + print(f" tail -f {log_dir}/.log", file=message) print(file=message) - print('To view the switch output pcap, check the pcap files in %s:' % pcap_dir, file=message) - print(' for example run: sudo tcpdump -xxx -r s1-eth1.pcap', file=message) + print( + f"To view the switch output pcap, check the pcap files in {pcap_dir}:", + file=message, + ) + print(" for example run: sudo tcpdump -xxx -r s1-eth1.pcap", file=message) print(file=message) -# print('To run the switch debugger, run this command from your host OS:', file=message) -# print(' bm_p4dbg' , file=message) -# print(file=message) + # print('To run the switch debugger, run this command from your host OS:', file=message) + # print(' bm_p4dbg' , file=message) + # print(file=message) - script_args.append('--cli-message "%s"' % message_file) + script_args.append(f'--cli-message "{message_file}"') + + program = f'"{sys.path[0]}/mininet/multi_switch_mininet.py"' + return run_command("python3 {} {}".format(program, " ".join(script_args))) - program = '"%s/mininet/multi_switch_mininet.py"' % sys.path[0] - return run_command('python3 %s %s' % (program, ' '.join(script_args))) def run_stf(manifest): output_file = run_compile_bmv2(manifest) - if not 'test' in manifest.target_config: - log_error('No STF test file provided.') + if "test" not in manifest.target_config: + log_error("No STF test file provided.") sys.exit(1) - stf_file = manifest.target_config['test'] + stf_file = manifest.target_config["test"] # Run the program using the BMV2 STF interpreter. stf_args = [] - stf_args.append('-v') + stf_args.append("-v") stf_args.append(os.path.join(args.build_dir, output_file)) stf_args.append(os.path.join(args.build_dir, stf_file)) - program = '"%s/stf/bmv2stf.py"' % sys.path[0] - rv = run_command('python3 %s %s' % (program, ' '.join(stf_args))) + program = f'"{sys.path[0]}/stf/bmv2stf.py"' + rv = run_command("python3 {} {}".format(program, " ".join(stf_args))) if rv != 0: sys.exit(1) return rv + def run_custom(manifest): output_file = run_compile_bmv2(manifest) - python_path = 'PYTHONPATH=$PYTHONPATH:/scripts/mininet/' + python_path = "PYTHONPATH=$PYTHONPATH:/scripts/mininet/" script_args = [] - script_args.append('--behavioral-exe "%s"' % 'simple_switch') - script_args.append('--json "%s"' % output_file) - script_args.append('--cli "%s"' % 'simple_switch_CLI') - if not 'program' in manifest.target_config: - log_error('No mininet program file provided.') - sys.exit(1) - program = manifest.target_config['program'] - rv = run_command('%s python3 %s %s' % (python_path, program, ' '.join(script_args))) + script_args.append('--behavioral-exe "{}"'.format("simple_switch")) + script_args.append(f'--json "{output_file}"') + script_args.append('--cli "{}"'.format("simple_switch_CLI")) + if "program" not in manifest.target_config: + log_error("No mininet program file provided.") + sys.exit(1) + program = manifest.target_config["program"] + rv = run_command("{} python3 {} {}".format(python_path, program, " ".join(script_args))) if rv != 0: sys.exit(1) return rv + def main(): - log('Entering build directory.') + log("Entering build directory.") os.chdir(args.build_dir) # A '.p4app' package is really just a '.tar.gz' archive. Extract it so we # can process its contents. - log('Extracting package.') - tar = tarfile.open(args.app) - tar.extractall() - tar.close() + log("Extracting package.") + with tarfile.open(args.app) as tar: + tar.extractall() - log('Reading package manifest.') - with open(args.manifest, 'r') as manifest_file: + log("Reading package manifest.") + with open(args.manifest, "r") as manifest_file: manifest = read_manifest(manifest_file) # Dispatch to the backend implementation for this target. backend = manifest.target - if 'use' in manifest.target_config: - backend = manifest.target_config['use'] + if "use" in manifest.target_config: + backend = manifest.target_config["use"] - if backend == 'mininet': + if backend == "mininet": rc = run_mininet(manifest) - elif backend == 'multiswitch': + elif backend == "multiswitch": rc = run_multiswitch(manifest) - elif backend == 'stf': + elif backend == "stf": rc = run_stf(manifest) - elif backend == 'custom': + elif backend == "custom": rc = run_custom(manifest) - elif backend == 'compile-bmv2': + elif backend == "compile-bmv2": run_compile_bmv2(manifest) rc = 0 else: - log_error('Target specifies unknown backend:', backend) + log_error("Target specifies unknown backend:", backend) sys.exit(1) sys.exit(rc) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/utils/p4runtime_lib/bmv2.py b/utils/p4runtime_lib/bmv2.py index 4ddf1f5f..48ed069d 100644 --- a/utils/p4runtime_lib/bmv2.py +++ b/utils/p4runtime_lib/bmv2.py @@ -8,7 +8,7 @@ def buildDeviceConfig(bmv2_json_file_path=None): "Builds the device config for BMv2" with open(bmv2_json_file_path) as f: - device_config = f.read().encode('utf-8') + device_config = f.read().encode("utf-8") return device_config diff --git a/utils/p4runtime_lib/convert.py b/utils/p4runtime_lib/convert.py index 22b747bf..38f38586 100644 --- a/utils/p4runtime_lib/convert.py +++ b/utils/p4runtime_lib/convert.py @@ -5,48 +5,63 @@ import re import socket -''' -This package contains several helper functions for encoding to and decoding from byte strings: +""" +This package contains several helper functions for encoding to and +decoding from byte strings: - integers - IPv4 address strings - Ethernet address strings -''' +""" + +mac_pattern = re.compile(r"^([\da-fA-F]{2}:){5}([\da-fA-F]{2})$") + -mac_pattern = re.compile(r'^([\da-fA-F]{2}:){5}([\da-fA-F]{2})$') def matchesMac(mac_addr_string): return mac_pattern.match(mac_addr_string) is not None + def encodeMac(mac_addr_string): - return bytes.fromhex(mac_addr_string.replace(':', '')) + return bytes.fromhex(mac_addr_string.replace(":", "")) + def decodeMac(encoded_mac_addr): - return ':'.join(s.hex() for s in encoded_mac_addr) + return ":".join(s.hex() for s in encoded_mac_addr) + + +ip_pattern = re.compile(r"^(\d{1,3}\.){3}(\d{1,3})$") + -ip_pattern = re.compile(r'^(\d{1,3}\.){3}(\d{1,3})$') def matchesIPv4(ip_addr_string): return ip_pattern.match(ip_addr_string) is not None + def encodeIPv4(ip_addr_string): return socket.inet_aton(ip_addr_string) + def decodeIPv4(encoded_ip_addr): return socket.inet_ntoa(encoded_ip_addr) + def matchesIPv6(ip_addr_string): try: socket.inet_pton(socket.AF_INET6, ip_addr_string) return True - except socket.error: + except OSError: return False + def encodeIPv6(ip_addr_string): return socket.inet_pton(socket.AF_INET6, ip_addr_string) + def decodeIPv6(encoded_ip_addr): - return socket.inet_ntop(socket.AF_INET6, encoded_ip_addr) + return socket.inet_ntop(socket.AF_INET6, encoded_ip_addr) + def bitwidthToBytes(bitwidth): - return int(math.ceil(bitwidth / 8.0)) + return math.ceil(bitwidth / 8.0) + def encodeNum(number, bitwidth): byte_len = bitwidthToBytes(bitwidth) @@ -54,27 +69,34 @@ def encodeNum(number, bitwidth): # 2's complement encoding would look like in 'bitwidth' bits. orig_number = number if number < 0: - if number < -(2 ** (bitwidth-1)): - raise Exception("Negative number, %d, has 2's complement representation that does not fit in %d bits" % (number, bitwidth)) - number = (2 ** bitwidth) + number - num_str = '%x' % number + if number < -(2 ** (bitwidth - 1)): + raise ValueError( + f"Negative number, {number}, has 2's complement" + " representation that does not fit in {bitwidth} bits" + ) + number = (2**bitwidth) + number + num_str = f"{number:x}" if orig_number < 0: - print("CONVERT_NEGATIVE_NUMBER debug: orig_number=%s number=%s bitwidth=%d num_str='%s'" - "" % (orig_number, number, bitwidth, num_str)) - if number >= 2 ** bitwidth: - raise Exception("Number, %d, does not fit in %d bits" % (number, bitwidth)) - return bytes.fromhex('0' * (byte_len * 2 - len(num_str)) + num_str) + print( + f"CONVERT_NEGATIVE_NUMBER debug: orig_number={orig_number} number={number}" + " bitwidth={bitwidth} num_str='{num_str}'" + ) + if number >= 2**bitwidth: + raise ValueError(f"Number, {number}, does not fit in {bitwidth} bits") + return bytes.fromhex("0" * (byte_len * 2 - len(num_str)) + num_str) + def decodeNum(encoded_number): return int(encoded_number.hex(), 16) + def encode(x, bitwidth): - 'Tries to infer the type of `x` and encode it' + "Tries to infer the type of `x` and encode it" byte_len = bitwidthToBytes(bitwidth) - if (type(x) == list or type(x) == tuple) and len(x) == 1: + if (isinstance(x, (list, tuple))) and len(x) == 1: x = x[0] encoded_bytes = None - if type(x) == str: + if isinstance(x, str): if matchesMac(x): encoded_bytes = encodeMac(x) elif matchesIPv4(x): @@ -84,67 +106,68 @@ def encode(x, bitwidth): else: # Assume that the string is already encoded encoded_bytes = x - elif type(x) == int: + elif isinstance(x, int): encoded_bytes = encodeNum(x, bitwidth) else: - raise Exception("Encoding objects of %r is not supported" % type(x)) - assert(len(encoded_bytes) == byte_len) + raise TypeError(f"Encoding objects of {type(x)!r} is not supported") + assert len(encoded_bytes) == byte_len return encoded_bytes -if __name__ == '__main__': + +if __name__ == "__main__": # TODO These tests should be moved out of main eventually # Test encoding and decoding for MAC address mac = "aa:bb:cc:dd:ee:ff" enc_mac = encodeMac(mac) - assert(enc_mac == '\xaa\xbb\xcc\xdd\xee\xff') + assert enc_mac == "\xaa\xbb\xcc\xdd\xee\xff" dec_mac = decodeMac(enc_mac) - assert(mac == dec_mac) + assert mac == dec_mac # Test encoding and decoding for IPv4 address ip0 = "10.0.0.1" enc_ipv4 = encodeIPv4(ip0) - assert(enc_ipv4 == '\x0a\x00\x00\x01') + assert enc_ipv4 == "\x0a\x00\x00\x01" dec_ipv4 = decodeIPv4(enc_ipv4) - assert(ip0 == dec_ipv4) + assert ip0 == dec_ipv4 # Test encoding and decoding for IPv6 address ip1 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334" enc_ipv6 = encodeIPv6(ip1) - assert(enc_ipv6 == '\x01\r\xb8\x85\xa3\x00\x00\x00\x00\x8a.\x03ps4') + assert enc_ipv6 == "\x01\r\xb8\x85\xa3\x00\x00\x00\x00\x8a.\x03ps4" dec_ipv6 = decodeIPv6(enc_ipv6) - assert(ip1 == dec_ipv6) + assert ip1 == dec_ipv6 # Test encoding and decoding for a number num = 1337 byte_len = 5 enc_num = encodeNum(num, byte_len * 8) - assert(enc_num == '\x00\x00\x00\x05\x39') + assert enc_num == "\x00\x00\x00\x05\x39" dec_num = decodeNum(enc_num) - assert(num == dec_num) + assert num == dec_num - assert(matchesIPv4('10.0.0.1')) - assert(not matchesIPv4('10.0.0.1.5')) - assert(not matchesIPv4('1000.0.0.1')) - assert(not matchesIPv4('10001')) + assert matchesIPv4("10.0.0.1") + assert not matchesIPv4("10.0.0.1.5") + assert not matchesIPv4("1000.0.0.1") + assert not matchesIPv4("10001") - assert(matchesIPv6('2001:0db8:85a3:0000:0000:8a2e:0370:7334')) - assert(not matchesIPv6('241.54.113.65')) - assert(not matchesIPv6('::1::2')) - assert(not matchesIPv6('192.168.1.1')) + assert matchesIPv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334") + assert not matchesIPv6("241.54.113.65") + assert not matchesIPv6("::1::2") + assert not matchesIPv6("192.168.1.1") # Test generic encoding function - assert(encode(mac, 6 * 8) == enc_mac) - assert(encode(ip0, 4 * 8) == enc_ipv4) - assert(encode(ip1, 16 * 8) == enc_ipv6) - assert(encode(num, 5 * 8) == enc_num) - assert(encode((num,), 5 * 8) == enc_num) - assert(encode([num], 5 * 8) == enc_num) + assert encode(mac, 6 * 8) == enc_mac + assert encode(ip0, 4 * 8) == enc_ipv4 + assert encode(ip1, 16 * 8) == enc_ipv6 + assert encode(num, 5 * 8) == enc_num + assert encode((num,), 5 * 8) == enc_num + assert encode([num], 5 * 8) == enc_num num = 256 byte_len = 2 try: enc_num = encodeNum(num, 8) - raise Exception("expected exception") - except Exception as e: + raise ValueError("expected exception") + except ValueError as e: print(e) diff --git a/utils/p4runtime_lib/error_utils.py b/utils/p4runtime_lib/error_utils.py index 1325b0c3..c2651c5f 100644 --- a/utils/p4runtime_lib/error_utils.py +++ b/utils/p4runtime_lib/error_utils.py @@ -13,21 +13,21 @@ # an incorrect format. class P4RuntimeErrorFormatException(Exception): def __init__(self, message): - super(P4RuntimeErrorFormatException, self).__init__(message) + super().__init__(message) -# Parse the binary details of the gRPC error. This is required to print some -# helpful debugging information in tha case of batched Write / Read -# requests. Returns None if there are no useful binary details and throws -# P4RuntimeErrorFormatException if the error is not formatted -# properly. Otherwise, returns a list of tuples with the first element being the -# index of the operation in the batch that failed and the second element being -# the p4.Error Protobuf message. +# Parse the binary details of the gRPC error. This is required to +# print some helpful debugging information in tha case of batched +# Write / Read requests. Returns None if there are no useful binary +# details and throws P4RuntimeErrorFormatException if the error is not +# formatted properly. Otherwise, returns a list of tuples with the +# first element being the index of the operation in the batch that +# failed and the second element being the p4.Error Protobuf message. def parseGrpcErrorBinaryDetails(grpc_error): # Check if grpc_error is None or not an instance of grpc.RpcError if not grpc_error or not isinstance(grpc_error, grpc.RpcError): raise P4RuntimeErrorFormatException(f"Invalid gRPC error object: {grpc_error}") - + if grpc_error.code() != grpc.StatusCode.UNKNOWN: return None @@ -50,7 +50,8 @@ def parseGrpcErrorBinaryDetails(grpc_error): p4_error = p4runtime_pb2.Error() if not one_error_any.Unpack(p4_error): raise P4RuntimeErrorFormatException( - "Cannot convert Any message to p4.Error") + "Cannot convert Any message to p4.Error" + ) if p4_error.canonical_code == code_pb2.OK: continue indexed_p4_errors += [(idx, p4_error)] @@ -66,12 +67,11 @@ def parseGrpcErrorBinaryDetails(grpc_error): # batch) in order to print error code + user-facing message. See P4Runtime # documentation for more details on error-reporting. def printGrpcError(grpc_error): - print("gRPC Error", grpc_error.details(), end=' ') + print("gRPC Error", grpc_error.details(), end=" ") status_code = grpc_error.code() - print("({})".format(status_code.name), end=' ') + print(f"({status_code.name})", end=" ") traceback = sys.exc_info()[2] - print("[{}:{}]".format( - traceback.tb_frame.f_code.co_filename, traceback.tb_lineno)) + print(f"[{traceback.tb_frame.f_code.co_filename}:{traceback.tb_lineno}]") if status_code != grpc.StatusCode.UNKNOWN: return p4_errors = parseGrpcErrorBinaryDetails(grpc_error) @@ -79,7 +79,5 @@ def printGrpcError(grpc_error): return print("Errors in batch:") for idx, p4_error in p4_errors: - code_name = code_pb2._CODE.values_by_number[ - p4_error.canonical_code].name - print("\t* At index {}: {}, '{}'\n".format( - idx, code_name, p4_error.message)) + code_name = code_pb2._CODE.values_by_number[p4_error.canonical_code].name + print(f"\t* At index {idx}: {code_name}, '{p4_error.message}'\n") diff --git a/utils/p4runtime_lib/helper.py b/utils/p4runtime_lib/helper.py index 55a79c44..89fdbe3a 100644 --- a/utils/p4runtime_lib/helper.py +++ b/utils/p4runtime_lib/helper.py @@ -10,13 +10,14 @@ from .convert import encode -class P4InfoHelper(object): +class P4InfoHelper: def __init__(self, p4_info_filepath): p4info = p4info_pb2.P4Info() # Load the p4info file into a skeleton P4Info object with open(p4_info_filepath) as p4info_f: - google.protobuf.text_format.Merge(p4info_f.read(), p4info, - allow_unknown_field=True) + google.protobuf.text_format.Merge( + p4info_f.read(), p4info, allow_unknown_field=True + ) self.p4info = p4info def get(self, entity_type, name=None, id=None): @@ -26,16 +27,16 @@ def get(self, entity_type, name=None, id=None): for o in getattr(self.p4info, entity_type): pre = o.preamble if name: - if (pre.name == name or pre.alias == name): + if pre.name == name or pre.alias == name: return o else: if pre.id == id: return o if name: - raise AttributeError("Could not find %r of type %s" % (name, entity_type)) + raise AttributeError(f"Could not find {name!r} of type {entity_type}") else: - raise AttributeError("Could not find id %r of type %s" % (id, entity_type)) + raise AttributeError(f"Could not find id {id!r} of type {entity_type}") def get_id(self, entity_type, name): return self.get(entity_type, name=name).preamble.id @@ -47,8 +48,9 @@ def get_alias(self, entity_type, id): return self.get(entity_type, id=id).preamble.alias def __getattr__(self, attr): - # Synthesize convenience functions for name to id lookups for top-level entities - # e.g. get_tables_id(name_string) or get_actions_id(name_string) + # Synthesize convenience functions for name to id lookups for + # top-level entities e.g. get_tables_id(name_string) or + # get_actions_id(name_string) m = re.search(r"^get_(\w+)_id$", attr) if m: primitive = m.group(1) @@ -61,7 +63,7 @@ def __getattr__(self, attr): primitive = m.group(1) return lambda id: self.get_name(primitive, id) - raise AttributeError("%r object has no attribute %r" % (self.__class__, attr)) + raise AttributeError(f"{self.__class__!r} object has no attribute {attr!r}") def get_match_field(self, table_name, name=None, id=None): for t in self.p4info.tables: @@ -71,10 +73,11 @@ def get_match_field(self, table_name, name=None, id=None): if name is not None: if mf.name == name: return mf - elif id is not None: - if mf.id == id: - return mf - raise AttributeError("%r has no attribute %r" % (table_name, name if name is not None else id)) + elif id is not None and mf.id == id: + return mf + raise AttributeError( + f"{table_name!r} has no attribute {name if name is not None else id!r}" + ) def get_match_field_id(self, table_name, match_field_name): return self.get_match_field(table_name, name=match_field_name).id @@ -104,23 +107,23 @@ def get_match_field_pb(self, table_name, match_field_name, value): range_entry.low = encode(value[0], bitwidth) range_entry.high = encode(value[1], bitwidth) else: - raise Exception("Unsupported match type with type %r" % match_type) + raise ValueError(f"Unsupported match type with type {match_type!r}") return p4runtime_match def get_match_field_value(self, match_field): match_type = match_field.WhichOneof("field_match_type") - if match_type == 'valid': + if match_type == "valid": return match_field.valid.value - elif match_type == 'exact': + elif match_type == "exact": return match_field.exact.value - elif match_type == 'lpm': + elif match_type == "lpm": return (match_field.lpm.value, match_field.lpm.prefix_len) - elif match_type == 'ternary': + elif match_type == "ternary": return (match_field.ternary.value, match_field.ternary.mask) - elif match_type == 'range': + elif match_type == "range": return (match_field.range.low, match_field.range.high) else: - raise Exception("Unsupported match type with type %r" % match_type) + raise ValueError(f"Unsupported match type with type {match_type!r}") def get_action_param(self, action_name, name=None, id=None): for a in self.p4info.actions: @@ -130,10 +133,11 @@ def get_action_param(self, action_name, name=None, id=None): if name is not None: if p.name == name: return p - elif id is not None: - if p.id == id: - return p - raise AttributeError("action %r has no param %r, (has: %r)" % (action_name, name if name is not None else id, a.params)) + elif id is not None and p.id == id: + return p + raise AttributeError( + f"action {action_name!r} has no param {name if name is not None else id!r}, (has: {a.params!r})" + ) def get_action_param_id(self, action_name, param_name): return self.get_action_param(action_name, name=param_name).id @@ -148,14 +152,16 @@ def get_action_param_pb(self, action_name, param_name, value): p4runtime_param.value = encode(value, p4info_param.bitwidth) return p4runtime_param - def buildTableEntry(self, - table_name, - match_fields=None, - default_action=False, - action_name=None, - action_params=None, - idle_timeout_ns=None, - priority=None): + def buildTableEntry( + self, + table_name, + match_fields=None, + default_action=False, + action_name=None, + action_params=None, + idle_timeout_ns=None, + priority=None, + ): table_entry = p4runtime_pb2.TableEntry() table_entry.table_id = self.get_tables_id(table_name) @@ -166,10 +172,12 @@ def buildTableEntry(self, table_entry.idle_timeout_ns = idle_timeout_ns if match_fields: - table_entry.match.extend([ - self.get_match_field_pb(table_name, match_field_name, value) - for match_field_name, value in match_fields.items() - ]) + table_entry.match.extend( + [ + self.get_match_field_pb(table_name, match_field_name, value) + for match_field_name, value in match_fields.items() + ] + ) if default_action: table_entry.is_default_action = True @@ -178,10 +186,12 @@ def buildTableEntry(self, action = table_entry.action.action action.action_id = self.get_actions_id(action_name) if action_params: - action.params.extend([ - self.get_action_param_pb(action_name, field_name, value) - for field_name, value in action_params.items() - ]) + action.params.extend( + [ + self.get_action_param_pb(action_name, field_name, value) + for field_name, value in action_params.items() + ] + ) return table_entry def buildMulticastGroupEntry(self, multicast_group_id, replicas): @@ -189,8 +199,8 @@ def buildMulticastGroupEntry(self, multicast_group_id, replicas): mc_entry.multicast_group_entry.multicast_group_id = multicast_group_id for replica in replicas: r = p4runtime_pb2.Replica() - r.egress_port = replica['egress_port'] - r.instance = replica['instance'] + r.egress_port = replica["egress_port"] + r.instance = replica["instance"] mc_entry.multicast_group_entry.replicas.extend([r]) return mc_entry @@ -198,11 +208,12 @@ def buildCloneSessionEntry(self, clone_session_id, replicas, packet_length_bytes clone_entry = p4runtime_pb2.PacketReplicationEngineEntry() clone_entry.clone_session_entry.session_id = clone_session_id clone_entry.clone_session_entry.packet_length_bytes = packet_length_bytes - clone_entry.clone_session_entry.class_of_service = 0 # PI currently supports only CoS=0 for clone session entry + clone_entry.clone_session_entry.class_of_service = ( + 0 # PI currently supports only CoS=0 for clone session entry + ) for replica in replicas: r = p4runtime_pb2.Replica() - r.egress_port = replica['egress_port'] - r.instance = replica['instance'] + r.egress_port = replica["egress_port"] + r.instance = replica["instance"] clone_entry.clone_session_entry.replicas.extend([r]) return clone_entry - diff --git a/utils/p4runtime_lib/simple_controller.py b/utils/p4runtime_lib/simple_controller.py index 35fcd795..f5d81b0b 100755 --- a/utils/p4runtime_lib/simple_controller.py +++ b/utils/p4runtime_lib/simple_controller.py @@ -14,46 +14,72 @@ def error(msg): - print(' - ERROR! ' + msg, file=sys.stderr) + print(" - ERROR! " + msg, file=sys.stderr) + def info(msg): - print(' - ' + msg, file=sys.stdout) + print(" - " + msg, file=sys.stdout) class ConfException(Exception): pass + class InvalidFileContentException(ConfException): pass def main(): - parser = argparse.ArgumentParser(description='P4Runtime Simple Controller') - - parser.add_argument('-a', '--p4runtime-server-addr', - help='address and port of the switch\'s P4Runtime server (e.g. 192.168.0.1:50051)', - type=str, action="store", required=True) - parser.add_argument('-d', '--device-id', - help='Internal device ID to use in P4Runtime messages', - type=int, action="store", required=True) - parser.add_argument('-p', '--proto-dump-file', - help='path to file where to dump protobuf messages sent to the switch', - type=str, action="store", required=True) - parser.add_argument("-c", '--runtime-conf-file', - help="path to input runtime configuration file (JSON)", - type=str, action="store", required=True) + parser = argparse.ArgumentParser(description="P4Runtime Simple Controller") + + parser.add_argument( + "-a", + "--p4runtime-server-addr", + help=( + "address and port of the switch's P4Runtime server (e.g. 192.168.0.1:50051)" + ), + type=str, + action="store", + required=True, + ) + parser.add_argument( + "-d", + "--device-id", + help="Internal device ID to use in P4Runtime messages", + type=int, + action="store", + required=True, + ) + parser.add_argument( + "-p", + "--proto-dump-file", + help="path to file where to dump protobuf messages sent to the switch", + type=str, + action="store", + required=True, + ) + parser.add_argument( + "-c", + "--runtime-conf-file", + help="path to input runtime configuration file (JSON)", + type=str, + action="store", + required=True, + ) args = parser.parse_args() if not os.path.exists(args.runtime_conf_file): - parser.error("File %s does not exist!" % args.runtime_conf_file) + parser.error(f"File {args.runtime_conf_file} does not exist!") workdir = os.path.dirname(os.path.abspath(args.runtime_conf_file)) - with open(args.runtime_conf_file, 'r') as sw_conf_file: - program_switch(addr=args.p4runtime_server_addr, - device_id=args.device_id, - sw_conf_file=sw_conf_file, - workdir=workdir, - proto_dump_fpath=args.proto_dump_file) + with open(args.runtime_conf_file, "r") as sw_conf_file: + program_switch( + addr=args.p4runtime_server_addr, + device_id=args.device_id, + sw_conf_file=sw_conf_file, + workdir=workdir, + proto_dump_fpath=args.proto_dump_file, + ) def check_switch_conf(sw_conf, workdir): @@ -63,82 +89,88 @@ def check_switch_conf(sw_conf, workdir): if "target" not in sw_conf: raise ConfException("missing key 'target'") - target = sw_conf['target'] + target = sw_conf["target"] if target not in target_choices: - raise ConfException("unknown target '%s'" % target) + raise ConfException(f"unknown target '{target}'") - if target == 'bmv2': + if target == "bmv2": required_keys.append("bmv2_json") files_to_check.append("bmv2_json") for conf_key in required_keys: if conf_key not in sw_conf or len(sw_conf[conf_key]) == 0: - raise ConfException("missing key '%s' or empty value" % conf_key) + raise ConfException(f"missing key '{conf_key}' or empty value") for conf_key in files_to_check: real_path = os.path.join(workdir, sw_conf[conf_key]) if not os.path.exists(real_path): - raise ConfException("file does not exist %s" % real_path) + raise ConfException(f"file does not exist {real_path}") # check for file content (e.g. JSON format for bmv2_json) if conf_key == "bmv2_json": - with open(real_path, 'r') as f: + with open(real_path, "r") as f: try: json.load(f) # Check if the file can be parsed as JSON except json.JSONDecodeError as e: - raise InvalidFileContentException(f"Invalid JSON content in {real_path}: {e}") + raise InvalidFileContentException( + f"Invalid JSON content in {real_path}: {e}" + ) -def program_switch(addr, device_id, sw_conf_file, workdir, proto_dump_fpath, runtime_json): +def program_switch( + addr, device_id, sw_conf_file, workdir, proto_dump_fpath, runtime_json +): sw_conf = json_load_byteified(sw_conf_file) try: check_switch_conf(sw_conf=sw_conf, workdir=workdir) except ConfException as e: - error("While parsing input runtime configuration: %s" % str(e)) + error(f"While parsing input runtime configuration: {e!s}") return - info('Using P4Info file %s...' % sw_conf['p4info']) - p4info_fpath = os.path.join(workdir, sw_conf['p4info']) + info("Using P4Info file {}...".format(sw_conf["p4info"])) + p4info_fpath = os.path.join(workdir, sw_conf["p4info"]) p4info_helper = helper.P4InfoHelper(p4info_fpath) - target = sw_conf['target'] + target = sw_conf["target"] - info("Connecting to P4Runtime server on %s (%s)..." % (addr, target)) + info(f"Connecting to P4Runtime server on {addr} ({target})...") if target == "bmv2": - sw = bmv2.Bmv2SwitchConnection(address=addr, device_id=device_id, - proto_dump_file=proto_dump_fpath) + sw = bmv2.Bmv2SwitchConnection( + address=addr, device_id=device_id, proto_dump_file=proto_dump_fpath + ) else: - raise Exception("Don't know how to connect to target %s" % target) + raise ValueError(f"Don't know how to connect to target {target}") try: sw.MasterArbitrationUpdate() if target == "bmv2": - info("Setting pipeline config (%s)..." % sw_conf['bmv2_json']) - bmv2_json_fpath = os.path.join(workdir, sw_conf['bmv2_json']) - sw.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, - bmv2_json_file_path=bmv2_json_fpath) + info("Setting pipeline config ({})...".format(sw_conf["bmv2_json"])) + bmv2_json_fpath = os.path.join(workdir, sw_conf["bmv2_json"]) + sw.SetForwardingPipelineConfig( + p4info=p4info_helper.p4info, bmv2_json_file_path=bmv2_json_fpath + ) else: - raise Exception("Should not be here") + raise ValueError("Should not be here") - if 'table_entries' in sw_conf: - table_entries = sw_conf['table_entries'] - info("Inserting %d table entries..." % len(table_entries)) + if "table_entries" in sw_conf: + table_entries = sw_conf["table_entries"] + info(f"Inserting {len(table_entries)} table entries...") for entry in table_entries: info(tableEntryToString(entry)) validateTableEntry(entry, p4info_helper, runtime_json) insertTableEntry(sw, entry, p4info_helper) - if 'multicast_group_entries' in sw_conf: - group_entries = sw_conf['multicast_group_entries'] - info("Inserting %d group entries..." % len(group_entries)) + if "multicast_group_entries" in sw_conf: + group_entries = sw_conf["multicast_group_entries"] + info(f"Inserting {len(group_entries)} group entries...") for entry in group_entries: info(groupEntryToString(entry)) insertMulticastGroupEntry(sw, entry, p4info_helper) - if 'clone_session_entries' in sw_conf: - clone_entries = sw_conf['clone_session_entries'] - info("Inserting %d clone entries..." % len(clone_entries)) + if "clone_session_entries" in sw_conf: + clone_entries = sw_conf["clone_session_entries"] + info(f"Inserting {len(clone_entries)} clone entries...") for entry in clone_entries: info(cloneEntryToString(entry)) insertCloneGroupEntry(sw, entry, p4info_helper) @@ -148,33 +180,32 @@ def program_switch(addr, device_id, sw_conf_file, workdir, proto_dump_fpath, run def validateTableEntry(flow, p4info_helper, runtime_json): - table_name = flow['table'] - match_fields = flow.get('match') # None if not found - priority = flow.get('priority') # None if not found + table_name = flow["table"] + match_fields = flow.get("match") # None if not found + priority = flow.get("priority") # None if not found match_types_with_priority = [ p4info_pb2.MatchField.TERNARY, p4info_pb2.MatchField.RANGE, - p4info_pb2.MatchField.OPTIONAL + p4info_pb2.MatchField.OPTIONAL, ] if match_fields is not None and (priority is None or priority == 0): - for match_field_name, _ in match_fields.items(): - p4info_match = p4info_helper.get_match_field( - table_name, match_field_name) + for match_field_name in match_fields: + p4info_match = p4info_helper.get_match_field(table_name, match_field_name) match_type = p4info_match.match_type if match_type in match_types_with_priority: raise AssertionError( - "non-zero 'priority' field is required for all entries for table {} in {}" - .format(table_name, runtime_json) + "non-zero 'priority' field is required" + f" for all entries for table {table_name} in {runtime_json}" ) def insertTableEntry(sw, flow, p4info_helper): - table_name = flow['table'] - match_fields = flow.get('match') # None if not found - action_name = flow['action_name'] - default_action = flow.get('default_action') # None if not found - action_params = flow['action_params'] - priority = flow.get('priority') # None if not found + table_name = flow["table"] + match_fields = flow.get("match") # None if not found + action_name = flow["action_name"] + default_action = flow.get("default_action") # None if not found + action_params = flow["action_params"] + priority = flow.get("priority") # None if not found table_entry = p4info_helper.buildTableEntry( table_name=table_name, @@ -182,7 +213,8 @@ def insertTableEntry(sw, flow, p4info_helper): default_action=default_action, action_name=action_name, action_params=action_params, - priority=priority) + priority=priority, + ) sw.WriteTableEntry(table_entry) @@ -194,7 +226,7 @@ def json_load_byteified(file_handle): def _byteify(data, ignore_dicts=False): # if this is a unicode string, return its string representation if isinstance(data, str): - return data.encode('utf-8') + return data.encode("utf-8") # if this is a list of values, return list of byteified values if isinstance(data, list): return [_byteify(item, ignore_dicts=True) for item in data] @@ -210,46 +242,57 @@ def _byteify(data, ignore_dicts=False): def tableEntryToString(flow): - if 'match' in flow: - match_str = ['%s=%s' % (match_name, str(flow['match'][match_name])) for match_name in - flow['match']] - match_str = ', '.join(match_str) - elif 'default_action' in flow and flow['default_action']: - match_str = '(default action)' + if "match" in flow: + match_str = [ + "{}={}".format(match_name, str(flow["match"][match_name])) + for match_name in flow["match"] + ] + match_str = ", ".join(match_str) + elif flow.get("default_action"): + match_str = "(default action)" else: - match_str = '(any)' - params = ['%s=%s' % (param_name, str(flow['action_params'][param_name])) for param_name in - flow['action_params']] - params = ', '.join(params) - return "%s: %s => %s(%s)" % ( - flow['table'], match_str, flow['action_name'], params) + match_str = "(any)" + params = [ + "{}={}".format(param_name, str(flow["action_params"][param_name])) + for param_name in flow["action_params"] + ] + params = ", ".join(params) + return "{}: {} => {}({})".format(flow["table"], match_str, flow["action_name"], params) def groupEntryToString(rule): group_id = rule["multicast_group_id"] - replicas = ['%d' % replica["egress_port"] for replica in rule['replicas']] - ports_str = ', '.join(replicas) - return 'Group {0} => ({1})'.format(group_id, ports_str) + replicas = [str(replica["egress_port"]) for replica in rule["replicas"]] + ports_str = ", ".join(replicas) + return f"Group {group_id} => ({ports_str})" + def cloneEntryToString(rule): clone_id = rule["clone_session_id"] if "packet_length_bytes" in rule: - packet_length_bytes = str(rule["packet_length_bytes"])+"B" + packet_length_bytes = str(rule["packet_length_bytes"]) + "B" else: packet_length_bytes = "NO_TRUNCATION" - replicas = ['%d' % replica["egress_port"] for replica in rule['replicas']] - ports_str = ', '.join(replicas) - return 'Clone Session {0} => ({1}) ({2})'.format(clone_id, ports_str, packet_length_bytes) + replicas = [str(replica["egress_port"]) for replica in rule["replicas"]] + ports_str = ", ".join(replicas) + return f"Clone Session {clone_id} => ({ports_str}) ({packet_length_bytes})" + def insertMulticastGroupEntry(sw, rule, p4info_helper): - mc_entry = p4info_helper.buildMulticastGroupEntry(rule["multicast_group_id"], rule['replicas']) + mc_entry = p4info_helper.buildMulticastGroupEntry( + rule["multicast_group_id"], rule["replicas"] + ) sw.WritePREEntry(mc_entry) + def insertCloneGroupEntry(sw, rule, p4info_helper): - clone_entry = p4info_helper.buildCloneSessionEntry(rule['clone_session_id'], rule['replicas'], - rule.get('packet_length_bytes', 0)) + clone_entry = p4info_helper.buildCloneSessionEntry( + rule["clone_session_id"], + rule["replicas"], + rule.get("packet_length_bytes", 0), + ) sw.WritePREEntry(clone_entry) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/utils/p4runtime_lib/switch.py b/utils/p4runtime_lib/switch.py index 50f021f8..12a0eed7 100644 --- a/utils/p4runtime_lib/switch.py +++ b/utils/p4runtime_lib/switch.py @@ -1,10 +1,10 @@ # SPDX-FileCopyrightText: 2017 Open Networking Foundation # # SPDX-License-Identifier: Apache-2.0 +import datetime +import threading from abc import abstractmethod -from datetime import datetime from queue import Queue -import threading import grpc from p4.v1 import p4runtime_pb2, p4runtime_pb2_grpc @@ -14,10 +14,12 @@ # List of all active connections connections = [] + def ShutdownAllSwitchConnections(): for c in connections: c.shutdown() + class StreamDispatcher: def __init__(self, stream): self.stream = stream @@ -48,11 +50,12 @@ def _dispatch_loop(self): def stop(self): self.running = False - -class SwitchConnection(object): - def __init__(self, name=None, address='127.0.0.1:50051', device_id=0, - proto_dump_file=None): + +class SwitchConnection: + def __init__( + self, name=None, address="127.0.0.1:50051", device_id=0, proto_dump_file=None + ): self.name = name self.address = address self.device_id = device_id @@ -63,19 +66,25 @@ def __init__(self, name=None, address='127.0.0.1:50051', device_id=0, self.channel = grpc.intercept_channel(self.channel, interceptor) self.client_stub = p4runtime_pb2_grpc.P4RuntimeStub(self.channel) self.requests_stream = IterableQueue() - self.stream_msg_resp = self.client_stub.StreamChannel(iter(self.requests_stream)) + self.stream_msg_resp = self.client_stub.StreamChannel( + iter(self.requests_stream) + ) self.dispatcher = StreamDispatcher(self.stream_msg_resp) self.proto_dump_file = proto_dump_file connections.append(self) @abstractmethod def buildDeviceConfig(self, **kwargs): - print("switch.py:SwitchConnection:buildDeviceConfig() should be overridden, but never called (?)", flush=True) + print( + "switch.py:SwitchConnection:buildDeviceConfig() should be" + " overridden, but never called (?)", + flush=True, + ) assert False def shutdown(self): self.requests_stream.close() - self.dispatcher.stop() + self.dispatcher.stop() def MasterArbitrationUpdate(self, dry_run=False, **kwargs): request = p4runtime_pb2.StreamMessageRequest() @@ -99,7 +108,9 @@ def SetForwardingPipelineConfig(self, p4info, dry_run=False, **kwargs): config.p4info.CopyFrom(p4info) config.p4_device_config = device_config - request.action = p4runtime_pb2.SetForwardingPipelineConfigRequest.VERIFY_AND_COMMIT + request.action = ( + p4runtime_pb2.SetForwardingPipelineConfigRequest.VERIFY_AND_COMMIT + ) if dry_run: print("P4Runtime SetForwardingPipelineConfig:", request) else: @@ -144,8 +155,7 @@ def ReadTableEntries(self, table_id=None, dry_run=False): if dry_run: print("P4Runtime Read:", request) else: - for response in self.client_stub.Read(request): - yield response + yield from self.client_stub.Read(request) def ReadCounters(self, counter_id=None, index=None, dry_run=False): request = p4runtime_pb2.ReadRequest() @@ -161,8 +171,7 @@ def ReadCounters(self, counter_id=None, index=None, dry_run=False): if dry_run: print("P4Runtime Read:", request) else: - for response in self.client_stub.Read(request): - yield response + yield from self.client_stub.Read(request) def WritePREEntry(self, pre_entry, dry_run=False): request = p4runtime_pb2.WriteRequest() @@ -192,9 +201,9 @@ def PacketOut(self, payload, metadatas): for meta in metadatas: item = p4runtime_pb2.PacketMetadata() item.metadata_id = i - item.value = meta["value"].to_bytes(meta["bitwidth"], 'big') + item.value = meta["value"].to_bytes(meta["bitwidth"], "big") metadata_list.append(item) - i +=1 + i += 1 packet_out.metadata.extend(metadata_list) request = p4runtime_pb2.StreamMessageRequest() @@ -207,26 +216,29 @@ def IdleTimeoutNotification(self, dry_run=False): print("P4 Runtime PacketIn: ", msg) else: return msg -class GrpcRequestLogger(grpc.UnaryUnaryClientInterceptor, - grpc.UnaryStreamClientInterceptor): + + +class GrpcRequestLogger( + grpc.UnaryUnaryClientInterceptor, grpc.UnaryStreamClientInterceptor +): """Implementation of a gRPC interceptor that logs request to a file""" def __init__(self, log_file): self.log_file = log_file - with open(self.log_file, 'w') as f: + with open(self.log_file, "w") as f: # Clear content if it exists. f.write("") def log_message(self, method_name, body): - with open(self.log_file, 'a') as f: - ts = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] + with open(self.log_file, "a") as f: + ts = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] msg = str(body) - f.write("\n[%s] %s\n---\n" % (ts, method_name)) + f.write(f"\n[{ts}] {method_name}\n---\n") if len(msg) < MSG_LOG_MAX_LEN: f.write(str(body)) else: - f.write("Message too long (%d bytes)! Skipping log...\n" % len(msg)) - f.write('---\n') + f.write(f"Message too long ({len(msg)} bytes)! Skipping log...\n") + f.write("---\n") def intercept_unary_unary(self, continuation, client_call_details, request): self.log_message(client_call_details.method, request) @@ -236,6 +248,7 @@ def intercept_unary_stream(self, continuation, client_call_details, request): self.log_message(client_call_details.method, request) return continuation(client_call_details, request) + class IterableQueue(Queue): _sentinel = object() diff --git a/utils/p4runtime_switch.py b/utils/p4runtime_switch.py index 35c6f831..ff7c0174 100644 --- a/utils/p4runtime_switch.py +++ b/utils/p4runtime_switch.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 import os +import sys import tempfile from time import sleep @@ -16,21 +17,27 @@ class P4RuntimeSwitch(P4Switch): "BMv2 switch with gRPC support" + next_grpc_port = 50051 next_thrift_port = 9090 - def __init__(self, name, sw_path = None, json_path = None, - grpc_port = None, - thrift_port = None, - pcap_dump = False, - log_console = False, - verbose = False, - device_id = None, - enable_debugger = False, - log_file = None, - **kwargs): + def __init__( + self, + name, + sw_path=None, + json_path=None, + grpc_port=None, + thrift_port=None, + pcap_dump=False, + log_console=False, + verbose=False, + device_id=None, + enable_debugger=False, + log_file=None, + **kwargs, + ): Switch.__init__(self, name, **kwargs) - assert (sw_path) + assert sw_path self.sw_path = sw_path # make sure that the provided sw_path is valid pathCheck(sw_path) @@ -38,8 +45,8 @@ def __init__(self, name, sw_path = None, json_path = None, if json_path is not None: # make sure that the provided JSON file exists if not os.path.isfile(json_path): - error("Invalid JSON file: {}\n".format(json_path)) - exit(1) + error(f"Invalid JSON file: {json_path}\n") + sys.exit(1) self.json_path = json_path else: self.json_path = None @@ -57,26 +64,29 @@ def __init__(self, name, sw_path = None, json_path = None, P4RuntimeSwitch.next_thrift_port += 1 if check_listening_on_port(self.grpc_port): - error('%s cannot bind port %d because it is bound by another process\n' % (self.name, self.grpc_port)) - exit(1) + error( + f"{self.name} cannot bind port {self.grpc_port} because it is bound" + " by another process\n" + ) + sys.exit(1) self.verbose = verbose - logfile = "/tmp/p4s.{}.log".format(self.name) - self.output = open(logfile, 'w') + logfile = f"/tmp/p4s.{self.name}.log" + self.output = open(logfile, "w") # noqa: SIM115 self.pcap_dump = pcap_dump self.enable_debugger = enable_debugger self.log_console = log_console if log_file is not None: self.log_file = log_file else: - self.log_file = "/tmp/p4s.{}.log".format(self.name) + self.log_file = f"/tmp/p4s.{self.name}.log" if device_id is not None: self.device_id = device_id P4Switch.device_id = max(P4Switch.device_id, device_id) else: self.device_id = P4Switch.device_id P4Switch.device_id += 1 - self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) + self.nanomsg = f"ipc:///tmp/bm-{self.device_id}-log.ipc" self.cpu_port = None if "cpu_port" in kwargs: @@ -86,7 +96,6 @@ def __init__(self, name, sw_path = None, json_path = None, if "priority_queues" in kwargs: self.priority_queues = kwargs["priority_queues"] - def check_switch_started(self, pid): for _ in range(SWITCH_START_TIMEOUT * 2): if not os.path.exists(os.path.join("/proc", str(pid))): @@ -96,16 +105,16 @@ def check_switch_started(self, pid): sleep(0.5) def start(self, controllers): - info("Starting P4 switch {}.\n".format(self.name)) + info(f"Starting P4 switch {self.name}.\n") args = [self.sw_path] for port, intf in list(self.intfs.items()): if not intf.IP(): - args.extend(['-i', str(port) + "@" + intf.name]) + args.extend(["-i", str(port) + "@" + intf.name]) if self.pcap_dump: - args.append("--pcap %s" % self.pcap_dump) + args.append(f"--pcap {self.pcap_dump}") if self.nanomsg: - args.extend(['--nanolog', self.nanomsg]) - args.extend(['--device-id', str(self.device_id)]) + args.extend(["--nanolog", self.nanomsg]) + args.extend(["--device-id", str(self.device_id)]) P4Switch.device_id += 1 if self.json_path: args.append(self.json_path) @@ -116,24 +125,23 @@ def start(self, controllers): if self.log_console: args.append("--log-console") if self.thrift_port: - args.append('--thrift-port ' + str(self.thrift_port)) + args.append("--thrift-port " + str(self.thrift_port)) if self.grpc_port: args.append("-- --grpc-server-addr 0.0.0.0:" + str(self.grpc_port)) if self.cpu_port: args.append("--cpu-port " + str(self.cpu_port)) if self.priority_queues: args.append("--priority-queues " + str(self.priority_queues)) - cmd = ' '.join(args) + cmd = " ".join(args) info(cmd + "\n") print(cmd + "\n") - pid = None with tempfile.NamedTemporaryFile() as f: - self.cmd(cmd + ' >' + self.log_file + ' 2>&1 & echo $! >> ' + f.name) + self.cmd(cmd + " >" + self.log_file + " 2>&1 & echo $! >> " + f.name) pid = int(f.read()) - debug("P4 switch {} PID is {}.\n".format(self.name, pid)) + debug(f"P4 switch {self.name} PID is {pid}.\n") if not self.check_switch_started(pid): - error("P4 switch {} did not start correctly.\n".format(self.name)) - exit(1) - info("P4 switch {} has been started.\n".format(self.name)) + error(f"P4 switch {self.name} did not start correctly.\n") + sys.exit(1) + info(f"P4 switch {self.name} has been started.\n") diff --git a/utils/run_exercise.py b/utils/run_exercise.py index 672f1998..f0301223 100755 --- a/utils/run_exercise.py +++ b/utils/run_exercise.py @@ -26,40 +26,42 @@ def configureP4Switch(**switch_args): - """ Helper class that is called by mininet to initialize - the virtual P4 switches. The purpose is to ensure each - switch's thrift server is using a unique port. + """Helper class that is called by mininet to initialize + the virtual P4 switches. The purpose is to ensure each + switch's thrift server is using a unique port. """ - if "sw_path" in switch_args and 'grpc' in switch_args['sw_path']: - # If grpc appears in the BMv2 switch target, we assume will start P4Runtime + if "sw_path" in switch_args and "grpc" in switch_args["sw_path"]: + # If grpc appears in the BMv2 switch target, we assume will + # start P4Runtime class ConfiguredP4RuntimeSwitch(P4RuntimeSwitch): def __init__(self, *opts, **kwargs): kwargs.update(switch_args) P4RuntimeSwitch.__init__(self, *opts, **kwargs) def describe(self): - print("%s -> gRPC port: %d" % (self.name, self.grpc_port)) + print(f"{self.name} -> gRPC port: {self.grpc_port}") return ConfiguredP4RuntimeSwitch else: + class ConfiguredP4Switch(P4Switch): next_thrift_port = 9090 + def __init__(self, *opts, **kwargs): - global next_thrift_port kwargs.update(switch_args) - kwargs['thrift_port'] = ConfiguredP4Switch.next_thrift_port + kwargs["thrift_port"] = ConfiguredP4Switch.next_thrift_port ConfiguredP4Switch.next_thrift_port += 1 P4Switch.__init__(self, *opts, **kwargs) def describe(self): - print("%s -> Thrift port: %d" % (self.name, self.thrift_port)) + print(f"{self.name} -> Thrift port: {self.thrift_port}") return ConfiguredP4Switch class ExerciseTopo(Topo): - """ The mininet topology class for the P4 tutorial exercises. - """ + """The mininet topology class for the P4 tutorial exercises.""" + def __init__(self, hosts, switches, links, log_dir, bmv2_exe, pcap_dir, **opts): Topo.__init__(self, **opts) host_links = [] @@ -67,7 +69,7 @@ def __init__(self, hosts, switches, links, log_dir, bmv2_exe, pcap_dir, **opts): # assumes host always comes first for host<-->switch links for link in links: - if link['node1'][0] == 'h': + if link["node1"][0] == "h": host_links.append(link) else: switch_links.append(link) @@ -75,18 +77,16 @@ def __init__(self, hosts, switches, links, log_dir, bmv2_exe, pcap_dir, **opts): for sw, params in switches.items(): if "program" in params: switchClass = configureP4Switch( - sw_path=bmv2_exe, - json_path=params["program"], - log_console=True, - pcap_dump=pcap_dir) + sw_path=bmv2_exe, + json_path=params["program"], + log_console=True, + pcap_dump=pcap_dir, + ) else: # add default switch switchClass = None - switch_opts = { - "log_file": "%s/%s.log" % (log_dir, sw), - "cls": switchClass - } + switch_opts = {"log_file": f"{log_dir}/{sw}.log", "cls": switchClass} for key in ("cpu_port", "priority_queues"): if key in params: @@ -95,102 +95,116 @@ def __init__(self, hosts, switches, links, log_dir, bmv2_exe, pcap_dir, **opts): self.addSwitch(sw, **switch_opts) for link in host_links: - host_name = link['node1'] - sw_name, sw_port = self.parse_switch_node(link['node2']) - host_ip = hosts[host_name]['ip'] - host_mac = hosts[host_name]['mac'] + host_name = link["node1"] + sw_name, sw_port = self.parse_switch_node(link["node2"]) + host_ip = hosts[host_name]["ip"] + host_mac = hosts[host_name]["mac"] self.addHost(host_name, ip=host_ip, mac=host_mac) - self.addLink(host_name, sw_name, - delay=link['latency'], bw=link['bandwidth'], - port2=sw_port) + self.addLink( + host_name, + sw_name, + delay=link["latency"], + bw=link["bandwidth"], + port2=sw_port, + ) for link in switch_links: - sw1_name, sw1_port = self.parse_switch_node(link['node1']) - sw2_name, sw2_port = self.parse_switch_node(link['node2']) - self.addLink(sw1_name, sw2_name, - port1=sw1_port, port2=sw2_port, - delay=link['latency'], bw=link['bandwidth']) - + sw1_name, sw1_port = self.parse_switch_node(link["node1"]) + sw2_name, sw2_port = self.parse_switch_node(link["node2"]) + self.addLink( + sw1_name, + sw2_name, + port1=sw1_port, + port2=sw2_port, + delay=link["latency"], + bw=link["bandwidth"], + ) def parse_switch_node(self, node): - assert(len(node.split('-')) == 2) - sw_name, sw_port = node.split('-') + assert len(node.split("-")) == 2 + sw_name, sw_port = node.split("-") try: sw_port = int(sw_port[1:]) - except: - raise Exception('Invalid switch node in topology file: {}'.format(node)) + except ValueError: + raise ValueError(f"Invalid switch node in topology file: {node}") return sw_name, sw_port class ExerciseRunner: """ - Attributes: - log_dir : string // directory for mininet log files - pcap_dir : string // directory for mininet switch pcap files - quiet : bool // determines if we print logger messages + Attributes: + log_dir : string // directory for mininet log files + pcap_dir : string // directory for mininet switch pcap files + quiet : bool // determines if we print logger messages - hosts : dict // mininet host names and their associated properties - switches : dict // mininet switch names and their associated properties - links : list // list of mininet link properties + hosts : dict // mininet host names and their associated properties + switches : dict // mininet switch names and their associated properties + links : list // list of mininet link properties - switch_json : string // json of the compiled p4 example - bmv2_exe : string // name or path of the p4 switch binary + switch_json : string // json of the compiled p4 example + bmv2_exe : string // name or path of the p4 switch binary - topo : Topo object // The mininet topology instance - net : Mininet object // The mininet instance + topo : Topo object // The mininet topology instance + net : Mininet object // The mininet instance """ + def logger(self, *items): if not self.quiet: - print(' '.join(items)) + print(" ".join(items)) - def format_latency(self, l): - """ Helper method for parsing link latencies from the topology json. """ - if isinstance(l, str): - return l + def format_latency(self, x): + """Helper method for parsing link latencies from the topology json.""" + if isinstance(x, str): + return x else: - return str(l) + "ms" - - - def __init__(self, topo_file, log_dir, pcap_dir, - switch_json, bmv2_exe='simple_switch', quiet=False): - """ Initializes some attributes and reads the topology json. Does not - actually run the exercise. Use run_exercise() for that. - - Arguments: - topo_file : string // A json file which describes the exercise's - mininet topology. - log_dir : string // Path to a directory for storing exercise logs - pcap_dir : string // Ditto, but for mininet switch pcap files - switch_json : string // Path to a compiled p4 json for bmv2 - bmv2_exe : string // Path to the p4 behavioral binary - quiet : bool // Enable/disable script debug messages + return str(x) + "ms" + + def __init__( + self, + topo_file, + log_dir, + pcap_dir, + switch_json, + bmv2_exe="simple_switch", + quiet=False, + ): + """Initializes some attributes and reads the topology json. Does not + actually run the exercise. Use run_exercise() for that. + + Arguments: + topo_file : string // A json file which describes the exercise's + mininet topology. + log_dir : string // Path to a directory for storing exercise logs + pcap_dir : string // Ditto, but for mininet switch pcap files + switch_json : string // Path to a compiled p4 json for bmv2 + bmv2_exe : string // Path to the p4 behavioral binary + quiet : bool // Enable/disable script debug messages """ self.quiet = quiet - self.logger('Reading topology file.') - with open(topo_file, 'r') as f: + self.logger("Reading topology file.") + with open(topo_file, "r") as f: topo = json.load(f) - self.hosts = topo['hosts'] - self.switches = topo['switches'] - self.links = self.parse_links(topo['links']) + self.hosts = topo["hosts"] + self.switches = topo["switches"] + self.links = self.parse_links(topo["links"]) # Ensure all the needed directories exist and are directories for dir_name in [log_dir, pcap_dir]: if not os.path.isdir(dir_name): if os.path.exists(dir_name): - raise Exception("'%s' exists and is not a directory!" % dir_name) + raise ValueError(f"'{dir_name}' exists and is not a directory!") os.mkdir(dir_name) self.log_dir = log_dir self.pcap_dir = pcap_dir self.switch_json = switch_json self.bmv2_exe = bmv2_exe - def run_exercise(self): - """ Sets up the mininet instance, programs the switches, - and starts the mininet CLI. This is the main method to run after - initializing the object. + """Sets up the mininet instance, programs the switches, + and starts the mininet CLI. This is the main method to run after + initializing the object. """ # Initialize mininet with the topology specified by the config self.create_network() @@ -208,125 +222,145 @@ def run_exercise(self): # stop right after the CLI is exited self.net.stop() - def parse_links(self, unparsed_links): - """ Given a list of links descriptions of the form [node1, node2, latency, bandwidth] - with the latency and bandwidth being optional, parses these descriptions - into dictionaries and store them as self.links + """Given a list of links descriptions of the form [node1, + node2, latency, bandwidth] with the latency and bandwidth + being optional, parses these descriptions into dictionaries + and store them as self.links """ links = [] for link in unparsed_links: # make sure each link's endpoints are ordered alphabetically - s, t, = link[0], link[1] + ( + s, + t, + ) = ( + link[0], + link[1], + ) if s > t: - s,t = t,s + s, t = t, s - link_dict = {'node1':s, - 'node2':t, - 'latency':'0ms', - 'bandwidth':None - } + link_dict = {"node1": s, "node2": t, "latency": "0ms", "bandwidth": None} if len(link) > 2: - link_dict['latency'] = self.format_latency(link[2]) + link_dict["latency"] = self.format_latency(link[2]) if len(link) > 3: - link_dict['bandwidth'] = link[3] + link_dict["bandwidth"] = link[3] - if link_dict['node1'][0] == 'h': - assert link_dict['node2'][0] == 's', 'Hosts should be connected to switches, not ' + str(link_dict['node2']) + if link_dict["node1"][0] == "h": + assert link_dict["node2"][0] == "s", ( + "Hosts should be connected to switches, not " + + str(link_dict["node2"]) + ) links.append(link_dict) return links - def create_network(self): - """ Create the mininet network object, and store it as self.net. + """Create the mininet network object, and store it as self.net. - Side effects: - - Mininet topology instance stored as self.topo - - Mininet instance stored as self.net + Side effects: + - Mininet topology instance stored as self.topo + - Mininet instance stored as self.net """ self.logger("Building mininet topology.") defaultSwitchClass = configureP4Switch( - sw_path=self.bmv2_exe, - json_path=self.switch_json, - log_console=True, - pcap_dump=self.pcap_dir) - - self.topo = ExerciseTopo(self.hosts, self.switches, self.links, self.log_dir, self.bmv2_exe, self.pcap_dir) - - self.net = Mininet(topo = self.topo, - link = TCLink, - host = P4Host, - switch = defaultSwitchClass, - controller = None) + sw_path=self.bmv2_exe, + json_path=self.switch_json, + log_console=True, + pcap_dump=self.pcap_dir, + ) + + self.topo = ExerciseTopo( + self.hosts, + self.switches, + self.links, + self.log_dir, + self.bmv2_exe, + self.pcap_dir, + ) + + self.net = Mininet( + topo=self.topo, + link=TCLink, + host=P4Host, + switch=defaultSwitchClass, + controller=None, + ) def program_switch_p4runtime(self, sw_name, sw_dict): - """ This method will use P4Runtime to program the switch using the - content of the runtime JSON file as input. + """This method will use P4Runtime to program the switch using the + content of the runtime JSON file as input. """ sw_obj = self.net.get(sw_name) grpc_port = sw_obj.grpc_port device_id = sw_obj.device_id - runtime_json = sw_dict['runtime_json'] - self.logger('Configuring switch %s using P4Runtime with file %s' % (sw_name, runtime_json)) - with open(runtime_json, 'r') as sw_conf_file: - outfile = '%s/%s-p4runtime-requests.txt' %(self.log_dir, sw_name) + runtime_json = sw_dict["runtime_json"] + self.logger( + f"Configuring switch {sw_name} using P4Runtime with file {runtime_json}" + ) + with open(runtime_json, "r") as sw_conf_file: + outfile = f"{self.log_dir}/{sw_name}-p4runtime-requests.txt" p4runtime_lib.simple_controller.program_switch( - addr='127.0.0.1:%d' % grpc_port, + addr=f"127.0.0.1:{grpc_port}", device_id=device_id, sw_conf_file=sw_conf_file, workdir=os.getcwd(), proto_dump_fpath=outfile, - runtime_json=runtime_json + runtime_json=runtime_json, ) def program_switch_cli(self, sw_name, sw_dict): - """ This method will start up the CLI and use the contents of the - command files as input. + """This method will start up the CLI and use the contents of the + command files as input. """ - cli = 'simple_switch_CLI' + cli = "simple_switch_CLI" # get the port for this particular switch's thrift server sw_obj = self.net.get(sw_name) thrift_port = sw_obj.thrift_port - cli_input_commands = sw_dict['cli_input'] - self.logger('Configuring switch %s with file %s' % (sw_name, cli_input_commands)) - with open(cli_input_commands, 'r') as fin: - cli_outfile = '%s/%s_cli_output.log'%(self.log_dir, sw_name) - with open(cli_outfile, 'w') as fout: - subprocess.Popen([cli, '--thrift-port', str(thrift_port)], - stdin=fin, stdout=fout) + cli_input_commands = sw_dict["cli_input"] + self.logger( + f"Configuring switch {sw_name} with file {cli_input_commands}" + ) + with open(cli_input_commands, "r") as fin: + cli_outfile = f"{self.log_dir}/{sw_name}_cli_output.log" + with open(cli_outfile, "w") as fout: + subprocess.Popen( + [cli, "--thrift-port", str(thrift_port)], stdin=fin, stdout=fout + ) def program_switches(self): - """ This method will program each switch using the BMv2 CLI and/or - P4Runtime, depending if any command or runtime JSON files were - provided for the switches. + """This method will program each switch using the BMv2 CLI and/or + P4Runtime, depending if any command or runtime JSON files were + provided for the switches. """ for sw_name, sw_dict in self.switches.items(): - if 'cli_input' not in sw_dict and 'runtime_json' not in sw_dict: - self.logger('Warning: No control plane file provided for switch %s.' % sw_name) + if "cli_input" not in sw_dict and "runtime_json" not in sw_dict: + self.logger( + f"Warning: No control plane file provided for switch {sw_name}." + ) continue - if 'cli_input' in sw_dict: + if "cli_input" in sw_dict: self.program_switch_cli(sw_name, sw_dict) - if 'runtime_json' in sw_dict: + if "runtime_json" in sw_dict: self.program_switch_p4runtime(sw_name, sw_dict) def program_hosts(self): - """ Execute any commands provided in the topology.json file on each Mininet host - """ + """Execute any commands provided in the topology.json file on + each Mininet host""" for host_name, host_info in list(self.hosts.items()): h = self.net.get(host_name) if "commands" in host_info: for cmd in host_info["commands"]: h.cmd(cmd) - def do_net_cli(self): - """ Starts up the mininet CLI and prints some helpful output. + """Starts up the mininet CLI and prints some helpful output. - Assumes: - - A mininet instance is stored as self.net and self.net.start() has - been called. + Assumes: + - A mininet instance is stored as self.net and self.net.start() has + been called. """ for s in self.net.switches: s.describe() @@ -335,57 +369,90 @@ def do_net_cli(self): self.logger("Starting mininet CLI") # Generate a message that will be printed by the Mininet CLI to make # interacting with the simple switch a little easier. - print('') - print('======================================================================') - print('Welcome to the BMV2 Mininet CLI!') - print('======================================================================') - print('Your P4 program is installed into the BMV2 software switch') - print('and your initial runtime configuration is loaded. You can interact') - print('with the network using the mininet CLI below.') - print('') + print() + print("======================================================================") + print("Welcome to the BMV2 Mininet CLI!") + print("======================================================================") + print("Your P4 program is installed into the BMV2 software switch") + print("and your initial runtime configuration is loaded. You can") + print("interact with the network using the mininet CLI below.") + print() if self.switch_json: - print('To inspect or change the switch configuration, connect to') - print('its CLI from your host operating system using this command:') - print(' simple_switch_CLI --thrift-port ') - print('') - print('To view a switch log, run this command from your host OS:') - print(' tail -f %s/.log' % self.log_dir) - print('') - print('To view the switch output pcap, check the pcap files in %s:' % self.pcap_dir) - print(' for example run: sudo tcpdump -xxx -r s1-eth1.pcap') - print('') - if 'grpc' in self.bmv2_exe: - print('To view the P4Runtime requests sent to the switch, check the') - print('corresponding txt file in %s:' % self.log_dir) - print(' for example run: cat %s/s1-p4runtime-requests.txt' % self.log_dir) - print('') + print("To inspect or change the switch configuration, connect to") + print("its CLI from your host operating system using this") + print("command:") + print(" simple_switch_CLI --thrift-port ") + print() + print("To view a switch log, run this command from your host OS:") + print(f" tail -f {self.log_dir}/.log") + print() + print( + f"To view the switch output pcap, check the pcap files in {self.pcap_dir}:" + ) + print(" for example run: sudo tcpdump -xxx -r s1-eth1.pcap") + print() + if "grpc" in self.bmv2_exe: + print("To view the P4Runtime requests sent to the switch, check") + print(f"the corresponding txt file in {self.log_dir}:") + print( + f" for example run: cat {self.log_dir}/s1-p4runtime-requests.txt" + ) + print() CLI(self.net) def get_args(): cwd = os.getcwd() - default_logs = os.path.join(cwd, 'logs') - default_pcaps = os.path.join(cwd, 'pcaps') + default_logs = os.path.join(cwd, "logs") + default_pcaps = os.path.join(cwd, "pcaps") parser = argparse.ArgumentParser() - parser.add_argument('-q', '--quiet', help='Suppress log messages.', - action='store_true', required=False, default=False) - parser.add_argument('-t', '--topo', help='Path to topology json', - type=str, required=False, default='./topology.json') - parser.add_argument('-l', '--log-dir', type=str, required=False, default=default_logs) - parser.add_argument('-p', '--pcap-dir', type=str, required=False, default=default_pcaps) - parser.add_argument('-j', '--switch_json', type=str, required=False) - parser.add_argument('-b', '--behavioral-exe', help='Path to behavioral executable', - type=str, required=False, default='simple_switch') + parser.add_argument( + "-q", + "--quiet", + help="Suppress log messages.", + action="store_true", + required=False, + default=False, + ) + parser.add_argument( + "-t", + "--topo", + help="Path to topology json", + type=str, + required=False, + default="./topology.json", + ) + parser.add_argument( + "-l", "--log-dir", type=str, required=False, default=default_logs + ) + parser.add_argument( + "-p", "--pcap-dir", type=str, required=False, default=default_pcaps + ) + parser.add_argument("-j", "--switch_json", type=str, required=False) + parser.add_argument( + "-b", + "--behavioral-exe", + help="Path to behavioral executable", + type=str, + required=False, + default="simple_switch", + ) return parser.parse_args() -if __name__ == '__main__': +if __name__ == "__main__": # from mininet.log import setLogLevel # setLogLevel("info") args = get_args() - exercise = ExerciseRunner(args.topo, args.log_dir, args.pcap_dir, - args.switch_json, args.behavioral_exe, args.quiet) + exercise = ExerciseRunner( + args.topo, + args.log_dir, + args.pcap_dir, + args.switch_json, + args.behavioral_exe, + args.quiet, + ) exercise.run_exercise() diff --git a/vm-ubuntu-20.04/py3localpath.py b/vm-ubuntu-20.04/py3localpath.py index 0955b9ea..96443930 100755 --- a/vm-ubuntu-20.04/py3localpath.py +++ b/vm-ubuntu-20.04/py3localpath.py @@ -7,19 +7,25 @@ import re import sys -l1=[x for x in sys.path if re.match(r'/usr/local/lib/python3.[0-9]+/dist-packages', x)] +l1 = [ + x for x in sys.path if re.match(r"/usr/local/lib/python3.[0-9]+/dist-packages", x) +] if len(l1) == 1: py3distdir = l1[0] - m = re.match(r'(/usr/local/lib/python3.[0-9]+)/dist-packages', l1[0]) + m = re.match(r"(/usr/local/lib/python3.[0-9]+)/dist-packages", l1[0]) if m: print(m.group(1)) else: - print("Inconceivable! Somehow the second pattern did not match but the first did.") + print( + "Inconceivable! Somehow the second pattern did not match" + " but the first did." + ) sys.exit(1) else: - print("Found %d matching entries in Python3 sys.path instead of 1: %s" - % (len(l1), l1)) + print( + f"Found {len(l1)} matching entries in Python3 sys.path instead of 1: {l1}" + ) sys.exit(1) sys.exit(0)