Commit 0bde502c authored by Suchakra Sharma's avatar Suchakra Sharma

Merge branch 'master' into open_probes

parents 416613aa b1e3b670
......@@ -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; })
......
......@@ -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));
......
......@@ -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:
......
# 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)
```
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.
# 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)
# 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
# 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)
# 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)
# 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)
# 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)
# 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)
# 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")
# 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)
# 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)
# 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")
#!/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))
#!/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()
# 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
# 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
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.
#!/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
#!/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)
#!/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()
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
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
{
}
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
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
/* 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);
}
// 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
......@@ -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("\nBucket %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
......
......@@ -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)
#!/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()
......@@ -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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment