* Updated the utils/run_exercise.py to allow exercises to customize host configuration from the topology.json file. Now hosts and `ping` each other in the basic exercise. Other Linux utilities should work as well (e.g. iperf). ``` mininet> h1 ping h2 PING 10.0.2.2 (10.0.2.2) 56(84) bytes of data. 64 bytes from 10.0.2.2: icmp_seq=1 ttl=62 time=3.11 ms 64 bytes from 10.0.2.2: icmp_seq=2 ttl=62 time=2.34 ms 64 bytes from 10.0.2.2: icmp_seq=3 ttl=62 time=2.15 ms ^C --- 10.0.2.2 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2003ms rtt min/avg/max/mdev = 2.153/2.540/3.118/0.416 ms mininet> pingall *** Ping: testing ping reachability h1 -> h2 h3 h2 -> h1 h3 h3 -> h1 h2 *** Results: 0% dropped (6/6 received) ``` Only updated basic exercise, still need to update other exercises. Also, updated the root-bootstrap.sh because I was running into issues with latest version of vagrant. * Accidentially added the solution to the basic exercise in the previous commit. Undoing that here ... * Updated the topology.json file and table entries for the basic_tunnel exercise. * Updated P4Runtime exercise with new topology and table entries. * Fixed MAC addresses in P4Runtime exercise. It is working now. * Fixed MAC addresses in P4Runtime exercise starter code * Updated ECN exercise to use new topology.json file. Updated the table entries / MAC addresses as well. * Updated the topology.json file and table entries for the MRI exercise. * Updated source_routing exercise with new topology file and verified correct functionality. * Updated load_balance exercise with new topology. * Moved basic exercise triangle topology into a separate folder * Added new topology for the basic exercise: a single pod of a fat-tree. * Updated Makefiles and run_exercise.py to allow exercises to configure each switch with a different P4 program. This is mainly for the firewall exercise. * Updated Makefiles of project to work with new utils/Makefile * Updated load_balance and p4runtime exercise Makefiles * Initial commit of the firewall exercise, which is a simple stateful firewall that uses a bloom filter. Need to update README files * Initial commit of the path_monitor exercise. It is working but still need to update the README and figure out what we want the tutorial attendees to implement. * Updated README file in firewall exercise. Also removed the bits from the starter code that we want the tutorial attendees to implement * Renamed path_monitor exercise to link_monitor * Updated the README in the link_monitor exercise and removed the bits from the starter code that we want the tutorial attendees to implement. * Updated README for the firewall exercise * Adding pod-topo.png image to basic exercise * Added firewall-topo.png image to firewall exercise * Added link-monitor-topo.png to link_monitor exercise * Updated README files to point to topology images * Updated top-level README to point to new exercises. * Fixed link for VM dependencies script in README * Updated bmv2/pi/p4c commits * Updated README files for exercises to fix some typos and added a note about the V1Model architecture. * Added a note about food for thought in the link_monitor README * Updated the firewall.p4 program to use two register arrays rather than a single one. This is to make the design more portable to high line rate devices which can only support a single access to each register array. * Minor fix to firewall exercise to get rid of compiler warning. * Updated comment in firewall exercise. * Minor (typo) fixes in the firewall ReadMe * More info in firewall exercise ReadMe step 2 * Updated firewall.p4 to reuse direction variable * More testing steps, small fixes in firewall exercise Readme * Added food for thought to firewall Readme * Cosmetic fixes to firewall ReadMe * Made a few updates to the basic exercise README and added more details to the link_monitor exercise README. Also added a command to install grip when provisioning the VM. This could be useful for rendering the markdown README files offline. * Updated top level README so it can be merged into the master branch. * Moved cmd to install grip from root-bootstrap to user-bootstrap
208 lines
8.5 KiB
Python
Executable File
208 lines
8.5 KiB
Python
Executable File
#!/usr/bin/env python2
|
|
import argparse
|
|
import grpc
|
|
import os
|
|
import sys
|
|
from time import sleep
|
|
|
|
# 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/'))
|
|
import p4runtime_lib.bmv2
|
|
from p4runtime_lib.error_utils import printGrpcError
|
|
from p4runtime_lib.switch import ShutdownAllSwitchConnections
|
|
import p4runtime_lib.helper
|
|
|
|
SWITCH_TO_HOST_PORT = 1
|
|
SWITCH_TO_SWITCH_PORT = 2
|
|
|
|
|
|
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
|
|
encapsulates traffic into a tunnel with the specified ID
|
|
2) A transit rule on the ingress switch that forwards traffic based on
|
|
the specified ID
|
|
3) An tunnel egress rule on the egress switch that decapsulates traffic
|
|
with the specified ID and sends it to the host
|
|
|
|
:param p4info_helper: the P4Info helper
|
|
:param ingress_sw: the ingress switch connection
|
|
:param egress_sw: the egress switch connection
|
|
:param tunnel_id: the specified tunnel ID
|
|
:param dst_eth_addr: the destination IP to match in the ingress rule
|
|
:param dst_ip_addr: the destination Ethernet address to write in the
|
|
egress rule
|
|
"""
|
|
# 1) Tunnel Ingress Rule
|
|
table_entry = p4info_helper.buildTableEntry(
|
|
table_name="MyIngress.ipv4_lpm",
|
|
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
|
|
|
|
# 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.
|
|
#
|
|
# 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.
|
|
|
|
# TODO build the transit rule
|
|
# TODO install the transit rule on the ingress switch
|
|
print "TODO Install transit tunnel rule"
|
|
|
|
# 3) Tunnel Egress Rule
|
|
# For our simple topology, the host will always be located on the
|
|
# SWITCH_TO_HOST_PORT (port 1).
|
|
# In general, you will need to keep track of which port the host is
|
|
# connected to.
|
|
table_entry = p4info_helper.buildTableEntry(
|
|
table_name="MyIngress.myTunnel_exact",
|
|
match_fields={
|
|
"hdr.myTunnel.dst_id": tunnel_id
|
|
},
|
|
action_name="MyIngress.myTunnel_egress",
|
|
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
|
|
|
|
|
|
def readTableRules(p4info_helper, sw):
|
|
"""
|
|
Reads the table entries from all tables on the switch.
|
|
|
|
:param p4info_helper: the P4Info helper
|
|
:param sw: the switch connection
|
|
"""
|
|
print '\n----- Reading tables rules for %s -----' % 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 '-----'
|
|
|
|
|
|
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.
|
|
|
|
:param p4info_helper: the P4Info helper
|
|
:param sw: the switch connection
|
|
:param counter_name: the name of the counter from the P4 program
|
|
:param index: the counter index (in our case, the tunnel ID)
|
|
"""
|
|
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
|
|
)
|
|
|
|
def main(p4info_file_path, bmv2_file_path):
|
|
# Instantiate a P4Runtime helper from the p4info file
|
|
p4info_helper = p4runtime_lib.helper.P4InfoHelper(p4info_file_path)
|
|
|
|
try:
|
|
# Create a switch connection object for s1 and s2;
|
|
# 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',
|
|
device_id=0,
|
|
proto_dump_file='logs/s1-p4runtime-requests.txt')
|
|
s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection(
|
|
name='s2',
|
|
address='127.0.0.1:50052',
|
|
device_id=1,
|
|
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)
|
|
s1.MasterArbitrationUpdate()
|
|
s2.MasterArbitrationUpdate()
|
|
|
|
# Install the P4 program on the switches
|
|
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)
|
|
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")
|
|
|
|
# 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
|
|
# readTableRules(p4info_helper, s1)
|
|
# readTableRules(p4info_helper, s2)
|
|
|
|
# Print the tunnel counters every 2 seconds
|
|
while True:
|
|
sleep(2)
|
|
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)
|
|
printCounter(p4info_helper, s1, "MyIngress.egressTunnelCounter", 200)
|
|
|
|
except KeyboardInterrupt:
|
|
print " Shutting down."
|
|
except grpc.RpcError as e:
|
|
printGrpcError(e)
|
|
|
|
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.txt')
|
|
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
|
|
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
|
|
parser.exit(1)
|
|
main(args.p4info, args.bmv2_json)
|