Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

p4c-ubpf: new uBPF back-end for p4c #2134

Merged
merged 60 commits into from
Mar 4, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
ecd39c0
Initial phase of implementing the p4c-ubpf compiler
osinstom Apr 8, 2019
ec22005
Adding support for UBPF parser
osinstom Apr 8, 2019
a687110
UBPF Parser - first implementation
osinstom Apr 13, 2019
75fdcf6
The UBPF parser implemented
osinstom Apr 14, 2019
2fcdcf8
UBPF initial control block.
kmateuszssak Apr 17, 2019
acfaa6e
Working Ubpf control block.
kmateuszssak May 6, 2019
99d6854
Change ubpf table name.
kmateuszssak May 6, 2019
78335fa
Small fixes
osinstom May 13, 2019
93643c2
Remove accept parameter from reference model.
kmateuszssak May 13, 2019
118b2b2
Change table key names.
kmateuszssak May 13, 2019
884c40a
Change table key names v2.
kmateuszssak May 13, 2019
d57742f
Some fixes to the p4c-ubpf + docs
osinstom May 21, 2019
dcf0903
Copyright headers added
osinstom May 21, 2019
3d64116
Update README.md
osinstom Jul 4, 2019
9231f53
Packet modifications support
osinstom Jul 16, 2019
dbb237e
Take size of table from P4 code.
kmateuszssak Aug 8, 2019
1090527
registers_support (#4)
kmateuszssak Aug 7, 2019
c0bc2c5
Merge pull request #5 from P4-Research/master2
osinstom Aug 8, 2019
0df4fa5
hash (#6)
kmateuszssak Sep 30, 2019
5a86d96
Refactor #include + Fix registers read (#7)
kmateuszssak Oct 1, 2019
b96fcca
Generate p4info
kmateuszssak Sep 30, 2019
b880030
Support for P4 metadata
osinstom Oct 1, 2019
3089cbb
Fix after p4info merge. Add semicolon after enum declaration.
kmateuszssak Oct 2, 2019
edcbb1d
Change max nr of table entries to 2^27 because of errors while loadin…
kmateuszssak Oct 4, 2019
cfaa628
Change ubpf_model filename, code cleaning
osinstom Oct 7, 2019
5cc9931
Adding tunneling support + PTF tests (#13)
kmateuszssak Dec 19, 2019
c63a939
p4c-ubpf doesn't compile after sync with master; fix build dependenci…
osinstom Dec 20, 2019
322f5c1
Uncomment ebpf_enable in CmakeLists.txt.
kmateuszssak Jan 7, 2020
1adffa0
T202: Update oko (p4rt-ovs) repository address and refactor scripts (…
kmateuszssak Jan 9, 2020
b3eb458
T207: Revert p4info support in p4c (temporary solution) (#15)
kmateuszssak Jan 10, 2020
5893f22
T208: Remove changes in ebpf p4c backend (#16)
kmateuszssak Jan 10, 2020
0ca94d3
T210: Review p4c once again (#17)
osinstom Jan 10, 2020
085294b
Addressing review comments: minor modifications to P4 programs and sc…
osinstom Jan 17, 2020
8ab0e2a
Compile-only tests + code cleanup
kmateuszssak Jan 20, 2020
302d279
Add not committed before symbolic links
kmateuszssak Jan 21, 2020
381d4ee
Fix symbolic links
kmateuszssak Jan 21, 2020
e4330a8
Remove dependencies on scapy in run-ubpf-test.py
osinstom Jan 21, 2020
1fd859e
Documentation for architecture model + code cleanup
osinstom Jan 28, 2020
08b7de0
Remove dependencies on BMv2 helpers.h
osinstom Jan 28, 2020
b8b3f06
Refactor of hash() generation. Generate tuple from Type_List.
osinstom Jan 24, 2020
e22ad6f
Remove useless code
kmateuszssak Jan 24, 2020
38acee5
Move metadata to tests
kmateuszssak Jan 24, 2020
c648326
Path expression code refactor
kmateuszssak Jan 24, 2020
c5c324e
Add check register type
kmateuszssak Jan 24, 2020
7f0b72e
Refactor method to generate register read()
osinstom Jan 28, 2020
2b6020c
Small fixes to test scripts and UBPFRegister
osinstom Jan 29, 2020
997d5db
Refactor pointer variables' handling in UBPFControl/UBPFRegister. Add…
osinstom Jan 29, 2020
db6c4db
Fix build error (invalid use of incomplete type 'class UBPF::UBPFCont…
osinstom Jan 29, 2020
2e12ee3
Refactor Exit and Return statement's handling
osinstom Jan 30, 2020
6bc8674
Implement checksum computation
osinstom Feb 3, 2020
7ec6f33
Optimizations of test environment installation script
osinstom Feb 7, 2020
2be7ec7
Add missing test file
osinstom Feb 7, 2020
b1479ad
Refactor check of packet's length in Parser
osinstom Jan 30, 2020
668e808
User-space only tests (#18)
osinstom Feb 12, 2020
6ae22e4
Fix p4c-ubpf error.
kmateuszssak Feb 18, 2020
cc59b0d
Fix test errors related to frontend
osinstom Feb 19, 2020
fb07d84
Register read refactor (#19)
kmateuszssak Feb 20, 2020
daad335
Macro redefinition
kmateuszssak Feb 20, 2020
f5bcc01
Update travis.yml to exclude ubpf tests for OSx
osinstom Feb 20, 2020
89eb992
Small code refactor and update of docs
osinstom Feb 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Documentation for architecture model + code cleanup
  • Loading branch information
osinstom authored and kmateuszssak committed Feb 14, 2020
commit 1fd859e2cf09cbb9c6eec326bee21cc4a6dcc14e
6 changes: 4 additions & 2 deletions backends/ubpf/docs/EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ The parameters are as follows:
- `key_type` - is bit array type (i.e. bit<32>) or struct like type
- `number_of_elements` - the maximum number of key-value pairs

Currently registers have a limitation - they are not being initialized with default values. Initialization has to be done by a control plane.
Currently, the `ubpf` architecture model does not allow to initialize registers with default values.
Initialization has to be done by a control plane.

### Rate limiter (rate-limiter.p4)

Expand Down Expand Up @@ -152,7 +153,8 @@ $ watch sudo ovs-ofctl dump-bpf-map br0 1 0

This is very simple example of stateful firewall. Every TCP packet is analyzed to track the state of the TCP connection.
If the traffic belongs to known connection it is passed. Otherwise, it is dropped.
Notice that the example program uses hash function which is constrained to hash only 64 bit values - that's why TCP connection is identified via IP source and destination address.
Notice that the example program uses hash function which is constrained to hash only 64 bit values - that's why TCP connection is identified via IP source and destination address.
This is the known limitation of the `uBPF` backend used in P4rt-OVS (to be fixed in the future).

Due to registers limitation before starting your own tests initialize simple firewall registers with zeros:

Expand Down
4 changes: 0 additions & 4 deletions backends/ubpf/examples/gtp.p4
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,6 @@ control pipe(inout Headers_t hdr, inout metadata meta) {
gtp_decap;
NoAction;
}

//const default_action = NoAction();
}

action gtp_encap(bit<32> tunnelId) {
Expand Down Expand Up @@ -189,8 +187,6 @@ control pipe(inout Headers_t hdr, inout metadata meta) {
gtp_encap;
NoAction;
}

//const default_action = NoAction();
}


Expand Down
2 changes: 1 addition & 1 deletion backends/ubpf/p4c-ubpf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ void compile(EbpfOptions& options) {
auto hook = options.getDebugHook();
bool isv1 = options.langVersion == CompilerOptions::FrontendVersion::P4_14;
if (isv1) {
::error("This compiler only handles P4-16");
::error(ErrorType::ERR_UNSUPPORTED, "- this compiler only handles P4-16");
return;
}
auto program = P4::parseP4File(options);
Expand Down
95 changes: 83 additions & 12 deletions backends/ubpf/p4include/ubpf_model.p4
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,108 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

#ifndef _FILTER_MODEL_P4_
#define _FILTER_MODEL_P4_
#ifndef _UBPF_MODEL_P4_
#define _UBPF_MODEL_P4_

#include <core.p4>

parser parse<H, M>(packet_in packet, out H headers, inout M meta);
control filter<H, M>(inout H headers, inout M meta);
@deparser
control deparser<H>(packet_out b, in H headers);

package ubpf<H, M>(parse<H, M> prs,
filter<H, M> filt,
deparser<H> dprs);

/*
* The uBPF target can currently pass the packet or drop it.
* By default, all packets are passed.
* The mark_to_drop() extern can be used to mark a packet to be dropped.
* The mark_to_drop() modifies only the state hidden from the user's P4 program.
* mark_to_drop() should be called only in the 'pipe' control.
*/
extern void mark_to_drop();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest documenting these functions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In particular, for mark_to_drop() and mark_to_pass(), what is the default behavior after a packet is done processing? Probably pass?

Then if the P4 program does mark_to_drop() and/or mark_to_pass() calls, whichever one was last determines whether the packet is passed or dropped later?

And there are no user-visible P4 variables that are modified by mark_to_drop() or mark_to_pass()? I would not ask, except this was an issue for several years with v1model's mark_to_drop(), and why it was changed to take a parameter later. It appears you have nothing that corresponds to standard_metadata, so it would make sense if in your architecture, mark_to_drop() and mark_to_pass() only modify state "hidden from the user's P4 program" inside the architecture implementation.

Another good thing to document about these extern functions is which of the architecture's parsers/controls they are allowed to be called in, e.g. maybe you intended something like "mark_to_drop() and mark_to_pass() may only be called in the filter control".

Comments in the p4include/v1model.p4 file would be good to look at for examples. If any of them happen to match the behavior you provide (e.g. that might be true for your Register extern compared to v1model's register extern), feel free to copy and paste whatever is accurate for your architecture.


/*
* The uBPF target can currently pass the packet or drop it.
* By default, all packets are passed.
* The mark_to_pass() extern can be used to mark a packet to be passed (it cancels previous mark_to_drop() action).
* The mark_to_pass() modifies only the state hidden from the user's P4 program.
* mark_to_pass() should be called only in the 'pipe' control.
*/
extern void mark_to_pass();


extern Register<T, S> {
/***
* A Register object is created by calling its constructor.
* You must provide a size of Register. The size specifies
* the maximum number of entries stored by Register.
* After constructing the Register object, you can use it in
* both actions or apply blocks.
* The Register is not intialized when created.
*/
Register(bit<32> size);

/***
* read() reads the state (T) of the register array stored at the
* specified index S, and returns it as the value written to the
* result parameter.
*
* @param index The index of the register array element to be
* read, normally a value in the range [0, size-1].
* @return Returns result of type T. Only types T that are bit<W>
* are currently supported. When index is in range, the value of
* result becomes the value read from the register
* array element. When index >= size, the final
* value of result is not specified, and should be
* ignored by the caller.
*/
T read (in S index);


void write (in S index, in T value);
}

/*
* The extern used to get the current timestamp in nanoseconds.
*/
extern bit<48> ubpf_time_get_ns();


enum HashAlgorithm {
lookup3
}

/***
* Calculate a hash function of the value specified by the data
* parameter. Due to the limitation of uBPF back-end the maximum width of data is bit<64>.
*
* Note that the types of all of the parameters may be the same as, or
* different from, each other, and thus their bit widths are allowed
* to be different.
*
* Note that the result will have always the bit<32> width.
*
* @param D Must be a tuple type where all the fields are bit-fields (type bit<W> or int<W>) or varbits.
* Maximum width of D is 64 bit (limitation of uBPF back-end).
*/
extern void hash<D>(out bit<32> result, in HashAlgorithm algo, in D data);

#endif
/*
* Architecture.
*
* M must be a struct.
*
* H must be a struct where every one of its members is of type
* header, header stack, or header_union.
*/

parser parse<H, M>(packet_in packet, out H headers, inout M meta);
control pipeline<H, M>(inout H headers, inout M meta);

/*
* The only legal statements in the body of the deparser control are:
* calls to the packet_out.emit() method.
*/
@deparser
control deparser<H>(packet_out b, in H headers);

package ubpf<H, M>(parse<H, M> prs,
pipeline<H, M> p,
deparser<H> dprs);

#endif /* _UBPF_MODEL_P4_ */

4 changes: 2 additions & 2 deletions backends/ubpf/run-ubpf-test.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ def compare_output(self):
c_differences = self.compare(options.cfilepath, reference_c_filepath)

reference_h_filepath = os.path.join(options.referencedir, options.cfilename).replace(".c", ".h")
compiled_c_filepath = options.cfilepath.replace(".c", ".h")
h_differences = self.compare(compiled_c_filepath, reference_h_filepath)
compiled_h_filepath = options.cfilepath.replace(".c", ".h")
h_differences = self.compare(compiled_h_filepath, reference_h_filepath)

return not c_differences and not h_differences

Expand Down
39 changes: 38 additions & 1 deletion backends/ubpf/target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ namespace UBPF {
void UbpfTarget::emitTableLookup(Util::SourceCodeBuilder *builder,
cstring tblName,
cstring key,
cstring value) const {
UNUSED cstring value) const {
builder->appendFormat("ubpf_map_lookup(&%s, &%s)",
tblName.c_str(), key.c_str());
}
Expand Down Expand Up @@ -68,4 +68,41 @@ namespace UBPF {
builder->appendFormat("ubpf_packet_data(%s)", ctxVar.c_str());
}

void UbpfTarget::emitUbpfHelpers(EBPF::CodeBuilder *builder) const {
builder->append(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks a bit brittle. Is there a guarantee that the numbers associated with these functions will never change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a strict requirement, which is specific to target that we use (P4rt-OVS). Perhaps, it could be generalized in the future, but at the moment the BPF program would not work otherwise.

"static void *(*ubpf_map_lookup)(const void *, const void *) = (void *)1;\n"
"static int (*ubpf_map_update)(void *, const void *, void *) = (void *)2;\n"
"static int (*ubpf_map_delete)(void *, const void *) = (void *)3;\n"
"static int (*ubpf_map_add)(void *, const void *) = (void *)4;\n"
"static uint64_t (*ubpf_time_get_ns)() = (void *)5;\n"
"static uint32_t (*ubpf_hash)(const void *, uint64_t) = (void *)6;\n"
"static void (*ubpf_printf)(const char *fmt, ...) = (void *)7;\n"
"static void *(*ubpf_packet_data)(const void *) = (void *)9;\n"
"static void *(*ubpf_adjust_head)(const void *, uint64_t) = (void *)8;\n"
"\n");
builder->newline();
builder->appendLine(
"#define write_partial(a, w, s, v) do { *((uint8_t*)a) = ((*((uint8_t*)a)) "
"& ~(BPF_MASK(uint8_t, w) << s)) | (v << s) ; } while (0)");
builder->appendLine("#define write_byte(base, offset, v) do { "
"*(uint8_t*)((base) + (offset)) = (v); "
"} while (0)");
builder->newline();
builder->append("static uint32_t\n"
"bpf_htonl(uint32_t val) {\n"
" return htonl(val);\n"
"}");
builder->newline();
builder->append("static uint16_t\n"
"bpf_htons(uint16_t val) {\n"
" return htons(val);\n"
"}");
builder->newline();
builder->append("static uint64_t\n"
"bpf_htonll(uint64_t val) {\n"
" return htonll(val);\n"
"}\n");
builder->newline();
}

}
74 changes: 36 additions & 38 deletions backends/ubpf/tests/testdata/test-simple-firewall.p4
Original file line number Diff line number Diff line change
Expand Up @@ -121,47 +121,45 @@ control pipe(inout Headers_t headers, inout metadata meta) {

apply {
if (headers.tcp.isValid()) {
@atomic {
if (headers.ipv4.srcAddr < headers.ipv4.dstAddr) {
hash(meta.conn_id, HashAlgorithm.lookup3, { headers.ipv4.srcAddr, headers.ipv4.dstAddr });
} else {
hash(meta.conn_id, HashAlgorithm.lookup3, { headers.ipv4.dstAddr, headers.ipv4.srcAddr });
}
if (headers.ipv4.srcAddr < headers.ipv4.dstAddr) {
hash(meta.conn_id, HashAlgorithm.lookup3, { headers.ipv4.srcAddr, headers.ipv4.dstAddr });
} else {
hash(meta.conn_id, HashAlgorithm.lookup3, { headers.ipv4.dstAddr, headers.ipv4.srcAddr });
}

meta.connInfo.s = conn_state.read(meta.conn_id);
meta.connInfo.srv_addr = conn_srv_addr.read(meta.conn_id);
if (meta.connInfo.s == 0 || meta.connInfo.srv_addr == 0) {
if (headers.tcp.syn == 1 && headers.tcp.ack == 0) {
// It's a SYN
update_conn_info(SYNSENT, headers.ipv4.dstAddr);
meta.connInfo.s = conn_state.read(meta.conn_id);
meta.connInfo.srv_addr = conn_srv_addr.read(meta.conn_id);
if (meta.connInfo.s == 0 || meta.connInfo.srv_addr == 0) {
if (headers.tcp.syn == 1 && headers.tcp.ack == 0) {
// It's a SYN
update_conn_info(SYNSENT, headers.ipv4.dstAddr);
}
} else if (meta.connInfo.srv_addr == headers.ipv4.srcAddr) {
if (meta.connInfo.s == SYNSENT) {
if (headers.tcp.syn == 1 && headers.tcp.ack == 1) {
// It's a SYN-ACK
update_conn_state(SYNACKED);
}
} else if (meta.connInfo.srv_addr == headers.ipv4.srcAddr) {
if (meta.connInfo.s == SYNSENT) {
if (headers.tcp.syn == 1 && headers.tcp.ack == 1) {
// It's a SYN-ACK
update_conn_state(SYNACKED);
}
} else if (meta.connInfo.s == SYNACKED) {
_drop();
return;
} else if (meta.connInfo.s == ESTABLISHED) {
if (headers.tcp.fin == 1 && headers.tcp.ack == 1) {
update_conn_info(0, 0); // clear register entry
}
} else if (meta.connInfo.s == SYNACKED) {
_drop();
return;
} else if (meta.connInfo.s == ESTABLISHED) {
if (headers.tcp.fin == 1 && headers.tcp.ack == 1) {
update_conn_info(0, 0); // clear register entry
}
}
} else {
if (meta.connInfo.s == SYNSENT) {
_drop();
return;
} else if (meta.connInfo.s == SYNACKED) {
if (headers.tcp.syn == 0 && headers.tcp.ack == 1) {
// It's a ACK
update_conn_state(ESTABLISHED);
}
} else {
if (meta.connInfo.s == SYNSENT) {
_drop();
return;
} else if (meta.connInfo.s == SYNACKED) {
if (headers.tcp.syn == 0 && headers.tcp.ack == 1) {
// It's a ACK
update_conn_state(ESTABLISHED);
}
} else if (meta.connInfo.s == ESTABLISHED) {
if (headers.tcp.fin == 1 && headers.tcp.ack == 1) {
update_conn_info(0, 0); // clear register entry
}
} else if (meta.connInfo.s == ESTABLISHED) {
if (headers.tcp.fin == 1 && headers.tcp.ack == 1) {
update_conn_info(0, 0); // clear register entry
}
}
}
Expand Down
21 changes: 0 additions & 21 deletions backends/ubpf/ubpfControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,47 +35,26 @@ namespace UBPF {
std::vector<cstring> pointerVariables;

explicit UBPFControlBodyTranslator(const UBPFControl *control);

virtual void adjustPacketHead(const IR::Expression *expression, bool add);

virtual void processMethod(const P4::ExternMethod *method);

virtual void processApply(const P4::ApplyMethod *method);

virtual void processFunction(const P4::ExternFunction *function);

bool preorder(const IR::PathExpression *expression) override;

bool preorder(const IR::MethodCallStatement *s) override;

bool preorder(const IR::MethodCallExpression *expression) override;

bool preorder(const IR::AssignmentStatement *a) override;

bool preorder(const IR::BlockStatement *s) override;

bool preorder(const IR::ExitStatement *) override;

bool preorder(const IR::ReturnStatement *) override;

bool preorder(const IR::IfStatement *statement) override;

bool preorder(const IR::SwitchStatement *statement) override;

bool preorder(const IR::Operation_Binary *b) override;

bool comparison(const IR::Operation_Relation *b);

bool preorder(const IR::Member *expression) override;

void addPadding(std::vector<cstring> &paddingInitializers, unsigned int remainingBits, int paddingIndex) const;

cstring createHashKeyInstance(const P4::ExternFunction *function);

void appendValueAtOperator(const IR::AssignmentStatement *a) const;

void emitAssignmentStatement(const IR::AssignmentStatement *a);

bool emitRegisterRead(const IR::AssignmentStatement *a, const IR::MethodCallExpression *method);
};

Expand Down
6 changes: 3 additions & 3 deletions backends/ubpf/ubpfDeparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ namespace UBPF {
EBPF::EBPFType *type) {

unsigned widthToEmit = dynamic_cast<EBPF::IHasWidth *>(type)->widthInBits();
osinstom marked this conversation as resolved.
Show resolved Hide resolved
unsigned loadSize;

unsigned loadSize = 0;
cstring swap = "";
if (widthToEmit <= 8) {
loadSize = 8;
Expand Down Expand Up @@ -153,7 +154,6 @@ namespace UBPF {
return;
}

auto program = deparser->program;
builder->emitIndent();
builder->append("if (");
visit(expr);
Expand Down Expand Up @@ -202,7 +202,7 @@ namespace UBPF {
bool UBPFDeparser::build() {
auto pl = controlBlock->container->type->applyParams;
if (pl->size() != 2) {
::error("Expected deparser to have exactly 2 parameters");
::error("%1%: Expected deparser to have exactly 2 parameters", controlBlock->getNode());
return false;
}

Expand Down
Loading