Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Dpdk Backend: Add IPSec support #3858

Merged
merged 8 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backends/dpdk/backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ void DpdkBackend::convert(const IR::ToplevelBlock *tlb) {
new P4::ClearTypeMap(typeMap),
new P4::TypeChecking(refMap, typeMap, true),
new P4::ResolveReferences(refMap),
new DpdkHandleIPSec(refMap, typeMap, &structure),
new StatementUnroll(refMap, &structure),
new IfStatementUnroll(refMap),
new P4::ClearTypeMap(typeMap),
Expand Down
8 changes: 8 additions & 0 deletions backends/dpdk/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,12 @@ const cstring tdiSchemaVersion = "0.1";
const unsigned initial_member_id = 0;
const unsigned initial_group_id = 0xFFFFFFFF;

// Ipsec related constants
#define IPSEC_SUCCESS 0
#define IPSEC_PORT_REG_INDEX 0
#define IPSEC_PORT_REG_SIZE 1
#define IPSEC_PORT_REG_INDEX_BITWIDTH 32
#define IPSEC_PORT_REG_INITVAL_BITWIDTH 32
#define NET_TO_HOST 0

#endif /* BACKENDS_DPDK_CONSTANTS_H_ */
147 changes: 146 additions & 1 deletion backends/dpdk/dpdkArch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ const IR::Node *AlignHdrMetaField::preorder(IR::Type_StructLike *st) {
}
Here, for "flags", information saved are :
ModifiedName = flags_fragOffset;
header str = ipv4_t; [This is used if multiple headers
header str = ipv4_t; [This is used if multiple headers
have field with same name]
width = 16;
offset = 3;
Expand Down Expand Up @@ -3035,4 +3035,149 @@ std::vector<std::pair<cstring, const IR::Type *>>
cstring DpdkAddPseudoHeaderDecl::pseudoHeaderInstanceName;
cstring DpdkAddPseudoHeaderDecl::pseudoHeaderTypeName;

// create and add register declaration instance to program
IR::IndexedVector<IR::StatOrDecl> *InsertReqDeclForIPSec::addRegDeclInstance(
std::vector<cstring> portRegs) {
auto decls = new IR::IndexedVector<IR::StatOrDecl>;
for (auto reg : portRegs)
decls->push_back(createRegDeclarationInstance(reg, IPSEC_PORT_REG_SIZE,
IPSEC_PORT_REG_INDEX_BITWIDTH,
IPSEC_PORT_REG_INITVAL_BITWIDTH));
return decls;
}

const IR::Node *InsertReqDeclForIPSec::preorder(IR::P4Program *program) {
if (!is_ipsec_used) return program;
cstring resName = "";
if (!reservedNames(refMap, registerInstanceNames, resName)) {
::error(ErrorType::ERR_RESERVED, "%1% name is reserved for DPDK IPSec port register",
usha1830 marked this conversation as resolved.
Show resolved Hide resolved
resName);
return program;
}
bool ipsecHdrAdded = false;
bool ipsecPortRegAdded = false;
auto new_objs = new IR::Vector<IR::Node>;

// Create platform_hdr structure with just one field to hold the security association index
IR::ID newHeaderFieldName("sa_id");
IR::IndexedVector<IR::StructField> newHeaderFields;
newHeaderFields.push_back(
new IR::StructField(newHeaderFieldName, IR::Type_Bits::get(sa_id_width)));
if (!reservedNames(refMap, {newHeaderName}, resName)) {
::error(ErrorType::ERR_RESERVED, "%1% type name is reserved for DPDK platform header",
newHeaderName);
return program;
}
ipsecHeader = new IR::Type_Header(IR::ID(newHeaderName), newHeaderFields);
Copy link
Contributor

Choose a reason for hiding this comment

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

If this header is always to going to present for all dpdk programs, perhaps it should be part of dpdk/pna.p4?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This will be only added if ipsec_accelerators enable method is called.


// Programs using ipsec accelerator must contain ethernet and ipv4 headers, hence a struct
// containing headerinstances for all headers must be present
BUG_CHECK(structure->header_type != "",
"Encapsulating struct containing all headers used in the program is missing");
for (auto obj : program->objects) {
Copy link
Contributor

Choose a reason for hiding this comment

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

why not use the visitor methods for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Visitors for Type_Struct and P4Parser? This is for adding the new header and register declarations to the program. Only for placing these new declarations better in the program, I have added check for Type_Struct and Parser.

if (auto st = obj->to<IR::Type_Struct>()) {
// Add new platform header before the encapsulating header struct
if (st->name.name == structure->header_type && !ipsecHdrAdded) {
ipsecHdrAdded = true;
new_objs->push_back(ipsecHeader);
}
} else if (obj->is<IR::P4Parser>()) {
if (!ipsecPortRegAdded) {
ipsecPortRegAdded = true;
// Add all 4 ipsec port register declarations before parser
auto decls = addRegDeclInstance(registerInstanceNames);
for (auto d : *decls) new_objs->push_back(d);
}
}
new_objs->push_back(obj);
}
if (!(ipsecHdrAdded && ipsecPortRegAdded))
BUG("Missing platform header/IPSec port Registers needed for IPSec accelerator");
program->objects = *new_objs;
return program;
}

// Create an instance of ipsec_hdr in the header struct
const IR::Node *InsertReqDeclForIPSec::preorder(IR::Type_Struct *s) {
if (!is_ipsec_used) return s;
if (s->name.name == structure->header_type) {
s->fields.push_back(
new IR::StructField(IR::ID("ipsec_hdr"), new IR::Type_Name(IR::ID("platform_hdr_t"))));
}
return s;
}

const IR::Node *InsertReqDeclForIPSec::preorder(IR::P4Control *c) {
if (!is_ipsec_used) return c;

auto insertIpsecHeader = [this](IR::P4Control *c) {
IR::IndexedVector<IR::StatOrDecl> stmt;
auto pkt_out = c->type->applyParams->parameters.at(0);
auto args = new IR::Vector<IR::Argument>();
structure->ipsec_header =
new IR::Member(new IR::PathExpression(IR::ID("h")), IR::ID("ipsec_hdr"));
args->push_back(new IR::Argument(structure->ipsec_header));
auto typeArgs = new IR::Vector<IR::Type>();
typeArgs->push_back(new IR::Type_Name(IR::ID(newHeaderName)));
auto regRdArgs = new IR::Vector<IR::Argument>();

// Register index is always 0 as the ipsec port registers are all single location registers
regRdArgs->push_back(
new IR::Argument(new IR::Constant(IR::Type::Bits::get(32), IPSEC_PORT_REG_INDEX)));
/* Set the ipsec output port based on packet direction and emit ipsec_hdr
if (ipsec_hdr.isValid())
if (direction == NET_TO_HOST)
port_out = ipsec_inbound_port_out.read(0);
else
port_out = ipsec_outbound_port_out.read(0);
emit(ipsec_hdr);
*/
auto regRdOutInbound = new IR::MethodCallExpression(
new IR::Member(new IR::PathExpression(IR::ID("ipsec_port_out_inbound")),
IR::ID("read")),
regRdArgs);
auto regRdOutOutbound = new IR::MethodCallExpression(
new IR::Member(new IR::PathExpression(IR::ID("ipsec_port_out_outbound")),
IR::ID("read")),
regRdArgs);
auto port_out = new IR::Member(new IR::PathExpression(IR::ID("m")),
IR::ID("pna_main_output_metadata_output_port"));
auto portOutInBound =
new IR::AssignmentStatement(c->body->srcInfo, port_out, regRdOutInbound);
auto portOutOutBound =
new IR::AssignmentStatement(c->body->srcInfo, port_out, regRdOutOutbound);
auto isNetToHost = new IR::Equ(c->body->srcInfo,
new IR::Member(new IR::PathExpression(IR::ID("m")),
IR::ID("pna_main_input_metadata_direction")),
new IR::Constant(NET_TO_HOST));
auto isValid =
new IR::Member(c->body->srcInfo,
new IR::Member(new IR::PathExpression(IR::ID("h")), IR::ID("ipsec_hdr")),
IR::ID(IR::Type_Header::isValid));

auto ipsecIsValid =
new IR::MethodCallExpression(c->body->srcInfo, IR::Type::Boolean::get(), isValid);
auto ifTrue =
new IR::IfStatement(c->body->srcInfo, isNetToHost, portOutInBound, portOutOutBound);
IR::Statement *fillIpsecPortOut = new IR::IfStatement(ipsecIsValid, ifTrue, nullptr);
IR::Statement *ipsecEmit = new IR::MethodCallStatement(new IR::MethodCallExpression(
new IR::Member(new IR::PathExpression(pkt_out->name), IR::ID("emit")), typeArgs, args));
stmt.push_back(fillIpsecPortOut);
stmt.push_back(ipsecEmit);
return stmt;
};

for (auto kv : structure->deparsers) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this could be also done in a visitor

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here, we need to insert this set of statements (from lambda function) in deparser. We have the information about which control blocks is/are deparser(s) stored in the "structure". This information is collected in the initial passes while inspecting the program as per specified architecture. Hence, I am checking that while visiting P4Control. I am not able to think of any other way to achieve this here. Please suggest if I am missing something.

if (kv.second->type->name == c->name) {
auto body = new IR::BlockStatement(c->body->srcInfo);
auto ipsecEmit = insertIpsecHeader(c);
for (auto s : ipsecEmit) body->push_back(s);
for (auto s : c->body->components) body->push_back(s);
c->body = body;
break;
}
}
return c;
}

} // namespace DPDK
102 changes: 101 additions & 1 deletion backends/dpdk/dpdkArch.h
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ class BreakLogicalExpressionParenthesis : public Transform {
if (auto lor2 = lor->left->to<IR::LOr>()) {
auto sub = new IR::LOr(lor2->right, lor->right);
return new IR::LOr(lor2->left, sub);
} else if (!lor->left->is<IR::LOr>() && !lor->left->is<IR::Equ>() &&
} else if (!lor->left->is<IR::LAnd>() && !lor->left->is<IR::Equ>() &&
!lor->left->is<IR::Neq>() && !lor->left->is<IR::Lss>() &&
!lor->left->is<IR::Grt>() && !lor->left->is<IR::MethodCallExpression>() &&
!lor->left->is<IR::PathExpression>() && !lor->left->is<IR::Member>()) {
Expand Down Expand Up @@ -1320,5 +1320,105 @@ class CollectProgramStructure : public PassManager {
}));
}
};

