Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
B
bcc
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
bcc
Commits
0bde502c
Commit
0bde502c
authored
Sep 25, 2015
by
Suchakra Sharma
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into open_probes
parents
416613aa
b1e3b670
Changes
36
Hide whitespace changes
Inline
Side-by-side
Showing
36 changed files
with
4071 additions
and
35 deletions
+4071
-35
src/cc/export/helpers.h
src/cc/export/helpers.h
+14
-0
src/cc/frontends/clang/b_frontend_action.cc
src/cc/frontends/clang/b_frontend_action.cc
+51
-8
src/cc/frontends/clang/b_frontend_action.h
src/cc/frontends/clang/b_frontend_action.h
+2
-0
src/cc/frontends/p4/README.md
src/cc/frontends/p4/README.md
+380
-0
src/cc/frontends/p4/compiler/README.txt
src/cc/frontends/p4/compiler/README.txt
+4
-0
src/cc/frontends/p4/compiler/compilationException.py
src/cc/frontends/p4/compiler/compilationException.py
+34
-0
src/cc/frontends/p4/compiler/ebpfAction.py
src/cc/frontends/p4/compiler/ebpfAction.py
+382
-0
src/cc/frontends/p4/compiler/ebpfConditional.py
src/cc/frontends/p4/compiler/ebpfConditional.py
+118
-0
src/cc/frontends/p4/compiler/ebpfCounter.py
src/cc/frontends/p4/compiler/ebpfCounter.py
+116
-0
src/cc/frontends/p4/compiler/ebpfInstance.py
src/cc/frontends/p4/compiler/ebpfInstance.py
+87
-0
src/cc/frontends/p4/compiler/ebpfParser.py
src/cc/frontends/p4/compiler/ebpfParser.py
+427
-0
src/cc/frontends/p4/compiler/ebpfProgram.py
src/cc/frontends/p4/compiler/ebpfProgram.py
+497
-0
src/cc/frontends/p4/compiler/ebpfScalarType.py
src/cc/frontends/p4/compiler/ebpfScalarType.py
+84
-0
src/cc/frontends/p4/compiler/ebpfStructType.py
src/cc/frontends/p4/compiler/ebpfStructType.py
+128
-0
src/cc/frontends/p4/compiler/ebpfTable.py
src/cc/frontends/p4/compiler/ebpfTable.py
+398
-0
src/cc/frontends/p4/compiler/ebpfType.py
src/cc/frontends/p4/compiler/ebpfType.py
+30
-0
src/cc/frontends/p4/compiler/p4toEbpf.py
src/cc/frontends/p4/compiler/p4toEbpf.py
+110
-0
src/cc/frontends/p4/compiler/programSerializer.py
src/cc/frontends/p4/compiler/programSerializer.py
+64
-0
src/cc/frontends/p4/compiler/target.py
src/cc/frontends/p4/compiler/target.py
+171
-0
src/cc/frontends/p4/compiler/typeFactory.py
src/cc/frontends/p4/compiler/typeFactory.py
+33
-0
src/cc/frontends/p4/scope.png
src/cc/frontends/p4/scope.png
+0
-0
src/cc/frontends/p4/test/README.txt
src/cc/frontends/p4/test/README.txt
+16
-0
src/cc/frontends/p4/test/cleanup.sh
src/cc/frontends/p4/test/cleanup.sh
+11
-0
src/cc/frontends/p4/test/endToEndTest.py
src/cc/frontends/p4/test/endToEndTest.py
+375
-0
src/cc/frontends/p4/test/testP4toEbpf.py
src/cc/frontends/p4/test/testP4toEbpf.py
+84
-0
src/cc/frontends/p4/test/testoutputs/.empty
src/cc/frontends/p4/test/testoutputs/.empty
+0
-0
src/cc/frontends/p4/test/testprograms/arrayKey.p4
src/cc/frontends/p4/test/testprograms/arrayKey.p4
+34
-0
src/cc/frontends/p4/test/testprograms/bitfields.p4
src/cc/frontends/p4/test/testprograms/bitfields.p4
+66
-0
src/cc/frontends/p4/test/testprograms/compositeArray.p4
src/cc/frontends/p4/test/testprograms/compositeArray.p4
+46
-0
src/cc/frontends/p4/test/testprograms/compositeKey.p4
src/cc/frontends/p4/test/testprograms/compositeKey.p4
+72
-0
src/cc/frontends/p4/test/testprograms/do_nothing.p4
src/cc/frontends/p4/test/testprograms/do_nothing.p4
+36
-0
src/cc/frontends/p4/test/testprograms/simple.p4
src/cc/frontends/p4/test/testprograms/simple.p4
+74
-0
src/python/bcc/__init__.py
src/python/bcc/__init__.py
+49
-23
tests/cc/CMakeLists.txt
tests/cc/CMakeLists.txt
+2
-0
tests/cc/test_histogram.py
tests/cc/test_histogram.py
+74
-0
tools/biolatency
tools/biolatency
+2
-4
No files found.
src/cc/export/helpers.h
View file @
0bde502c
...
...
@@ -36,6 +36,7 @@ struct _name##_table_t { \
int (*update) (_key_type *, _leaf_type *); \
int (*delete) (_key_type *); \
void (*call) (void *, int index); \
void (*increment) (_key_type); \
_leaf_type data[_max_entries]; \
}; \
__attribute__((section("maps/" _table_type))) \
...
...
@@ -55,6 +56,19 @@ struct _name##_table_t _name
#define BPF_HASH(...) \
BPF_HASHX(__VA_ARGS__, BPF_HASH3, BPF_HASH2, BPF_HASH1)(__VA_ARGS__)
#define BPF_HIST1(_name) \
BPF_TABLE("histogram", int, u64, _name, 64)
#define BPF_HIST2(_name, _key_type) \
BPF_TABLE("histogram", _key_type, u64, _name, 64)
#define BPF_HIST3(_name, _key_type, _size) \
BPF_TABLE("histogram", _key_type, u64, _name, _size)
#define BPF_HISTX(_1, _2, _3, NAME, ...) NAME
// Define a histogram, some arguments optional
// BPF_HISTOGRAM(name, key_type=int, size=64)
#define BPF_HISTOGRAM(...) \
BPF_HISTX(__VA_ARGS__, BPF_HIST3, BPF_HIST2, BPF_HIST1)(__VA_ARGS__)
// packet parsing state machine helpers
#define cursor_advance(_cursor, _len) \
({ void *_tmp = _cursor; _cursor += _len; _tmp; })
...
...
src/cc/frontends/clang/b_frontend_action.cc
View file @
0bde502c
...
...
@@ -67,9 +67,11 @@ bool BMapDeclVisitor::VisitRecordDecl(RecordDecl *D) {
for
(
auto
F
:
D
->
getDefinition
()
->
fields
())
{
result_
+=
"["
;
if
(
F
->
getType
()
->
isPointerType
())
result_
+=
"
\"
unsigned long long
\"
"
;
result_
+=
"
\"
"
+
F
->
getName
().
str
()
+
"
\"
,
\"
unsigned long long
\"
"
;
else
TraverseDecl
(
F
);
if
(
const
ConstantArrayType
*
T
=
dyn_cast
<
ConstantArrayType
>
(
F
->
getType
()))
result_
+=
", ["
+
T
->
getSize
().
toString
(
10
,
false
)
+
"]"
;
if
(
F
->
isBitField
())
result_
+=
", "
+
to_string
(
F
->
getBitWidthValue
(
C
));
result_
+=
"], "
;
...
...
@@ -158,6 +160,23 @@ bool ProbeVisitor::VisitBinaryOperator(BinaryOperator *E) {
}
return
true
;
}
bool
ProbeVisitor
::
VisitUnaryOperator
(
UnaryOperator
*
E
)
{
if
(
E
->
getOpcode
()
!=
UO_Deref
)
return
true
;
if
(
memb_visited_
.
find
(
E
)
!=
memb_visited_
.
end
())
return
true
;
if
(
!
ProbeChecker
(
E
,
ptregs_
).
needs_probe
())
return
true
;
memb_visited_
.
insert
(
E
);
Expr
*
sub
=
E
->
getSubExpr
();
string
rhs
=
rewriter_
.
getRewrittenText
(
SourceRange
(
sub
->
getLocStart
(),
sub
->
getLocEnd
()));
string
text
;
text
=
"({ typeof("
+
E
->
getType
().
getAsString
()
+
") _val; memset(&_val, 0, sizeof(_val));"
;
text
+=
" bpf_probe_read(&_val, sizeof(_val), (u64)"
;
text
+=
rhs
+
"); _val; })"
;
rewriter_
.
ReplaceText
(
SourceRange
(
E
->
getLocStart
(),
E
->
getLocEnd
()),
text
);
return
true
;
}
bool
ProbeVisitor
::
VisitMemberExpr
(
MemberExpr
*
E
)
{
if
(
memb_visited_
.
find
(
E
)
!=
memb_visited_
.
end
())
return
true
;
...
...
@@ -194,7 +213,7 @@ bool ProbeVisitor::VisitMemberExpr(MemberExpr *E) {
}
BTypeVisitor
::
BTypeVisitor
(
ASTContext
&
C
,
Rewriter
&
rewriter
,
vector
<
TableDesc
>
&
tables
)
:
C
(
C
),
rewriter_
(
rewriter
),
out_
(
llvm
::
errs
()),
tables_
(
tables
)
{
:
C
(
C
),
diag_
(
C
.
getDiagnostics
()),
rewriter_
(
rewriter
),
out_
(
llvm
::
errs
()),
tables_
(
tables
)
{
}
bool
BTypeVisitor
::
VisitFunctionDecl
(
FunctionDecl
*
D
)
{
...
...
@@ -276,7 +295,7 @@ bool BTypeVisitor::VisitCallExpr(CallExpr *Call) {
string
map_update_policy
=
"BPF_ANY"
;
string
txt
;
if
(
memb_name
==
"lookup_or_init"
)
{
string
map_update_policy
=
"BPF_NOEXIST"
;
map_update_policy
=
"BPF_NOEXIST"
;
string
name
=
Ref
->
getDecl
()
->
getName
();
string
arg0
=
rewriter_
.
getRewrittenText
(
SourceRange
(
Call
->
getArg
(
0
)
->
getLocStart
(),
Call
->
getArg
(
0
)
->
getLocEnd
()));
...
...
@@ -291,6 +310,19 @@ bool BTypeVisitor::VisitCallExpr(CallExpr *Call) {
txt
+=
" if (!leaf) return 0;"
;
txt
+=
"}"
;
txt
+=
"leaf;})"
;
}
else
if
(
memb_name
==
"increment"
)
{
string
name
=
Ref
->
getDecl
()
->
getName
();
string
arg0
=
rewriter_
.
getRewrittenText
(
SourceRange
(
Call
->
getArg
(
0
)
->
getLocStart
(),
Call
->
getArg
(
0
)
->
getLocEnd
()));
string
lookup
=
"bpf_map_lookup_elem_(bpf_pseudo_fd(1, "
+
fd
+
")"
;
string
update
=
"bpf_map_update_elem_(bpf_pseudo_fd(1, "
+
fd
+
")"
;
txt
=
"({ typeof("
+
name
+
".key) _key = "
+
arg0
+
"; "
;
if
(
table_it
->
type
==
BPF_MAP_TYPE_HASH
)
{
txt
+=
"typeof("
+
name
+
".leaf) _zleaf; memset(&_zleaf, 0, sizeof(_zleaf)); "
;
txt
+=
update
+
", &_key, &_zleaf, BPF_NOEXIST); "
;
}
txt
+=
"typeof("
+
name
+
".leaf) *_leaf = "
+
lookup
+
", &_key); "
;
txt
+=
"if (_leaf) (*_leaf)++; })"
;
}
else
{
if
(
memb_name
==
"lookup"
)
{
prefix
=
"bpf_map_lookup_elem"
;
...
...
@@ -463,11 +495,21 @@ bool BTypeVisitor::VisitVarDecl(VarDecl *Decl) {
++
i
;
}
bpf_map_type
map_type
=
BPF_MAP_TYPE_UNSPEC
;
if
(
A
->
getName
()
==
"maps/hash"
)
if
(
A
->
getName
()
==
"maps/hash"
)
{
map_type
=
BPF_MAP_TYPE_HASH
;
else
if
(
A
->
getName
()
==
"maps/array"
)
}
else
if
(
A
->
getName
()
==
"maps/array"
)
{
map_type
=
BPF_MAP_TYPE_ARRAY
;
else
if
(
A
->
getName
()
==
"maps/prog"
)
{
}
else
if
(
A
->
getName
()
==
"maps/histogram"
)
{
if
(
table
.
key_desc
==
"
\"
int
\"
"
)
map_type
=
BPF_MAP_TYPE_ARRAY
;
else
map_type
=
BPF_MAP_TYPE_HASH
;
if
(
table
.
leaf_desc
!=
"
\"
unsigned long long
\"
"
)
{
unsigned
diag_id
=
diag_
.
getCustomDiagID
(
DiagnosticsEngine
::
Error
,
"histogram leaf type must be u64, got %0"
);
diag_
.
Report
(
Decl
->
getLocStart
(),
diag_id
)
<<
table
.
leaf_desc
;
}
}
else
if
(
A
->
getName
()
==
"maps/prog"
)
{
struct
utsname
un
;
if
(
uname
(
&
un
)
==
0
)
{
int
major
=
0
,
minor
=
0
;
...
...
@@ -485,8 +527,9 @@ bool BTypeVisitor::VisitVarDecl(VarDecl *Decl) {
table
.
type
=
map_type
;
table
.
fd
=
bpf_create_map
(
map_type
,
table
.
key_size
,
table
.
leaf_size
,
table
.
max_entries
);
if
(
table
.
fd
<
0
)
{
C
.
getDiagnostics
().
Report
(
Decl
->
getLocStart
(),
diag
::
err_expected
)
<<
"valid bpf fd"
;
unsigned
diag_id
=
C
.
getDiagnostics
().
getCustomDiagID
(
DiagnosticsEngine
::
Error
,
"could not open bpf map: %0"
);
C
.
getDiagnostics
().
Report
(
Decl
->
getLocStart
(),
diag_id
)
<<
strerror
(
errno
);
return
false
;
}
tables_
.
push_back
(
std
::
move
(
table
));
...
...
src/cc/frontends/clang/b_frontend_action.h
View file @
0bde502c
...
...
@@ -71,6 +71,7 @@ class BTypeVisitor : public clang::RecursiveASTVisitor<BTypeVisitor> {
private:
clang
::
ASTContext
&
C
;
clang
::
DiagnosticsEngine
&
diag_
;
clang
::
Rewriter
&
rewriter_
;
/// modifications to the source go into this class
llvm
::
raw_ostream
&
out_
;
/// for debugging
std
::
vector
<
TableDesc
>
&
tables_
;
/// store the open FDs
...
...
@@ -85,6 +86,7 @@ class ProbeVisitor : public clang::RecursiveASTVisitor<ProbeVisitor> {
bool
VisitVarDecl
(
clang
::
VarDecl
*
Decl
);
bool
VisitCallExpr
(
clang
::
CallExpr
*
Call
);
bool
VisitBinaryOperator
(
clang
::
BinaryOperator
*
E
);
bool
VisitUnaryOperator
(
clang
::
UnaryOperator
*
E
);
bool
VisitMemberExpr
(
clang
::
MemberExpr
*
E
);
void
set_ptreg
(
clang
::
Decl
*
D
)
{
ptregs_
.
insert
(
D
);
}
private:
...
...
src/cc/frontends/p4/README.md
0 → 100644
View file @
0bde502c
# Compiling P4 to EBPF
Mihai Budiu - mbudiu@barefootnetworks.com
September 22, 2015
## Abstract
This document describes a prototype compiler that translates programs
written in the P4 programming languages to eBPF programs. The
translation is performed by generating programs written in a subset of
the C programming language, that are converted to EBPF using the BPF
Compiler Collection tools.
The compiler code is licensed under an [Apache v2.0 license]
(http://www.apache.org/licenses/LICENSE-2.0.html).
## Preliminaries
In this section we give a brief overview of P4 and EBPF. A detailed
treatment of these topics is outside the scope of this text.
### P4
P4 (http://p4.org) is a domain-specific programming language for
specifying the behavior of the dataplanes of network-forwarding
elements. The name of the programming language comes from the title
of a paper published in the proceedings of SIGCOMM Computer
Communications Review in 2014:
http://www.sigcomm.org/ccr/papers/2014/July/0000000.0000004:
"Programming Protocol-Independent Packet Processors".
P4 itself is protocol-independent but allows programmers to express a
rich set of data plane behaviors and protocols. The core P4
abstractions are:
*
Header definitions describe the format (the set of fields and their
sizes) of each header within a packet.
*
Parse graphs (finite-state machines) describe the permitted header
sequences within received packets.
*
Tables associate keys to actions. P4 tables generalize traditional
forwarding tables; they can be used to implement routing tables,
flow lookup tables, access-control lists, etc.
*
Actions describe how packet header fields and metadata are manipulated.
*
Match-action units stitch together tables and actions, and perform
the following sequence of operations:
*
Construct lookup keys from packet fields or computed metadata,
*
Use the constructed lookup key to index into tables, choosing an
action to execute,
*
Finally, execute the selected action.
*
Control flow is expressed as an imperative program describing the
data-dependent packet processing within a pipeline, including the
data-dependent sequence of match-action unit invocations.
P4 programs describe the behavior of network-processing dataplanes. A
P4 program is designed to operate in concert with a separate
*
control
plane
*
program. The control plane is responsible for managing at
runtime the contents of the P4 tables. P4 cannot be used to specify
control-planes; however, a P4 program implicitly specifies the
interface between the data-plane and the control-plane.
The P4 language is under active development; the current stable
version is 1.0.2 (see http://p4.org/spec); a reference implementation
of a compiler and associated tools is freely available using a Apache
2 open-source license (see http://p4.org/code).
### EBPF
#### Safe code
EBPF is a acronym that stands for Extended Berkeley Packet Filters.
In essence EBPF is a low-level programming language (similar to
machine code); EBPF programs are traditionally executed by a virtual
machine that resides in the Linux kernel. EBPF programs can be
inserted and removed from a live kernel using dynamic code
instrumentation. The main feature of EBPF programs is their
*
static
safety
*
: prior to execution all EBPF programs have to be validated as
being safe, and unsafe programs cannot be executed. A safe program
provably cannot compromise the machine it is running on:
*
it can only access a restricted memory region (on the local stack)
*
it can run only for a limited amount of time; during execution it
cannot block, sleep or take any locks
*
it cannot use any kernel resources with the exception of a limited
set of kernel services which have been specifically whitelisted,
including operations to manipulate tables (described below)
#### Kernel hooks
EBPF programs are inserted into the kernel using
*hooks*
. There are
several types of hooks available:
*
any function entry point in the kernel can act as a hook; attaching
an EBPF program to a function
`foo()`
will cause the EBPF program to
execute every time some kernel thread executes
`foo()`
.
*
EBPF programs can also be attached using the Linux Traffic Control
(TC) subsystem, in the network packet processing datapath. Such
programs can be used as TC classifiers and actions.
*
EBPF programs can also be attached to sockets or network interfaces.
In this case they can be used for processing packets that flow
through the socket/interface.
EBPF programs can be used for many purposes; the main use cases are
dynamic tracing and monitoring, and packet procesisng. We are mostly
interested in the latter use case in this document.
#### EBPF Tables
The EBPF runtime exposes a bi-directional kernel-userspace data
communication channel, called
*tables*
(also called maps in some EBPF
documents and code samples). EBPF tables are essentially key-value
stores, where keys and values are arbitrary fixed-size bitstrings.
The key width, value width and table size (maximum number of entries
that can be stored) are declared statically, at table creation time.
In user-space tables handles are exposed as file descriptors. Both
user- and kernel-space programs can manipulate tables, by inserting,
deleting, looking up, modifying, and enumerating entries in a table.
In kernel space the keys and values are exposed as pointers to the raw
underlying data stored in the table, whereas in user-space the
pointers point to copies of the data.
#### Concurrency
An important aspect to understand related to EBPF is the execution
model. An EBPF program is triggered by a kernel hook; multiple
instances of the same kernel hook can be running simultaneously on
different cores.
Each table however has a single instances across all the cores. A
single table may be accessed simultaneously by multiple instances of
the same EBPF program running as separate kernel threads on different
cores. EBPF tables are native kernel objects, and access to the table
contents is protected using the kernel RCU mechanism. This makes
access to table entries safe under concurrent execution; for example,
the memory associated to a value cannot be accidentally freed while an
EBPF program holds a pointer to the respective value. However,
accessing tables is prone to data races; since EBPF programs cannot
use locks, some of these races often cannot be avoided.
EBPF and the associated tools are also under active development, and
new capabilities are added frequently. The P4 compiler generates code
that can be compiled using the BPF Compiler Collection (BCC)
(https://github.com/iovisor/bcc)
## Compiling P4 to EBPF
From the above description it is apparent that the P4 and EBPF
programming languages have different expressive powers. However,
there is a significant overlap in their capabilities, in particular,
in the domain of network packet processing. The following image
illustrates the situation:
![
P4 and EBPF overlap in capabilities
](
scope.png
)
We expect that the overlapping region will grow in size as both P4 and
EBPF continue to mature.
The current version of the P4 to EBPF compiler translates programs
written in the version 1.1 of the P4 programming language to programs
written in a restricted subset of C. The subset of C is chosen such
that it should be compilable to EBPF using BCC.
```
-------------- -------
P4 ---> | P4-to-EBPF | ---> C ----> | BCC | --> EBPF
-------------- -------
```
The P4 program only describes the packet processing
*data plane*
, that
runs in the Linux kernel. The
*control plane*
must be separately
implemented by the user. The BCC tools simplify this task
considerably, by generating C and/or Python APIs that expose the
dataplane/control-plane APIs.
### Dependencies
EBPF programs require a Linux kernel with version 4.2 or newer.
In order to use the P4 to EBPF compiler the following software must be installed:
*
The compiler itself is written in the Python (v2.x) programming
language.
*
the P4 compiler front-end: (https://github.com/p4lang/p4-hlir).
This is required for parsing the P4 programs.
*
the BCC compiler collection tools: (https://github.com/iovisor/bcc).
This is required for compiling the generated code. Also, BCC comes
with a set of Python utilities which can be used to implement
control-plane programs that operate in concert with the kernel EBPF
datapath.
The P4 to EBPF compiler generates code that is designed for being used
as a classifier using the Linux TC subsystem.
Furthermore, the test code provided is written using the Python (v3.x)
programming language and requires several Python packages to be
installed.
### Supported capabilities
The current version of the P4 to EBPF compiler supports a relatively
narrow subset of the P4 language, but still powerful enough to write
very complex packet filters and simple packet forwarding engines. In
the spirit of open-source "release early, release often", we expect
that the compiler's capabilities will improve gradually.
*
Packet filtering is peformed using the
`drop()`
action. Packets
that are not dropped will be forwarded.
*
Packet forwarding is performed by setting the
`standard_metadata.egress_port`
to the index of the destination
network interface
Here are some limitations imposed on the P4 programs:
*
Currently both the ingress and the egress P4 pipelines are executed
at the same hook (wherever the user chooses to insert the generated
EBPF program). In the future the compiler should probably generate
two separate EBPF programs.
*
arbirary parsers can be compiled, but the BCC compiler will reject
parsers that contain cycles
*
arithmetic on data wider than 32 bits is not supported
*
mutating the network packet does not work. The P4 programs allow
users to express packet header mutations; however, the generated
code does not currently include packet reassembly (i.e., a P4
"deparser"), which would store the mutated headers back into the
network packet. Some of this functionality could be implemented in
the future, but currently the EBPF interfaces to the kernel do not
permit arbitrary packet mutations.
*
EBPF does not offer support for ternary or LPM tables
*
cloning and recirculation and not supported, since the underlying
TC-based framework does not support some of this functionality.
*
meters and registers are not supported; only direct counters are
currently supported. EBPF can potentially support registers and
arbitrary counters, so these may appear in the future.
*
learning (i.e.
`generate_digest`
) is not implemented
### Translating P4 to C
To simplify the translation, the P4 programmer should refrain using
identifiers whose name starts with
`ebpf_`
.
The following table provides a brief summary of how each P4 construct
is mapped to a corresponding C construct:
#### Translating parsers
P4 Construct | C Translation
----------|------------
`header_type`
|
`struct`
type
`header`
|
`struct`
instance with an additional
`valid`
bit
`metadata`
|
`struct`
instance
parser state | code block
state transition |
`goto`
statement
`extract`
| load/shift/mask data from packet buffer
#### Translating match-action pipelines
P4 Construct | C Translation
----------|------------
table | 2 EBPF tables: second one used just for the default action
table key |
`struct`
type
table
`actions`
block | tagged
`union`
with all possible actions
`action`
arguments |
`struct`
table
`reads`
| EBPF table access
`action`
body | code block
table
`apply`
|
`switch`
statement
counters | additional EBPF table
### Code organization
The compiler code is organized in two folders:
*
`compiler`
: the complete compiler source code, in Python v2.x
The compiler entry point is
`p4toEbpf.py`
.
*
`test`
: testing code and data. There are two testing programs:
*
`testP4toEbpf.py`
: which compiles all P4 files in the testprograms folder
*
`endToEndTest.py`
: which compiles and executes the simple.p4
program, and includes a simple control plane
Currently the compiler contains no installation capabilities.
### Invoking the compiler
Invoking the compiler is just a matter of invoking the python program
with a suitable input P4 file:
```
p4toEbpf.py file.p4 -o file.c
```
#### Compiler options
The P4 compiler first runs the C preprocessor on the input P4 file.
Some of the command-line options are passed directly to the
preprocesor.
The following compiler options are available:
Option | Meaning
-------|--------
`-D macro`
| Option passed to C preprocessor
`-I path`
| Option passed to C preprocessor
`-U macro`
| Option passed to C preprocessor
`-g [router|filter]`
| Controls whether the generated code behaves like a router or a filter.
`-o outoutFile`
| writes the generated C code to the specified output file.
The
`-g`
option controls the nature of the generated code:
*
`-g filter`
generates a filter; the only P4 action that has an
effect is the
`drop()`
action. Setting metadata in P4 (e.g.,
`egress_port`
) has no effect.
*
`-g router`
generates a simple router; both
`drop()`
and
`egress_port`
impact packet processing.
#### Using the generated code
The resulting file contains the complete data structures, tables, and
a C function named
`ebpf_filter`
that implements the P4-specified
data-plane. This C file can be manipulated using the BCC tools;
please refer to the BCC project documentation and sample test files of
the P4 to EBPF source code for an in-depth understanding. A minimal
Python program that compiles and loads into the kernel the generated
file into EBPF is:
```
#!/usr/bin/env python3
from
bcc
import
BPF
b
=
BPF
(
src_file
=
"file.c"
,
debug
=
0
)
fn
=
b
.
load_func
(
"ebpf_filter"
,
BPF
.
SCHED_CLS
)
```
##### Connecting the generated program with the TC
The EBPF code that is generated is intended to be used as a classifier
attached to the ingress packet path using the Linux TC subsystem. The
same EBPF code should be attached to all interfaces. Note however
that all EBPF code instances share a single set of tables, which are
used to control the program behavior.
The following code fragment illustrates how the EBPF code can be
hooked up to the
`eth0`
interface using a Python program. (The
`fn`
variable is the one produced by the previous code fragment).
```
from pyroute2 import IPRoute
ipr = IPRoute()
interface_name="eth0"
if_index = ipr.link_lookup(ifname=interface_name)[0]
ipr.tc("add", "ingress", if_index, "ffff:")
ipr.tc("add-filter", "bpf", if_index, ":1", fd=fn.fd,
name=fn.name, parent="ffff:", action="ok", classid=1)
```
src/cc/frontends/p4/compiler/README.txt
0 → 100644
View file @
0bde502c
This folder contains an implementation of a simple compiler that
translates a programs written in a subset of P4 into C that can in
turn be compiled into EBPF using the IOVisor bcc compiler.
src/cc/frontends/p4/compiler/compilationException.py
0 → 100644
View file @
0bde502c
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
class
CompilationException
(
Exception
):
"""Signals an error during compilation"""
def
__init__
(
self
,
isBug
,
format
,
*
message
):
# isBug: indicates that this is a compiler bug
super
(
CompilationException
,
self
).
__init__
()
assert
isinstance
(
format
,
str
)
assert
isinstance
(
isBug
,
bool
)
self
.
message
=
message
self
.
format
=
format
self
.
isBug
=
isBug
def
show
(
self
):
# TODO: format this message nicely
return
self
.
format
.
format
(
*
self
.
message
)
class
NotSupportedException
(
Exception
):
archError
=
" not supported by EBPF"
def
__init__
(
self
,
format
,
*
message
):
super
(
NotSupportedException
,
self
).
__init__
()
assert
isinstance
(
format
,
str
)
self
.
message
=
message
self
.
format
=
format
def
show
(
self
):
# TODO: format this message nicely
return
(
self
.
format
+
NotSupportedException
.
archError
).
format
(
*
self
.
message
)
src/cc/frontends/p4/compiler/ebpfAction.py
0 → 100644
View file @
0bde502c
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from
p4_hlir.hlir
import
p4_action
,
p4_field
from
p4_hlir.hlir
import
p4_signature_ref
,
p4_header_instance
import
ebpfProgram
from
programSerializer
import
ProgramSerializer
from
compilationException
import
*
import
ebpfScalarType
import
ebpfCounter
import
ebpfType
import
ebpfInstance
class
EbpfActionData
(
object
):
def
__init__
(
self
,
name
,
argtype
):
self
.
name
=
name
self
.
argtype
=
argtype
class
EbpfActionBase
(
object
):
def
__init__
(
self
,
p4action
):
self
.
name
=
p4action
.
name
self
.
hliraction
=
p4action
self
.
builtin
=
False
self
.
arguments
=
[]
def
serializeArgumentsAsStruct
(
self
,
serializer
):
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"/* no arguments for {0} */"
,
self
.
name
)
serializer
.
newline
()
def
serializeBody
(
self
,
serializer
,
valueName
,
program
):
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"/* no body for {0} */"
,
self
.
name
)
serializer
.
newline
()
def
__str__
(
self
):
return
"EbpfAction({0})"
.
format
(
self
.
name
)
class
EbpfAction
(
EbpfActionBase
):
unsupported
=
[
# The following cannot be done in EBPF
"add_header"
,
"remove_header"
,
"execute_meter"
,
"clone_ingress_pkt_to_egress"
,
"clone_egress_pkt_to_egress"
,
"generate_digest"
,
"resubmit"
,
"modify_field_with_hash_based_offset"
,
"truncate"
,
"push"
,
"pop"
,
# The following could be done, but are not yet implemented
# The situation with copy_header is complicated,
# because we don't do checksums
"copy_header"
,
"count"
,
"register_read"
,
"register_write"
]
# noinspection PyUnresolvedReferences
def
__init__
(
self
,
p4action
,
program
):
super
(
EbpfAction
,
self
).
__init__
(
p4action
)
assert
isinstance
(
p4action
,
p4_action
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
self
.
builtin
=
False
self
.
invalid
=
False
# a leaf action which is never
# called from a table can be invalid.
for
i
in
range
(
0
,
len
(
p4action
.
signature
)):
param
=
p4action
.
signature
[
i
]
width
=
p4action
.
signature_widths
[
i
]
if
width
is
None
:
self
.
invalid
=
True
return
argtype
=
ebpfScalarType
.
EbpfScalarType
(
p4action
,
width
,
False
,
program
.
config
)
actionData
=
EbpfActionData
(
param
,
argtype
)
self
.
arguments
.
append
(
actionData
)
def
serializeArgumentsAsStruct
(
self
,
serializer
):
if
self
.
invalid
:
raise
CompilationException
(
True
,
"{0} Attempting to generate code for an invalid action"
,
self
.
hliraction
)
# Build a struct containing all action arguments.
serializer
.
emitIndent
()
serializer
.
append
(
"struct "
)
serializer
.
blockStart
()
assert
isinstance
(
serializer
,
ProgramSerializer
)
for
arg
in
self
.
arguments
:
assert
isinstance
(
arg
,
EbpfActionData
)
serializer
.
emitIndent
()
argtype
=
arg
.
argtype
assert
isinstance
(
argtype
,
ebpfType
.
EbpfType
)
argtype
.
declare
(
serializer
,
arg
.
name
,
False
)
serializer
.
endOfStatement
(
True
)
serializer
.
blockEnd
(
False
)
serializer
.
space
()
serializer
.
append
(
self
.
name
)
serializer
.
endOfStatement
(
True
)
def
serializeBody
(
self
,
serializer
,
dataContainer
,
program
):
if
self
.
invalid
:
raise
CompilationException
(
True
,
"{0} Attempting to generate code for an invalid action"
,
self
.
hliraction
)
# TODO: generate PARALLEL implementation
# dataContainer is a string containing the variable name
# containing the action data
assert
isinstance
(
serializer
,
ProgramSerializer
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
assert
isinstance
(
dataContainer
,
str
)
callee_list
=
self
.
hliraction
.
flat_call_sequence
for
e
in
callee_list
:
action
=
e
[
0
]
assert
isinstance
(
action
,
p4_action
)
arguments
=
e
[
1
]
assert
isinstance
(
arguments
,
list
)
self
.
serializeCallee
(
self
,
action
,
arguments
,
serializer
,
dataContainer
,
program
)
def
checkSize
(
self
,
call
,
args
,
program
):
size
=
None
for
a
in
args
:
if
a
is
None
:
continue
if
size
is
None
:
size
=
a
elif
a
!=
size
:
program
.
emitWarning
(
"{0}: Arguments do not have the same size {1} and {2}"
,
call
,
size
,
a
)
return
size
@
staticmethod
def
translateActionToOperator
(
actionName
):
if
actionName
==
"add"
or
actionName
==
"add_to_field"
:
return
"+"
elif
actionName
==
"bit_and"
:
return
"&"
elif
actionName
==
"bit_or"
:
return
"|"
elif
actionName
==
"bit_xor"
:
return
"^"
elif
actionName
==
"subtract"
or
actionName
==
"subtract_from_field"
:
return
"-"
else
:
raise
CompilationException
(
True
,
"Unexpected primitive action {0}"
,
actionName
)
def
serializeCount
(
self
,
caller
,
arguments
,
serializer
,
dataContainer
,
program
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
assert
isinstance
(
arguments
,
list
)
assert
len
(
arguments
)
==
2
counter
=
arguments
[
0
]
index
=
ArgInfo
(
arguments
[
1
],
caller
,
dataContainer
,
program
)
ctr
=
program
.
getCounter
(
counter
.
name
)
assert
isinstance
(
ctr
,
ebpfCounter
.
EbpfCounter
)
serializer
.
emitIndent
()
serializer
.
blockStart
()
# This is actually incorrect, since the key is not always an u32.
# This code is currently disabled
key
=
program
.
reservedPrefix
+
"index"
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"u32 {0} = {1};"
,
key
,
index
.
asString
)
serializer
.
newline
()
ctr
.
serializeCode
(
key
,
serializer
,
program
)
serializer
.
blockEnd
(
True
)
def
serializeCallee
(
self
,
caller
,
callee
,
arguments
,
serializer
,
dataContainer
,
program
):
if
self
.
invalid
:
raise
CompilationException
(
True
,
"{0} Attempting to generate code for an invalid action"
,
self
.
hliraction
)
assert
isinstance
(
serializer
,
ProgramSerializer
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
assert
isinstance
(
callee
,
p4_action
)
assert
isinstance
(
arguments
,
list
)
if
callee
.
name
in
EbpfAction
.
unsupported
:
raise
NotSupportedException
(
"{0}"
,
callee
)
# This is not yet ready
#if callee.name == "count":
# self.serializeCount(caller, arguments,
# serializer, dataContainer, program)
# return
serializer
.
emitIndent
()
args
=
self
.
transformArguments
(
arguments
,
caller
,
dataContainer
,
program
)
if
callee
.
name
==
"modify_field"
:
dst
=
args
[
0
]
src
=
args
[
1
]
size
=
self
.
checkSize
(
callee
,
[
a
.
widthInBits
()
for
a
in
args
],
program
)
if
size
is
None
:
raise
CompilationException
(
True
,
"Cannot infer width for arguments {0}"
,
callee
)
elif
size
<=
32
:
serializer
.
appendFormat
(
"{0} = {1};"
,
dst
.
asString
,
src
.
asString
)
else
:
if
not
dst
.
isLvalue
:
raise
NotSupportedException
(
"Constants wider than 32-bit: {0}({1})"
,
dst
.
caller
,
dst
.
asString
)
if
not
src
.
isLvalue
:
raise
NotSupportedException
(
"Constants wider than 32-bit: {0}({1})"
,
src
.
caller
,
src
.
asString
)
serializer
.
appendFormat
(
"memcpy(&{0}, &{1}, {2});"
,
dst
.
asString
,
src
.
asString
,
size
/
8
)
elif
(
callee
.
name
==
"add"
or
callee
.
name
==
"bit_and"
or
callee
.
name
==
"bit_or"
or
callee
.
name
==
"bit_xor"
or
callee
.
name
==
"subtract"
):
size
=
self
.
checkSize
(
callee
,
[
a
.
widthInBits
()
for
a
in
args
],
program
)
if
size
is
None
:
raise
CompilationException
(
True
,
"Cannot infer width for arguments {0}"
,
callee
)
if
size
>
32
:
raise
NotSupportedException
(
"{0}: Arithmetic on {1}-bits"
,
callee
,
size
)
op
=
EbpfAction
.
translateActionToOperator
(
callee
.
name
)
serializer
.
appendFormat
(
"{0} = {1} {2} {3};"
,
args
[
0
].
asString
,
args
[
1
].
asString
,
op
,
args
[
2
].
asString
)
elif
(
callee
.
name
==
"add_to_field"
or
callee
.
name
==
"subtract_from_field"
):
size
=
self
.
checkSize
(
callee
,
[
a
.
widthInBits
()
for
a
in
args
],
program
)
if
size
is
None
:
raise
CompilationException
(
True
,
"Cannot infer width for arguments {0}"
,
callee
)
if
size
>
32
:
raise
NotSupportedException
(
"{0}: Arithmetic on {1}-bits"
,
callee
,
size
)
op
=
EbpfAction
.
translateActionToOperator
(
callee
.
name
)
serializer
.
appendFormat
(
"{0} = {0} {1} {2};"
,
args
[
0
].
asString
,
op
,
args
[
1
].
asString
)
elif
callee
.
name
==
"no_op"
:
serializer
.
append
(
"/* noop */"
)
elif
callee
.
name
==
"drop"
:
serializer
.
appendFormat
(
"{0} = 1;"
,
program
.
dropBit
)
elif
callee
.
name
==
"push"
or
callee
.
name
==
"pop"
:
raise
CompilationException
(
True
,
"{0} push/pop not yet implemented"
,
callee
)
else
:
raise
CompilationException
(
True
,
"Unexpected primitive action {0}"
,
callee
)
serializer
.
newline
()
def
transformArguments
(
self
,
arguments
,
caller
,
dataContainer
,
program
):
result
=
[]
for
a
in
arguments
:
t
=
ArgInfo
(
a
,
caller
,
dataContainer
,
program
)
result
.
append
(
t
)
return
result
class
BuiltinAction
(
EbpfActionBase
):
def
__init__
(
self
,
p4action
):
super
(
BuiltinAction
,
self
).
__init__
(
p4action
)
self
.
builtin
=
True
def
serializeBody
(
self
,
serializer
,
valueName
,
program
):
# This is ugly; there should be a better way
if
self
.
name
==
"drop"
:
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0} = 1;"
,
program
.
dropBit
)
serializer
.
newline
()
else
:
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"/* no body for {0} */"
,
self
.
name
)
serializer
.
newline
()
class
ArgInfo
(
object
):
# noinspection PyUnresolvedReferences
# Represents an argument passed to an action
def
__init__
(
self
,
argument
,
caller
,
dataContainer
,
program
):
self
.
width
=
None
self
.
asString
=
None
self
.
isLvalue
=
True
self
.
caller
=
caller
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
assert
isinstance
(
caller
,
EbpfAction
)
if
isinstance
(
argument
,
int
):
self
.
asString
=
str
(
argument
)
self
.
isLvalue
=
False
# size is unknown
elif
isinstance
(
argument
,
p4_field
):
if
ebpfProgram
.
EbpfProgram
.
isArrayElementInstance
(
argument
.
instance
):
if
isinstance
(
argument
.
instance
.
index
,
int
):
index
=
"["
+
str
(
argument
.
instance
.
index
)
+
"]"
else
:
raise
CompilationException
(
True
,
"Unexpected index for array {0}"
,
argument
.
instance
.
index
)
stackInstance
=
program
.
getStackInstance
(
argument
.
instance
.
base_name
)
assert
isinstance
(
stackInstance
,
ebpfInstance
.
EbpfHeaderStack
)
fieldtype
=
stackInstance
.
basetype
.
getField
(
argument
.
name
)
self
.
width
=
fieldtype
.
widthInBits
()
self
.
asString
=
"{0}.{1}{3}.{2}"
.
format
(
program
.
headerStructName
,
stackInstance
.
name
,
argument
.
name
,
index
)
else
:
instance
=
program
.
getInstance
(
argument
.
instance
.
base_name
)
if
isinstance
(
instance
,
ebpfInstance
.
EbpfHeader
):
parent
=
program
.
headerStructName
else
:
parent
=
program
.
metadataStructName
fieldtype
=
instance
.
type
.
getField
(
argument
.
name
)
self
.
width
=
fieldtype
.
widthInBits
()
self
.
asString
=
"{0}.{1}.{2}"
.
format
(
parent
,
instance
.
name
,
argument
.
name
)
elif
isinstance
(
argument
,
p4_signature_ref
):
refarg
=
caller
.
arguments
[
argument
.
idx
]
self
.
asString
=
"{0}->u.{1}.{2}"
.
format
(
dataContainer
,
caller
.
name
,
refarg
.
name
)
self
.
width
=
caller
.
arguments
[
argument
.
idx
].
argtype
.
widthInBits
()
elif
isinstance
(
argument
,
p4_header_instance
):
# This could be a header array element
# Unfortunately for push and pop, the user mean the whole array,
# but the representation contains just the first element here.
# This looks like a bug in the HLIR.
if
ebpfProgram
.
EbpfProgram
.
isArrayElementInstance
(
argument
):
if
isinstance
(
argument
.
index
,
int
):
index
=
"["
+
str
(
argument
.
index
)
+
"]"
else
:
raise
CompilationException
(
True
,
"Unexpected index for array {0}"
,
argument
.
index
)
stackInstance
=
program
.
getStackInstance
(
argument
.
base_name
)
assert
isinstance
(
stackInstance
,
ebpfInstance
.
EbpfHeaderStack
)
fieldtype
=
stackInstance
.
basetype
self
.
width
=
fieldtype
.
widthInBits
()
self
.
asString
=
"{0}.{1}{2}"
.
format
(
program
.
headerStructName
,
stackInstance
.
name
,
index
)
else
:
instance
=
program
.
getInstance
(
argument
.
name
)
instancetype
=
instance
.
type
self
.
width
=
instancetype
.
widthInBits
()
self
.
asString
=
"{0}.{1}"
.
format
(
program
.
headerStructName
,
argument
.
name
)
else
:
raise
CompilationException
(
True
,
"Unexpected action argument {0}"
,
argument
)
def
widthInBits
(
self
):
return
self
.
width
src/cc/frontends/p4/compiler/ebpfConditional.py
0 → 100644
View file @
0bde502c
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from
p4_hlir.hlir
import
p4_conditional_node
,
p4_expression
from
p4_hlir.hlir
import
p4_header_instance
,
p4_field
from
programSerializer
import
ProgramSerializer
from
compilationException
import
CompilationException
import
ebpfProgram
import
ebpfInstance
class
EbpfConditional
(
object
):
@
staticmethod
def
translate
(
op
):
if
op
==
"not"
:
return
"!"
elif
op
==
"or"
:
return
"||"
elif
op
==
"and"
:
return
"&&"
return
op
def
__init__
(
self
,
p4conditional
,
program
):
assert
isinstance
(
p4conditional
,
p4_conditional_node
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
self
.
hlirconditional
=
p4conditional
self
.
name
=
p4conditional
.
name
def
emitNode
(
self
,
node
,
serializer
,
program
):
if
isinstance
(
node
,
p4_expression
):
self
.
emitExpression
(
node
,
serializer
,
program
,
False
)
elif
node
is
None
:
pass
elif
isinstance
(
node
,
int
):
serializer
.
append
(
node
)
elif
isinstance
(
node
,
p4_header_instance
):
header
=
program
.
getInstance
(
node
.
name
)
assert
isinstance
(
header
,
ebpfInstance
.
EbpfHeader
)
# TODO: stacks?
serializer
.
appendFormat
(
"{0}.{1}"
,
program
.
headerStructName
,
header
.
name
)
elif
isinstance
(
node
,
p4_field
):
instance
=
node
.
instance
einstance
=
program
.
getInstance
(
instance
.
name
)
if
isinstance
(
einstance
,
ebpfInstance
.
EbpfHeader
):
base
=
program
.
headerStructName
else
:
base
=
program
.
metadataStructName
serializer
.
appendFormat
(
"{0}.{1}.{2}"
,
base
,
einstance
.
name
,
node
.
name
)
else
:
raise
CompilationException
(
True
,
"{0} Unexpected expression "
,
node
)
def
emitExpression
(
self
,
expression
,
serializer
,
program
,
toplevel
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
assert
isinstance
(
expression
,
p4_expression
)
assert
isinstance
(
toplevel
,
bool
)
left
=
expression
.
left
op
=
expression
.
op
right
=
expression
.
right
assert
isinstance
(
op
,
str
)
if
op
==
"valid"
:
self
.
emitNode
(
right
,
serializer
,
program
)
serializer
.
append
(
".valid"
)
return
if
not
toplevel
:
serializer
.
append
(
"("
)
self
.
emitNode
(
left
,
serializer
,
program
)
op
=
EbpfConditional
.
translate
(
op
)
serializer
.
append
(
op
)
self
.
emitNode
(
right
,
serializer
,
program
)
if
not
toplevel
:
serializer
.
append
(
")"
)
def
generateCode
(
self
,
serializer
,
program
,
nextNode
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
serializer
.
emitIndent
()
serializer
.
blockStart
()
trueBranch
=
self
.
hlirconditional
.
next_
[
True
]
if
trueBranch
is
None
:
trueBranch
=
nextNode
falseBranch
=
self
.
hlirconditional
.
next_
[
False
]
if
falseBranch
is
None
:
falseBranch
=
nextNode
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0}:"
,
program
.
getLabel
(
self
.
hlirconditional
))
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
append
(
"if ("
)
self
.
emitExpression
(
self
.
hlirconditional
.
condition
,
serializer
,
program
,
True
)
serializer
.
appendLine
(
")"
)
serializer
.
increaseIndent
()
label
=
program
.
getLabel
(
trueBranch
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"goto {0};"
,
label
)
serializer
.
newline
()
serializer
.
decreaseIndent
()
serializer
.
emitIndent
()
serializer
.
appendLine
(
"else"
)
serializer
.
increaseIndent
()
label
=
program
.
getLabel
(
falseBranch
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"goto {0};"
,
label
)
serializer
.
newline
()
serializer
.
decreaseIndent
()
serializer
.
blockEnd
(
True
)
src/cc/frontends/p4/compiler/ebpfCounter.py
0 → 100644
View file @
0bde502c
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from
p4_hlir.hlir
import
p4_counter
,
P4_DIRECT
,
P4_COUNTER_BYTES
from
programSerializer
import
ProgramSerializer
from
compilationException
import
*
import
ebpfTable
import
ebpfProgram
class
EbpfCounter
(
object
):
# noinspection PyUnresolvedReferences
def
__init__
(
self
,
hlircounter
,
program
):
assert
isinstance
(
hlircounter
,
p4_counter
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
self
.
name
=
hlircounter
.
name
self
.
hlircounter
=
hlircounter
width
=
hlircounter
.
min_width
# ebpf counters only work on 64-bits
if
width
<=
64
:
self
.
valueTypeName
=
program
.
config
.
uprefix
+
"64"
else
:
raise
NotSupportedException
(
"{0}: Counters with {1} bits"
,
hlircounter
,
width
)
self
.
dataMapName
=
self
.
name
if
((
hlircounter
.
binding
is
None
)
or
(
hlircounter
.
binding
[
0
]
!=
P4_DIRECT
)):
raise
NotSupportedException
(
"{0}: counter which is not direct"
,
hlircounter
)
self
.
autoIncrement
=
(
hlircounter
.
binding
!=
None
and
hlircounter
.
binding
[
0
]
==
P4_DIRECT
)
if
hlircounter
.
type
is
P4_COUNTER_BYTES
:
self
.
increment
=
"{0}->len"
.
format
(
program
.
packetName
)
else
:
self
.
increment
=
"1"
def
getSize
(
self
,
program
):
if
self
.
hlircounter
.
instance_count
is
not
None
:
return
self
.
hlircounter
.
instance_count
if
self
.
autoIncrement
:
return
self
.
getTable
(
program
).
size
program
.
emitWarning
(
"{0} does not specify a max_size; using 1024"
,
self
.
hlircounter
)
return
1024
def
getTable
(
self
,
program
):
table
=
program
.
getTable
(
self
.
hlircounter
.
binding
[
1
].
name
)
assert
isinstance
(
table
,
ebpfTable
.
EbpfTable
)
return
table
def
serialize
(
self
,
serializer
,
program
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
# Direct counters have the same key as the associated table
# Static counters have integer keys
if
self
.
autoIncrement
:
keyTypeName
=
"struct "
+
self
.
getTable
(
program
).
keyTypeName
else
:
keyTypeName
=
program
.
config
.
uprefix
+
"32"
program
.
config
.
serializeTableDeclaration
(
serializer
,
self
.
dataMapName
,
True
,
keyTypeName
,
self
.
valueTypeName
,
self
.
getSize
(
program
))
def
serializeCode
(
self
,
keyname
,
serializer
,
program
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"/* Update counter {0} */"
,
self
.
name
)
serializer
.
newline
()
valueName
=
"ctrvalue"
initValuename
=
"init_val"
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0} *{1};"
,
self
.
valueTypeName
,
valueName
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0} {1};"
,
self
.
valueTypeName
,
initValuename
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendLine
(
"/* perform lookup */"
)
serializer
.
emitIndent
()
program
.
config
.
serializeLookup
(
serializer
,
self
.
dataMapName
,
keyname
,
valueName
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"if ({0} != NULL) "
,
valueName
)
serializer
.
newline
()
serializer
.
increaseIndent
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"__sync_fetch_and_add({0}, {1});"
,
valueName
,
self
.
increment
)
serializer
.
newline
()
serializer
.
decreaseIndent
()
serializer
.
emitIndent
()
serializer
.
append
(
"else "
)
serializer
.
blockStart
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0} = {1};"
,
initValuename
,
self
.
increment
)
serializer
.
newline
()
serializer
.
emitIndent
()
program
.
config
.
serializeUpdate
(
serializer
,
self
.
dataMapName
,
keyname
,
initValuename
)
serializer
.
newline
()
serializer
.
blockEnd
(
True
)
src/cc/frontends/p4/compiler/ebpfInstance.py
0 → 100644
View file @
0bde502c
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from
p4_hlir.hlir
import
p4_header_instance
from
ebpfType
import
EbpfType
from
compilationException
import
CompilationException
from
programSerializer
import
ProgramSerializer
import
typeFactory
class
EbpfInstanceBase
(
object
):
def
__init__
(
self
):
pass
class
SimpleInstance
(
EbpfInstanceBase
):
# A header or a metadata instance (but not array elements)
def
__init__
(
self
,
hlirInstance
,
factory
,
isMetadata
):
super
(
SimpleInstance
,
self
).
__init__
()
self
.
hlirInstance
=
hlirInstance
self
.
name
=
hlirInstance
.
base_name
self
.
type
=
factory
.
build
(
hlirInstance
.
header_type
,
isMetadata
)
def
declare
(
self
,
serializer
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
self
.
type
.
declare
(
serializer
,
self
.
name
,
False
)
class
EbpfHeader
(
SimpleInstance
):
""" Represents a header instance from a P4 program """
def
__init__
(
self
,
hlirHeaderInstance
,
factory
):
super
(
EbpfHeader
,
self
).
__init__
(
hlirHeaderInstance
,
factory
,
False
)
if
hlirHeaderInstance
.
metadata
:
raise
CompilationException
(
True
,
"Metadata passed to EpbfHeader"
)
if
hlirHeaderInstance
.
index
is
not
None
:
self
.
name
+=
"_"
+
str
(
hlirHeaderInstance
.
index
)
class
EbpfMetadata
(
SimpleInstance
):
"""Represents a metadata instance from a P4 program"""
def
__init__
(
self
,
hlirMetadataInstance
,
factory
):
super
(
EbpfMetadata
,
self
).
__init__
(
hlirMetadataInstance
,
factory
,
True
)
if
not
hlirMetadataInstance
.
metadata
:
raise
CompilationException
(
True
,
"Header instance passed to EpbfMetadata {0}"
,
hlirMetadataInstance
)
if
hlirMetadataInstance
.
index
is
not
None
:
raise
CompilationException
(
True
,
"Unexpected metadata array {0}"
,
self
.
hlirInstance
)
if
hasattr
(
hlirMetadataInstance
,
"initializer"
):
self
.
initializer
=
hlirMetadataInstance
.
initializer
else
:
self
.
initializer
=
None
def
emitInitializer
(
self
,
serializer
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
if
self
.
initializer
is
None
:
self
.
type
.
emitInitializer
(
serializer
)
else
:
for
key
in
self
.
initializer
.
keys
():
serializer
.
appendFormat
(
".{0} = {1},"
,
key
,
self
.
initializer
[
key
])
class
EbpfHeaderStack
(
EbpfInstanceBase
):
"""Represents a header stack instance; there is one instance of
this class for each STACK, and not for each
element of the stack, as in the HLIR"""
def
__init__
(
self
,
hlirInstance
,
indexVar
,
factory
):
super
(
EbpfHeaderStack
,
self
).
__init__
()
# indexVar: name of the ebpf variable that
# holds the current index for this stack
assert
isinstance
(
indexVar
,
str
)
assert
isinstance
(
factory
,
typeFactory
.
EbpfTypeFactory
)
assert
isinstance
(
hlirInstance
,
p4_header_instance
)
self
.
indexVar
=
indexVar
self
.
name
=
hlirInstance
.
base_name
self
.
basetype
=
factory
.
build
(
hlirInstance
.
header_type
,
False
)
assert
isinstance
(
self
.
basetype
,
EbpfType
)
self
.
arraySize
=
hlirInstance
.
max_index
+
1
self
.
hlirInstance
=
hlirInstance
def
declare
(
self
,
serializer
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
self
.
basetype
.
declareArray
(
serializer
,
self
.
name
,
self
.
arraySize
)
src/cc/frontends/p4/compiler/ebpfParser.py
0 → 100644
View file @
0bde502c
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from
p4_hlir.hlir
import
parse_call
,
p4_field
,
p4_parse_value_set
,
\
P4_DEFAULT
,
p4_parse_state
,
p4_table
,
\
p4_conditional_node
,
p4_parser_exception
,
\
p4_header_instance
,
P4_NEXT
import
ebpfProgram
import
ebpfStructType
import
ebpfInstance
import
programSerializer
from
compilationException
import
*
class
EbpfParser
(
object
):
def
__init__
(
self
,
hlirParser
):
# hlirParser is a P4 parser
self
.
parser
=
hlirParser
self
.
name
=
hlirParser
.
name
def
serialize
(
self
,
serializer
,
program
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0}: "
,
self
.
name
)
serializer
.
blockStart
()
for
op
in
self
.
parser
.
call_sequence
:
self
.
serializeOperation
(
serializer
,
op
,
program
)
self
.
serializeBranch
(
serializer
,
self
.
parser
.
branch_on
,
self
.
parser
.
branch_to
,
program
)
serializer
.
blockEnd
(
True
)
def
serializeSelect
(
self
,
selectVarName
,
serializer
,
branch_on
,
program
):
# selectVarName - name of temp variable to use for the select expression
assert
isinstance
(
selectVarName
,
str
)
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
totalWidth
=
0
switchValue
=
""
for
e
in
branch_on
:
if
isinstance
(
e
,
p4_field
):
instance
=
e
.
instance
assert
isinstance
(
instance
,
p4_header_instance
)
index
=
""
if
ebpfProgram
.
EbpfProgram
.
isArrayElementInstance
(
instance
):
ebpfStack
=
program
.
getStackInstance
(
instance
.
base_name
)
assert
isinstance
(
ebpfStack
,
ebpfInstance
.
EbpfHeaderStack
)
if
isinstance
(
instance
.
index
,
int
):
index
=
"["
+
str
(
instance
.
index
)
+
"]"
elif
instance
.
index
is
P4_NEXT
:
index
=
"["
+
ebpfStack
.
indexVar
+
"]"
else
:
raise
CompilationException
(
True
,
"Unexpected index for array {0}"
,
instance
.
index
)
basetype
=
ebpfStack
.
basetype
name
=
ebpfStack
.
name
else
:
ebpfHeader
=
program
.
getInstance
(
instance
.
name
)
assert
isinstance
(
ebpfHeader
,
ebpfInstance
.
EbpfHeader
)
basetype
=
ebpfHeader
.
type
name
=
ebpfHeader
.
name
ebpfField
=
basetype
.
getField
(
e
.
name
)
assert
isinstance
(
ebpfField
,
ebpfStructType
.
EbpfField
)
totalWidth
+=
ebpfField
.
widthInBits
()
fieldReference
=
(
program
.
headerStructName
+
"."
+
name
+
index
+
"."
+
ebpfField
.
name
)
if
switchValue
==
""
:
switchValue
=
fieldReference
else
:
switchValue
=
(
"("
+
switchValue
+
" << "
+
str
(
ebpfField
.
widthInBits
())
+
")"
)
switchValue
=
switchValue
+
" | "
+
fieldReference
elif
isinstance
(
e
,
tuple
):
switchValue
=
self
.
currentReferenceAsString
(
e
,
program
)
else
:
raise
CompilationException
(
True
,
"Unexpected element in match {0}"
,
e
)
if
totalWidth
>
32
:
raise
NotSupportedException
(
"{0}: Matching on {1}-bit value"
,
branch_on
,
totalWidth
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0}32 {1} = {2};"
,
program
.
config
.
uprefix
,
selectVarName
,
switchValue
)
serializer
.
newline
()
def
generatePacketLoad
(
self
,
startBit
,
width
,
alignment
,
program
):
# Generates an expression that does a load_*, shift and mask
# to load 'width' bits starting at startBit from the current
# packet offset.
# alignment is an integer <= 8 that holds the current alignment
# of of the packet offset.
assert
width
>
0
assert
alignment
<
8
assert
isinstance
(
startBit
,
int
)
assert
isinstance
(
width
,
int
)
assert
isinstance
(
alignment
,
int
)
firstBitIndex
=
startBit
+
alignment
lastBitIndex
=
startBit
+
width
+
alignment
-
1
firstWordIndex
=
firstBitIndex
/
8
lastWordIndex
=
lastBitIndex
/
8
wordsToRead
=
lastWordIndex
-
firstWordIndex
+
1
if
wordsToRead
==
1
:
load
=
"load_byte"
loadSize
=
8
elif
wordsToRead
==
2
:
load
=
"load_half"
loadSize
=
16
elif
wordsToRead
<=
4
:
load
=
"load_word"
loadSize
=
32
elif
wordsToRead
<=
8
:
load
=
"load_dword"
loadSize
=
64
else
:
raise
CompilationException
(
True
,
"Attempt to load more than 1 word"
)
readtype
=
program
.
config
.
uprefix
+
str
(
loadSize
)
loadInstruction
=
"{0}({1}, ({2} + {3}) / 8)"
.
format
(
load
,
program
.
packetName
,
program
.
offsetVariableName
,
startBit
)
shift
=
loadSize
-
alignment
-
width
load
=
"(({0}) >> ({1}))"
.
format
(
loadInstruction
,
shift
)
if
width
!=
loadSize
:
mask
=
" & EBPF_MASK({0}, {1})"
.
format
(
readtype
,
width
)
else
:
mask
=
""
return
load
+
mask
def
currentReferenceAsString
(
self
,
tpl
,
program
):
# a string describing an expression of the form current(position, width)
# The assumption is that at this point the packet cursor is ALWAYS
# byte aligned. This should be true because headers are supposed
# to have sizes an integral number of bytes.
assert
isinstance
(
tpl
,
tuple
)
if
len
(
tpl
)
!=
2
:
raise
CompilationException
(
True
,
"{0} Expected a tuple with 2 elements"
,
tpl
)
minIndex
=
tpl
[
0
]
totalWidth
=
tpl
[
1
]
result
=
self
.
generatePacketLoad
(
minIndex
,
totalWidth
,
0
,
program
)
# alignment is 0
return
result
def
serializeCases
(
self
,
selectVarName
,
serializer
,
branch_to
,
program
):
assert
isinstance
(
selectVarName
,
str
)
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
branches
=
0
seenDefault
=
False
for
e
in
branch_to
.
keys
():
serializer
.
emitIndent
()
value
=
branch_to
[
e
]
if
isinstance
(
e
,
int
):
serializer
.
appendFormat
(
"if ({0} == {1})"
,
selectVarName
,
e
)
elif
isinstance
(
e
,
tuple
):
serializer
.
appendFormat
(
"if (({0} & {1}) == {2})"
,
selectVarName
,
e
[
0
],
e
[
1
])
elif
isinstance
(
e
,
p4_parse_value_set
):
raise
NotSupportedException
(
"{0}: Parser value sets"
,
e
)
elif
e
is
P4_DEFAULT
:
seenDefault
=
True
if
branches
>
0
:
serializer
.
append
(
"else"
)
else
:
raise
CompilationException
(
True
,
"Unexpected element in match case {0}"
,
e
)
branches
+=
1
serializer
.
newline
()
serializer
.
increaseIndent
()
serializer
.
emitIndent
()
label
=
program
.
getLabel
(
value
)
if
isinstance
(
value
,
p4_parse_state
):
serializer
.
appendFormat
(
"goto {0};"
,
label
)
elif
isinstance
(
value
,
p4_table
):
serializer
.
appendFormat
(
"goto {0};"
,
label
)
elif
isinstance
(
value
,
p4_conditional_node
):
serializer
.
appendFormat
(
"goto {0};"
,
label
)
elif
isinstance
(
value
,
p4_parser_exception
):
raise
CompilationException
(
True
,
"Not yet implemented"
)
else
:
raise
CompilationException
(
True
,
"Unexpected element in match case {0}"
,
value
)
serializer
.
decreaseIndent
()
serializer
.
newline
()
# Must create default if it is missing
if
not
seenDefault
:
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0} = p4_pe_unhandled_select;"
,
program
.
errorName
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"default: goto end;"
)
serializer
.
newline
()
def
serializeBranch
(
self
,
serializer
,
branch_on
,
branch_to
,
program
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
if
branch_on
==
[]:
dest
=
branch_to
.
values
()[
0
]
serializer
.
emitIndent
()
name
=
program
.
getLabel
(
dest
)
serializer
.
appendFormat
(
"goto {0};"
,
name
)
serializer
.
newline
()
elif
isinstance
(
branch_on
,
list
):
tmpvar
=
program
.
generateNewName
(
"tmp"
)
self
.
serializeSelect
(
tmpvar
,
serializer
,
branch_on
,
program
)
self
.
serializeCases
(
tmpvar
,
serializer
,
branch_to
,
program
)
else
:
raise
CompilationException
(
True
,
"Unexpected branch_on {0}"
,
branch_on
)
def
serializeOperation
(
self
,
serializer
,
op
,
program
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
operation
=
op
[
0
]
if
operation
is
parse_call
.
extract
:
self
.
serializeExtract
(
serializer
,
op
[
1
],
program
)
elif
operation
is
parse_call
.
set
:
self
.
serializeMetadataSet
(
serializer
,
op
[
1
],
op
[
2
],
program
)
else
:
raise
CompilationException
(
True
,
"Unexpected operation in parser {0}"
,
op
)
def
serializeFieldExtract
(
self
,
serializer
,
headerInstanceName
,
index
,
field
,
alignment
,
program
):
assert
isinstance
(
index
,
str
)
assert
isinstance
(
headerInstanceName
,
str
)
assert
isinstance
(
field
,
ebpfStructType
.
EbpfField
)
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
assert
isinstance
(
alignment
,
int
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
fieldToExtractTo
=
headerInstanceName
+
index
+
"."
+
field
.
name
serializer
.
emitIndent
()
width
=
field
.
widthInBits
()
if
field
.
name
==
"valid"
:
serializer
.
appendFormat
(
"{0}.{1} = 1;"
,
program
.
headerStructName
,
fieldToExtractTo
)
serializer
.
newline
()
return
serializer
.
appendFormat
(
"if ({0}->len < BYTES({1} + {2})) "
,
program
.
packetName
,
program
.
offsetVariableName
,
width
)
serializer
.
blockStart
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0} = p4_pe_header_too_short;"
,
program
.
errorName
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendLine
(
"goto end;"
)
# TODO: jump to correct exception handler
serializer
.
blockEnd
(
True
)
if
width
<=
32
:
serializer
.
emitIndent
()
load
=
self
.
generatePacketLoad
(
0
,
width
,
alignment
,
program
)
serializer
.
appendFormat
(
"{0}.{1} = {2};"
,
program
.
headerStructName
,
fieldToExtractTo
,
load
)
serializer
.
newline
()
else
:
# Destination is bigger than 4 bytes and
# represented as a byte array.
if
alignment
==
0
:
shift
=
0
else
:
shift
=
8
-
alignment
assert
shift
>=
0
if
shift
==
0
:
method
=
"load_byte"
else
:
method
=
"load_half"
b
=
(
width
+
7
)
/
8
for
i
in
range
(
0
,
b
):
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0}.{1}[{2}] = ({3}8)"
,
program
.
headerStructName
,
fieldToExtractTo
,
i
,
program
.
config
.
uprefix
)
serializer
.
appendFormat
(
"(({0}({1}, ({2} / 8) + {3}) >> {4})"
,
method
,
program
.
packetName
,
program
.
offsetVariableName
,
i
,
shift
)
if
(
i
==
b
-
1
)
and
(
width
%
8
!=
0
):
serializer
.
appendFormat
(
" & EBPF_MASK({0}8, {1})"
,
program
.
config
.
uprefix
,
width
%
8
)
serializer
.
append
(
")"
)
serializer
.
endOfStatement
(
True
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0} += {1};"
,
program
.
offsetVariableName
,
width
)
serializer
.
newline
()
def
serializeExtract
(
self
,
serializer
,
headerInstance
,
program
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
assert
isinstance
(
headerInstance
,
p4_header_instance
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
if
ebpfProgram
.
EbpfProgram
.
isArrayElementInstance
(
headerInstance
):
ebpfStack
=
program
.
getStackInstance
(
headerInstance
.
base_name
)
assert
isinstance
(
ebpfStack
,
ebpfInstance
.
EbpfHeaderStack
)
# write bounds check
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"if ({0} >= {1}) "
,
ebpfStack
.
indexVar
,
ebpfStack
.
arraySize
)
serializer
.
blockStart
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0} = p4_pe_index_out_of_bounds;"
,
program
.
errorName
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendLine
(
"goto end;"
)
serializer
.
blockEnd
(
True
)
if
isinstance
(
headerInstance
.
index
,
int
):
index
=
"["
+
str
(
headerInstance
.
index
)
+
"]"
elif
headerInstance
.
index
is
P4_NEXT
:
index
=
"["
+
ebpfStack
.
indexVar
+
"]"
else
:
raise
CompilationException
(
True
,
"Unexpected index for array {0}"
,
headerInstance
.
index
)
basetype
=
ebpfStack
.
basetype
else
:
ebpfHeader
=
program
.
getHeaderInstance
(
headerInstance
.
name
)
basetype
=
ebpfHeader
.
type
index
=
""
# extract all fields
alignment
=
0
for
field
in
basetype
.
fields
:
assert
isinstance
(
field
,
ebpfStructType
.
EbpfField
)
self
.
serializeFieldExtract
(
serializer
,
headerInstance
.
base_name
,
index
,
field
,
alignment
,
program
)
alignment
+=
field
.
widthInBits
()
alignment
=
alignment
%
8
if
ebpfProgram
.
EbpfProgram
.
isArrayElementInstance
(
headerInstance
):
# increment stack index
ebpfStack
=
program
.
getStackInstance
(
headerInstance
.
base_name
)
assert
isinstance
(
ebpfStack
,
ebpfInstance
.
EbpfHeaderStack
)
# write bounds check
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0}++;"
,
ebpfStack
.
indexVar
)
serializer
.
newline
()
def
serializeMetadataSet
(
self
,
serializer
,
field
,
value
,
program
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
assert
isinstance
(
field
,
p4_field
)
dest
=
program
.
getInstance
(
field
.
instance
.
name
)
assert
isinstance
(
dest
,
ebpfInstance
.
SimpleInstance
)
destType
=
dest
.
type
assert
isinstance
(
destType
,
ebpfStructType
.
EbpfStructType
)
destField
=
destType
.
getField
(
field
.
name
)
if
destField
.
widthInBits
()
>
32
:
useMemcpy
=
True
bytesToCopy
=
destField
.
widthInBits
()
/
8
if
destField
.
widthInBits
()
%
8
!=
0
:
raise
CompilationException
(
True
,
"{0}: Not implemented: wide field w. sz not multiple of 8"
,
field
)
else
:
useMemcpy
=
False
bytesToCopy
=
None
# not needed, but compiler is confused
serializer
.
emitIndent
()
destination
=
"{0}.{1}.{2}"
.
format
(
program
.
metadataStructName
,
dest
.
name
,
destField
.
name
)
if
isinstance
(
value
,
int
):
source
=
str
(
value
)
if
useMemcpy
:
raise
CompilationException
(
True
,
"{0}: Not implemented: copying from wide constant"
,
value
)
elif
isinstance
(
value
,
tuple
):
source
=
self
.
currentReferenceAsString
(
value
,
program
)
elif
isinstance
(
value
,
p4_field
):
source
=
program
.
getInstance
(
value
.
instance
.
name
)
if
isinstance
(
source
,
ebpfInstance
.
EbpfMetadata
):
sourceStruct
=
program
.
metadataStructName
else
:
sourceStruct
=
program
.
headerStructName
source
=
"{0}.{1}.{2}"
.
format
(
sourceStruct
,
source
.
name
,
value
.
name
)
else
:
raise
CompilationException
(
True
,
"Unexpected type for parse_call.set {0}"
,
value
)
if
useMemcpy
:
serializer
.
appendFormat
(
"memcpy(&{0}, &{1}, {2})"
,
destination
,
source
,
bytesToCopy
)
else
:
serializer
.
appendFormat
(
"{0} = {1}"
,
destination
,
source
)
serializer
.
endOfStatement
(
True
)
src/cc/frontends/p4/compiler/ebpfProgram.py
0 → 100644
View file @
0bde502c
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from
p4_hlir.hlir
import
p4_header_instance
,
p4_table
,
\
p4_conditional_node
,
p4_action
,
p4_parse_state
from
p4_hlir.main
import
HLIR
import
typeFactory
import
ebpfTable
import
ebpfParser
import
ebpfAction
import
ebpfInstance
import
ebpfConditional
import
ebpfCounter
import
programSerializer
import
target
from
compilationException
import
*
class
EbpfProgram
(
object
):
def
__init__
(
self
,
name
,
hlir
,
isRouter
,
config
):
"""Representation of an EbpfProgram (in fact,
a C program that is converted to EBPF)"""
assert
isinstance
(
hlir
,
HLIR
)
assert
isinstance
(
isRouter
,
bool
)
assert
isinstance
(
config
,
target
.
TargetConfig
)
self
.
hlir
=
hlir
self
.
name
=
name
self
.
uniqueNameCounter
=
0
self
.
config
=
config
self
.
isRouter
=
isRouter
self
.
reservedPrefix
=
"ebpf_"
assert
isinstance
(
config
,
target
.
TargetConfig
)
self
.
packetName
=
self
.
reservedPrefix
+
"packet"
self
.
dropBit
=
self
.
reservedPrefix
+
"drop"
self
.
license
=
"GPL"
self
.
offsetVariableName
=
self
.
reservedPrefix
+
"packetOffsetInBits"
self
.
zeroKeyName
=
self
.
reservedPrefix
+
"zero"
self
.
arrayIndexType
=
self
.
config
.
uprefix
+
"32"
# all array tables must be indexed with u32 values
self
.
errorName
=
self
.
reservedPrefix
+
"error"
self
.
functionName
=
self
.
reservedPrefix
+
"filter"
self
.
egressPortName
=
"egress_port"
# Hardwired in P4 definition
self
.
typeFactory
=
typeFactory
.
EbpfTypeFactory
(
config
)
self
.
errorCodes
=
[
"p4_pe_no_error"
,
"p4_pe_index_out_of_bounds"
,
"p4_pe_out_of_packet"
,
"p4_pe_header_too_long"
,
"p4_pe_header_too_short"
,
"p4_pe_unhandled_select"
,
"p4_pe_checksum"
]
self
.
actions
=
[]
self
.
conditionals
=
[]
self
.
tables
=
[]
self
.
headers
=
[]
# header instances
self
.
metadata
=
[]
# metadata instances
self
.
stacks
=
[]
# header stack instances EbpfHeaderStack
self
.
parsers
=
[]
# all parsers
self
.
entryPoints
=
[]
# control-flow entry points from parser
self
.
counters
=
[]
self
.
entryPointLabels
=
{}
# maps p4_node from entryPoints
# to labels in the C program
self
.
egressEntry
=
None
self
.
construct
()
self
.
headersStructTypeName
=
self
.
reservedPrefix
+
"headers_t"
self
.
headerStructName
=
self
.
reservedPrefix
+
"headers"
self
.
metadataStructTypeName
=
self
.
reservedPrefix
+
"metadata_t"
self
.
metadataStructName
=
self
.
reservedPrefix
+
"metadata"
def
construct
(
self
):
if
len
(
self
.
hlir
.
p4_field_list_calculations
)
>
0
:
raise
NotSupportedException
(
"{0} calculated field"
,
self
.
hlir
.
p4_field_list_calculations
.
values
()[
0
].
name
)
for
h
in
self
.
hlir
.
p4_header_instances
.
values
():
if
h
.
max_index
is
not
None
:
assert
isinstance
(
h
,
p4_header_instance
)
if
h
.
index
==
0
:
# header stack; allocate only for zero-th index
indexVarName
=
self
.
generateNewName
(
h
.
base_name
+
"_index"
)
stack
=
ebpfInstance
.
EbpfHeaderStack
(
h
,
indexVarName
,
self
.
typeFactory
)
self
.
stacks
.
append
(
stack
)
elif
h
.
metadata
:
metadata
=
ebpfInstance
.
EbpfMetadata
(
h
,
self
.
typeFactory
)
self
.
metadata
.
append
(
metadata
)
else
:
header
=
ebpfInstance
.
EbpfHeader
(
h
,
self
.
typeFactory
)
self
.
headers
.
append
(
header
)
for
p
in
self
.
hlir
.
p4_parse_states
.
values
():
parser
=
ebpfParser
.
EbpfParser
(
p
)
self
.
parsers
.
append
(
parser
)
for
a
in
self
.
hlir
.
p4_actions
.
values
():
if
self
.
isInternalAction
(
a
):
continue
action
=
ebpfAction
.
EbpfAction
(
a
,
self
)
self
.
actions
.
append
(
action
)
for
c
in
self
.
hlir
.
p4_counters
.
values
():
counter
=
ebpfCounter
.
EbpfCounter
(
c
,
self
)
self
.
counters
.
append
(
counter
)
for
t
in
self
.
hlir
.
p4_tables
.
values
():
table
=
ebpfTable
.
EbpfTable
(
t
,
self
,
self
.
config
)
self
.
tables
.
append
(
table
)
for
n
in
self
.
hlir
.
p4_ingress_ptr
.
keys
():
self
.
entryPoints
.
append
(
n
)
for
n
in
self
.
hlir
.
p4_conditional_nodes
.
values
():
conditional
=
ebpfConditional
.
EbpfConditional
(
n
,
self
)
self
.
conditionals
.
append
(
conditional
)
self
.
egressEntry
=
self
.
hlir
.
p4_egress_ptr
def
isInternalAction
(
self
,
action
):
# This is a heuristic really to guess which actions are built-in
# Unfortunately there seems to be no other way to do this
return
action
.
lineno
<
0
@
staticmethod
def
isArrayElementInstance
(
headerInstance
):
assert
isinstance
(
headerInstance
,
p4_header_instance
)
return
headerInstance
.
max_index
is
not
None
def
emitWarning
(
self
,
formatString
,
*
message
):
assert
isinstance
(
formatString
,
str
)
print
(
"WARNING: "
,
formatString
.
format
(
*
message
))
def
toC
(
self
,
serializer
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
self
.
generateIncludes
(
serializer
)
self
.
generatePreamble
(
serializer
)
self
.
generateTypes
(
serializer
)
self
.
generateTables
(
serializer
)
serializer
.
newline
()
serializer
.
emitIndent
()
self
.
config
.
serializeCodeSection
(
serializer
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"int {0}(struct __sk_buff* {1}) "
,
self
.
functionName
,
self
.
packetName
)
serializer
.
blockStart
()
self
.
generateHeaderInstance
(
serializer
)
serializer
.
append
(
" = "
)
self
.
generateInitializeHeaders
(
serializer
)
serializer
.
endOfStatement
(
True
)
self
.
generateMetadataInstance
(
serializer
)
serializer
.
append
(
" = "
)
self
.
generateInitializeMetadata
(
serializer
)
serializer
.
endOfStatement
(
True
)
self
.
createLocalVariables
(
serializer
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendLine
(
"goto start;"
)
self
.
generateParser
(
serializer
)
self
.
generatePipeline
(
serializer
)
serializer
.
emitIndent
()
serializer
.
appendLine
(
"end:"
)
serializer
.
emitIndent
()
if
isinstance
(
self
.
config
,
target
.
KernelSamplesConfig
):
serializer
.
appendFormat
(
"return {0};"
,
self
.
dropBit
)
serializer
.
newline
()
elif
isinstance
(
self
.
config
,
target
.
BccConfig
):
if
self
.
isRouter
:
serializer
.
appendFormat
(
"if (!{0})"
,
self
.
dropBit
)
serializer
.
newline
()
serializer
.
increaseIndent
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"bpf_clone_redirect({0}, {1}.standard_metadata.{2}, 0);"
,
self
.
packetName
,
self
.
metadataStructName
,
self
.
egressPortName
)
serializer
.
newline
()
serializer
.
decreaseIndent
()
serializer
.
emitIndent
()
serializer
.
appendLine
(
"return TC_ACT_SHOT /* drop packet; clone is forwarded */;"
)
else
:
serializer
.
appendFormat
(
"return {1} ? TC_ACT_SHOT : TC_ACT_PIPE;"
,
self
.
dropBit
)
serializer
.
newline
()
else
:
raise
CompilationException
(
True
,
"Unexpected target configuration {0}"
,
self
.
config
.
targetName
)
serializer
.
blockEnd
(
True
)
self
.
generateLicense
(
serializer
)
serializer
.
append
(
self
.
config
.
postamble
)
def
generateLicense
(
self
,
serializer
):
self
.
config
.
serializeLicense
(
serializer
,
self
.
license
)
# noinspection PyMethodMayBeStatic
def
generateIncludes
(
self
,
serializer
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
serializer
.
append
(
self
.
config
.
getIncludes
())
def
getLabel
(
self
,
p4node
):
# C label that corresponds to this point in the control-flow
if
p4node
is
None
:
return
"end"
elif
isinstance
(
p4node
,
p4_parse_state
):
label
=
p4node
.
name
self
.
entryPointLabels
[
p4node
.
name
]
=
label
if
p4node
.
name
not
in
self
.
entryPointLabels
:
label
=
self
.
generateNewName
(
p4node
.
name
)
self
.
entryPointLabels
[
p4node
.
name
]
=
label
return
self
.
entryPointLabels
[
p4node
.
name
]
# noinspection PyMethodMayBeStatic
def
generatePreamble
(
self
,
serializer
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
serializer
.
emitIndent
()
serializer
.
append
(
"enum ErrorCode "
)
serializer
.
blockStart
()
for
error
in
self
.
errorCodes
:
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0},"
,
error
)
serializer
.
newline
()
serializer
.
blockEnd
(
False
)
serializer
.
endOfStatement
(
True
)
serializer
.
newline
()
serializer
.
appendLine
(
"#define EBPF_MASK(t, w) ((((t)(1)) << (w)) - (t)1)"
)
serializer
.
appendLine
(
"#define BYTES(w) ((w + 7) / 8)"
)
self
.
config
.
generateDword
(
serializer
)
# noinspection PyMethodMayBeStatic
def
generateNewName
(
self
,
base
):
# base is a string
"""Generates a fresh name based on the specified base name"""
# TODO: this should be made "safer"
assert
isinstance
(
base
,
str
)
base
+=
"_"
+
str
(
self
.
uniqueNameCounter
)
self
.
uniqueNameCounter
+=
1
return
base
def
generateTypes
(
self
,
serializer
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
for
t
in
self
.
typeFactory
.
type_map
.
values
():
t
.
serialize
(
serializer
)
# generate a new struct type for the packet itself
serializer
.
appendFormat
(
"struct {0} "
,
self
.
headersStructTypeName
)
serializer
.
blockStart
()
for
h
in
self
.
headers
:
serializer
.
emitIndent
()
h
.
declare
(
serializer
)
serializer
.
endOfStatement
(
True
)
for
h
in
self
.
stacks
:
assert
isinstance
(
h
,
ebpfInstance
.
EbpfHeaderStack
)
serializer
.
emitIndent
()
h
.
declare
(
serializer
)
serializer
.
endOfStatement
(
True
)
serializer
.
blockEnd
(
False
)
serializer
.
endOfStatement
(
True
)
# generate a new struct type for the metadata
serializer
.
appendFormat
(
"struct {0} "
,
self
.
metadataStructTypeName
)
serializer
.
blockStart
()
for
h
in
self
.
metadata
:
assert
isinstance
(
h
,
ebpfInstance
.
EbpfMetadata
)
serializer
.
emitIndent
()
h
.
declare
(
serializer
)
serializer
.
endOfStatement
(
True
)
serializer
.
blockEnd
(
False
)
serializer
.
endOfStatement
(
True
)
def
generateTables
(
self
,
serializer
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
for
t
in
self
.
tables
:
t
.
serialize
(
serializer
,
self
)
for
c
in
self
.
counters
:
c
.
serialize
(
serializer
,
self
)
def
generateHeaderInstance
(
self
,
serializer
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"struct {0} {1}"
,
self
.
headersStructTypeName
,
self
.
headerStructName
)
def
generateInitializeHeaders
(
self
,
serializer
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
serializer
.
blockStart
()
for
h
in
self
.
headers
:
serializer
.
emitIndent
()
serializer
.
appendFormat
(
".{0} = "
,
h
.
name
)
h
.
type
.
emitInitializer
(
serializer
)
serializer
.
appendLine
(
","
)
serializer
.
blockEnd
(
False
)
def
generateMetadataInstance
(
self
,
serializer
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"struct {0} {1}"
,
self
.
metadataStructTypeName
,
self
.
metadataStructName
)
def
generateInitializeMetadata
(
self
,
serializer
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
serializer
.
blockStart
()
for
h
in
self
.
metadata
:
serializer
.
emitIndent
()
serializer
.
appendFormat
(
".{0} = "
,
h
.
name
)
h
.
emitInitializer
(
serializer
)
serializer
.
appendLine
(
","
)
serializer
.
blockEnd
(
False
)
def
createLocalVariables
(
self
,
serializer
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"unsigned {0} = 0;"
,
self
.
offsetVariableName
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"enum ErrorCode {0} = p4_pe_no_error;"
,
self
.
errorName
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0}8 {1} = 0;"
,
self
.
config
.
uprefix
,
self
.
dropBit
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0} {1} = 0;"
,
self
.
arrayIndexType
,
self
.
zeroKeyName
)
serializer
.
newline
()
for
h
in
self
.
stacks
:
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0}8 {0} = 0;"
,
self
.
config
.
uprefix
,
h
.
indexVar
)
serializer
.
newline
()
def
getStackInstance
(
self
,
name
):
assert
isinstance
(
name
,
str
)
for
h
in
self
.
stacks
:
if
h
.
name
==
name
:
assert
isinstance
(
h
,
ebpfInstance
.
EbpfHeaderStack
)
return
h
raise
CompilationException
(
True
,
"Could not locate header stack named {0}"
,
name
)
def
getHeaderInstance
(
self
,
name
):
assert
isinstance
(
name
,
str
)
for
h
in
self
.
headers
:
if
h
.
name
==
name
:
assert
isinstance
(
h
,
ebpfInstance
.
EbpfHeader
)
return
h
raise
CompilationException
(
True
,
"Could not locate header instance named {0}"
,
name
)
def
getInstance
(
self
,
name
):
assert
isinstance
(
name
,
str
)
for
h
in
self
.
headers
:
if
h
.
name
==
name
:
return
h
for
h
in
self
.
metadata
:
if
h
.
name
==
name
:
return
h
raise
CompilationException
(
True
,
"Could not locate instance named {0}"
,
name
)
def
getAction
(
self
,
p4action
):
assert
isinstance
(
p4action
,
p4_action
)
for
a
in
self
.
actions
:
if
a
.
name
==
p4action
.
name
:
return
a
newAction
=
ebpfAction
.
BuiltinAction
(
p4action
)
self
.
actions
.
append
(
newAction
)
return
newAction
def
getTable
(
self
,
name
):
assert
isinstance
(
name
,
str
)
for
t
in
self
.
tables
:
if
t
.
name
==
name
:
return
t
raise
CompilationException
(
True
,
"Could not locate table named {0}"
,
name
)
def
getCounter
(
self
,
name
):
assert
isinstance
(
name
,
str
)
for
t
in
self
.
counters
:
if
t
.
name
==
name
:
return
t
raise
CompilationException
(
True
,
"Could not locate counters named {0}"
,
name
)
def
getConditional
(
self
,
name
):
assert
isinstance
(
name
,
str
)
for
c
in
self
.
conditionals
:
if
c
.
name
==
name
:
return
c
raise
CompilationException
(
True
,
"Could not locate conditional named {0}"
,
name
)
def
generateParser
(
self
,
serializer
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
for
p
in
self
.
parsers
:
p
.
serialize
(
serializer
,
self
)
def
generateIngressPipeline
(
self
,
serializer
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
for
t
in
self
.
tables
:
assert
isinstance
(
t
,
ebpfTable
.
EbpfTable
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0}:"
,
t
.
name
)
serializer
.
newline
()
def
generateControlFlowNode
(
self
,
serializer
,
node
,
nextEntryPoint
):
# nextEntryPoint is used as a target whenever the target is None
# nextEntryPoint may also be None
if
isinstance
(
node
,
p4_table
):
table
=
self
.
getTable
(
node
.
name
)
assert
isinstance
(
table
,
ebpfTable
.
EbpfTable
)
table
.
serializeCode
(
serializer
,
self
,
nextEntryPoint
)
elif
isinstance
(
node
,
p4_conditional_node
):
conditional
=
self
.
getConditional
(
node
.
name
)
assert
isinstance
(
conditional
,
ebpfConditional
.
EbpfConditional
)
conditional
.
generateCode
(
serializer
,
self
,
nextEntryPoint
)
else
:
raise
CompilationException
(
True
,
"{0} Unexpected control flow node "
,
node
)
def
generatePipelineInternal
(
self
,
serializer
,
nodestoadd
,
nextEntryPoint
):
assert
isinstance
(
serializer
,
programSerializer
.
ProgramSerializer
)
assert
isinstance
(
nodestoadd
,
set
)
done
=
set
()
while
len
(
nodestoadd
)
>
0
:
todo
=
nodestoadd
.
pop
()
if
todo
is
None
:
todo
=
nextEntryPoint
if
todo
is
None
:
continue
if
todo
in
done
:
continue
print
(
"Generating "
,
todo
.
name
)
done
.
add
(
todo
)
self
.
generateControlFlowNode
(
serializer
,
todo
,
nextEntryPoint
)
for
n
in
todo
.
next_
.
values
():
nodestoadd
.
add
(
n
)
def
generatePipeline
(
self
,
serializer
):
todo
=
set
()
for
e
in
self
.
entryPoints
:
todo
.
add
(
e
)
self
.
generatePipelineInternal
(
serializer
,
todo
,
self
.
egressEntry
)
src/cc/frontends/p4/compiler/ebpfScalarType.py
0 → 100644
View file @
0bde502c
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from
p4_hlir.hlir
import
P4_AUTO_WIDTH
from
ebpfType
import
*
from
compilationException
import
*
from
programSerializer
import
ProgramSerializer
class
EbpfScalarType
(
EbpfType
):
__doc__
=
"Represents a scalar type"
def
__init__
(
self
,
parent
,
widthInBits
,
isSigned
,
config
):
super
(
EbpfScalarType
,
self
).
__init__
(
None
)
assert
isinstance
(
widthInBits
,
int
)
assert
isinstance
(
isSigned
,
bool
)
self
.
width
=
widthInBits
self
.
isSigned
=
isSigned
self
.
config
=
config
if
widthInBits
is
P4_AUTO_WIDTH
:
raise
NotSupportedException
(
"{0} Variable-width field"
,
parent
)
def
widthInBits
(
self
):
return
self
.
width
@
staticmethod
def
bytesRequired
(
width
):
return
(
width
+
7
)
/
8
def
asString
(
self
):
if
self
.
isSigned
:
prefix
=
self
.
config
.
iprefix
else
:
prefix
=
self
.
config
.
uprefix
if
self
.
width
<=
8
:
name
=
prefix
+
"8"
elif
self
.
width
<=
16
:
name
=
prefix
+
"16"
elif
self
.
width
<=
32
:
name
=
prefix
+
"32"
else
:
name
=
"char*"
return
name
def
alignment
(
self
):
if
self
.
width
<=
8
:
return
1
elif
self
.
width
<=
16
:
return
2
elif
self
.
width
<=
32
:
return
4
else
:
return
1
# Char array
def
serialize
(
self
,
serializer
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
serializer
.
append
(
self
.
asString
())
def
declareArray
(
self
,
serializer
,
identifier
,
size
):
raise
CompilationException
(
True
,
"Arrays of base type not expected in P4"
)
def
declare
(
self
,
serializer
,
identifier
,
asPointer
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
assert
isinstance
(
asPointer
,
bool
)
assert
isinstance
(
identifier
,
str
)
if
self
.
width
<=
32
:
self
.
serialize
(
serializer
)
if
asPointer
:
serializer
.
append
(
"*"
)
serializer
.
space
()
serializer
.
append
(
identifier
)
else
:
if
asPointer
:
serializer
.
append
(
"char*"
)
else
:
serializer
.
appendFormat
(
"char {0}[{1}]"
,
identifier
,
EbpfScalarType
.
bytesRequired
(
self
.
width
))
def
emitInitializer
(
self
,
serializer
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
serializer
.
append
(
"0"
)
src/cc/frontends/p4/compiler/ebpfStructType.py
0 → 100644
View file @
0bde502c
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from
p4_hlir.hlir
import
P4_SIGNED
,
P4_SATURATING
from
ebpfScalarType
import
*
class
EbpfField
(
object
):
__doc__
=
"represents a field in a struct type, not in an instance"
def
__init__
(
self
,
hlirParentType
,
name
,
widthInBits
,
attributes
,
config
):
self
.
name
=
name
self
.
width
=
widthInBits
self
.
hlirType
=
hlirParentType
signed
=
False
if
P4_SIGNED
in
attributes
:
signed
=
True
if
P4_SATURATING
in
attributes
:
raise
NotSupportedException
(
"{0}.{1}: Saturated types"
,
self
.
hlirType
,
self
.
name
)
try
:
self
.
type
=
EbpfScalarType
(
self
.
hlirType
,
widthInBits
,
signed
,
config
)
except
CompilationException
,
e
:
raise
CompilationException
(
e
.
isBug
,
"{0}.{1}: {2}"
,
hlirParentType
,
self
.
name
,
e
.
show
())
def
widthInBits
(
self
):
return
self
.
width
class
EbpfStructType
(
EbpfType
):
# Abstract base class for HeaderType and MetadataType.
# They are both represented by a p4 header_type
def
__init__
(
self
,
hlirHeader
,
config
):
super
(
EbpfStructType
,
self
).
__init__
(
hlirHeader
)
self
.
name
=
hlirHeader
.
name
self
.
fields
=
[]
for
(
fieldName
,
fieldSize
)
in
self
.
hlirType
.
layout
.
items
():
attributes
=
self
.
hlirType
.
attributes
[
fieldName
]
field
=
EbpfField
(
hlirHeader
,
fieldName
,
fieldSize
,
attributes
,
config
)
self
.
fields
.
append
(
field
)
def
serialize
(
self
,
serializer
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"struct {0} "
,
self
.
name
)
serializer
.
blockStart
()
for
field
in
self
.
fields
:
serializer
.
emitIndent
()
field
.
type
.
declare
(
serializer
,
field
.
name
,
False
)
serializer
.
appendFormat
(
"; /* {0} bits */"
,
field
.
widthInBits
())
serializer
.
newline
()
serializer
.
blockEnd
(
False
)
serializer
.
endOfStatement
(
True
)
def
declare
(
self
,
serializer
,
identifier
,
asPointer
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
assert
isinstance
(
identifier
,
str
)
assert
isinstance
(
asPointer
,
bool
)
serializer
.
appendFormat
(
"struct {0} "
,
self
.
name
)
if
asPointer
:
serializer
.
append
(
"*"
)
serializer
.
append
(
identifier
)
def
widthInBits
(
self
):
return
self
.
hlirType
.
length
*
8
def
getField
(
self
,
name
):
assert
isinstance
(
name
,
str
)
for
f
in
self
.
fields
:
assert
isinstance
(
f
,
EbpfField
)
if
f
.
name
==
name
:
return
f
raise
CompilationException
(
True
,
"Could not locate field {0}.{1}"
,
self
,
name
)
class
EbpfHeaderType
(
EbpfStructType
):
def
__init__
(
self
,
hlirHeader
,
config
):
super
(
EbpfHeaderType
,
self
).
__init__
(
hlirHeader
,
config
)
validField
=
EbpfField
(
hlirHeader
,
"valid"
,
1
,
set
(),
config
)
# check that no "valid" field exists already
for
f
in
self
.
fields
:
if
f
.
name
==
"valid"
:
raise
CompilationException
(
True
,
"Header type contains a field named `valid': {0}"
,
f
)
self
.
fields
.
append
(
validField
)
def
emitInitializer
(
self
,
serializer
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
serializer
.
blockStart
()
serializer
.
emitIndent
()
serializer
.
appendLine
(
".valid = 0"
)
serializer
.
blockEnd
(
False
)
def
declareArray
(
self
,
serializer
,
identifier
,
size
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
serializer
.
appendFormat
(
"struct {0} {1}[{2}]"
,
self
.
name
,
identifier
,
size
)
class
EbpfMetadataType
(
EbpfStructType
):
def
__init__
(
self
,
hlirHeader
,
config
):
super
(
EbpfMetadataType
,
self
).
__init__
(
hlirHeader
,
config
)
def
emitInitializer
(
self
,
serializer
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
serializer
.
blockStart
()
for
field
in
self
.
fields
:
serializer
.
emitIndent
()
serializer
.
appendFormat
(
".{0} = "
,
field
.
name
)
field
.
type
.
emitInitializer
(
serializer
)
serializer
.
append
(
","
)
serializer
.
newline
()
serializer
.
blockEnd
(
False
)
src/cc/frontends/p4/compiler/ebpfTable.py
0 → 100644
View file @
0bde502c
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from
p4_hlir.hlir
import
p4_match_type
,
p4_field
,
p4_table
,
p4_header_instance
from
programSerializer
import
ProgramSerializer
from
compilationException
import
*
import
ebpfProgram
import
ebpfInstance
import
ebpfCounter
import
ebpfStructType
import
ebpfAction
class
EbpfTableKeyField
(
object
):
def
__init__
(
self
,
fieldname
,
instance
,
field
,
mask
):
assert
isinstance
(
instance
,
ebpfInstance
.
EbpfInstanceBase
)
assert
isinstance
(
field
,
ebpfStructType
.
EbpfField
)
self
.
keyFieldName
=
fieldname
self
.
instance
=
instance
self
.
field
=
field
self
.
mask
=
mask
def
serializeType
(
self
,
serializer
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
ftype
=
self
.
field
.
type
serializer
.
emitIndent
()
ftype
.
declare
(
serializer
,
self
.
keyFieldName
,
False
)
serializer
.
endOfStatement
(
True
)
def
serializeConstruction
(
self
,
keyName
,
serializer
,
program
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
assert
isinstance
(
keyName
,
str
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
if
self
.
mask
is
not
None
:
maskExpression
=
" & {0}"
.
format
(
self
.
mask
)
else
:
maskExpression
=
""
if
isinstance
(
self
.
instance
,
ebpfInstance
.
EbpfMetadata
):
base
=
program
.
metadataStructName
else
:
base
=
program
.
headerStructName
if
isinstance
(
self
.
instance
,
ebpfInstance
.
SimpleInstance
):
source
=
"{0}.{1}.{2}"
.
format
(
base
,
self
.
instance
.
name
,
self
.
field
.
name
)
else
:
assert
isinstance
(
self
.
instance
,
ebpfInstance
.
EbpfHeaderStack
)
source
=
"{0}.{1}[{2}].{3}"
.
format
(
base
,
self
.
instance
.
name
,
self
.
instance
.
hlirInstance
.
index
,
self
.
field
.
name
)
destination
=
"{0}.{1}"
.
format
(
keyName
,
self
.
keyFieldName
)
size
=
self
.
field
.
widthInBits
()
serializer
.
emitIndent
()
if
size
<=
32
:
serializer
.
appendFormat
(
"{0} = ({1}){2};"
,
destination
,
source
,
maskExpression
)
else
:
if
maskExpression
!=
""
:
raise
NotSupportedException
(
"{0} Mask wider than 32 bits"
,
self
.
field
.
hlirType
)
serializer
.
appendFormat
(
"memcpy(&{0}, &{1}, {2});"
,
destination
,
source
,
size
/
8
)
serializer
.
newline
()
class
EbpfTableKey
(
object
):
def
__init__
(
self
,
match_fields
,
program
):
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
self
.
expressions
=
[]
self
.
fields
=
[]
self
.
masks
=
[]
self
.
fieldNamePrefix
=
"key_field_"
self
.
program
=
program
fieldNumber
=
0
for
f
in
match_fields
:
field
=
f
[
0
]
matchType
=
f
[
1
]
mask
=
f
[
2
]
if
((
matchType
is
p4_match_type
.
P4_MATCH_TERNARY
)
or
(
matchType
is
p4_match_type
.
P4_MATCH_LPM
)
or
(
matchType
is
p4_match_type
.
P4_MATCH_RANGE
)):
raise
NotSupportedException
(
False
,
"Match type {0}"
,
matchType
)
if
matchType
is
p4_match_type
.
P4_MATCH_VALID
:
# we should be really checking the valid field;
# p4_field is a header instance
assert
isinstance
(
field
,
p4_header_instance
)
instance
=
field
fieldname
=
"valid"
else
:
assert
isinstance
(
field
,
p4_field
)
instance
=
field
.
instance
fieldname
=
field
.
name
if
ebpfProgram
.
EbpfProgram
.
isArrayElementInstance
(
instance
):
ebpfStack
=
program
.
getStackInstance
(
instance
.
base_name
)
assert
isinstance
(
ebpfStack
,
ebpfInstance
.
EbpfHeaderStack
)
basetype
=
ebpfStack
.
basetype
eInstance
=
program
.
getStackInstance
(
instance
.
base_name
)
else
:
ebpfHeader
=
program
.
getInstance
(
instance
.
name
)
assert
isinstance
(
ebpfHeader
,
ebpfInstance
.
SimpleInstance
)
basetype
=
ebpfHeader
.
type
eInstance
=
program
.
getInstance
(
instance
.
base_name
)
ebpfField
=
basetype
.
getField
(
fieldname
)
assert
isinstance
(
ebpfField
,
ebpfStructType
.
EbpfField
)
fieldName
=
self
.
fieldNamePrefix
+
str
(
fieldNumber
)
fieldNumber
+=
1
keyField
=
EbpfTableKeyField
(
fieldName
,
eInstance
,
ebpfField
,
mask
)
self
.
fields
.
append
(
keyField
)
self
.
masks
.
append
(
mask
)
@
staticmethod
def
fieldRank
(
field
):
assert
isinstance
(
field
,
EbpfTableKeyField
)
return
field
.
field
.
type
.
alignment
()
def
serializeType
(
self
,
serializer
,
keyTypeName
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"struct {0} "
,
keyTypeName
)
serializer
.
blockStart
()
# Sort fields in decreasing size; this will ensure that
# there is no padding.
# Padding may cause the ebpf verification to fail,
# since padding fields are not initalized
fieldOrder
=
sorted
(
self
.
fields
,
key
=
EbpfTableKey
.
fieldRank
,
reverse
=
True
)
for
f
in
fieldOrder
:
assert
isinstance
(
f
,
EbpfTableKeyField
)
f
.
serializeType
(
serializer
)
serializer
.
blockEnd
(
False
)
serializer
.
endOfStatement
(
True
)
def
serializeConstruction
(
self
,
serializer
,
keyName
,
program
):
serializer
.
emitIndent
()
serializer
.
appendLine
(
"/* construct key */"
)
for
f
in
self
.
fields
:
f
.
serializeConstruction
(
keyName
,
serializer
,
program
)
class
EbpfTable
(
object
):
# noinspection PyUnresolvedReferences
def
__init__
(
self
,
hlirtable
,
program
,
config
):
assert
isinstance
(
hlirtable
,
p4_table
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
self
.
name
=
hlirtable
.
name
self
.
hlirtable
=
hlirtable
self
.
config
=
config
self
.
defaultActionMapName
=
(
program
.
reservedPrefix
+
self
.
name
+
"_miss"
)
self
.
key
=
EbpfTableKey
(
hlirtable
.
match_fields
,
program
)
self
.
size
=
hlirtable
.
max_size
if
self
.
size
is
None
:
program
.
emitWarning
(
"{0} does not specify a max_size; using 1024"
,
hlirtable
)
self
.
size
=
1024
self
.
isHash
=
True
# TODO: try to guess arrays when possible
self
.
dataMapName
=
self
.
name
self
.
actionEnumName
=
program
.
generateNewName
(
self
.
name
+
"_actions"
)
self
.
keyTypeName
=
program
.
generateNewName
(
self
.
name
+
"_key"
)
self
.
valueTypeName
=
program
.
generateNewName
(
self
.
name
+
"_value"
)
self
.
actions
=
[]
if
hlirtable
.
action_profile
is
not
None
:
raise
NotSupportedException
(
"{0}: action_profile tables"
,
hlirtable
)
if
hlirtable
.
support_timeout
:
program
.
emitWarning
(
"{0}: table timeout {1}; ignoring"
,
hlirtable
,
NotSupportedException
.
archError
)
self
.
counters
=
[]
if
(
hlirtable
.
attached_counters
is
not
None
):
for
c
in
hlirtable
.
attached_counters
:
ctr
=
program
.
getCounter
(
c
.
name
)
assert
isinstance
(
ctr
,
ebpfCounter
.
EbpfCounter
)
self
.
counters
.
append
(
ctr
)
if
(
len
(
hlirtable
.
attached_meters
)
>
0
or
len
(
hlirtable
.
attached_registers
)
>
0
):
program
.
emitWarning
(
"{0}: meters/registers {1}; ignored"
,
hlirtable
,
NotSupportedException
.
archError
)
for
a
in
hlirtable
.
actions
:
action
=
program
.
getAction
(
a
)
self
.
actions
.
append
(
action
)
def
serializeKeyType
(
self
,
serializer
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
self
.
key
.
serializeType
(
serializer
,
self
.
keyTypeName
)
def
serializeActionArguments
(
self
,
serializer
,
action
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
assert
isinstance
(
action
,
ebpfAction
.
EbpfActionBase
)
action
.
serializeArgumentsAsStruct
(
serializer
)
def
serializeValueType
(
self
,
serializer
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
# create an enum with tags for all actions
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"enum {0} "
,
self
.
actionEnumName
)
serializer
.
blockStart
()
for
a
in
self
.
actions
:
name
=
a
.
name
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0}_{1},"
,
self
.
name
,
name
)
serializer
.
newline
()
serializer
.
blockEnd
(
False
)
serializer
.
endOfStatement
(
True
)
# a type-safe union: a struct with a tag and an union
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"struct {0} "
,
self
.
valueTypeName
)
serializer
.
blockStart
()
serializer
.
emitIndent
()
#serializer.appendFormat("enum {0} action;", self.actionEnumName)
# teporary workaround bcc bug
serializer
.
appendFormat
(
"{0}32 action;"
,
self
.
config
.
uprefix
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
append
(
"union "
)
serializer
.
blockStart
()
for
a
in
self
.
actions
:
self
.
serializeActionArguments
(
serializer
,
a
)
serializer
.
blockEnd
(
False
)
serializer
.
space
()
serializer
.
appendLine
(
"u;"
)
serializer
.
blockEnd
(
False
)
serializer
.
endOfStatement
(
True
)
def
serialize
(
self
,
serializer
,
program
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
self
.
serializeKeyType
(
serializer
)
self
.
serializeValueType
(
serializer
)
self
.
config
.
serializeTableDeclaration
(
serializer
,
self
.
dataMapName
,
self
.
isHash
,
"struct "
+
self
.
keyTypeName
,
"struct "
+
self
.
valueTypeName
,
self
.
size
)
self
.
config
.
serializeTableDeclaration
(
serializer
,
self
.
defaultActionMapName
,
False
,
program
.
arrayIndexType
,
"struct "
+
self
.
valueTypeName
,
1
)
def
serializeCode
(
self
,
serializer
,
program
,
nextNode
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
assert
isinstance
(
program
,
ebpfProgram
.
EbpfProgram
)
hitVarName
=
program
.
reservedPrefix
+
"hit"
keyname
=
"key"
valueName
=
"value"
serializer
.
emitIndent
()
serializer
.
blockStart
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0}8 {1};"
,
program
.
config
.
uprefix
,
hitVarName
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"struct {0} {1};"
,
self
.
keyTypeName
,
keyname
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"struct {0} *{1};"
,
self
.
valueTypeName
,
valueName
)
serializer
.
newline
()
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0}:"
,
program
.
getLabel
(
self
))
serializer
.
newline
()
self
.
key
.
serializeConstruction
(
serializer
,
keyname
,
program
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0} = 1;"
,
hitVarName
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendLine
(
"/* perform lookup */"
)
serializer
.
emitIndent
()
program
.
config
.
serializeLookup
(
serializer
,
self
.
dataMapName
,
keyname
,
valueName
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"if ({0} == NULL) "
,
valueName
)
serializer
.
blockStart
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"{0} = 0;"
,
hitVarName
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendLine
(
"/* miss; find default action */"
)
serializer
.
emitIndent
()
program
.
config
.
serializeLookup
(
serializer
,
self
.
defaultActionMapName
,
program
.
zeroKeyName
,
valueName
)
serializer
.
newline
()
serializer
.
blockEnd
(
True
)
if
len
(
self
.
counters
)
>
0
:
serializer
.
emitIndent
()
serializer
.
append
(
"else "
)
serializer
.
blockStart
()
for
c
in
self
.
counters
:
assert
isinstance
(
c
,
ebpfCounter
.
EbpfCounter
)
if
c
.
autoIncrement
:
serializer
.
emitIndent
()
serializer
.
blockStart
()
c
.
serializeCode
(
keyname
,
serializer
,
program
)
serializer
.
blockEnd
(
True
)
serializer
.
blockEnd
(
True
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"if ({0} != NULL) "
,
valueName
)
serializer
.
blockStart
()
serializer
.
emitIndent
()
serializer
.
appendLine
(
"/* run action */"
)
self
.
runAction
(
serializer
,
self
.
name
,
valueName
,
program
,
nextNode
)
nextNode
=
self
.
hlirtable
.
next_
if
"hit"
in
nextNode
:
node
=
nextNode
[
"hit"
]
if
node
is
None
:
node
=
nextNode
label
=
program
.
getLabel
(
node
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"if (hit) goto {0};"
,
label
)
serializer
.
newline
()
node
=
nextNode
[
"miss"
]
if
node
is
None
:
node
=
nextNode
label
=
program
.
getLabel
(
node
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"else goto {0};"
,
label
)
serializer
.
newline
()
serializer
.
blockEnd
(
True
)
serializer
.
blockEnd
(
True
)
def
runAction
(
self
,
serializer
,
tableName
,
valueName
,
program
,
nextNode
):
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"switch ({0}->action) "
,
valueName
)
serializer
.
blockStart
()
for
a
in
self
.
actions
:
assert
isinstance
(
a
,
ebpfAction
.
EbpfActionBase
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"case {0}_{1}: "
,
tableName
,
a
.
name
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
blockStart
()
a
.
serializeBody
(
serializer
,
valueName
,
program
)
serializer
.
blockEnd
(
True
)
serializer
.
emitIndent
()
nextNodes
=
self
.
hlirtable
.
next_
if
a
.
hliraction
in
nextNodes
:
node
=
nextNodes
[
a
.
hliraction
]
if
node
is
None
:
node
=
nextNode
label
=
program
.
getLabel
(
node
)
serializer
.
appendFormat
(
"goto {0};"
,
label
)
else
:
serializer
.
appendFormat
(
"break;"
)
serializer
.
newline
()
serializer
.
blockEnd
(
True
)
src/cc/frontends/p4/compiler/ebpfType.py
0 → 100644
View file @
0bde502c
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from
compilationException
import
CompilationException
class
EbpfType
(
object
):
__doc__
=
"Base class for representing a P4 type"
def
__init__
(
self
,
hlirType
):
self
.
hlirType
=
hlirType
# Methods to override
def
serialize
(
self
,
serializer
):
# the type itself
raise
CompilationException
(
True
,
"Method must be overridden"
)
def
declare
(
self
,
serializer
,
identifier
,
asPointer
):
# declaration of an identifier with this type
# asPointer is a boolean;
# if true, the identifier is declared as a pointer
raise
CompilationException
(
True
,
"Method must be overridden"
)
def
emitInitializer
(
self
,
serializer
):
# A default initializer suitable for this type
raise
CompilationException
(
True
,
"Method must be overridden"
)
def
declareArray
(
self
,
serializer
,
identifier
,
size
):
# Declare an identifier with an array type with the specified size
raise
CompilationException
(
True
,
"Method must be overridden"
)
src/cc/frontends/p4/compiler/p4toEbpf.py
0 → 100755
View file @
0bde502c
#!/usr/bin/env python
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
# Compiler from P4 to EBPF
# (See http://www.slideshare.net/PLUMgrid/ebpf-and-linux-networking).
# This compiler in fact generates a C source file
# which can be compiled to EBPF using the LLVM compiler
# with the ebpf target.
#
# Main entry point.
import
argparse
import
os
import
traceback
import
sys
import
target
from
p4_hlir.main
import
HLIR
from
ebpfProgram
import
EbpfProgram
from
compilationException
import
*
from
programSerializer
import
ProgramSerializer
def
get_parser
():
parser
=
argparse
.
ArgumentParser
(
description
=
'p4toEbpf arguments'
)
parser
.
add_argument
(
'source'
,
metavar
=
'source'
,
type
=
str
,
help
=
'a P4 source file to compile'
)
parser
.
add_argument
(
'-g'
,
dest
=
'generated'
,
default
=
"router"
,
help
=
"kind of output produced: filter or router"
)
parser
.
add_argument
(
'-o'
,
dest
=
'output_file'
,
default
=
"output.c"
,
help
=
"generated C file name"
)
return
parser
def
process
(
input_args
):
parser
=
get_parser
()
args
,
unparsed_args
=
parser
.
parse_known_args
(
input_args
)
has_remaining_args
=
False
preprocessor_args
=
[]
for
a
in
unparsed_args
:
if
a
[:
2
]
==
"-D"
or
a
[:
2
]
==
"-I"
or
a
[:
2
]
==
"-U"
:
input_args
.
remove
(
a
)
preprocessor_args
.
append
(
a
)
else
:
has_remaining_args
=
True
# trigger error
if
has_remaining_args
:
parser
.
parse_args
(
input_args
)
if
args
.
generated
==
"router"
:
isRouter
=
True
elif
args
.
generated
==
"filter"
:
isRouter
=
False
else
:
print
(
"-g should be one of 'filter' or 'router'"
)
print
(
"*** Compiling "
,
args
.
source
)
return
compileP4
(
args
.
source
,
args
.
output_file
,
isRouter
,
preprocessor_args
)
class
CompileResult
(
object
):
def
__init__
(
self
,
kind
,
error
):
self
.
kind
=
kind
self
.
error
=
error
def
__str__
(
self
):
if
self
.
kind
==
"OK"
:
return
"Compilation successful"
else
:
return
"Compilation failed with error: "
+
self
.
error
def
compileP4
(
inputFile
,
gen_file
,
isRouter
,
preprocessor_args
):
h
=
HLIR
(
inputFile
)
for
parg
in
preprocessor_args
:
h
.
add_preprocessor_args
(
parg
)
if
not
h
.
build
():
return
CompileResult
(
"HLIR"
,
"Error while building HLIR"
)
try
:
basename
=
os
.
path
.
basename
(
inputFile
)
basename
=
os
.
path
.
splitext
(
basename
)[
0
]
config
=
target
.
BccConfig
()
e
=
EbpfProgram
(
basename
,
h
,
isRouter
,
config
)
serializer
=
ProgramSerializer
()
e
.
toC
(
serializer
)
f
=
open
(
gen_file
,
'w'
)
f
.
write
(
serializer
.
toString
())
return
CompileResult
(
"OK"
,
""
)
except
CompilationException
,
e
:
prefix
=
""
if
e
.
isBug
:
prefix
=
"### Compiler bug: "
return
CompileResult
(
"bug"
,
prefix
+
e
.
show
())
except
NotSupportedException
,
e
:
return
CompileResult
(
"not supported"
,
e
.
show
())
except
:
return
CompileResult
(
"exception"
,
traceback
.
format_exc
())
# main entry point
if
__name__
==
"__main__"
:
result
=
process
(
sys
.
argv
[
1
:])
if
result
.
kind
!=
"OK"
:
print
(
str
(
result
))
src/cc/frontends/p4/compiler/programSerializer.py
0 → 100644
View file @
0bde502c
#!/usr/bin/env python
# helper for building C program source text
from
compilationException
import
*
class
ProgramSerializer
(
object
):
def
__init__
(
self
):
self
.
program
=
""
self
.
eol
=
"
\
n
"
self
.
currentIndent
=
0
self
.
INDENT_AMOUNT
=
4
# default indent amount
def
__str__
(
self
):
return
self
.
program
def
increaseIndent
(
self
):
self
.
currentIndent
+=
self
.
INDENT_AMOUNT
def
decreaseIndent
(
self
):
self
.
currentIndent
-=
self
.
INDENT_AMOUNT
if
self
.
currentIndent
<
0
:
raise
CompilationException
(
True
,
"Negative indentation level"
)
def
toString
(
self
):
return
self
.
program
def
space
(
self
):
self
.
append
(
" "
)
def
newline
(
self
):
self
.
program
+=
self
.
eol
def
endOfStatement
(
self
,
addNewline
):
self
.
append
(
";"
)
if
addNewline
:
self
.
newline
()
def
append
(
self
,
string
):
self
.
program
+=
str
(
string
)
def
appendFormat
(
self
,
format
,
*
args
):
string
=
format
.
format
(
*
args
)
self
.
append
(
string
)
def
appendLine
(
self
,
string
):
self
.
append
(
string
)
self
.
newline
()
def
emitIndent
(
self
):
self
.
program
+=
" "
*
self
.
currentIndent
def
blockStart
(
self
):
self
.
append
(
"{"
)
self
.
newline
()
self
.
increaseIndent
()
def
blockEnd
(
self
,
addNewline
):
self
.
decreaseIndent
()
self
.
emitIndent
()
self
.
append
(
"}"
)
if
addNewline
:
self
.
newline
()
src/cc/frontends/p4/compiler/target.py
0 → 100644
View file @
0bde502c
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from
programSerializer
import
ProgramSerializer
# abstraction for isolating target-specific features
# Base class for representing target-specific configuration
class
TargetConfig
(
object
):
def
__init__
(
self
,
target
):
self
.
targetName
=
target
def
getIncludes
(
self
):
return
""
def
serializeLookup
(
self
,
serializer
,
tableName
,
key
,
value
):
serializer
.
appendFormat
(
"{0} = bpf_map_lookup_elem(&{1}, &{2});"
,
value
,
tableName
,
key
)
def
serializeUpdate
(
self
,
serializer
,
tableName
,
key
,
value
):
serializer
.
appendFormat
(
"bpf_map_update_elem(&{0}, &{1}, &{2}, BPF_ANY);"
,
tableName
,
key
,
value
)
def
serializeLicense
(
self
,
serializer
,
licenseString
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"char _license[] {0}(
\
"
license
\
"
) =
\
"
{1}
\
"
;"
,
self
.
config
.
section
,
licenseString
)
serializer
.
newline
()
def
serializeCodeSection
(
self
,
serializer
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
serializer
.
appendFormat
(
"{0}(
\
"
{1}
\
"
)"
,
self
.
section
,
self
.
entrySection
)
def
serializeTableDeclaration
(
self
,
serializer
,
tableName
,
isHash
,
keyType
,
valueType
,
size
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
assert
isinstance
(
tableName
,
str
)
assert
isinstance
(
isHash
,
bool
)
assert
isinstance
(
keyType
,
str
)
assert
isinstance
(
valueType
,
str
)
assert
isinstance
(
size
,
int
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
"struct {0} {1}(
\
"
maps
\
"
) {2} = "
,
self
.
tableName
,
self
.
section
,
tableName
)
serializer
.
blockStart
()
serializer
.
emitIndent
()
serializer
.
append
(
".type = "
)
if
isHash
:
serializer
.
appendLine
(
"BPF_MAP_TYPE_HASH,"
)
else
:
serializer
.
appendLine
(
"BPF_MAP_TYPE_ARRAY,"
)
serializer
.
emitIndent
()
serializer
.
appendFormat
(
".{0} = sizeof(struct {1}), "
,
self
.
tableKeyAttribute
,
keyType
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
".{0} = sizeof(struct {1}), "
,
self
.
tableValueAttribute
,
valueType
)
serializer
.
newline
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
".{0} = {1}, "
,
self
.
tableSizeAttribute
,
size
)
serializer
.
newline
()
serializer
.
blockEnd
(
False
)
serializer
.
endOfStatement
(
True
)
def
generateDword
(
self
,
serializer
):
serializer
.
appendFormat
(
"static inline {0}64 load_dword(void *skb, {0}64 off)"
,
self
.
uprefix
)
serializer
.
newline
()
serializer
.
blockStart
()
serializer
.
emitIndent
()
serializer
.
appendFormat
(
(
"return (({0}64)load_word(skb, off) << 32) | "
+
"load_word(skb, off + 4);"
),
self
.
uprefix
)
serializer
.
newline
()
serializer
.
blockEnd
(
True
)
# Represents a target that is compiled within the kernel
# source tree samples folder and which attaches to a socket
class
KernelSamplesConfig
(
TargetConfig
):
def
__init__
(
self
):
super
(
SocketConfig
,
self
).
__init__
(
"Socket"
)
self
.
entrySection
=
"socket1"
self
.
section
=
"SEC"
self
.
uprefix
=
"u"
self
.
iprefix
=
"i"
self
.
tableKeyAttribute
=
"key_size"
self
.
tableValueAttribute
=
"value_size"
self
.
tableSizeAttribute
=
"max_entries"
self
.
tableName
=
"bpf_map_def"
self
.
postamble
=
""
def
getIncludes
(
self
):
return
"""
#include <uapi/linux/bpf.h>
#include <uapi/linux/if_ether.h>
#include <uapi/linux/if_packet.h>
#include <uapi/linux/ip.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include "bpf_helpers.h"
"""
# Represents a target compiled by bcc that uses the TC
class
BccConfig
(
TargetConfig
):
def
__init__
(
self
):
super
(
BccConfig
,
self
).
__init__
(
"BCC"
)
self
.
uprefix
=
"u"
self
.
iprefix
=
"i"
self
.
postamble
=
""
def
serializeTableDeclaration
(
self
,
serializer
,
tableName
,
isHash
,
keyType
,
valueType
,
size
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
assert
isinstance
(
tableName
,
str
)
assert
isinstance
(
isHash
,
bool
)
assert
isinstance
(
keyType
,
str
)
assert
isinstance
(
valueType
,
str
)
assert
isinstance
(
size
,
int
)
serializer
.
emitIndent
()
if
isHash
:
kind
=
"hash"
else
:
kind
=
"array"
serializer
.
appendFormat
(
"BPF_TABLE(
\
"
{0}
\
"
, {1}, {2}, {3}, {4});"
,
kind
,
keyType
,
valueType
,
tableName
,
size
)
serializer
.
newline
()
def
serializeLookup
(
self
,
serializer
,
tableName
,
key
,
value
):
serializer
.
appendFormat
(
"{0} = {1}.lookup(&{2});"
,
value
,
tableName
,
key
)
def
serializeUpdate
(
self
,
serializer
,
tableName
,
key
,
value
):
serializer
.
appendFormat
(
"{0}.update(&{1}, &{2});"
,
tableName
,
key
,
value
)
def
generateDword
(
self
,
serializer
):
pass
def
serializeCodeSection
(
self
,
serializer
):
pass
def
getIncludes
(
self
):
return
"""
#include <uapi/linux/bpf.h>
#include <uapi/linux/if_ether.h>
#include <uapi/linux/if_packet.h>
#include <uapi/linux/ip.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/pkt_cls.h>
"""
def
serializeLicense
(
self
,
serializer
,
licenseString
):
assert
isinstance
(
serializer
,
ProgramSerializer
)
pass
src/cc/frontends/p4/compiler/typeFactory.py
0 → 100644
View file @
0bde502c
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from
p4_hlir.hlir
import
p4_header
from
ebpfStructType
import
*
class
EbpfTypeFactory
(
object
):
def
__init__
(
self
,
config
):
self
.
type_map
=
{}
self
.
config
=
config
def
build
(
self
,
hlirType
,
asMetadata
):
name
=
hlirType
.
name
if
hlirType
.
name
in
self
.
type_map
:
retval
=
self
.
type_map
[
name
]
if
((
not
asMetadata
and
isinstance
(
retval
,
EbpfMetadataType
))
or
(
asMetadata
and
isinstance
(
retval
,
EbpfHeaderType
))):
raise
CompilationException
(
True
,
"Same type used both as a header and metadata {0}"
,
hlirType
)
if
isinstance
(
hlirType
,
p4_header
):
if
asMetadata
:
type
=
EbpfMetadataType
(
hlirType
,
self
.
config
)
else
:
type
=
EbpfHeaderType
(
hlirType
,
self
.
config
)
else
:
raise
CompilationException
(
True
,
"Unexpected type {0}"
,
hlirType
)
self
.
registerType
(
name
,
type
)
return
type
def
registerType
(
self
,
name
,
ebpfType
):
self
.
type_map
[
name
]
=
ebpfType
src/cc/frontends/p4/scope.png
0 → 100644
View file @
0bde502c
98.7 KB
src/cc/frontends/p4/test/README.txt
0 → 100644
View file @
0bde502c
This folder contains tests for the P4->C->EBPF compiler
- cleanup.sh should be run if for some reason endToEndTest.py crashes
and leaves garbage namespaces or links
- testP4toEbpf.py compiles all P4 files in the testprograms folder and
deposits the corresponding C files in the testoutputs folder
- endToEndTest.py runs a complete end-to-end test compiling the
testprograms/simple.p4 program, creating a virtual network with 3
boxes (using network namespaces): client, server, switch, loading
the EBPF into the kernel of the switch box using the TC, and
implementing the forwarding in the switch solely using the P4
program.
src/cc/frontends/p4/test/cleanup.sh
0 → 100755
View file @
0bde502c
#!/bin/bash
# Run this script if for some reason the endToEndTest.py crashed
# and left some garbage state
ip netns del sw
ip netns del srv
ip netns del clt
ip
link
del dev veth-clt-sw
ip
link
del dev veth-srv-sw
src/cc/frontends/p4/test/endToEndTest.py
0 → 100755
View file @
0bde502c
#!/usr/bin/env python3
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
# Testing example for P4->EBPF compiler
#
# This program exercises the simple.c EBPF program
# generated from the simple.p4 source file.
import
subprocess
import
ctypes
import
time
import
sys
import
os
from
bcc
import
BPF
from
pyroute2
import
IPRoute
,
NSPopen
,
NetNS
from
netaddr
import
IPAddress
### This part is a simple generic network simulaton toolkit
class
Base
(
object
):
def
__init__
(
self
):
self
.
verbose
=
True
def
message
(
self
,
*
args
):
if
self
.
verbose
:
print
(
*
args
)
class
Endpoint
(
Base
):
# a network interface really
def
__init__
(
self
,
ipaddress
,
ethaddress
):
Base
.
__init__
(
self
)
self
.
mac_addr
=
ethaddress
self
.
ipaddress
=
ipaddress
self
.
prefixlen
=
24
self
.
parent
=
None
def
__str__
(
self
):
return
"Endpoint "
+
str
(
self
.
ipaddress
)
def
set_parent
(
self
,
parent
):
assert
isinstance
(
parent
,
Node
)
self
.
parent
=
parent
def
get_ip_address
(
self
):
return
IPAddress
(
self
.
ipaddress
)
class
Node
(
Base
):
# Used to represent one of clt, sw, srv
# Each lives in its own namespace
def
__init__
(
self
,
name
):
Base
.
__init__
(
self
)
self
.
name
=
name
self
.
endpoints
=
[]
self
.
get_ns
()
# as a side-effect creates namespace
def
add_endpoint
(
self
,
endpoint
):
assert
isinstance
(
endpoint
,
Endpoint
)
self
.
endpoints
.
append
(
endpoint
)
endpoint
.
set_parent
(
self
)
def
__str__
(
self
):
return
"Node "
+
self
.
name
def
get_ns_name
(
self
):
return
self
.
name
def
get_ns
(
self
):
nsname
=
self
.
get_ns_name
()
ns
=
NetNS
(
nsname
)
return
ns
def
remove
(
self
):
ns
=
self
.
get_ns
();
ns
.
close
()
ns
.
remove
()
def
execute
(
self
,
command
):
# Run a command in the node's namespace
# Return the command's exit code
self
.
message
(
self
.
name
,
"Executing"
,
command
)
nsn
=
self
.
get_ns_name
()
pipe
=
NSPopen
(
nsn
,
command
)
result
=
pipe
.
wait
()
pipe
.
release
()
return
result
def
set_arp
(
self
,
destination
):
assert
isinstance
(
destination
,
Endpoint
)
command
=
[
"arp"
,
"-s"
,
str
(
destination
.
ipaddress
),
str
(
destination
.
mac_addr
)]
self
.
execute
(
command
)
class
NetworkBase
(
Base
):
def
__init__
(
self
):
Base
.
__init__
(
self
)
self
.
ipr
=
IPRoute
()
self
.
nodes
=
[]
def
add_node
(
self
,
node
):
assert
isinstance
(
node
,
Node
)
self
.
nodes
.
append
(
node
)
def
get_interface_name
(
self
,
source
,
dest
):
assert
isinstance
(
source
,
Node
)
assert
isinstance
(
dest
,
Node
)
interface_name
=
"veth-"
+
source
.
name
+
"-"
+
dest
.
name
return
interface_name
def
get_interface
(
self
,
ifname
):
interfaces
=
self
.
ipr
.
link_lookup
(
ifname
=
ifname
)
if
len
(
interfaces
)
!=
1
:
raise
Exception
(
"Could not identify interface "
+
ifname
)
ix
=
interfaces
[
0
]
assert
isinstance
(
ix
,
int
)
return
ix
def
set_interface_ipaddress
(
self
,
node
,
ifname
,
address
,
mask
):
# Ask a node to set the specified interface address
if
address
is
None
:
return
assert
isinstance
(
node
,
Node
)
command
=
[
"ip"
,
"addr"
,
"add"
,
str
(
address
)
+
"/"
+
str
(
mask
),
"dev"
,
str
(
ifname
)]
result
=
node
.
execute
(
command
)
assert
(
result
==
0
)
def
create_link
(
self
,
src
,
dest
):
assert
isinstance
(
src
,
Endpoint
)
assert
isinstance
(
dest
,
Endpoint
)
ifname
=
self
.
get_interface_name
(
src
.
parent
,
dest
.
parent
)
destname
=
self
.
get_interface_name
(
dest
.
parent
,
src
.
parent
)
self
.
ipr
.
link_create
(
ifname
=
ifname
,
kind
=
"veth"
,
peer
=
destname
)
self
.
message
(
"Create"
,
ifname
,
"link"
)
# Set source endpoint information
ix
=
self
.
get_interface
(
ifname
)
self
.
ipr
.
link
(
"set"
,
index
=
ix
,
address
=
src
.
mac_addr
)
# push source endpoint into source namespace
self
.
ipr
.
link
(
"set"
,
index
=
ix
,
net_ns_fd
=
src
.
parent
.
get_ns_name
(),
state
=
"up"
)
# Set interface ip address; seems to be
# lost of set prior to moving to namespace
self
.
set_interface_ipaddress
(
src
.
parent
,
ifname
,
src
.
ipaddress
,
src
.
prefixlen
)
# Sef destination endpoint information
ix
=
self
.
get_interface
(
destname
)
self
.
ipr
.
link
(
"set"
,
index
=
ix
,
address
=
dest
.
mac_addr
)
# push destination endpoint into the destination namespace
self
.
ipr
.
link
(
"set"
,
index
=
ix
,
net_ns_fd
=
dest
.
parent
.
get_ns_name
(),
state
=
"up"
)
# Set interface ip address
self
.
set_interface_ipaddress
(
dest
.
parent
,
destname
,
dest
.
ipaddress
,
dest
.
prefixlen
)
def
show_interfaces
(
self
,
node
):
cmd
=
[
"ip"
,
"addr"
]
if
node
is
None
:
# Run with no namespace
subprocess
.
call
(
cmd
)
else
:
# Run in node's namespace
assert
isinstance
(
node
,
Node
)
self
.
message
(
"Enumerating all interfaces in "
,
node
.
name
)
node
.
execute
(
cmd
)
def
delete
(
self
):
self
.
message
(
"Deleting virtual network"
)
for
n
in
self
.
nodes
:
n
.
remove
()
self
.
ipr
.
close
()
### Here begins the concrete instantiation of the network
# Network setup:
# Each of these is a separate namespace.
#
# 62:ce:1b:48:3e:61 a2:59:94:cf:51:09
# 96:a4:85:fe:2a:11 62:ce:1b:48:3e:60
# /------------------\ /-----------------\
# ---------- -------- ---------
# | clt | | sw | | srv |
# ---------- -------- ---------
# 10.0.0.11 10.0.0.10
#
class
SimulatedNetwork
(
NetworkBase
):
def
__init__
(
self
):
NetworkBase
.
__init__
(
self
)
self
.
client
=
Node
(
"clt"
)
self
.
add_node
(
self
.
client
)
self
.
client_endpoint
=
Endpoint
(
"10.0.0.11"
,
"96:a4:85:fe:2a:11"
)
self
.
client
.
add_endpoint
(
self
.
client_endpoint
)
self
.
server
=
Node
(
"srv"
)
self
.
add_node
(
self
.
server
)
self
.
server_endpoint
=
Endpoint
(
"10.0.0.10"
,
"a2:59:94:cf:51:09"
)
self
.
server
.
add_endpoint
(
self
.
server_endpoint
)
self
.
switch
=
Node
(
"sw"
)
self
.
add_node
(
self
.
switch
)
self
.
sw_clt_endpoint
=
Endpoint
(
None
,
"62:ce:1b:48:3e:61"
)
self
.
sw_srv_endpoint
=
Endpoint
(
None
,
"62:ce:1b:48:3e:60"
)
self
.
switch
.
add_endpoint
(
self
.
sw_clt_endpoint
)
self
.
switch
.
add_endpoint
(
self
.
sw_srv_endpoint
)
def
run_method_in_node
(
self
,
node
,
method
,
args
):
# run a method of the SimulatedNetwork class in a different namespace
# return the exit code
assert
isinstance
(
node
,
Node
)
assert
isinstance
(
args
,
list
)
torun
=
__file__
args
.
insert
(
0
,
torun
)
args
.
insert
(
1
,
method
)
return
node
.
execute
(
args
)
# runs the command argv[0] method args
def
instantiate
(
self
):
# Creates the various namespaces
self
.
message
(
"Creating virtual network"
)
self
.
message
(
"Create client-switch link"
)
self
.
create_link
(
self
.
client_endpoint
,
self
.
sw_clt_endpoint
)
self
.
message
(
"Create server-switch link"
)
self
.
create_link
(
self
.
server_endpoint
,
self
.
sw_srv_endpoint
)
self
.
show_interfaces
(
self
.
client
)
self
.
show_interfaces
(
self
.
server
)
self
.
show_interfaces
(
self
.
switch
)
self
.
message
(
"Set ARP mappings"
)
self
.
client
.
set_arp
(
self
.
server_endpoint
)
self
.
server
.
set_arp
(
self
.
client_endpoint
)
def
setup_switch
(
self
):
# This method is run in the switch namespace.
self
.
message
(
"Compiling and loading BPF program"
)
b
=
BPF
(
src_file
=
"./simple.c"
,
debug
=
0
)
fn
=
b
.
load_func
(
"ebpf_filter"
,
BPF
.
SCHED_CLS
)
self
.
message
(
"BPF program loaded"
)
self
.
message
(
"Discovering tables"
)
routing_tbl
=
b
.
get_table
(
"routing"
)
routing_miss_tbl
=
b
.
get_table
(
"ebpf_routing_miss"
)
cnt_tbl
=
b
.
get_table
(
"cnt"
)
self
.
message
(
"Hooking up BPF classifiers using TC"
)
interfname
=
self
.
get_interface_name
(
self
.
switch
,
self
.
server
)
sw_srv_idx
=
self
.
get_interface
(
interfname
)
self
.
ipr
.
tc
(
"add"
,
"ingress"
,
sw_srv_idx
,
"ffff:"
)
self
.
ipr
.
tc
(
"add-filter"
,
"bpf"
,
sw_srv_idx
,
":1"
,
fd
=
fn
.
fd
,
name
=
fn
.
name
,
parent
=
"ffff:"
,
action
=
"ok"
,
classid
=
1
)
interfname
=
self
.
get_interface_name
(
self
.
switch
,
self
.
client
)
sw_clt_idx
=
self
.
get_interface
(
interfname
)
self
.
ipr
.
tc
(
"add"
,
"ingress"
,
sw_clt_idx
,
"ffff:"
)
self
.
ipr
.
tc
(
"add-filter"
,
"bpf"
,
sw_clt_idx
,
":1"
,
fd
=
fn
.
fd
,
name
=
fn
.
name
,
parent
=
"ffff:"
,
action
=
"ok"
,
classid
=
1
)
self
.
message
(
"Populating tables from the control plane"
)
cltip
=
self
.
client_endpoint
.
get_ip_address
()
srvip
=
self
.
server_endpoint
.
get_ip_address
()
# BCC does not support tbl.Leaf when the type contains a union,
# so we have to make up the value type manually. Unfortunately
# these sizes are not portable...
class
Forward
(
ctypes
.
Structure
):
_fields_
=
[(
"port"
,
ctypes
.
c_ushort
)]
class
Nop
(
ctypes
.
Structure
):
_fields_
=
[]
class
Union
(
ctypes
.
Union
):
_fields_
=
[(
"nop"
,
Nop
),
(
"forward"
,
Forward
)]
class
Value
(
ctypes
.
Structure
):
_fields_
=
[(
"action"
,
ctypes
.
c_uint
),
(
"u"
,
Union
)]
if
False
:
# This is how it should ideally be done, but it does not work
routing_tbl
[
routing_tbl
.
Key
(
int
(
cltip
))]
=
routing_tbl
.
Leaf
(
1
,
sw_clt_idx
)
routing_tbl
[
routing_tbl
.
Key
(
int
(
srvip
))]
=
routing_tbl
.
Leaf
(
1
,
sw_srv_idx
)
else
:
v1
=
Value
()
v1
.
action
=
1
v1
.
u
.
forward
.
port
=
sw_clt_idx
v2
=
Value
()
v2
.
action
=
1
;
v2
.
u
.
forward
.
port
=
sw_srv_idx
routing_tbl
[
routing_tbl
.
Key
(
int
(
cltip
))]
=
v1
routing_tbl
[
routing_tbl
.
Key
(
int
(
srvip
))]
=
v2
self
.
message
(
"Dumping table contents"
)
for
key
,
leaf
in
routing_tbl
.
items
():
self
.
message
(
str
(
IPAddress
(
key
.
key_field_0
)),
leaf
.
action
,
leaf
.
u
.
forward
.
port
)
def
run
(
self
):
self
.
message
(
"Pinging server from client"
)
ping
=
[
"ping"
,
self
.
server_endpoint
.
ipaddress
,
"-c"
,
"2"
]
result
=
self
.
client
.
execute
(
ping
)
if
result
!=
0
:
raise
Exception
(
"Test failed"
)
else
:
print
(
"Test succeeded!"
)
def
prepare_switch
(
self
):
self
.
message
(
"Configuring switch"
)
# Re-invokes this script in the switch namespace;
# this causes the setup_switch method to be run in that context.
# This is the same as running self.setup_switch()
# but in the switch namespace
self
.
run_method_in_node
(
self
.
switch
,
"setup_switch"
,
[])
def
compile
(
source
,
destination
):
try
:
status
=
subprocess
.
call
(
"../compiler/p4toEbpf.py "
+
source
+
" -o "
+
destination
,
shell
=
True
)
if
status
<
0
:
print
(
"Child was terminated by signal"
,
-
status
,
file
=
sys
.
stderr
)
else
:
print
(
"Child returned"
,
status
,
file
=
sys
.
stderr
)
except
OSError
as
e
:
print
(
"Execution failed:"
,
e
,
file
=
sys
.
stderr
)
raise
e
def
start_simulation
():
compile
(
"testprograms/simple.p4"
,
"simple.c"
)
network
=
SimulatedNetwork
()
network
.
instantiate
()
network
.
prepare_switch
()
network
.
run
()
network
.
delete
()
os
.
remove
(
"simple.c"
)
def
main
(
argv
):
print
(
str
(
argv
))
if
len
(
argv
)
==
1
:
# Main entry point: start simulation
start_simulation
()
else
:
# We are invoked with some arguments (probably in a different namespace)
# First argument is a method name, rest are method arguments.
# Create a SimulatedNetwork and invoke the specified method with the
# specified arguments.
network
=
SimulatedNetwork
()
methodname
=
argv
[
1
]
arguments
=
argv
[
2
:]
method
=
getattr
(
network
,
methodname
)
method
(
*
arguments
)
if
__name__
==
'__main__'
:
main
(
sys
.
argv
)
src/cc/frontends/p4/test/testP4toEbpf.py
0 → 100755
View file @
0bde502c
#!/usr/bin/env python
# Copyright (c) Barefoot Networks, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
# Runs the compiler on all files in the 'testprograms' folder
# Writes outputs in the 'testoutputs' folder
from
bcc
import
BPF
import
os
,
sys
sys
.
path
.
append
(
"../compiler"
)
# To get hold of p4toEbpf
# We want to run it without installing it
import
p4toEbpf
import
os
def
drop_extension
(
filename
):
return
os
.
path
.
splitext
(
os
.
path
.
basename
(
filename
))[
0
]
filesFailed
=
{}
# map error kind -> list[ (file, error) ]
def
set_error
(
kind
,
file
,
error
):
if
kind
in
filesFailed
:
filesFailed
[
kind
].
append
((
file
,
error
))
else
:
filesFailed
[
kind
]
=
[(
file
,
error
)]
def
is_root
():
# Is this code portable?
return
os
.
getuid
()
==
0
def
main
():
testpath
=
"testprograms"
destFolder
=
"testoutputs"
files
=
os
.
listdir
(
testpath
)
files
.
sort
()
filesDone
=
0
errors
=
0
if
not
is_root
():
print
"Loading EBPF programs requires root priviledge."
print
"Will only test compilation, not loading."
print
"(Run with sudo to test program loading.)"
for
f
in
files
:
path
=
os
.
path
.
join
(
testpath
,
f
)
if
not
os
.
path
.
isfile
(
path
):
continue
if
not
path
.
endswith
(
".p4"
):
continue
destname
=
drop_extension
(
path
)
+
".c"
destname
=
os
.
path
.
join
(
destFolder
,
destname
)
args
=
[
path
,
"-o"
,
destname
]
result
=
p4toEbpf
.
process
(
args
)
if
result
.
kind
!=
"OK"
:
errors
+=
1
print
path
,
result
.
error
set_error
(
result
.
kind
,
path
,
result
.
error
)
else
:
# Try to load the compiled function
if
is_root
():
try
:
print
(
"Compiling and loading BPF program"
)
b
=
BPF
(
src_file
=
destname
,
debug
=
0
)
fn
=
b
.
load_func
(
"ebpf_filter"
,
BPF
.
SCHED_CLS
)
except
Exception
as
e
:
print
(
e
)
set_error
(
"BPF error"
,
path
,
str
(
e
))
filesDone
+=
1
print
"Compiled"
,
filesDone
,
"files"
,
errors
,
"errors"
for
key
in
sorted
(
filesFailed
):
print
key
,
":"
,
len
(
filesFailed
[
key
]),
"programs"
for
v
in
filesFailed
[
key
]:
print
"
\
t
"
,
v
exit
(
len
(
filesFailed
)
!=
0
)
if
__name__
==
"__main__"
:
main
()
src/cc/frontends/p4/test/testoutputs/.empty
0 → 100644
View file @
0bde502c
src/cc/frontends/p4/test/testprograms/arrayKey.p4
0 → 100644
View file @
0bde502c
header_type ethernet_t {
fields {
dstAddr : 48;
srcAddr : 48;
etherType : 16;
}
}
parser start {
return parse_ethernet;
}
header ethernet_t ethernet;
parser parse_ethernet {
extract(ethernet);
return ingress;
}
action nop()
{}
table routing {
reads {
ethernet.dstAddr: exact;
}
actions { nop; }
size : 512;
}
control ingress
{
apply(routing);
}
\ No newline at end of file
src/cc/frontends/p4/test/testprograms/bitfields.p4
0 → 100644
View file @
0bde502c
header_type ht
{
fields
{
f1 : 1;
f2 : 2;
f3 : 3;
f4 : 4;
f5 : 5;
f6 : 6;
f7 : 7;
f8 : 8;
f9 : 9;
f10 : 10;
f11 : 11;
f12 : 12;
f13 : 13;
f14 : 14;
f15 : 15;
f16 : 16;
f17 : 17;
f18 : 18;
f19 : 19;
f20 : 20;
f21 : 21;
f22 : 22;
f23 : 23;
f24 : 24;
f25 : 25;
f26 : 26;
f27 : 27;
f28 : 28;
f29 : 29;
f30 : 30;
f31 : 31;
f32 : 32;
}
}
header_type larget
{
fields
{
f48 : 48;
f1: 1;
f49 : 48;
f2 : 1;
f64 : 64;
f3 : 1;
f128 : 128;
}
}
header ht h;
header larget large;
parser start
{
extract(h);
extract(large);
return ingress;
}
control ingress
{
}
src/cc/frontends/p4/test/testprograms/compositeArray.p4
0 → 100644
View file @
0bde502c
header_type ethernet_t {
fields {
dstAddr : 48;
}
}
header_type ipv4_t {
fields {
srcAddr : 32;
}
}
parser start {
return parse_ethernet;
}
header ethernet_t ethernet;
parser parse_ethernet {
extract(ethernet);
return parse_ipv4;
}
action nop()
{}
header ipv4_t ipv4;
parser parse_ipv4 {
extract(ipv4);
return ingress;
}
table routing {
reads {
ethernet.dstAddr: exact;
ipv4.srcAddr: exact;
}
actions { nop; }
size : 512;
}
control ingress
{
apply(routing);
}
\ No newline at end of file
src/cc/frontends/p4/test/testprograms/compositeKey.p4
0 → 100644
View file @
0bde502c
header_type ethernet_t {
fields {
dstAddr : 48;
srcAddr : 48;
etherType : 16;
}
}
header_type ipv4_t {
fields {
version : 4;
ihl : 4;
diffserv : 8;
totalLen : 16;
identification : 16;
flags : 3;
fragOffset : 13;
ttl : 8;
protocol : 8;
hdrChecksum : 16;
srcAddr : 32;
dstAddr: 32;
}
}
parser start {
return parse_ethernet;
}
header ethernet_t ethernet;
parser parse_ethernet {
extract(ethernet);
return select(latest.etherType) {
0x800 : parse_ipv4;
default: ingress;
}
}
action nop()
{}
action forward(port)
{
modify_field(standard_metadata.egress_port, port);
}
header ipv4_t ipv4;
parser parse_ipv4 {
extract(ipv4);
return ingress;
}
table routing {
reads {
ipv4.dstAddr: exact;
ipv4.srcAddr: exact;
}
actions { nop; forward; }
size : 512;
}
counter cnt {
type: bytes;
direct: routing;
}
control ingress
{
apply(routing);
}
\ No newline at end of file
src/cc/frontends/p4/test/testprograms/do_nothing.p4
0 → 100644
View file @
0bde502c
/* Sample P4 program */
header_type ethernet_t {
fields {
dstAddr : 48;
srcAddr : 48;
etherType : 16;
}
}
parser start {
return parse_ethernet;
}
header ethernet_t ethernet;
parser parse_ethernet {
extract(ethernet);
return ingress;
}
action action_0(){
no_op();
}
table table_0 {
reads {
ethernet.etherType : exact;
}
actions {
action_0;
}
}
control ingress {
apply(table_0);
}
src/cc/frontends/p4/test/testprograms/simple.p4
0 → 100644
View file @
0bde502c
// Routes a packet to an interface based on its IPv4 address
// Maintains a set of counters on the routing table
header_type ethernet_t {
fields {
dstAddr : 48;
srcAddr : 48;
etherType : 16;
}
}
header_type ipv4_t {
fields {
version : 4;
ihl : 4;
diffserv : 8;
totalLen : 16;
identification : 16;
flags : 3;
fragOffset : 13;
ttl : 8;
protocol : 8;
hdrChecksum : 16;
srcAddr : 32;
dstAddr: 32;
}
}
parser start {
return parse_ethernet;
}
header ethernet_t ethernet;
parser parse_ethernet {
extract(ethernet);
return select(latest.etherType) {
0x800 : parse_ipv4;
default: ingress;
}
}
action nop()
{}
action forward(port)
{
modify_field(standard_metadata.egress_port, port);
}
header ipv4_t ipv4;
parser parse_ipv4 {
extract(ipv4);
return ingress;
}
table routing {
reads {
ipv4.dstAddr: exact;
}
actions { nop; forward; }
size : 512;
}
counter cnt {
type: bytes;
direct: routing;
}
control ingress
{
apply(routing);
}
\ No newline at end of file
src/python/bcc/__init__.py
View file @
0bde502c
...
...
@@ -99,7 +99,7 @@ KALLSYMS = "/proc/kallsyms"
ksym_addrs
=
[]
ksym_names
=
[]
ksym_loaded
=
0
stars_max
=
38
stars_max
=
40
@
atexit
.
register
def
cleanup_kprobes
():
...
...
@@ -238,38 +238,60 @@ class BPF(object):
text
=
text
[:
-
1
]
+
"+"
return
text
def
print_log2_hist
(
self
,
val_type
=
"value"
):
"""print_log2_hist(
type=value
)
def
print_log2_hist
(
self
,
val_type
=
"value"
,
bucket_type
=
"ptr"
):
"""print_log2_hist(
val_type="value", bucket_type="ptr"
)
Prints a table as a log2 histogram. The table must be stored as
log2. The type argument is optional, and is a column header.
log2. The val_type argument is optional, and is a column header.
If the histogram has a secondary key, multiple tables will print
and bucket_type can be used as a header description for each.
"""
if
isinstance
(
self
.
Key
(),
ct
.
Structure
):
tmp
=
{}
f1
=
self
.
Key
.
_fields_
[
0
][
0
]
f2
=
self
.
Key
.
_fields_
[
1
][
0
]
for
k
,
v
in
self
.
items
():
bucket
=
getattr
(
k
,
f1
)
vals
=
tmp
[
bucket
]
=
tmp
.
get
(
bucket
,
[
0
]
*
65
)
slot
=
getattr
(
k
,
f2
)
vals
[
slot
]
=
v
.
value
for
bucket
,
vals
in
tmp
.
items
():
print
(
"
\
n
Bucket %s = %r"
%
(
bucket_type
,
bucket
))
self
.
_print_log2_hist
(
vals
,
val_type
,
0
)
else
:
vals
=
[
0
]
*
65
for
k
,
v
in
self
.
items
():
vals
[
k
.
value
]
=
v
.
value
self
.
_print_log2_hist
(
vals
,
val_type
,
0
)
def
_print_log2_hist
(
self
,
vals
,
val_type
,
val_max
):
global
stars_max
log2_dist_max
=
64
idx_max
=
-
1
val_max
=
0
for
i
in
range
(
1
,
log2_dist_max
+
1
):
try
:
val
=
self
[
ct
.
c_int
(
i
)].
value
if
(
val
>
0
):
idx_max
=
i
if
(
val
>
val_max
):
val_max
=
val
except
:
break
for
i
,
v
in
enumerate
(
vals
):
if
v
>
0
:
idx_max
=
i
if
v
>
val_max
:
val_max
=
v
if
idx_max
<=
32
:
header
=
" %-19s : count distribution"
body
=
"%10d -> %-10d : %-8d |%-*s|"
stars
=
stars_max
else
:
header
=
" %-29s : count distribution"
body
=
"%20d -> %-20d : %-8d |%-*s|"
stars
=
int
(
stars_max
/
2
)
if
idx_max
>
0
:
print
(
" %-15s : count distribution"
%
val_type
);
print
(
header
%
val_type
);
for
i
in
range
(
1
,
idx_max
+
1
):
low
=
(
1
<<
i
)
>>
1
high
=
(
1
<<
i
)
-
1
if
(
low
==
high
):
low
-=
1
try
:
val
=
self
[
ct
.
c_int
(
i
)].
value
print
(
"%8d -> %-8d : %-8d |%-*s|"
%
(
low
,
high
,
val
,
stars_max
,
self
.
_stars
(
val
,
val_max
,
stars_max
)))
except
:
break
val
=
vals
[
i
]
print
(
body
%
(
low
,
high
,
val
,
stars
,
self
.
_stars
(
val
,
val_max
,
stars
)))
def
__iter__
(
self
):
...
...
@@ -407,7 +429,6 @@ class BPF(object):
u"_Bool"
:
ct
.
c_bool
,
u"char"
:
ct
.
c_char
,
u"wchar_t"
:
ct
.
c_wchar
,
u"char"
:
ct
.
c_byte
,
u"unsigned char"
:
ct
.
c_ubyte
,
u"short"
:
ct
.
c_short
,
u"unsigned short"
:
ct
.
c_ushort
,
...
...
@@ -430,7 +451,12 @@ class BPF(object):
if
len
(
t
)
==
2
:
fields
.
append
((
t
[
0
],
BPF
.
_decode_table_type
(
t
[
1
])))
elif
len
(
t
)
==
3
:
fields
.
append
((
t
[
0
],
BPF
.
_decode_table_type
(
t
[
1
]),
t
[
2
]))
if
isinstance
(
t
[
2
],
list
):
fields
.
append
((
t
[
0
],
BPF
.
_decode_table_type
(
t
[
1
])
*
t
[
2
][
0
]))
else
:
fields
.
append
((
t
[
0
],
BPF
.
_decode_table_type
(
t
[
1
]),
t
[
2
]))
else
:
raise
Exception
(
"Failed to decode type %s"
%
str
(
t
))
cls
=
type
(
str
(
desc
[
0
]),
(
ct
.
Structure
,),
dict
(
_fields_
=
fields
))
return
cls
...
...
tests/cc/CMakeLists.txt
View file @
0bde502c
...
...
@@ -40,3 +40,5 @@ add_test(NAME py_test_brb2 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND
${
TEST_WRAPPER
}
py_brb2_c sudo
${
CMAKE_CURRENT_SOURCE_DIR
}
/test_brb2.py test_brb2.c
)
add_test
(
NAME py_test_clang WORKING_DIRECTORY
${
CMAKE_CURRENT_SOURCE_DIR
}
COMMAND
${
TEST_WRAPPER
}
py_clang sudo
${
CMAKE_CURRENT_SOURCE_DIR
}
/test_clang.py
)
add_test
(
NAME py_test_histogram WORKING_DIRECTORY
${
CMAKE_CURRENT_SOURCE_DIR
}
COMMAND
${
TEST_WRAPPER
}
py_histogram sudo
${
CMAKE_CURRENT_SOURCE_DIR
}
/test_histogram.py
)
tests/cc/test_histogram.py
0 → 100755
View file @
0bde502c
#!/usr/bin/env python
# Copyright (c) PLUMgrid, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from
bcc
import
BPF
from
ctypes
import
c_int
,
c_ulonglong
import
random
import
time
from
unittest
import
main
,
TestCase
class
TestHistogram
(
TestCase
):
def
test_simple
(
self
):
b
=
BPF
(
text
=
"""
#include <uapi/linux/ptrace.h>
#include <linux/bpf.h>
BPF_HISTOGRAM(hist1);
BPF_HASH(stub);
int kprobe__htab_map_delete_elem(struct pt_regs *ctx, struct bpf_map *map, u64 *k) {
hist1.increment(bpf_log2l(*k));
return 0;
}
"""
)
for
i
in
range
(
0
,
32
):
for
j
in
range
(
0
,
random
.
randint
(
1
,
10
)):
try
:
del
b
[
"stub"
][
c_ulonglong
(
1
<<
i
)]
except
:
pass
b
[
"hist1"
].
print_log2_hist
()
for
i
in
range
(
32
,
64
):
for
j
in
range
(
0
,
random
.
randint
(
1
,
10
)):
try
:
del
b
[
"stub"
][
c_ulonglong
(
1
<<
i
)]
except
:
pass
b
[
"hist1"
].
print_log2_hist
()
def
test_struct
(
self
):
b
=
BPF
(
text
=
"""
#include <uapi/linux/ptrace.h>
#include <linux/bpf.h>
typedef struct { void *map; u64 slot; } Key;
BPF_HISTOGRAM(hist1, Key, 1024);
BPF_HASH(stub1);
BPF_HASH(stub2);
int kprobe__htab_map_delete_elem(struct pt_regs *ctx, struct bpf_map *map, u64 *k) {
hist1.increment((Key){map, bpf_log2l(*k)});
return 0;
}
"""
)
for
i
in
range
(
0
,
64
):
for
j
in
range
(
0
,
random
.
randint
(
1
,
10
)):
try
:
del
b
[
"stub1"
][
c_ulonglong
(
1
<<
i
)]
except
:
pass
try
:
del
b
[
"stub2"
][
c_ulonglong
(
1
<<
i
)]
except
:
pass
b
[
"hist1"
].
print_log2_hist
()
def
test_chars
(
self
):
b
=
BPF
(
text
=
"""
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
typedef struct { char name[TASK_COMM_LEN]; u64 slot; } Key;
BPF_HISTOGRAM(hist1, Key, 1024);
int kprobe__finish_task_switch(struct pt_regs *ctx, struct task_struct *prev) {
Key k = {.slot = bpf_log2l(prev->real_start_time)};
if (!bpf_get_current_comm(&k.name, sizeof(k.name)))
hist1.increment(k);
return 0;
}
"""
)
for
i
in
range
(
0
,
100
):
time
.
sleep
(
0.01
)
b
[
"hist1"
].
print_log2_hist
()
if
__name__
==
"__main__"
:
main
()
tools/biolatency
View file @
0bde502c
...
...
@@ -45,7 +45,7 @@ bpf_text = """
#include <uapi/linux/ptrace.h>
#include <linux/blkdev.h>
BPF_
TABLE(
\
"
array
\
"
, int, u64, dist, 64
);
BPF_
HISTOGRAM(dist
);
BPF_HASH(start, struct request *);
// time block I/O
...
...
@@ -70,9 +70,7 @@ int trace_req_completion(struct pt_regs *ctx, struct request *req)
FACTOR
// store as histogram
int index = bpf_log2l(delta);
u64 *leaf = dist.lookup(&index);
if (leaf) (*leaf)++;
dist.increment(bpf_log2l(delta));
start.delete(&req);
return 0;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment