From 3d4a2f5748d610b2c0fb5fc7429c9157f2b2fdce Mon Sep 17 00:00:00 2001 From: Brian O'Connor Date: Tue, 7 Nov 2017 07:54:59 -0800 Subject: [PATCH] Adding starter code and solution for p4runtime exercise (#81) Summary of changes: - Adding the p4runtime starter code and solution. - Adding NO_P4, BMV2_SWITCH_EXE and P4C_ARGS to utils/Makefile - Updated p4runtime/Makefile to use variables - Adding conversion functions for match and action param values - Separating P4Info and P4Runtime libraries - Updating global README and adding p4runtime/README.md - Disabling screen saver on VM GUI - Adding desktop icons for Terminal, Wireshare and Sublime Text - Updating topo.pdf -> png for Markdown viewing in basic_tunnel and p4runtime READMEs --- .gitignore | 3 + P4D2_2017_Fall/exercises/README.md | 41 ++-- .../exercises/basic_tunnel/README.md | 2 +- .../exercises/basic_tunnel/topo.png | Bin 0 -> 94185 bytes P4D2_2017_Fall/exercises/p4runtime/Makefile | 13 +- P4D2_2017_Fall/exercises/p4runtime/README.md | 146 ++++++++++++++ .../exercises/p4runtime/advanced_tunnel.p4 | 34 ++-- .../exercises/p4runtime/mycontroller.py | 159 +++++++++++++++ .../exercises/p4runtime/p4info/p4browser.py | 135 ------------- .../{switches => p4runtime_lib}/__init__.py | 0 .../{switches => p4runtime_lib}/bmv2.py | 1 - .../p4runtime/p4runtime_lib/convert.py | 119 ++++++++++++ .../p4runtime/p4runtime_lib/helper.py | 183 ++++++++++++++++++ .../p4runtime/p4runtime_lib/switch.py | 88 +++++++++ .../exercises/p4runtime/switches/switch.py | 130 ------------- P4D2_2017_Fall/utils/Makefile | 16 +- P4D2_2017_Fall/utils/run_exercise.py | 2 +- P4D2_2017_Fall/vm/root-bootstrap.sh | 3 + P4D2_2017_Fall/vm/user-bootstrap.sh | 36 ++++ 19 files changed, 789 insertions(+), 322 deletions(-) create mode 100644 P4D2_2017_Fall/exercises/basic_tunnel/topo.png create mode 100644 P4D2_2017_Fall/exercises/p4runtime/README.md mode change 100644 => 100755 P4D2_2017_Fall/exercises/p4runtime/advanced_tunnel.p4 create mode 100755 P4D2_2017_Fall/exercises/p4runtime/mycontroller.py delete mode 100644 P4D2_2017_Fall/exercises/p4runtime/p4info/p4browser.py rename P4D2_2017_Fall/exercises/p4runtime/{switches => p4runtime_lib}/__init__.py (100%) rename P4D2_2017_Fall/exercises/p4runtime/{switches => p4runtime_lib}/bmv2.py (95%) create mode 100644 P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/convert.py create mode 100644 P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/helper.py create mode 100644 P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/switch.py delete mode 100644 P4D2_2017_Fall/exercises/p4runtime/switches/switch.py diff --git a/.gitignore b/.gitignore index d80c61f..7e0adce 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,9 @@ solution*/ # Build folders build*/ +# Logs folders +logs/ + # Vagrant .vagrant/ diff --git a/P4D2_2017_Fall/exercises/README.md b/P4D2_2017_Fall/exercises/README.md index 2cf3de8..6e95c09 100644 --- a/P4D2_2017_Fall/exercises/README.md +++ b/P4D2_2017_Fall/exercises/README.md @@ -7,24 +7,27 @@ Welcome to the P4 Tutorial! We've prepared a set of exercises to help you get started with P4 programming, organized into four modules: -1. Introduction +1. Introduction and Language Basics * [Basic Forwarding](./basic) * [Basic Tunneling](./basic_tunnel) -2. Monitoring and Debugging +2. P4 Runtime and the Control Plane +* [P4 Runtime](./p4runtime) + +3. Monitoring and Debugging * [Explicit Congestion Notification](./ecn) * [Multi-Hop Route Inspection](./mri) -3. Advanced Data Structures +4. Advanced Data Structures * [Source Routing](./source_routing) * [Calculator](./calc) -4. Dynamic Behavior +5. Dynamic Behavior * [Load Balancing](./load_balance) ## Obtaining required software -If you are starting this tutorial at SIGCOMM 2017, then we've already +If you are starting this tutorial at the Fall 2017 P4 Developer Day, then we've already provided you with a virtual machine that has all of the required software installed. @@ -36,25 +39,11 @@ To build the virtual machine: - `cd vm` - `vagrant up` - Log in with username `p4` and password `p4` and issue the command `sudo shutdown -r now` -- When the machine reboots, you should have a graphical desktop machine with the required software pre-installed. +- When the machine reboots, you should have a graphical desktop machine with the required +software pre-installed. -To install dependences by hand: -- `git clone https://github.com/p4lang/behavioral-model.git` -- `git clone https://github.com/p4lang/p4c` -- `git clone https://github.com/p4lang/tutorials` -Then follow the instructions for how to build each package. Each of -these repositories come with dependencies, which can be installed -using the supplied instructions. The first repository -([behavioral-model](https://github.com/p4lang/behavioral-model)) -contains the P4 behavioral model. It is a C++ software switch that -will implement the functionality specified in your P4 program. The -second repository ([p4c](https://github.com/p4lang/p4c-bm)) is the -compiler for the behavioral model. It takes P4 program and produces a -JSON file which can be loaded by the behavioral model. The third -repository ([tutorial](https://github.com/p4lang/tutorial)) is the P4 -Tutorial itself. You will also need to install `mininet`. On Ubuntu, -it would look like this: - -``` -$ sudo apt-get install mininet -``` +To install dependencies by hand, please reference the [vm](../vm) installation scripts. +They contain the dependencies, versions, and installation procedure. +You can run them directly on an Ubuntu 16.04 machine: +- `sudo ./root-bootstrap.sh` +- `sudo ./user-bootstrap.sh` diff --git a/P4D2_2017_Fall/exercises/basic_tunnel/README.md b/P4D2_2017_Fall/exercises/basic_tunnel/README.md index e4250e4..43b7281 100644 --- a/P4D2_2017_Fall/exercises/basic_tunnel/README.md +++ b/P4D2_2017_Fall/exercises/basic_tunnel/README.md @@ -98,7 +98,7 @@ Your job will be to do the following: 6. **TODO:** Update the deparser to emit the `ethernet`, then `myTunnel`, then `ipv4` headers. Remember that the deparser will only emit a header if it is valid. A header's implicit validity bit is set by the parser upon extraction. So there is no need to check header validity here. 7. **TODO:** Add static rules for your newly defined table so that the switches will forward correctly for each possible value of `dst_id`. See the diagram below for the topology's port configuration as well as how we will assign IDs to hosts. For this step you will need to add your forwarding rules to the `sX-commands.txt` files. -![topology](./topo.pdf) +![topology](./topo.png) ## Step 3: Run your solution diff --git a/P4D2_2017_Fall/exercises/basic_tunnel/topo.png b/P4D2_2017_Fall/exercises/basic_tunnel/topo.png new file mode 100644 index 0000000000000000000000000000000000000000..12ff49e1de56ecf4a26e2956ff0ccf9b71d328b0 GIT binary patch literal 94185 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 **Spoiler alert:** There is a reference solution in the `solution` +> sub-directory. Feel free to compare your implementation to the +> reference. + +## Step 1: Run the (incomplete) starter code + +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 +two hosts. + +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 +are working as expected. + +1. In your shell, run: + ```bash + make + ``` + This will: + * compile `advanced_tunnel.p4`, and + * start a Mininet instance with three switches (`s1`, `s2`, `s3`) + configured in a triangle, each connected to one host (`h1`, `h2`, + and `h3`). + * 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: + ```bash + mininet> h1 ping h2 + ``` + Because there are no rules on the switches, you should **not** receive any + replies yet. + +3. Open another shell and run the starter code: + ```bash + cd ~/tutorials/P4D2_2017_Fall/exercises/p4runtime + ./mycontroller.py + ``` + This will install the `advanced_tunnel.p4` program on the switches and push the + tunnel ingress rules. + The program prints the tunnel ingress and egress counters every 2 seconds. + You should see the ingress tunnel counter for s1 increasing: + ``` + s1 ingressTunnelCounter 100: 2 packets + ``` + The other counters should remain at zero. + +4. Press `Ctrl-C` to the second shell to stop `mycontroller.py` + +Each switch is currently mapping traffic into tunnels based on the destination IP +address. Your job is to write the rules that forward the traffic between the switches +based on the tunnel ID. + +### A note about the control plane + +A P4 program defines a packet-processing pipeline, but the rules +within each table are inserted by the control plane. In this case, +`mycontroller.py` implements our control plane, instead of installing static +table entries like we have in the previous exercises. + +**Important:** A P4 program also defines the interface between the +switch pipeline and control plane. This interface is defined in the +`advanced_tunnel.p4info` file. The table entries that you build in `mycontroller.py` +refer to specific tables, keys, and actions by name, and we use a P4Info helper +to convert the names into the IDs that are required for P4 Runtime. Any changes +in the P4 program that add or rename tables, keys, or actions will need to be +reflected in your table entries. + +## Step 2: Implement Tunnel Forwarding + +The `mycontroller.py` file is a basic controller plane that does the following: +1. Establishes a gRPC connection to the switches for the P4 Runtime service. +2. Pushes the P4 program to each switch. +3. Writes tunnel ingress and tunnel egress rules for two tunnels between h1 and h2. +4. Reads tunnel ingress and egress counters every 2 seconds. + +It also contains comments marked with `TODO` which indicate the functionality +that you need to implement. + +Your job will be to write the tunnel transit rule in the `writeTunnelRules` function +that will match on tunnel ID and forward packets to the next hop. + +![topology](../basic_tunnel/topo.png) + +## Step 3: Run your solution + +Follow the instructions from Step 1. If your Mininet network is still running, +you will just need to run the following in your second shell: +```bash +./my_controller.py +``` + +You should start to see ICMP replies in your Mininet prompt, and you should start to +see the values for all counters start to increment. + +### Extra Credit and Food for Thought + +You might notice that the rules that are printed by `mycontroller.py` contain the entity +IDs rather than the table names. You can use the P4Info helper to translate these IDs +into entry names. + +Also, you may want to think about the following: +- What assumptions about the topology are baked into your implementation? How would you +need to change it for a more realistic network? + +- Why are the byte counters different between the ingress and egress counters? + +- 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. + +#### Cleaning up Mininet + +If the Mininet shell crashes, it may leave a Mininet instance +running in the background. Use the following command to clean up: +```bash +make clean +``` + +#### Running the reference solution + +To run the reference solution, you should run the following command from the +`~/tutorials/P4D2_2017_Fall/exercises/p4runtime` directory: +```bash +solution/my_controller.py +``` + + +## Next Steps + +Congratulations, your implementation works! Move onto the next assignment +[ecn](../ecn)! + diff --git a/P4D2_2017_Fall/exercises/p4runtime/advanced_tunnel.p4 b/P4D2_2017_Fall/exercises/p4runtime/advanced_tunnel.p4 old mode 100644 new mode 100755 index 11e2ae1..5ea8fc0 --- a/P4D2_2017_Fall/exercises/p4runtime/advanced_tunnel.p4 +++ b/P4D2_2017_Fall/exercises/p4runtime/advanced_tunnel.p4 @@ -4,6 +4,7 @@ const bit<16> TYPE_MYTUNNEL = 0x1212; const bit<16> TYPE_IPV4 = 0x800; +const bit<32> MAX_TUNNEL_ID = 1 << 16; /************************************************************************* *********************** H E A D E R S *********************************** @@ -103,6 +104,9 @@ control MyIngress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) { + counter(MAX_TUNNEL_ID, CounterType.packets_and_bytes) ingressTunnelCounter; + counter(MAX_TUNNEL_ID, CounterType.packets_and_bytes) egressTunnelCounter; + action drop() { mark_to_drop(); } @@ -119,8 +123,21 @@ control MyIngress(inout headers hdr, hdr.myTunnel.dst_id = dst_id; hdr.myTunnel.proto_id = hdr.ethernet.etherType; hdr.ethernet.etherType = TYPE_MYTUNNEL; + ingressTunnelCounter.count((bit<32>) hdr.myTunnel.dst_id); } - + + action myTunnel_forward(egressSpec_t port) { + standard_metadata.egress_spec = port; + } + + action myTunnel_egress(macAddr_t dstAddr, egressSpec_t port) { + standard_metadata.egress_spec = port; + hdr.ethernet.dstAddr = dstAddr; + hdr.ethernet.etherType = hdr.myTunnel.proto_id; + hdr.myTunnel.setInvalid(); + egressTunnelCounter.count((bit<32>) hdr.myTunnel.dst_id); + } + table ipv4_lpm { key = { hdr.ipv4.dstAddr: lpm; @@ -135,19 +152,6 @@ control MyIngress(inout headers hdr, default_action = NoAction(); } - direct_counter(CounterType.packets_and_bytes) tunnelCount; - - action myTunnel_forward(egressSpec_t port) { - standard_metadata.egress_spec = port; - tunnelCount.count(); - } - - action myTunnel_egress(egressSpec_t port) { - standard_metadata.egress_spec = port; - hdr.myTunnel.setInvalid(); - tunnelCount.count(); - } - table myTunnel_exact { key = { hdr.myTunnel.dst_id: exact; @@ -158,12 +162,10 @@ control MyIngress(inout headers hdr, drop; } size = 1024; - counters = tunnelCount; default_action = drop(); } apply { - if (hdr.ipv4.isValid() && !hdr.myTunnel.isValid()) { // Process only non-tunneled IPv4 packets. ipv4_lpm.apply(); diff --git a/P4D2_2017_Fall/exercises/p4runtime/mycontroller.py b/P4D2_2017_Fall/exercises/p4runtime/mycontroller.py new file mode 100755 index 0000000..6d5e58f --- /dev/null +++ b/P4D2_2017_Fall/exercises/p4runtime/mycontroller.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python2 +import argparse +import os +from time import sleep + +import p4runtime_lib.bmv2 +import p4runtime_lib.helper + +SWITCH_TO_HOST_PORT = 1 +SWITCH_TO_SWITCH_PORT = 2 + +def writeTunnelRules(p4info_helper, ingressSw, egressSw, tunnelId, dstEthAddr, dstIpAddr): + ''' + 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 ingressSw: the ingress switch connection + :param egressSw: the egress switch connection + :param tunnelId: the specified tunnel ID + :param dstEthAddr: the destination IP to match in the ingress rule + :param dstIpAddr: 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": (dstIpAddr, 32) + }, + action_name="myTunnel_ingress", + action_params={ + "dst_id": tunnelId, + }) + ingressSw.WriteTableEntry(table_entry) + print "Installed ingress tunnel rule on %s" % ingressSw.name + + # 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 tunnel ID (hdr.myTunnel.dst_id). + # For our simple topology, transit traffic will need to be forwarded using the myTunnel_egress action to + # 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 + # modifications. + 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="myTunnel_exact", + match_fields={ + "hdr.myTunnel.dst_id": tunnelId + }, + action_name="myTunnel_egress", + action_params={ + "dstAddr": dstEthAddr, + "port": SWITCH_TO_HOST_PORT + }) + egressSw.WriteTableEntry(table_entry) + print "Installed egress tunnel rule on %s" % egressSw.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 + 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 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, ingressSw=s1, egressSw=s2, tunnelId=100, + dstEthAddr="00:00:00:00:02:02", dstIpAddr="10.0.2.2") + + # Write the rules that tunnel traffic from h2 to h1 + writeTunnelRules(p4info_helper, ingressSw=s2, egressSw=s1, tunnelId=200, + dstEthAddr="00:00:00:00:01:01", dstIpAddr="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) diff --git a/P4D2_2017_Fall/exercises/p4runtime/p4info/p4browser.py b/P4D2_2017_Fall/exercises/p4runtime/p4info/p4browser.py deleted file mode 100644 index 1065917..0000000 --- a/P4D2_2017_Fall/exercises/p4runtime/p4info/p4browser.py +++ /dev/null @@ -1,135 +0,0 @@ -# Copyright 2017-present Open Networking Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -import re - -import google.protobuf.text_format -from p4 import p4runtime_pb2 -from p4.config import p4info_pb2 - - -class P4InfoBrowser(object): - 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) - self.p4info = p4info - - def get(self, entity_type, name=None, id=None): - if name is not None and id is not None: - raise AssertionError("name or id must be None") - - for o in getattr(self.p4info, entity_type): - pre = o.preamble - if 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)) - else: - raise AttributeError("Could not find id %r of type %s" % (id, entity_type)) - - def get_id(self, entity_type, name): - return self.get(entity_type, name=name).preamble.id - - def get_name(self, entity_type, id): - return self.get(entity_type, id=id).preamble.name - - 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_table_id() or get_action_id() - m = re.search("^get_(\w+)_id$", attr) - if m: - primitive = m.group(1) - return lambda name: self.get_id(primitive, name) - - # Synthesize convenience functions for id to name lookups - m = re.search("^get_(\w+)_name$", attr) - if m: - primitive = m.group(1) - return lambda id: self.get_name(primitive, id) - - raise AttributeError("%r object has no attribute %r" % (self.__class__, attr)) - - # TODO remove - def get_table_entry(self, table_name): - t = self.get(table_name, "table") - entry = p4runtime_pb2.TableEntry() - entry.table_id = t.preamble.id - entry - pass - - def get_match_field(self, table_name, match_field_name): - for t in self.p4info.tables: - pre = t.preamble - if pre.name == table_name: - for mf in t.match_fields: - if mf.name == match_field_name: - return mf - - def get_match_field_id(self, table_name, match_field_name): - return self.get_match_field(table_name,match_field_name).id - - def get_match_field_pb(self, table_name, match_field_name, value): - p4info_match = self.get_match_field(table_name, match_field_name) - bw = p4info_match.bitwidth - p4runtime_match = p4runtime_pb2.FieldMatch() - p4runtime_match.field_id = p4info_match.id - # TODO switch on match type and map the value into the appropriate message type - match_type = p4info_pb2._MATCHFIELD_MATCHTYPE.values_by_number[ - p4info_match.match_type].name - if match_type == 'EXACT': - exact = p4runtime_match.exact - exact.value = value - elif match_type == 'LPM': - lpm = p4runtime_match.lpm - lpm.value = value[0] - lpm.prefix_len = value[1] - # TODO finish cases and validate types and bitwidth - # VALID = 1; - # EXACT = 2; - # LPM = 3; - # TERNARY = 4; - # RANGE = 5; - # and raise exception - return p4runtime_match - - def get_action_param(self, action_name, param_name): - for a in self.p4info.actions: - pre = a.preamble - if pre.name == action_name: - for p in a.params: - if p.name == param_name: - return p - raise AttributeError("%r has no attribute %r" % (action_name, param_name)) - - - def get_action_param_id(self, action_name, param_name): - return self.get_action_param(action_name, param_name).id - - def get_action_param_pb(self, action_name, param_name, value): - p4info_param = self.get_action_param(action_name, param_name) - #bw = p4info_param.bitwidth - p4runtime_param = p4runtime_pb2.Action.Param() - p4runtime_param.param_id = p4info_param.id - p4runtime_param.value = value # TODO make sure it's the correct bitwidth - return p4runtime_param \ No newline at end of file diff --git a/P4D2_2017_Fall/exercises/p4runtime/switches/__init__.py b/P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/__init__.py similarity index 100% rename from P4D2_2017_Fall/exercises/p4runtime/switches/__init__.py rename to P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/__init__.py diff --git a/P4D2_2017_Fall/exercises/p4runtime/switches/bmv2.py b/P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/bmv2.py similarity index 95% rename from P4D2_2017_Fall/exercises/p4runtime/switches/bmv2.py rename to P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/bmv2.py index 5d9f53c..7f483f4 100644 --- a/P4D2_2017_Fall/exercises/p4runtime/switches/bmv2.py +++ b/P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/bmv2.py @@ -20,7 +20,6 @@ def buildDeviceConfig(bmv2_json_file_path=None): "Builds the device config for BMv2" device_config = p4config_pb2.P4DeviceConfig() device_config.reassign = True - # set device_config.extra to default instance with open(bmv2_json_file_path) as f: device_config.device_data = f.read() return device_config diff --git a/P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/convert.py b/P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/convert.py new file mode 100644 index 0000000..0375e17 --- /dev/null +++ b/P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/convert.py @@ -0,0 +1,119 @@ +# Copyright 2017-present Open Networking Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import re +import socket + +import math + +''' +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('^([\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 mac_addr_string.replace(':', '').decode('hex') + +def decodeMac(encoded_mac_addr): + return ':'.join(s.encode('hex') for s in encoded_mac_addr) + +ip_pattern = re.compile('^(\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 bitwidthToBytes(bitwidth): + return int(math.ceil(bitwidth / 8.0)) + +def encodeNum(number, bitwidth): + byte_len = bitwidthToBytes(bitwidth) + num_str = '%x' % number + if number >= 2 ** bitwidth: + raise Exception("Number, %d, does not fit in %d bits" % (number, bitwidth)) + return ('0' * (byte_len * 2 - len(num_str)) + num_str).decode('hex') + +def decodeNum(encoded_number): + return int(encoded_number.encode('hex'), 16) + +def encode(x, bitwidth): + '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: + x = x[0] + encoded_bytes = None + if type(x) == str: + if matchesMac(x): + encoded_bytes = encodeMac(x) + elif matchesIPv4(x): + encoded_bytes = encodeIPv4(x) + else: + # Assume that the string is already encoded + encoded_bytes = x + elif type(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) + return encoded_bytes + +if __name__ == '__main__': + # TODO These tests should be moved out of main eventually + mac = "aa:bb:cc:dd:ee:ff" + enc_mac = encodeMac(mac) + assert(enc_mac == '\xaa\xbb\xcc\xdd\xee\xff') + dec_mac = decodeMac(enc_mac) + assert(mac == dec_mac) + + ip = "10.0.0.1" + enc_ip = encodeIPv4(ip) + assert(enc_ip == '\x0a\x00\x00\x01') + dec_ip = decodeIPv4(enc_ip) + assert(ip == dec_ip) + + num = 1337 + byte_len = 5 + enc_num = encodeNum(num, byte_len * 8) + assert(enc_num == '\x00\x00\x00\x05\x39') + dec_num = decodeNum(enc_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(encode(mac, 6 * 8) == enc_mac) + assert(encode(ip, 4 * 8) == enc_ip) + 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: + print e diff --git a/P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/helper.py b/P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/helper.py new file mode 100644 index 0000000..4906567 --- /dev/null +++ b/P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/helper.py @@ -0,0 +1,183 @@ +# Copyright 2017-present Open Networking Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import re + +import google.protobuf.text_format +from p4 import p4runtime_pb2 +from p4.config import p4info_pb2 + +from p4runtime_lib.convert import encode + +class P4InfoHelper(object): + 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) + self.p4info = p4info + + def get(self, entity_type, name=None, id=None): + if name is not None and id is not None: + raise AssertionError("name or id must be None") + + for o in getattr(self.p4info, entity_type): + pre = o.preamble + if 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)) + else: + raise AttributeError("Could not find id %r of type %s" % (id, entity_type)) + + def get_id(self, entity_type, name): + return self.get(entity_type, name=name).preamble.id + + def get_name(self, entity_type, id): + return self.get(entity_type, id=id).preamble.name + + 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) + m = re.search("^get_(\w+)_id$", attr) + if m: + primitive = m.group(1) + return lambda name: self.get_id(primitive, name) + + # Synthesize convenience functions for id to name lookups + # e.g. get_tables_name(id) or get_actions_name(id) + m = re.search("^get_(\w+)_name$", attr) + if m: + primitive = m.group(1) + return lambda id: self.get_name(primitive, id) + + raise AttributeError("%r object has no attribute %r" % (self.__class__, attr)) + + def get_match_field(self, table_name, name=None, id=None): + for t in self.p4info.tables: + pre = t.preamble + if pre.name == table_name: + for mf in t.match_fields: + 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)) + + def get_match_field_id(self, table_name, match_field_name): + return self.get_match_field(table_name, name=match_field_name).id + + def get_match_field_name(self, table_name, match_field_id): + return self.get_match_field(table_name, id=match_field_id).name + + def get_match_field_pb(self, table_name, match_field_name, value): + p4info_match = self.get_match_field(table_name, match_field_name) + bitwidth = p4info_match.bitwidth + p4runtime_match = p4runtime_pb2.FieldMatch() + p4runtime_match.field_id = p4info_match.id + match_type = p4info_match.match_type + if match_type == p4info_pb2.MatchField.VALID: + valid = p4runtime_match.valid + valid.value = bool(value) + elif match_type == p4info_pb2.MatchField.EXACT: + exact = p4runtime_match.exact + exact.value = encode(value, bitwidth) + elif match_type == p4info_pb2.MatchField.LPM: + lpm = p4runtime_match.lpm + lpm.value = encode(value[0], bitwidth) + lpm.prefix_len = value[1] + elif match_type == p4info_pb2.MatchField.TERNARY: + lpm = p4runtime_match.ternary + lpm.value = encode(value[0], bitwidth) + lpm.mask = encode(value[1], bitwidth) + elif match_type == p4info_pb2.MatchField.RANGE: + lpm = p4runtime_match.range + lpm.low = encode(value[0], bitwidth) + lpm.high = encode(value[1], bitwidth) + else: + raise Exception("Unsupported match type with type %r" % match_type) + return p4runtime_match + + def get_match_field_value(self, match_field): + match_type = match_field.WhichOneof("field_match_type") + if match_type == 'valid': + return match_field.valid.value + elif match_type == 'exact': + return match_field.exact.value + elif match_type == 'lpm': + return (match_field.lpm.value, match_field.lpm.prefix_len) + elif match_type == 'ternary': + return (match_field.ternary.value, match_field.ternary.mask) + elif match_type == 'range': + return (match_field.range.low, match_field.range.high) + else: + raise Exception("Unsupported match type with type %r" % match_type) + + def get_action_param(self, action_name, name=None, id=None): + for a in self.p4info.actions: + pre = a.preamble + if pre.name == action_name: + for p in a.params: + 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" % (action_name, name if name is not None else id)) + + def get_action_param_id(self, action_name, param_name): + return self.get_action_param(action_name, name=param_name).id + + def get_action_param_name(self, action_name, param_id): + return self.get_action_param(action_name, id=param_id).name + + def get_action_param_pb(self, action_name, param_name, value): + p4info_param = self.get_action_param(action_name, param_name) + p4runtime_param = p4runtime_pb2.Action.Param() + p4runtime_param.param_id = p4info_param.id + p4runtime_param.value = encode(value, p4info_param.bitwidth) + return p4runtime_param + + def buildTableEntry(self, + table_name, + match_fields={}, + action_name=None, + action_params={}): + table_entry = p4runtime_pb2.TableEntry() + table_entry.table_id = self.get_tables_id(table_name) + 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.iteritems() + ]) + if action_name: + 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.iteritems() + ]) + return table_entry \ No newline at end of file diff --git a/P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/switch.py b/P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/switch.py new file mode 100644 index 0000000..077698e --- /dev/null +++ b/P4D2_2017_Fall/exercises/p4runtime/p4runtime_lib/switch.py @@ -0,0 +1,88 @@ +# Copyright 2017-present Open Networking Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from abc import abstractmethod + +import grpc +from p4 import p4runtime_pb2 +from p4.tmp import p4config_pb2 + +class SwitchConnection(object): + def __init__(self, name, address='127.0.0.1:50051', device_id=0): + self.name = name + self.address = address + self.device_id = device_id + self.p4info = None + self.channel = grpc.insecure_channel(self.address) + self.client_stub = p4runtime_pb2.P4RuntimeStub(self.channel) + + @abstractmethod + def buildDeviceConfig(self, **kwargs): + return p4config_pb2.P4DeviceConfig() + + def SetForwardingPipelineConfig(self, p4info, dry_run=False, **kwargs): + device_config = self.buildDeviceConfig(**kwargs) + request = p4runtime_pb2.SetForwardingPipelineConfigRequest() + config = request.configs.add() + config.device_id = self.device_id + config.p4info.CopyFrom(p4info) + config.p4_device_config = device_config.SerializeToString() + request.action = p4runtime_pb2.SetForwardingPipelineConfigRequest.VERIFY_AND_COMMIT + if dry_run: + print "P4 Runtime SetForwardingPipelineConfig:", request + else: + self.client_stub.SetForwardingPipelineConfig(request) + + def WriteTableEntry(self, table_entry, dry_run=False): + request = p4runtime_pb2.WriteRequest() + request.device_id = self.device_id + update = request.updates.add() + update.type = p4runtime_pb2.Update.INSERT + update.entity.table_entry.CopyFrom(table_entry) + if dry_run: + print "P4 Runtime Write:", request + else: + self.client_stub.Write(request) + + def ReadTableEntries(self, table_id=None, dry_run=False): + request = p4runtime_pb2.ReadRequest() + request.device_id = self.device_id + entity = request.entities.add() + table_entry = entity.table_entry + if table_id is not None: + table_entry.table_id = table_id + else: + table_entry.table_id = 0 + if dry_run: + print "P4 Runtime Read:", request + else: + for response in self.client_stub.Read(request): + yield response + + def ReadCounters(self, counter_id=None, index=None, dry_run=False): + request = p4runtime_pb2.ReadRequest() + request.device_id = self.device_id + entity = request.entities.add() + counter_entry = entity.counter_entry + if counter_id is not None: + counter_entry.counter_id = counter_id + else: + counter_entry.counter_id = 0 + if index is not None: + counter_entry.index = index + if dry_run: + print "P4 Runtime Read:", request + else: + for response in self.client_stub.Read(request): + yield response diff --git a/P4D2_2017_Fall/exercises/p4runtime/switches/switch.py b/P4D2_2017_Fall/exercises/p4runtime/switches/switch.py deleted file mode 100644 index 18880ab..0000000 --- a/P4D2_2017_Fall/exercises/p4runtime/switches/switch.py +++ /dev/null @@ -1,130 +0,0 @@ -# Copyright 2017-present Open Networking Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -from abc import abstractmethod - -import grpc -from p4 import p4runtime_pb2 -from p4.tmp import p4config_pb2 - -from p4info import p4browser - - -def buildSetPipelineRequest(p4info, device_config, device_id): - request = p4runtime_pb2.SetForwardingPipelineConfigRequest() - config = request.configs.add() - config.device_id = device_id - config.p4info.CopyFrom(p4info) - config.p4_device_config = device_config.SerializeToString() - request.action = p4runtime_pb2.SetForwardingPipelineConfigRequest.VERIFY_AND_COMMIT - return request - - -def buildTableEntry(p4info_browser, - table_name, - match_fields={}, - action_name=None, - action_params={}): - table_entry = p4runtime_pb2.TableEntry() - table_entry.table_id = p4info_browser.get_tables_id(table_name) - if match_fields: - table_entry.match.extend([ - p4info_browser.get_match_field_pb(table_name, match_field_name, value) - for match_field_name, value in match_fields.iteritems() - ]) - if action_name: - action = table_entry.action.action - action.action_id = p4info_browser.get_actions_id(action_name) - if action_params: - action.params.extend([ - p4info_browser.get_action_param_pb(action_name, field_name, value) - for field_name, value in action_params.iteritems() - ]) - return table_entry - - -class SwitchConnection(object): - def __init__(self, name, address='127.0.0.1:50051', device_id=0): - self.name = name - self.address = address - self.device_id = device_id - self.p4info = None - self.channel = grpc.insecure_channel(self.address) - # TODO Do want to do a better job managing stub? - self.client_stub = p4runtime_pb2.P4RuntimeStub(self.channel) - - @abstractmethod - def buildDeviceConfig(self, **kwargs): - return p4config_pb2.P4DeviceConfig() - - def SetForwardingPipelineConfig(self, p4info_file_path, dry_run=False, **kwargs): - p4info_broswer = p4browser.P4InfoBrowser(p4info_file_path) - device_config = self.buildDeviceConfig(**kwargs) - request = buildSetPipelineRequest(p4info_broswer.p4info, device_config, self.device_id) - if dry_run: - print "P4 Runtime SetForwardingPipelineConfig:", request - else: - self.client_stub.SetForwardingPipelineConfig(request) - # Update the local P4 Info reference - self.p4info_broswer = p4info_broswer - - def buildTableEntry(self, - table_name, - match_fields={}, - action_name=None, - action_params={}): - return buildTableEntry(self.p4info_broswer, table_name, match_fields, action_name, action_params) - - def WriteTableEntry(self, table_entry, dry_run=False): - request = p4runtime_pb2.WriteRequest() - request.device_id = self.device_id - update = request.updates.add() - update.type = p4runtime_pb2.Update.INSERT - update.entity.table_entry.CopyFrom(table_entry) - if dry_run: - print "P4 Runtime Write:", request - else: - print self.client_stub.Write(request) - - def ReadTableEntries(self, table_name, dry_run=False): - request = p4runtime_pb2.ReadRequest() - request.device_id = self.device_id - entity = request.entities.add() - table_entry = entity.table_entry - table_entry.table_id = self.p4info_broswer.get_tables_id(table_name) - if dry_run: - print "P4 Runtime Read:", request - else: - for response in self.client_stub.Read(request): - yield response - - def ReadDirectCounters(self, table_name=None, counter_name=None, table_entry=None, dry_run=False): - request = p4runtime_pb2.ReadRequest() - request.device_id = self.device_id - entity = request.entities.add() - counter_entry = entity.direct_counter_entry - if counter_name: - counter_entry.counter_id = self.p4info_broswer.get_direct_counters_id(counter_name) - else: - counter_entry.counter_id = 0 - # TODO we may not need this table entry - if table_name: - table_entry.table_id = self.p4info_broswer.get_tables_id(table_name) - counter_entry.table_entry.CopyFrom(table_entry) - counter_entry.data.packet_count = 0 - if dry_run: - print "P4 Runtime Read:", request - else: - for response in self.client_stub.Read(request): - print response diff --git a/P4D2_2017_Fall/utils/Makefile b/P4D2_2017_Fall/utils/Makefile index 0591ad4..99e44fb 100644 --- a/P4D2_2017_Fall/utils/Makefile +++ b/P4D2_2017_Fall/utils/Makefile @@ -11,10 +11,20 @@ outfile := $(source:.p4=.json) compiled_json := $(BUILD_DIR)/$(outfile) +# Define NO_P4 to start BMv2 without a program +ifndef NO_P4 +run_args += -j $(compiled_json) +endif + +# Set BMV2_SWITCH_EXE to override the BMv2 target +ifdef BMV2_SWITCH_EXE +run_args += -b $(BMV2_SWITCH_EXE) +endif + all: run -run: build - sudo python $(RUN_SCRIPT) -t $(TOPO) -j $(compiled_json) +run: build + sudo python $(RUN_SCRIPT) -t $(TOPO) $(run_args) stop: sudo mn -c @@ -22,7 +32,7 @@ stop: build: dirs $(compiled_json) $(BUILD_DIR)/%.json: %.p4 - $(P4C) --p4v 16 -o $@ $< + $(P4C) --p4v 16 $(P4C_ARGS) -o $@ $< dirs: mkdir -p $(BUILD_DIR) $(PCAP_DIR) $(LOG_DIR) diff --git a/P4D2_2017_Fall/utils/run_exercise.py b/P4D2_2017_Fall/utils/run_exercise.py index e0ff3db..dcb22bc 100755 --- a/P4D2_2017_Fall/utils/run_exercise.py +++ b/P4D2_2017_Fall/utils/run_exercise.py @@ -325,11 +325,11 @@ class ExerciseRunner: - A mininet instance is stored as self.net and self.net.start() has been called. """ - self.logger("Starting mininet CLI") for s in self.net.switches: s.describe() for h in self.net.hosts: h.describe() + 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('') diff --git a/P4D2_2017_Fall/vm/root-bootstrap.sh b/P4D2_2017_Fall/vm/root-bootstrap.sh index 398c34b..8e770d1 100755 --- a/P4D2_2017_Fall/vm/root-bootstrap.sh +++ b/P4D2_2017_Fall/vm/root-bootstrap.sh @@ -76,3 +76,6 @@ ln -s p4-logo.png lubuntu-default-wallpaper.png rm /home/vagrant/p4-logo.png cd /home/vagrant sed -i s@#background=@background=/usr/share/lubuntu/wallpapers/1604-lubuntu-default-wallpaper.png@ /etc/lightdm/lightdm-gtk-greeter.conf + +# Disable screensaver +apt-get -y remove light-locker diff --git a/P4D2_2017_Fall/vm/user-bootstrap.sh b/P4D2_2017_Fall/vm/user-bootstrap.sh index 7318f53..d3d38ac 100644 --- a/P4D2_2017_Fall/vm/user-bootstrap.sh +++ b/P4D2_2017_Fall/vm/user-bootstrap.sh @@ -131,3 +131,39 @@ cd /home/vagrant sudo mv .vim /home/p4/.vim sudo chown -R p4:p4 /home/p4/.vim +# Adding Desktop icons +DESKTOP=/home/${USER}/Desktop +mkdir -p ${DESKTOP} + +cat > ${DESKTOP}/Terminal << EOF +[Desktop Entry] +Encoding=UTF-8 +Type=Application +Name=Terminal +Name[en_US]=Terminal +Icon=konsole +Exec=/usr/bin/x-terminal-emulator +Comment[en_US]= +EOF + +cat > ${DESKTOP}/Wireshark << EOF +[Desktop Entry] +Encoding=UTF-8 +Type=Application +Name=Wireshark +Name[en_US]=Wireshark +Icon=wireshark +Exec=/usr/bin/wireshark +Comment[en_US]= +EOF + +cat > ${DESKTOP}/Sublime\ Text << EOF +[Desktop Entry] +Encoding=UTF-8 +Type=Application +Name=Sublime Text +Name[en_US]=Sublime Text +Icon=sublime-text +Exec=/opt/sublime_text/sublime_text +Comment[en_US]= +EOF \ No newline at end of file