/* Helper class to detect use of IPSec accelerator */
class CollectIPSecInfo : public Inspector {
bool &is_ipsec_used;
int &sa_id_width;
P4::ReferenceMap *refMap;
P4::TypeMap *typeMap;
DpdkProgramStructure *structure;

public:
CollectIPSecInfo(bool &is_ipsec_used, int &sa_id_width, P4::ReferenceMap *refMap,
P4::TypeMap *typeMap, DpdkProgramStructure *structure)
: is_ipsec_used(is_ipsec_used),
sa_id_width(sa_id_width),
refMap(refMap),
typeMap(typeMap),
structure(structure) {}
bool preorder(const IR::MethodCallStatement *mcs) override {
auto mi = P4::MethodInstance::resolve(mcs->methodCall, refMap, typeMap);
if (auto a = mi->to<P4::ExternMethod>()) {
if (a->originalExternType->getName().name == "ipsec_accelerator") {
if (structure->isPSA()) {
::error(ErrorType::ERR_MODEL, "%1% is not available for PSA programs",
Copy link
Contributor

Choose a reason for hiding this comment

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

this can only happen if someone added a declaration of the extern by hand...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since we are using PNA direction and PNA output port in the code. It is safer to reject any usage of this extern for PSA. I would prefer to keep this check.

a->originalExternType->getName().name);
return false;
}
if (a->method->getName().name == "enable") {
is_ipsec_used = true;
} else if (a->method->getName().name == "set_sa_index") {
auto typeArgs = a->expr->typeArguments;
if (typeArgs->size() != 1) {
::error(ErrorType::ERR_MODEL, "Unexpected number of type arguments for %1%",
a->method->name);
return false;
}
auto width = typeArgs->at(0);
if (!width->is<IR::Type_Bits>()) {
::error(ErrorType::ERR_MODEL, "Unexpected width type %1% for sa_index",
width);
return false;
}
sa_id_width = width->to<IR::Type_Bits>()->width_bits();
}
}
}
return false;
}
};

