From 37ca96c40cdd398616ad952a4c09a2fb901f97e7 Mon Sep 17 00:00:00 2001 From: Alan Lo Date: Mon, 3 Oct 2022 15:47:23 -0600 Subject: [PATCH] This example expands on the definition of crypto_accelerator (https://github.com/p4lang/pna/pull/53). The example adds two methods for encrypt/decrypt that assumes that inline accelerators operate immediately on the packet (e.g. deparse, decrypt and reparse). Packet recirculation is not necessary for either inline method. The example shows the use of inline encrypt and decrypt, as well as how the crypto accelerator results can be used. --- examples/include/crypto-accelerator.p4 | 55 ++++ examples/ipsec-acc-inline.p4 | 412 +++++++++++++++++++++++++ 2 files changed, 467 insertions(+) create mode 100644 examples/ipsec-acc-inline.p4 diff --git a/examples/include/crypto-accelerator.p4 b/examples/include/crypto-accelerator.p4 index 96891a2..bc74232 100644 --- a/examples/include/crypto-accelerator.p4 +++ b/examples/include/crypto-accelerator.p4 @@ -28,6 +28,12 @@ enum crypto_results_e { HW_ERROR } +enum crypto_mode_e { + TUNNEL, + TRANSPORT, + TRANSPORT_NAT_T +} + /// special value to indicate that ICV is after the crypto payload #define ICV_AFTER_PAYLOAD ((int<32>)-1) @@ -122,6 +128,55 @@ extern crypto_accelerator { void enable_encrypt(in T enable_auth); void enable_decrypt(in T enable_auth); + // crypto accelerator runs immediately and returns control flow to the current pipeline + // stage. The method is responsible for defining the contents of the ESP header, + // calculating the payload offset and lengths, encrypting the payload appropriately and + // reparsing the packet. User can decide if to proceed or reinject. + // + // Pre-conditions: The parser must have been executed prior to this extern. The packet + // headers and metadata from the parser are provided as inout params. + // Post-conditions: The deparser will be executed prior to encapsulation, the packet + // bytestream will be updated and encryption will be performed on the payload. The + // packet will be reparsed and parser states updated. + // Side-effects: parser states will be re-evaluated if crypto has succeeded. + // + // H - inout Headers is the output of the parser block + // M - inout Metadata is from the parser block and shared with the control + // T - in enable_auth flag enables authentication check + // S - in seq is the optional sequence number + // I - in iv is the initialization vector + crypto_results_e encrypt_inline(packet_in pkt, + inout H hdr, + inout M meta, + in crypto_mode_e mode, + in T enable_auth, + in bit<32> spi, + in S seq, + in I iv); + + // crypto accelerator runs immediately and returns control flow to the current pipeline + // stage. The method is responsible for decrypting the payload appropriately, removing + // the ESP header, calculating the payload offset and lengths, and reparsing the packet. + // The user should then check the status. + // + // Pre-conditions: The parser will have been executed prior to this extern. The packet + // headers and metadata from the parser are provided as inout params. + // Post-conditions: The deparser will be executed prior to decapsulation, the packet + // bytestream will be updated and decryption will be performed on the payload. The + // packet will be reparsed and parser states recalculated. + // Side-effects: parser states will be re-evaluated if crypto has succeeded. + // + // H - inout Headers is the output of the parser block + // M - inout Metadata is from the parser block and shared with the control + // T - in enable_auth flag enables authentication check + // S - in seq is the optional sequence number + crypto_results_e decrypt_inline(packet_in pkt, + inout H hdr, + inout M meta, + in crypto_mode_e mode, + in T enable_auth, + in S seq); + // disable crypto engine. Between enable and disable methods, // whichever method is called last overrides the previous calls void disable(); diff --git a/examples/ipsec-acc-inline.p4 b/examples/ipsec-acc-inline.p4 new file mode 100644 index 0000000..f3d51c2 --- /dev/null +++ b/examples/ipsec-acc-inline.p4 @@ -0,0 +1,412 @@ +/* +Copyright 2022 Advanced Micro Devices, Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/// p4test --Wdisable='unused' --excludeMidendPasses Predication ./ipsec-acc.p4 2>&1 | tee make.out + +/// IPSec tunnel mode example using crypto-accelerator extern object +#include +#include "../pna.p4" +#include "./include/crypto-accelerator.p4" + +// Helper Externs (could not find it in pna spec/existign code) +// Vendor specific implementation to cause a packet to get recirculated +extern void recirc_packet(); + +/// Headers + +#define ETHERTYPE_IPV4 0x0800 + +#define IP_PROTO_TCP 0x06 +#define IP_PROTO_UDP 0x11 +#define IP_PROTO_ESP 0x50 + +typedef bit<48> EthernetAddress; + +header ethernet_h { + EthernetAddress dstAddr; + EthernetAddress srcAddr; + bit<16> etherType; +} + +header ipv4_h { + bit<4> version; + bit<4> ihl; + bit<8> diffserv; + bit<16> totalLen; + bit<16> identification; + bit<3> flags; + bit<13> fragOffset; + bit<8> ttl; + bit<8> protocol; + bit<16> hdrChecksum; + bit<32> srcAddr; + bit<32> dstAddr; +} + +header tcp_h { + bit<16> srcPort; + bit<16> dstPort; + bit<32> seqNo; + bit<32> ackNo; + bit<4> dataOffset; + bit<4> res; + bit<8> flags; + bit<16> window; + bit<16> checksum; + bit<16> urgentPtr; +} + +header udp_h { + bit<16> srcPort; + bit<16> dstPort; + bit<16> len; + bit<16> checksum; +} + +header esp_h { + bit<32> spi; + bit<32> seq; +} + +// rfc4106 esp IV header on the wire +header esp_iv_h { + bit<64> iv; // IV on the wire excludes the salt +} +#define IPSEC_OP_NONE 0 +#define IPSEC_OP_ENCRYPT 1 +#define IPSEC_OP_DECRYPT 2 + +// Program defined header used during recirculation +header recirc_header_h { + bit<2> ipsec_op; +} + +// User-defined struct containing all of those headers parsed in the +// main parser. +struct headers_t { + recirc_header_h recirc_header; + ethernet_h ethernet; + ipv4_h ipv4_1; + udp_h udp; + tcp_h tcp; + esp_h esp; + esp_iv_h esp_iv; + + // inner layer - ipsec in tunnel mode + ipv4_h ipv4_2; +} + +/// Metadata + +struct main_metadata_t { + bit<32> sa_index; + bit<1> ipsec_decrypt_done; +} + +// Helper Extern (reparse the packet inline to the pipeline) +// Vendor specific implementation to cause a packet to get reparsed +extern void reparse_packet(packet_in pkt, out headers_t hdr, inout main_metadata_t meta, in pna_main_parser_input_metadata_t istd); + +/// Instantiate crypto accelerator for AES-GCM algorithm +crypto_accelerator(crypto_algorithm_e.AES_GCM) ipsec_acc; + +control PreControlImpl( + in headers_t hdr, + inout main_metadata_t meta, + in pna_pre_input_metadata_t istd, + inout pna_pre_output_metadata_t ostd) +{ + apply { + // Not used in this example + } +} + +parser MainParserImpl( + packet_in pkt, + out headers_t hdr, + inout main_metadata_t main_meta, + in pna_main_parser_input_metadata_t istd) +{ + bit<2> ipsec_op = 0; + + state start { + main_meta.sa_index = 1; // just for example, used for encrypt + + // TODO: can't find better indication of recirc in the existing pna.p4 + // This should be a field in istd or and extern + transition select(istd.loopedback) { + true : parse_recirc_header; + default : parse_packet; + } + } + + state parse_recirc_header { + pkt.extract(hdr.recirc_header); + ipsec_op = hdr.recirc_header.ipsec_op; + transition parse_packet; + } + + state parse_packet { + pkt.extract(hdr.ethernet); + transition select(hdr.ethernet.etherType) { + ETHERTYPE_IPV4 : parse_ipv4; + default : accept; + } + } + + state parse_ipv4 { + pkt.extract(hdr.ipv4_1); + transition select(hdr.ipv4_1.protocol) { + IP_PROTO_TCP : parse_tcp; + IP_PROTO_UDP : parse_udp; + IP_PROTO_ESP : parse_crypto; + default : accept; + } + } + + state parse_crypto { + transition select(ipsec_op) { + // ESP header is present after decrypt operation + (IPSEC_OP_DECRYPT &&& 0x3) : parse_post_decrypt; + // If not recic, this is an encrypted packet, yet to be decrypted + (0x0 &&& 0x0) : parse_esp; + default : reject; + } + } + state parse_post_decrypt { + main_meta.ipsec_decrypt_done = 1; + + pkt.extract(hdr.esp); + pkt.extract(hdr.esp_iv); + + // Next header is the decrypted inner ip header parse it again. + // Pipeline code will have to check the decrypt results and + // remove any pad, esp trailer and esp auth data + transition parse_ipv4; + } + state parse_esp { + pkt.extract(hdr.esp); + pkt.extract(hdr.esp_iv); + main_meta.sa_index = hdr.esp.spi; + transition accept; + } + state parse_tcp { + pkt.extract(hdr.tcp); + transition accept; + } + + state parse_udp { + pkt.extract(hdr.udp); + transition accept; + } +} + +/// This example assumes ESP implementaion as described in rfc 4303 +/// The packet format for encapsulated packet on the wire is as follows (from RFC) +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Security Parameters Index (SPI) | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Sequence Number | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--- +/// | IV (optional) | ^ p +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | a +/// | Rest of Payload Data (variable) | | y +/// ~ ~ | l +/// | | | o +/// + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | a +/// | | TFC Padding * (optional, variable) | v d +/// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--- +/// | | Padding (0-255 bytes) | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | | Pad Length | Next Header | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Integrity Check Value-ICV (variable) | +/// ~ ~ +/// | | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +control ipsec_crypto( inout headers_t hdr, + inout main_metadata_t main_meta, + in pna_main_input_metadata_t istd) +{ + action ipsec_esp_decrypt(bit<32> spi, + bit<32> salt, + bit<256> key, + bit<9> key_size, + bit<1> ext_esn_en, + bit<1> enable_auth, + bit<64> esn) { + + // IV(nonce) needed for AES-GCM algorithm + // it consists of iv that is sent on the wire plus an internally maintained salt value + bit<128> iv = (bit<128>)(salt ++ hdr.esp_iv.iv); + ipsec_acc.set_iv(iv); + + ipsec_acc.set_key(key, key_size); + + // Add protocol specific auth data and provide its offset and len + // For this exmaple 32bit seq num is used + // payload_offset : points inner(original) ip header which follows the esp_iv header + // It is possible to remove the outer (tunnel) headers if desired, this example + // retains those headers during decrypt operation and removes them on recirc + // Encrypted payload_len + // Remove protocol specific header (E.g. RFC4106) + // Extern - these are calculated internally + ipsec_acc.set_payload_offset(16w0); + ipsec_acc.set_payload_len(16w0); + + // packet is decrypted immediately. Both packet rewrite and + // reparsing occurs + if (ipsec_acc.decrypt_inline(hdr, main_meta, crypto_mode_e.TUNNEL, + enable_auth, esn[31:0]) != crypto_results_e.SUCCESS) { + // TODO: + // Check if this is AUTH error or some other error. + // Drop the packet or do other things as needed + drop_packet(); + return; + } + } + + action ipsec_esp_encrypt(bit<32> spi, + bit<32> salt, + bit<256> key, + bit<9> key_size, + bit<1> ext_esn_en, + bit<1> enable_auth, + bit<64> esn) { + + // update sequence number for each transmitted packet + // This can be done using stateful registers currently defined in P4 + // it is not shown in this example + // esn = esn + 1; + + // Set IV information needed for encryption + // For ipsec combine salt and esn + bit<128> iv = (bit<128>)(salt ++ esn); + ipsec_acc.set_iv(iv); + + ipsec_acc.set_key(key, key_size); + + // For tunnel mode, operation, copy original IP header that needs to + // be encrypted. This header will be emitted after ESP header. + // Add protocol specific headers to the packet (rfc4106) + // 32bit seq number is used + // update tunnel ip header + // Set outer header's next header as ESP + // payload_offset : points inner(original) ip header which follows the esp header + // Done by the extern inline + ipsec_acc.set_auth_data_offset(16w0); + ipsec_acc.set_auth_data_len(16w0); + ipsec_acc.set_payload_offset(16w0); + ipsec_acc.set_payload_len(16w0); + + // TODO: compute padding, build esp_trailer etc. + + // instruct engine to add icv after encrypted payload + ipsec_acc.set_icv_offset(ICV_AFTER_PAYLOAD); + ipsec_acc.set_icv_len(32w4); // Four bytes of ICV value. + + // run encryption w/ authentication + if (ipsec_acc.encrypt_inline(hdr, main_meta, + crypto_mode_e.TUNNEL, + enable_auth, + spi, + esn[31:0], + iv) != + crypto_results_e.SUCCESS) { + // TODO: + // Check if this is AUTH error or some other error. + // Drop the packet or do other things as needed + drop_packet(); + return; + } + } + + action ipsec_sa_action(bit<32> spi, + bit<32> salt, + bit<256> key, + bit<9> key_size, + bit<1> ext_esn_en, + bit<1> auth_en, + bit<1> valid_flag, + bit<64> esn) { + if (valid_flag == 0) { + return; + } + if (!hdr.esp.isValid()) { + ipsec_esp_encrypt(spi, salt, key, key_size, ext_esn_en, auth_en, esn); + } else { + ipsec_esp_decrypt(spi, salt, key, key_size, ext_esn_en, auth_en, esn); + } + } + // setup crypto accelerator for encryption/decryption - get info from sa table + // lookup sa_table using esp.spi + // In this example same SA index is used for encrypt/decrypt, in real + // implementation it can be separated into two entries + table ipsec_sa { + key = { + // For encrypt case get sa_idx from parser + // for decrypt case esp hdr spi value will be used as sa_idx + main_meta.sa_index : exact; + } + actions = { + ipsec_sa_action; + } + // default_action = ipsec_sa_action; + } + apply { + ipsec_sa.apply(); + } +} + +control MainControlImpl( + inout headers_t hdr, + inout main_metadata_t main_meta, + in pna_main_input_metadata_t istd, + inout pna_main_output_metadata_t ostd) +{ + apply { + ipsec_crypto.apply(hdr, main_meta, istd); + } +} + +control MainDeparserImpl( + packet_out pkt, + in headers_t hdr, // from main control + in main_metadata_t user_meta, // from main control + in pna_main_output_metadata_t ostd) +{ + apply { + pkt.emit(hdr.recirc_header); + pkt.emit(hdr.ethernet); + pkt.emit(hdr.ipv4_1); + pkt.emit(hdr.esp); + pkt.emit(hdr.esp_iv); + pkt.emit(hdr.ipv4_2); + pkt.emit(hdr.tcp); + pkt.emit(hdr.udp); + } +} + +// Package_Instantiation +PNA_NIC( + MainParserImpl(), + PreControlImpl(), + MainControlImpl(), + MainDeparserImpl() + ) main;