-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
27 changed files
with
1,075 additions
and
0 deletions.
There are no files selected for viewing
File renamed without changes.
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
#!/bin/bash | ||
|
||
# Check if an argument is provided | ||
if [ $# -ne 1 ]; then | ||
echo "Usage: $0 <filename>" | ||
exit 1 | ||
fi | ||
|
||
filename="$1" | ||
current_dir="$(pwd)" | ||
|
||
while true; do | ||
# Check if the file exists in the current directory | ||
if [ -e "${current_dir}/${filename}" ]; then | ||
# Return the absolute path to the file | ||
echo "${current_dir}/${filename}" | ||
break | ||
fi | ||
|
||
# Check if the current directory is root | ||
if [ "${current_dir}" = "/" ]; then | ||
# File not found, exit with an error message | ||
# echo "[findfile.sh warning] File '${filename}' not found" >&2 | ||
exit 2 | ||
fi | ||
|
||
# Move up to the parent directory | ||
current_dir="$(dirname "${current_dir}")" | ||
done | ||
|
||
exit 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# lucid.sh is a wrapper to the dockerized lucid build system. | ||
FIND_LUCID_SH := ./findfile.sh lucid.sh | ||
LUCID_SH := $(shell $(FIND_LUCID_SH) || echo "echo 'lucid.sh not found'; exit 1" | sh) | ||
|
||
# "dpt" is the lucid interpreter binary. This is for if you have a | ||
# local build on your machine, or if you ssh into the docker | ||
# image and want to run the makefile from there. | ||
FIND_DPT := ./findfile.sh dpt | ||
DPT := $(shell $(FIND_DPT) || echo "-" ) | ||
|
||
|
||
# SRC is the lucid source program. It can be passed | ||
# as an argument to this makefile, or as a default | ||
# value the first .dpt file found is used. | ||
ifndef SRC | ||
SRC := $(firstword $(wildcard *.dpt)) | ||
endif | ||
|
||
.PHONY: interp interp_local build_p4 clean dptc force current_makefile | ||
|
||
# run the lucid interpreter on SRC, using the dockerized lucid.sh script. | ||
interp: | ||
@$(LUCID_SH) interp $(SRC) | ||
|
||
# run the lucid interpreter on SRC, using the local build | ||
# of the lucid interpreter (which is named dpt) | ||
interp_local: | ||
@if [ "$(DPT)" = "-" ]; then \ | ||
echo "local dpt not found. falling back to docker"; \ | ||
$(LUCID_SH) interp $(SRC); \ | ||
else \ | ||
$(DPT) $(SRC); \ | ||
fi | ||
|
||
# everything below is related to compiling | ||
# lucid to P4-tofino. Ignore if you are only | ||
# using the interpeter. | ||
|
||
# P4_OUTPUT is the lucid-generate p4 program. | ||
P4_OUTPUT := $(DPT_BUILD_DIR)/lucid.p4 | ||
|
||
|
||
build_p4: $(P4_OUTPUT) | ||
|
||
$(P4_OUTPUT): $(SRC) | ||
$(DPTC) $< -o $(DPT_BUILD_DIR) | ||
|
||
clean: | ||
rm -rf $(DPT_BUILD_DIR) | ||
|
||
dptc: | ||
cd $(dir $(DPTC)) && make | ||
|
||
force: current_makefile | ||
touch $(SRC) | ||
$(MAKE) all -f $(call current_makefile) SRC=$(SRC) | ||
|
||
current_makefile: | ||
@echo $(lastword $(MAKEFILE_LIST)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// some constants -- not important. | ||
const int OUT_PORT = 1; | ||
const int SERVER_PORT = 2; | ||
|
||
// type definitions in lucid are structs | ||
// in c. | ||
// The types we define here specify | ||
// the formats of packet headers. | ||
type eth_t = { | ||
int<48> dmac; | ||
int<48> smac; | ||
int<16> etype; | ||
} | ||
type ip_t = { | ||
int src; | ||
int dst; | ||
int<16> len; | ||
} // note: for simplicity, ip_t is not the full packet header. | ||
|
||
|
||
/********** Events *********/ | ||
// A lucid program is event driven. Events have names and carry data. | ||
// Events abstract two things: | ||
// 1) Packets from the outside network. For example, | ||
// in this program, there is an "eth_ip" event that | ||
// represents an ethernet / IP packet. | ||
// 2) Internal messages and delayed computations. In the process | ||
// of handling an event, your program can generate other events | ||
// that get raised at the current switch, or at other connected | ||
// switches. In this program, there is a | ||
// "prepare_report" internal event, which does some processing | ||
// before sending a report event to an external monitoring server. | ||
|
||
event eth_ip(eth_t eth, ip_t ip); | ||
|
||
event prepare_report(eth_t eth, ip_t ip); | ||
|
||
event report(int src, int dst, int<16> len) {skip;} | ||
|
||
|
||
/********** Handlers *********/ | ||
// When an event arrives, it triggers the handler with the same name. | ||
// A handler typically generates other events, to process | ||
// at a later point in time or at a different location in the network. | ||
// The "generate(<event>)" command produces an event that will be | ||
// handled later on the same node. | ||
// The "generate_port(<port>, <event>)" command produces an event that | ||
// will be sent out of the specified port for processing at another node. | ||
handle eth_ip(eth_t eth, ip_t ip) { | ||
// 1. forward an event representing the packet out of port 1. | ||
generate_port(OUT_PORT, eth_ip(eth, ip)); | ||
// 2. prepare a report to send to the monitoring server. | ||
generate(prepare_report(eth, ip)); | ||
} | ||
|
||
handle prepare_report(eth_t eth, ip_t ip) { | ||
printf("sending report about packet {src=%d; dst=%d; len=%d} to monitor on port %d", ip#src, ip#dst, ip#len, SERVER_PORT); | ||
// make an report event and send it to the monitoring server. | ||
event r = report(ip#src, ip#dst, ip#len); | ||
generate_port(SERVER_PORT, r); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"switches": 1, | ||
"max time": 9999999, | ||
"events": [ | ||
{"name":"eth_ip", "args": [11, 22, 2048, 1, 2, 128]} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
## monitor.dpt | ||
|
||
This is a simple monitoring application that runs on a switch. Packets, represented by the `eth_ip` event, arrive at the switch and trigger an `eth_ip` handler, which does two things: | ||
|
||
1) **forwarding**: it sends a copy of the packet out of a physical port on the switch. | ||
|
||
2) **basic monitoring**: it generates a report summarizing each packet, and sends it to a monitoring server connected to a different port on the switch. | ||
|
||
### Lucid basics | ||
|
||
Before running the example, it is useful to understand some basics about the lucid language and its interpreter. | ||
|
||
**Core Lucid abstractions** | ||
|
||
This program introduces the two most fundamental lucid primitives: events and handlers. Take a look at the `monitor.dpt` source file for an introduction to events, handlers, and Lcid's processing model. | ||
|
||
**The Lucid interpreter** | ||
|
||
The Lucid interpreter type checks a lucid program and optionally runs the program in a simulated network. You configure the simulation with a *interpreter specification file*, which is typically defined in `<progname>.json`. So, for this example `monitor.dpt`, the interpreter runs on `monitor.json` by default. | ||
|
||
The most important part of an interpreter specification file is the list of input events. Here's `monitor.json`: | ||
|
||
``` | ||
{ | ||
"switches": 1, | ||
"max time": 9999999, | ||
"events": [ | ||
{"name":"eth_ip", "args": [11, 22, 2048, 1, 2, 128]} | ||
] | ||
} | ||
``` | ||
|
||
- "switches" tells the interpreter how many switches are in the network. If there are more than 1, you will also need to specify the topology of the network by defining links between switches. A later example will cover that. | ||
|
||
- "max time" defines when the simulation ends, in _nanoseconds_. | ||
|
||
- "events" is a list of input events to the simulated network. Each event record has a name and a list of arguments. For events that have record types as arguments (e.g., `eth_ip` has an argument of type `eth_t`, which itself contains multiple fields), the arguments in the json are flattened. For example, the event in `monitor.json`: `{"name":"eth_ip", "args": [11, 22, 2048, 1, 2, 128]}` is an `eth_ip` event with arguments `eth#dmac = 11; eth#smac = 22; eth#etype = 2048; ip#src = 1; ip#dst = 2; ip#len = 128;` | ||
|
||
|
||
#### Usage | ||
|
||
Finally, to run the program, make sure that you have docker and the lucid-docker image installed, then run `make interp`. | ||
|
||
**Expected output** | ||
|
||
Here's the expected output from `make interp`. We will break it down below. | ||
``` | ||
dpt: -------Checking well-formedness--------- | ||
dpt: ---------typing1--------- | ||
dpt: ---------Concretizing symbolics------------- | ||
dpt: ---------Aliasing Modules------------- | ||
dpt: ---------Making returns explicit------------- | ||
dpt: -----------renaming----------- | ||
dpt: -------Performing parser slot analysis--------- | ||
dpt: -------Eliminating modules--------- | ||
dpt: -------Inlining size declarations--------- | ||
dpt: ---------typing2--------- | ||
dpt: -------Eliminating type aliases 2--------- | ||
dpt: -----------inlining functions----------- | ||
dpt: -----------inlining tables----------- | ||
dpt: ---------Eliminating events with global arguments---------- | ||
dpt: ---------------typing3------------- | ||
dpt: -------Making user types concrete------- | ||
dpt: ---------------typing4------------- | ||
dpt: -------Eliminating vectors------- | ||
dpt: ---------------typing5------------- | ||
dpt: -----------re-renaming----------- | ||
dpt: -------Eliminating EStmts------- | ||
dpt: ---------------typing6------------- | ||
dpt: -------Eliminating records------- | ||
dpt: ---------------typing7------------- | ||
dpt: -------Eliminating tuples------- | ||
dpt: ---------------typing8------------- | ||
dpt: -------Inlining Constants------- | ||
dpt: -----------re-re-renaming----------- | ||
dpt: ---------------typing again------------- | ||
dpt: -------Translating to core syntax--------- | ||
dpt: -------Partial interpreting--------- | ||
dpt: Simulating... | ||
dpt: Using random seed: 1700512048 | ||
t=0: Handling event eth_ip(11,22,2048,1,2,128) at switch 0, port 0 | ||
t=600: Handling event prepare_report(11,22,2048,1,2,128) at switch 0, port 196 | ||
sending report about packet {src=1; dst=2; len=128} to monitor on port 2 | ||
dpt: Final State: | ||
Switch 0 : { | ||
Pipeline : [ ] | ||
Events : [ ] | ||
Exits : [ | ||
eth_ip(11,22,2048,1,2,128) at port 1, t=600 | ||
report(1,2,128) at port 2, t=1200 | ||
] | ||
Drops : [ ] | ||
packet events handled: 0 | ||
total events handled: 2 | ||
} | ||
``` | ||
|
||
**reading the intepreter output** | ||
|
||
The first few lines from `make interp` are output from the lucid.sh script, where it is generating the docker command to run. e.g., up to: | ||
`` | ||
running command: docker run --rm -it --mount type=bind,source=/Users/jsonch/Desktop/gits/lucid/tutorials/interp/01monitor/monitor.dpt,target=/app/inputs/monitor.dpt --mount type=bind,source=/Users/jsonch/Desktop/gits/lucid/tutorials/interp/01monitor/monitor.json,target=/app/inputs/monitor.json jsonch/lucid:lucid /bin/sh -c "./dpt /app/inputs/monitor.dpt --spec /app/inputs/monitor.json" | ||
`` | ||
|
||
Next, the lines prefixed with `dpt:` are messages from the lucid compiler frontend as it type checks and transforms the program into a simpler form that the interpreter can run. If your program has an error, typically the interpreter will print an error message and halt at some point in between these messages. | ||
|
||
Finally, after the `dpt: Simulating...` line, we see the output of the lucid simulator. The simulator's output has two components: | ||
1. a trace summarizing the events that were handled at each switch in the simulation, interleaved with any "printf" statements that executed in the program. In this example, the trace is: | ||
``` | ||
t=0: Handling packet event eth_ip(11,22,2048,1,2,128) at switch 0, port 0 | ||
t=600: Handling event prepare_report(11,22,2048,1,2,128) at switch 0, port 196 | ||
sending report about packet {src=1; dst=2; len=128} to monitor on port 2 | ||
``` | ||
the lines with `t=...` are reports about events arriving at switches in the simulation, printed by the Lucid interpreter. The line `sending report...` is printed from the `printf` statement in the `prepare_report` handler. | ||
|
||
2. a summary of the state of each simulated switch at the end of the simulation. The fields of the summary are: | ||
- `Pipeline`: the contents of any global variables in the switch at the end of simulation. A later tutorial will go into more details about global variables. | ||
- `Events`: a list of events that were queued at the switch, but not yet handled by the time the simulation ended. | ||
- `Exits`: event packets generated by the switch that were sent to external components. | ||
- `packet events handled` and `total events handled` the number of packet events and events handled. "Packet events" are packets that have been parsed into events by a user-written parser (which is covered later). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
const int OUT_PORT = 1; | ||
const int SERVER_PORT = 2; | ||
|
||
type eth_t = { | ||
int<48> dmac; | ||
int<48> smac; | ||
int<16> etype; | ||
} | ||
type ip_t = { | ||
int src; | ||
int dst; | ||
int<16> len; | ||
} | ||
|
||
/********** Events *********/ | ||
event eth_ip(eth_t eth, ip_t ip); | ||
|
||
event prepare_report(eth_t eth, ip_t ip); | ||
|
||
event report(int src, int dst, int<16> len) {skip;} | ||
|
||
/********** Tables *********/ | ||
// When you call a table, it will | ||
// execute a pre-installed action. | ||
// Action constructors are used to | ||
// build those actions. | ||
action_constr mk_result(bool b) = { | ||
return action bool acn () { | ||
return b; | ||
}; | ||
}; | ||
|
||
// Tables have types. A table's type | ||
// specifies the size of its keys, | ||
// the types of the arguments to actions | ||
// and the type of the action's return value. | ||
table_type filter_table_t = { | ||
key_type: (int, int) | ||
arg_type: () | ||
ret_type: bool | ||
} | ||
|
||
global filter_table_t filter_table = | ||
table_create<filter_table_t>( | ||
(mk_result), | ||
1024, | ||
mk_result(false) | ||
); | ||
|
||
|
||
/********** Filtering functions *********/ | ||
|
||
fun bool filter_with_if(ip_t ip) { | ||
if (ip#src == 1 && ip#dst == 2) { | ||
return true; | ||
} | ||
else { | ||
if (ip#src == 3 && ip#dst == 4) { | ||
return true; | ||
} | ||
else { | ||
return false; | ||
} | ||
} | ||
} | ||
|
||
fun bool filter_with_match(ip_t ip) { | ||
match (ip#src, ip#dst) with | ||
| 1, 2 -> {return true;} | ||
| 3, 4 -> {return true;} | ||
| _ -> {return false;} | ||
} | ||
|
||
|
||
fun bool filter_with_table(ip_t ip) { | ||
bool r = table_match( | ||
filter_table, | ||
(ip#src, ip#dst), | ||
()); | ||
return r; | ||
} | ||
|
||
/********** Handlers *********/ | ||
handle eth_ip(eth_t eth, ip_t ip) { | ||
generate_port(OUT_PORT, eth_ip(eth, ip)); | ||
|
||
// if (filter_with_if(ip)) { | ||
// if (filter_with_match(ip)) { | ||
if (filter_with_table(ip)) { | ||
generate(prepare_report(eth, ip)); | ||
} | ||
} | ||
|
||
handle prepare_report(eth_t eth, ip_t ip) { | ||
printf("sending report about packet {src=%d; dst=%d; len=%d} to monitor on port %d", ip#src, ip#dst, ip#len, SERVER_PORT); | ||
event r = report(ip#src, ip#dst, ip#len); | ||
generate_port(SERVER_PORT, r); | ||
} |
Oops, something went wrong.