VM Updates (#83)
- Minor fixes to p4runtime exercise and README - Adding p4runtime/solution - Adding p4runtime/topology.json - Updating .gitignore to include solution directory and topology.json - Fixing root-bootstrap to exit on errors - Updating VM name in Vagrantfile - Setting up VM to automatically log 'p4' user in on startup
This commit is contained in:
parent
9af6750bec
commit
b2161b8a27
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,11 +8,13 @@
|
|||||||
# Compiled JSON
|
# Compiled JSON
|
||||||
*.json
|
*.json
|
||||||
!*p4app.json
|
!*p4app.json
|
||||||
|
!topology.json
|
||||||
|
|
||||||
*.pcap
|
*.pcap
|
||||||
|
|
||||||
# Extracted solutions
|
# Extracted solutions
|
||||||
solution*/
|
solution*/
|
||||||
|
!solution/
|
||||||
|
|
||||||
# Build folders
|
# Build folders
|
||||||
build*/
|
build*/
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
In this exercise, we will be using P4 Runtime to send flow entries to the
|
In this exercise, we will be using P4 Runtime to send flow entries to the
|
||||||
switch, instead of using the switch's CLI. We will be using the same P4
|
switch instead of using the switch's CLI. We will be building on the same P4
|
||||||
program that you used in the previous in the basic_tunnel exercise. The
|
program that you used in the [basic_tunnel](../basic_tunnel) exercise. The
|
||||||
P4 program has be renamed to `advanced_tunnel.py` and has been augmented
|
P4 program has be renamed to `advanced_tunnel.py` and has been augmented
|
||||||
with a counter, `tunnelCount`, and two new actions, `myTunnel_ingress`
|
with two counters (`ingressTunnelCounter`, `egressTunnelCounter`) and
|
||||||
and `myTunnel_egress`.
|
two new actions (`myTunnel_ingress`, `myTunnel_egress`).
|
||||||
|
|
||||||
You will use the starter program, `mycontroller.py`, and a few helper
|
You will use the starter program, `mycontroller.py`, and a few helper
|
||||||
libraries in the `p4runtime_lib` directory to create the table entries
|
libraries in the `p4runtime_lib` directory to create the table entries
|
||||||
@ -19,12 +19,12 @@ necessary to tunnel traffic between host 1 and 2.
|
|||||||
|
|
||||||
## Step 1: Run the (incomplete) starter code
|
## Step 1: Run the (incomplete) starter code
|
||||||
|
|
||||||
The starter code for this assignment is in a file called `mycontroller.py`
|
The starter code for this assignment is in a file called `mycontroller.py`,
|
||||||
and it will install only some of the rules that you need tunnel traffic between
|
and it will install only some of the rules that you need tunnel traffic between
|
||||||
two hosts.
|
two hosts.
|
||||||
|
|
||||||
Let's first compile the new P4 program, start the network, use `mycontroller.py`
|
Let's first compile the new P4 program, start the network, use `mycontroller.py`
|
||||||
to install a few rules, and look at the tunnel ingress counter to see that things
|
to install a few rules, and look at the `ingressTunnelCounter` to see that things
|
||||||
are working as expected.
|
are working as expected.
|
||||||
|
|
||||||
1. In your shell, run:
|
1. In your shell, run:
|
||||||
@ -32,18 +32,17 @@ are working as expected.
|
|||||||
make
|
make
|
||||||
```
|
```
|
||||||
This will:
|
This will:
|
||||||
* compile `advanced_tunnel.p4`, and
|
* compile `advanced_tunnel.p4`,
|
||||||
* start a Mininet instance with three switches (`s1`, `s2`, `s3`)
|
* start a Mininet instance with three switches (`s1`, `s2`, `s3`)
|
||||||
configured in a triangle, each connected to one host (`h1`, `h2`,
|
configured in a triangle, each connected to one host (`h1`, `h2`, `h3`), and
|
||||||
and `h3`).
|
* assign IPs of `10.0.1.1`, `10.0.2.2`, `10.0.3.3` to the respective hosts.
|
||||||
* The hosts are assigned IPs of `10.0.1.1`, `10.0.2.2`, etc.
|
|
||||||
|
|
||||||
2. You should now see a Mininet command prompt. Start a ping between h1 and h2:
|
2. You should now see a Mininet command prompt. Start a ping between h1 and h2:
|
||||||
```bash
|
```bash
|
||||||
mininet> h1 ping h2
|
mininet> h1 ping h2
|
||||||
```
|
```
|
||||||
Because there are no rules on the switches, you should **not** receive any
|
Because there are no rules on the switches, you should **not** receive any
|
||||||
replies yet.
|
replies yet. You should leave the ping running in this shell.
|
||||||
|
|
||||||
3. Open another shell and run the starter code:
|
3. Open another shell and run the starter code:
|
||||||
```bash
|
```bash
|
||||||
@ -65,6 +64,25 @@ Each switch is currently mapping traffic into tunnels based on the destination I
|
|||||||
address. Your job is to write the rules that forward the traffic between the switches
|
address. Your job is to write the rules that forward the traffic between the switches
|
||||||
based on the tunnel ID.
|
based on the tunnel ID.
|
||||||
|
|
||||||
|
### Potential Issues
|
||||||
|
|
||||||
|
If you see the following error message when running `mycontroller.py`, then
|
||||||
|
the gRPC server is not running on one or more switches.
|
||||||
|
|
||||||
|
```
|
||||||
|
p4@p4:~/tutorials/P4D2_2017_Fall/exercises/p4runtime$ ./mycontroller.py
|
||||||
|
...
|
||||||
|
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with (StatusCode.UNAVAILABLE, Connect Failed)>
|
||||||
|
```
|
||||||
|
|
||||||
|
You can check to see which of gRPC ports are listening on the machine by running:
|
||||||
|
```bash
|
||||||
|
sudo netstat -lpnt
|
||||||
|
```
|
||||||
|
|
||||||
|
The easiest solution is to enter `Ctrl-D` or `exit` in the `mininet>` prompt,
|
||||||
|
and re-run `make`.
|
||||||
|
|
||||||
### A note about the control plane
|
### A note about the control plane
|
||||||
|
|
||||||
A P4 program defines a packet-processing pipeline, but the rules
|
A P4 program defines a packet-processing pipeline, but the rules
|
||||||
@ -96,6 +114,26 @@ that will match on tunnel ID and forward packets to the next hop.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
In this exercise, you will be interacting with some of the classes and methods in
|
||||||
|
the `p4runtime_lib` directory. Here is a summary of each of the files in the directory:
|
||||||
|
- `helper.py`
|
||||||
|
- Contains the `P4InfoHelper` class which is used to parse the `p4info` files.
|
||||||
|
- Provides translation methods from entity name to and from ID number.
|
||||||
|
- Builds P4 program-dependendent sections of P4 Runtime table entries.
|
||||||
|
- `switch.py`
|
||||||
|
- Contains the `SwitchConnection` class which grabs the gRPC client stub, and
|
||||||
|
establishes connections to the switches.
|
||||||
|
- Provides helper methods that construct the P4 Runtime protocol buffer messages
|
||||||
|
and makes the P4 Runtime gRPC service calls.
|
||||||
|
- `bmv2.py`
|
||||||
|
- Contains `Bmv2SwitchConnection` which extends `SwitchConnections` and provides
|
||||||
|
the BMv2-specific device payload to load the P4 program.
|
||||||
|
- `convert.py`
|
||||||
|
- Provides convenience methods to encode and decode from friendly strings and
|
||||||
|
numbers to the byte strings required for the protocol buffer messages.
|
||||||
|
- Used by `helper.py`
|
||||||
|
|
||||||
|
|
||||||
## Step 3: Run your solution
|
## Step 3: Run your solution
|
||||||
|
|
||||||
Follow the instructions from Step 1. If your Mininet network is still running,
|
Follow the instructions from Step 1. If your Mininet network is still running,
|
||||||
@ -122,6 +160,10 @@ need to change it for a more realistic network?
|
|||||||
- What is the TTL in the ICMP replies? Why is it the value that it is?
|
- What is the TTL in the ICMP replies? Why is it the value that it is?
|
||||||
Hint: The default TTL is 64 for packets sent by the hosts.
|
Hint: The default TTL is 64 for packets sent by the hosts.
|
||||||
|
|
||||||
|
If you are interested, you can find the protocol buffer and gRPC definitions here:
|
||||||
|
- [P4 Runtime](https://github.com/p4lang/PI/blob/master/proto/p4/p4runtime.proto)
|
||||||
|
- [P4 Info](https://github.com/p4lang/PI/blob/master/proto/p4/config/p4info.proto)
|
||||||
|
|
||||||
#### Cleaning up Mininet
|
#### Cleaning up Mininet
|
||||||
|
|
||||||
If the Mininet shell crashes, it may leave a Mininet instance
|
If the Mininet shell crashes, it may leave a Mininet instance
|
||||||
|
@ -9,62 +9,69 @@ import p4runtime_lib.helper
|
|||||||
SWITCH_TO_HOST_PORT = 1
|
SWITCH_TO_HOST_PORT = 1
|
||||||
SWITCH_TO_SWITCH_PORT = 2
|
SWITCH_TO_SWITCH_PORT = 2
|
||||||
|
|
||||||
def writeTunnelRules(p4info_helper, ingressSw, egressSw, tunnelId, dstEthAddr, dstIpAddr):
|
def writeTunnelRules(p4info_helper, ingress_sw, egress_sw, tunnel_id,
|
||||||
|
dst_eth_addr, dst_ip_addr):
|
||||||
'''
|
'''
|
||||||
Installs three rules:
|
Installs three rules:
|
||||||
1) An tunnel ingress rule on the ingress switch in the ipv4_lpm table that encapsulates traffic
|
1) An tunnel ingress rule on the ingress switch in the ipv4_lpm table that
|
||||||
into a tunnel with the specified ID
|
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
|
2) A transit rule on the ingress switch that forwards traffic based on
|
||||||
3) An tunnel egress rule on the egress switch that decapsulates traffic with the specified ID
|
the specified ID
|
||||||
and sends it to the host
|
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 p4info_helper: the P4Info helper
|
||||||
:param ingressSw: the ingress switch connection
|
:param ingress_sw: the ingress switch connection
|
||||||
:param egressSw: the egress switch connection
|
:param egress_sw: the egress switch connection
|
||||||
:param tunnelId: the specified tunnel ID
|
:param tunnel_id: the specified tunnel ID
|
||||||
:param dstEthAddr: the destination IP to match in the ingress rule
|
:param dst_eth_addr: the destination IP to match in the ingress rule
|
||||||
:param dstIpAddr: the destination Ethernet address to write in the egress rule
|
:param dst_ip_addr: the destination Ethernet address to write in the
|
||||||
|
egress rule
|
||||||
'''
|
'''
|
||||||
# 1) Tunnel Ingress Rule
|
# 1) Tunnel Ingress Rule
|
||||||
table_entry = p4info_helper.buildTableEntry(
|
table_entry = p4info_helper.buildTableEntry(
|
||||||
table_name="ipv4_lpm",
|
table_name="ipv4_lpm",
|
||||||
match_fields={
|
match_fields={
|
||||||
"hdr.ipv4.dstAddr": (dstIpAddr, 32)
|
"hdr.ipv4.dstAddr": (dst_ip_addr, 32)
|
||||||
},
|
},
|
||||||
action_name="myTunnel_ingress",
|
action_name="myTunnel_ingress",
|
||||||
action_params={
|
action_params={
|
||||||
"dst_id": tunnelId,
|
"dst_id": tunnel_id,
|
||||||
})
|
})
|
||||||
ingressSw.WriteTableEntry(table_entry)
|
ingress_sw.WriteTableEntry(table_entry)
|
||||||
print "Installed ingress tunnel rule on %s" % ingressSw.name
|
print "Installed ingress tunnel rule on %s" % ingress_sw.name
|
||||||
|
|
||||||
# 2) Tunnel Transit Rule
|
# 2) Tunnel Transit Rule
|
||||||
# TODO you will need to implement this rule
|
# The rule will need to be added to the myTunnel_exact table and match on
|
||||||
# The rule will need to be added to the myTunnel_exact table and match on the tunnel ID (hdr.myTunnel.dst_id).
|
# the tunnel ID (hdr.myTunnel.dst_id). For our simple topology, transit
|
||||||
# For our simple topology, transit traffic will need to be forwarded using the myTunnel_egress action to
|
# traffic will need to be forwarded on the using the myTunnel_forward action
|
||||||
# the SWITCH_TO_SWITCH_PORT (port 2).
|
# on the SWITCH_TO_SWITCH_PORT (port 2).
|
||||||
# We will only need on 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 one)
|
|
||||||
#
|
#
|
||||||
# If you are stuck, start by copying the tunnel ingress rule from above. Then, try to make the suggested
|
# We will only need on transit rule on the ingress switch because we are
|
||||||
# modifications.
|
# using a simple topology. In general, you'll need on transit rule for
|
||||||
|
# each switch in the path (except the last one).
|
||||||
|
|
||||||
|
# TODO build the transit rule
|
||||||
|
# TODO install the transit rule on the ingress switch
|
||||||
print "TODO Install transit tunnel rule"
|
print "TODO Install transit tunnel rule"
|
||||||
|
|
||||||
# 3) Tunnel Egress Rule
|
# 3) Tunnel Egress Rule
|
||||||
# For our simple topology, the host will always be located on the SWITCH_TO_HOST_PORT (port 1).
|
# For our simple topology, the host will always be located on the
|
||||||
# In general, you will need to keep track of which port the host is connected to.
|
# 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_entry = p4info_helper.buildTableEntry(
|
||||||
table_name="myTunnel_exact",
|
table_name="myTunnel_exact",
|
||||||
match_fields={
|
match_fields={
|
||||||
"hdr.myTunnel.dst_id": tunnelId
|
"hdr.myTunnel.dst_id": tunnel_id
|
||||||
},
|
},
|
||||||
action_name="myTunnel_egress",
|
action_name="myTunnel_egress",
|
||||||
action_params={
|
action_params={
|
||||||
"dstAddr": dstEthAddr,
|
"dstAddr": dst_eth_addr,
|
||||||
"port": SWITCH_TO_HOST_PORT
|
"port": SWITCH_TO_HOST_PORT
|
||||||
})
|
})
|
||||||
egressSw.WriteTableEntry(table_entry)
|
egress_sw.WriteTableEntry(table_entry)
|
||||||
print "Installed egress tunnel rule on %s" % egressSw.name
|
print "Installed egress tunnel rule on %s" % egress_sw.name
|
||||||
|
|
||||||
def readTableRules(p4info_helper, sw):
|
def readTableRules(p4info_helper, sw):
|
||||||
'''
|
'''
|
||||||
@ -77,14 +84,16 @@ def readTableRules(p4info_helper, sw):
|
|||||||
for response in sw.ReadTableEntries():
|
for response in sw.ReadTableEntries():
|
||||||
for entity in response.entities:
|
for entity in response.entities:
|
||||||
entry = entity.table_entry
|
entry = entity.table_entry
|
||||||
# TODO For extra credit, you can use the p4info_helper to translate the IDs the entry to names
|
# TODO For extra credit, you can use the p4info_helper to translate
|
||||||
|
# the IDs the entry to names
|
||||||
print entry
|
print entry
|
||||||
print '-----'
|
print '-----'
|
||||||
|
|
||||||
def printCounter(p4info_helper, sw, counter_name, index):
|
def printCounter(p4info_helper, sw, counter_name, index):
|
||||||
'''
|
'''
|
||||||
Reads the specified counter at the specified index from the switch. In our program, the index
|
Reads the specified counter at the specified index from the switch. In our
|
||||||
is the tunnel ID. If the index is 0, it will return all values from the counter.
|
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 p4info_helper: the P4Info helper
|
||||||
:param sw: the switch connection
|
:param sw: the switch connection
|
||||||
@ -104,23 +113,26 @@ def main(p4info_file_path, bmv2_file_path):
|
|||||||
# Instantiate a P4 Runtime helper from the p4info file
|
# Instantiate a P4 Runtime helper from the p4info file
|
||||||
p4info_helper = p4runtime_lib.helper.P4InfoHelper(p4info_file_path)
|
p4info_helper = p4runtime_lib.helper.P4InfoHelper(p4info_file_path)
|
||||||
|
|
||||||
# Create a switch connection object for s1 and s2; this is backed by a P4 Runtime gRPC connection
|
# Create a switch connection object for s1 and s2;
|
||||||
|
# this is backed by a P4 Runtime gRPC connection
|
||||||
s1 = p4runtime_lib.bmv2.Bmv2SwitchConnection('s1', address='127.0.0.1:50051')
|
s1 = p4runtime_lib.bmv2.Bmv2SwitchConnection('s1', address='127.0.0.1:50051')
|
||||||
s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection('s2', address='127.0.0.1:50052')
|
s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection('s2', address='127.0.0.1:50052')
|
||||||
|
|
||||||
# Install the P4 program on the switches
|
# 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 %s" % s1.name
|
print "Installed P4 Program using SetForwardingPipelineConfig on %s" % s1.name
|
||||||
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 %s" % s2.name
|
print "Installed P4 Program using SetForwardingPipelineConfig on %s" % s2.name
|
||||||
|
|
||||||
# Write the rules that tunnel traffic from h1 to h2
|
# Write the rules that tunnel traffic from h1 to h2
|
||||||
writeTunnelRules(p4info_helper, ingressSw=s1, egressSw=s2, tunnelId=100,
|
writeTunnelRules(p4info_helper, ingress_sw=s1, egress_sw=s2, tunnel_id=100,
|
||||||
dstEthAddr="00:00:00:00:02:02", dstIpAddr="10.0.2.2")
|
dst_eth_addr="00:00:00:00:02:02", dst_ip_addr="10.0.2.2")
|
||||||
|
|
||||||
# Write the rules that tunnel traffic from h2 to h1
|
# Write the rules that tunnel traffic from h2 to h1
|
||||||
writeTunnelRules(p4info_helper, ingressSw=s2, egressSw=s1, tunnelId=200,
|
writeTunnelRules(p4info_helper, ingress_sw=s2, egress_sw=s1, tunnel_id=200,
|
||||||
dstEthAddr="00:00:00:00:01:01", dstIpAddr="10.0.1.1")
|
dst_eth_addr="00:00:00:00:01:01", 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, s1)
|
||||||
@ -142,9 +154,11 @@ def main(p4info_file_path, bmv2_file_path):
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser(description='P4Runtime Controller')
|
parser = argparse.ArgumentParser(description='P4Runtime Controller')
|
||||||
parser.add_argument('--p4info', help='p4info proto in text format from p4c',
|
parser.add_argument('--p4info', help='p4info proto in text format from p4c',
|
||||||
type=str, action="store", required=False, default='./build/advanced_tunnel.p4info')
|
type=str, action="store", required=False,
|
||||||
|
default='./build/advanced_tunnel.p4info')
|
||||||
parser.add_argument('--bmv2-json', help='BMv2 JSON file from p4c',
|
parser.add_argument('--bmv2-json', help='BMv2 JSON file from p4c',
|
||||||
type=str, action="store", required=False, default='./build/advanced_tunnel.json')
|
type=str, action="store", required=False,
|
||||||
|
default='./build/advanced_tunnel.json')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if not os.path.exists(args.p4info):
|
if not os.path.exists(args.p4info):
|
||||||
|
195
P4D2_2017_Fall/exercises/p4runtime/solution/mycontroller.py
Executable file
195
P4D2_2017_Fall/exercises/p4runtime/solution/mycontroller.py
Executable file
@ -0,0 +1,195 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
# NOTE: Appending to the PYTHON_PATH is only required in the `solution` directory.
|
||||||
|
# It is not required for mycontroller.py in the top-level directory.
|
||||||
|
import sys
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
|
||||||
|
import p4runtime_lib.bmv2
|
||||||
|
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="ipv4_lpm",
|
||||||
|
match_fields={
|
||||||
|
"hdr.ipv4.dstAddr": (dst_ip_addr, 32)
|
||||||
|
},
|
||||||
|
action_name="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). For our simple topology, transit
|
||||||
|
# traffic will need to be forwarded on the using the myTunnel_forward action
|
||||||
|
# on the SWITCH_TO_SWITCH_PORT (port 2).
|
||||||
|
#
|
||||||
|
# We will only need on 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 one).
|
||||||
|
table_entry = p4info_helper.buildTableEntry(
|
||||||
|
table_name="myTunnel_exact",
|
||||||
|
match_fields={
|
||||||
|
"hdr.myTunnel.dst_id": tunnel_id
|
||||||
|
},
|
||||||
|
action_name="myTunnel_forward",
|
||||||
|
action_params={
|
||||||
|
"port": SWITCH_TO_SWITCH_PORT
|
||||||
|
})
|
||||||
|
ingress_sw.WriteTableEntry(table_entry)
|
||||||
|
print "Installed transit tunnel rule on %s" % ingress_sw.name
|
||||||
|
|
||||||
|
# 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="myTunnel_exact",
|
||||||
|
match_fields={
|
||||||
|
"hdr.myTunnel.dst_id": tunnel_id
|
||||||
|
},
|
||||||
|
action_name="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 the entry to names
|
||||||
|
table_name = p4info_helper.get_tables_name(entry.table_id)
|
||||||
|
print '%s: ' % table_name,
|
||||||
|
for m in entry.match:
|
||||||
|
print p4info_helper.get_match_field_name(table_name, m.field_id),
|
||||||
|
print '%r' % (p4info_helper.get_match_field_value(m),),
|
||||||
|
action = entry.action.action
|
||||||
|
action_name = p4info_helper.get_actions_name(action.action_id)
|
||||||
|
print '->', action_name,
|
||||||
|
for p in action.params:
|
||||||
|
print p4info_helper.get_action_param_name(action_name, p.param_id),
|
||||||
|
print '%r' % p.value,
|
||||||
|
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 P4 Runtime helper from the p4info file
|
||||||
|
p4info_helper = p4runtime_lib.helper.P4InfoHelper(p4info_file_path)
|
||||||
|
|
||||||
|
# Create a switch connection object for s1 and s2;
|
||||||
|
# this is backed by a P4 Runtime gRPC connection
|
||||||
|
s1 = p4runtime_lib.bmv2.Bmv2SwitchConnection('s1', address='127.0.0.1:50051')
|
||||||
|
s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection('s2', address='127.0.0.1:50052')
|
||||||
|
|
||||||
|
# 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 %s" % s1.name
|
||||||
|
s2.SetForwardingPipelineConfig(p4info=p4info_helper.p4info,
|
||||||
|
bmv2_json_file_path=bmv2_file_path)
|
||||||
|
print "Installed P4 Program using SetForwardingPipelineConfig on %s" % s2.name
|
||||||
|
|
||||||
|
# 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="00:00:00:00:02:02", 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="00:00:00:00:01:01", 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
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
sleep(2)
|
||||||
|
print '\n----- Reading tunnel counters -----'
|
||||||
|
printCounter(p4info_helper, s1, "ingressTunnelCounter", 100)
|
||||||
|
printCounter(p4info_helper, s2, "egressTunnelCounter", 100)
|
||||||
|
printCounter(p4info_helper, s2, "ingressTunnelCounter", 200)
|
||||||
|
printCounter(p4info_helper, s1, "egressTunnelCounter", 200)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print " Shutting down."
|
||||||
|
|
||||||
|
|
||||||
|
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.p4info')
|
||||||
|
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)
|
16
P4D2_2017_Fall/exercises/p4runtime/topology.json
Executable file
16
P4D2_2017_Fall/exercises/p4runtime/topology.json
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"hosts": [
|
||||||
|
"h1",
|
||||||
|
"h2",
|
||||||
|
"h3"
|
||||||
|
],
|
||||||
|
"switches": {
|
||||||
|
"s1": {},
|
||||||
|
"s2": {},
|
||||||
|
"s3": {}
|
||||||
|
},
|
||||||
|
"links": [
|
||||||
|
["h1", "s1"], ["s1", "s2"], ["s1", "s3"],
|
||||||
|
["s3", "s2"], ["s2", "h2"], ["s3", "h3"]
|
||||||
|
]
|
||||||
|
}
|
3
P4D2_2017_Fall/vm/Vagrantfile
vendored
3
P4D2_2017_Fall/vm/Vagrantfile
vendored
@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
Vagrant.configure(2) do |config|
|
Vagrant.configure(2) do |config|
|
||||||
config.vm.box = "bento/ubuntu-16.04"
|
config.vm.box = "bento/ubuntu-16.04"
|
||||||
|
config.vm.define "p4-tutorial" do |tutorial|
|
||||||
|
end
|
||||||
config.vm.provider "virtualbox" do |vb|
|
config.vm.provider "virtualbox" do |vb|
|
||||||
|
vb.name = "P4 Tutorial" + Time.now.strftime(" %Y-%m-%d")
|
||||||
vb.gui = true
|
vb.gui = true
|
||||||
vb.memory = 2048
|
vb.memory = 2048
|
||||||
vb.cpus = 2
|
vb.cpus = 2
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
set -x
|
# Print commands and exit on errors
|
||||||
|
set -xe
|
||||||
|
|
||||||
sudo add-apt-repository ppa:webupd8team/sublime-text-3
|
sudo add-apt-repository ppa:webupd8team/sublime-text-3
|
||||||
sudo add-apt-repository ppa:webupd8team/atom
|
sudo add-apt-repository ppa:webupd8team/atom
|
||||||
@ -79,3 +80,11 @@ sed -i s@#background=@background=/usr/share/lubuntu/wallpapers/1604-lubuntu-defa
|
|||||||
|
|
||||||
# Disable screensaver
|
# Disable screensaver
|
||||||
apt-get -y remove light-locker
|
apt-get -y remove light-locker
|
||||||
|
|
||||||
|
# Automatically log into the P4 user
|
||||||
|
cat << EOF | tee -a /etc/lightdm/lightdm.conf.d/10-lightdm.conf
|
||||||
|
[SeatDefaults]
|
||||||
|
autologin-user=p4
|
||||||
|
autologin-user-timeout=0
|
||||||
|
user-session=Lubuntu
|
||||||
|
EOF
|
||||||
|
Loading…
x
Reference in New Issue
Block a user