# 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 ebpfDeparser
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.deparser = None
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
self.deparser = ebpfDeparser.EbpfDeparser(self.hlir)
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)
self.generateDeparser(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 generateDeparser(self, serializer):
self.deparser.serialize(serializer, self)
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 in done:
continue
if todo is None:
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)
todo = set()
todo.add(self.egressEntry)
self.generatePipelineInternal(serializer, todo, None)