/* DPDK uses some fixed registers to hold the ipsec inbound/outbound input and output ports and a
* pseudo compiler inserted header which shall be emitted in front of all headers. This class helps
* insert required registers and a pseudo header for enabling IPSec encryption and decryption. It
* also handles setting of output port in the deparser.
*/
class InsertReqDeclForIPSec : public Transform {
P4::ReferenceMap *refMap;
DpdkProgramStructure *structure;
bool &is_ipsec_used;
int &sa_id_width;
cstring newHeaderName = "platform_hdr_t";
IR::Type_Header *ipsecHeader = nullptr;
std::vector<cstring> registerInstanceNames = {
"ipsec_port_out_inbound", "ipsec_port_out_outbound", "ipsec_port_in_inbound",
"ipsec_port_in_outbound"};

public:
InsertReqDeclForIPSec(P4::ReferenceMap *refMap, DpdkProgramStructure *structure,
bool &is_ipsec_used, int &sa_id_width)
: refMap(refMap),
structure(structure),
is_ipsec_used(is_ipsec_used),
sa_id_width(sa_id_width) {
setName("InsertReqDeclForIPSec");
}

const IR::Node *preorder(IR::P4Program *program) override;
const IR::Node *preorder(IR::Type_Struct *s) override;
const IR::Node *preorder(IR::P4Control *c) override;
IR::IndexedVector<IR::StatOrDecl> *addRegDeclInstance(std::vector<cstring> portRegs);
};

struct DpdkHandleIPSec : public PassManager {
P4::ReferenceMap *refMap;
P4::TypeMap *typeMap;
DpdkProgramStructure *structure;
bool is_ipsec_used = false;
int sa_id_width = 32;

public:
DpdkHandleIPSec(P4::ReferenceMap *refMap, P4::TypeMap *typeMap, DpdkProgramStructure *structure)
: refMap(refMap), typeMap(typeMap), structure(structure) {
passes.push_back(
new CollectIPSecInfo(is_ipsec_used, sa_id_width, refMap, typeMap, structure));
passes.push_back(new InsertReqDeclForIPSec(refMap, structure, is_ipsec_used, sa_id_width));
passes.push_back(new P4::ClearTypeMap(typeMap));
passes.push_back(new P4::ResolveReferences(refMap));
passes.push_back(new P4::TypeInference(refMap, typeMap, false));
}
};

}; // namespace DPDK
#endif /* BACKENDS_DPDK_DPDKARCH_H_ */
69 changes: 69 additions & 0 deletions backends/dpdk/dpdkHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,57 @@ bool ConvertStatementToDpdk::preorder(const IR::AssignmentStatement *a) {
if (e->method->getName().name == "lookahead") {
i = new IR::DpdkLookaheadStatement(left);
}
} else if (e->originalExternType->getName().name == "ipsec_accelerator") {
if (e->method->getName().name == "from_ipsec") {
auto argSize = e->expr->arguments->size();
if (argSize != 1) {
::error(ErrorType::ERR_MODEL,
"Expected 1 argument for status of ipsec encryption",
e->object->getName());
return false;
}
auto status = (*e->expr->arguments)[0]->expression;
// For DPDK, the status is always SUCCESS
add_instr(new IR::DpdkMovStatement(status, new IR::Constant(IPSEC_SUCCESS)));
BUG_CHECK(metadataStruct, "Metadata structure missing unexpectedly!");
IR::ID portInInbound(refmap->newName("ipsec_port_inbound"));
IR::ID portInOutbound(refmap->newName("ipsec_port_outbound"));
auto port_in_inbound =
new IR::Member(new IR::PathExpression("m"), portInInbound);
auto port_in_outbound =
new IR::Member(new IR::PathExpression("m"), portInOutbound);
metadataStruct->fields.push_back(
new IR::StructField(portInInbound, IR::Type_Bits::get(32)));
metadataStruct->fields.push_back(
new IR::StructField(portInOutbound, IR::Type_Bits::get(32)));
add_instr(new IR::DpdkRegisterReadStatement(
port_in_inbound, "ipsec_port_in_inbound", new IR::Constant(0)));
add_instr(new IR::DpdkRegisterReadStatement(
port_in_outbound, "ipsec_port_in_outbound", new IR::Constant(0)));
add_instr(new IR::DpdkMovStatement(left, new IR::Constant(false)));
auto true_label = refmap->newName("label_true");
auto end_label = refmap->newName("label_end");

// left = (port_in == ipsec_port_in_inbound) || (port_in ==
// ipsec_port_in_outbound)
add_instr(new IR::DpdkJmpEqualStatement(
true_label,
new IR::Member(new IR::PathExpression(IR::ID("m")),
IR::ID("pna_main_input_metadata_input_port")),
port_in_inbound));
add_instr(new IR::DpdkJmpEqualStatement(
true_label,
new IR::Member(new IR::PathExpression(IR::ID("m")),
IR::ID("pna_main_input_metadata_input_port")),
port_in_outbound));
add_instr(new IR::DpdkJmpLabelStatement(end_label));
add_instr(new IR::DpdkLabelStatement(true_label));
add_instr(new IR::DpdkMovStatement(left, new IR::Constant(true)));
i = new IR::DpdkLabelStatement(end_label);
} else {
BUG("ipsec_accelerator function %1% not implemented",
e->method->getName().name);
}
} else {
BUG("%1% Not implemented", e->originalExternType->name);
}
Expand Down Expand Up @@ -1143,6 +1194,24 @@ bool ConvertStatementToDpdk::preorder(const IR::MethodCallStatement *s) {
auto reg = a->object->getName();
add_instr(new IR::DpdkRegisterWriteStatement(reg, index, src));
}
} else if (a->originalExternType->getName().name == "ipsec_accelerator") {
if (a->method->getName().name == "enable") {
add_instr(new IR::DpdkValidateStatement(structure->ipsec_header));
} else if (a->method->getName().name == "set_sa_index") {
auto args = a->expr->arguments;
if (args->size() != 1) {
::error(ErrorType::ERR_UNEXPECTED, "Unexpected number of arguments for %1%",
a->method->name);
return false;
}
auto index = args->at(0)->expression;
add_instr(new IR::DpdkMovStatement(
new IR::Member(structure->ipsec_header, IR::ID("sa_id")), index));
} else if (a->method->getName().name == "disable") {
add_instr(new IR::DpdkInvalidateStatement(structure->ipsec_header));
} else {
BUG("ipsec_accelerator function %1% not implemented", a->method->getName().name);
}
} else {
::error(ErrorType::ERR_UNKNOWN, "%1%: Unknown extern function.", s);
}
Expand Down
5 changes: 3 additions & 2 deletions backends/dpdk/dpdkProgramStructure.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ struct DpdkProgramStructure {
std::set<cstring> non_pipeline_controls;

IR::Type_Struct *metadataStruct;
cstring local_metadata_type;
cstring header_type;
IR::Expression *ipsec_header;
cstring local_metadata_type = "";
cstring header_type = "";
IR::IndexedVector<IR::StructField> compiler_added_fields;
IR::IndexedVector<IR::StructField> key_fields;
IR::Vector<IR::Type> used_metadata;
Expand Down
Loading