diff --git a/.github/workflows/dash-bmv2-ci.yml b/.github/workflows/dash-bmv2-ci.yml index be19deeb1..3db115e23 100644 --- a/.github/workflows/dash-bmv2-ci.yml +++ b/.github/workflows/dash-bmv2-ci.yml @@ -62,18 +62,24 @@ jobs: run: make docker-bmv2-bldr - name: Generate SAI API run: DOCKER_FLAGS=$docker_fg_flags make sai + - name: Pull/Build docker dpapp image + run: make docker-dash-dpapp + - name: Build bmv2 dpapp + run: DOCKER_FLAGS=$docker_fg_flags make dpapp - name: Check if SAI spec is updated run: DOCKER_FLAGS=$docker_fg_flags make check-sai-spec - name: Build libsai c++ tests run: DOCKER_FLAGS=$docker_fg_flags make test - name: Prepare network - run: DOCKER_FLAGS=$docker_fg_flags make network + run: DOCKER_FLAGS=$docker_fg_flags make network HAVE_DPAPP=y - name: Run P4 software switch (bmv2) with P4Runtime - run: DOCKER_FLAGS=$docker_bg_flags make run-switch + run: DOCKER_FLAGS=$docker_bg_flags make run-switch HAVE_DPAPP=y - name: Force bmv2 to load forwarding pipeline config via dummy libsai call run: DOCKER_FLAGS=$docker_fg_flags make init-switch - name: Test SAI library over P4RT to switch run: DOCKER_FLAGS=$docker_fg_flags make run-libsai-test + - name: Run dpapp + run: DOCKER_FLAGS=$docker_bg_flags make run-dpapp HAVE_DPAPP=y - name: Generate saithrift-server run: DOCKER_FLAGS=$docker_fg_flags make saithrift-server - name: Generate saithrift-client local docker diff --git a/.wordlist.txt b/.wordlist.txt index 087b60021..85f447754 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -559,6 +559,7 @@ sai saic SAIC saichallenger +saidashdpapp saigen sairedis SAIRPC diff --git a/dash-pipeline/dpapp/CMakeLists.txt b/dash-pipeline/dpapp/CMakeLists.txt new file mode 100644 index 000000000..82f6ab3a1 --- /dev/null +++ b/dash-pipeline/dpapp/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.5) + +project(dash-plugin) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I /SAI/SAI/inc -I /SAI/SAI/experimental") + +find_package(VPP) + +add_subdirectory(dash) diff --git a/dash-pipeline/dpapp/Makefile b/dash-pipeline/dpapp/Makefile new file mode 100644 index 000000000..ca966ef5b --- /dev/null +++ b/dash-pipeline/dpapp/Makefile @@ -0,0 +1,24 @@ +SHELL=/bin/bash +BUILD_DIR=build +CMAKE_ARGS= + +ifeq ($(V),1) +CMAKE_ARGS += --verbose +endif + +all: dpapp + +.PHONY:configure install clean + +configure: + @cmake $(CMAKE_ARGS) -G Ninja -S . -B $(BUILD_DIR) + +dpapp: configure + @cmake --build $(BUILD_DIR) $(CMAKE_ARGS) + +clean: + @cmake --build $(BUILD_DIR) $(CMAKE_ARGS) -- clean + +install: + @sudo cmake --build $(BUILD_DIR) $(CMAKE_ARGS) -- install + diff --git a/dash-pipeline/dpapp/README.md b/dash-pipeline/dpapp/README.md new file mode 100644 index 000000000..fd2719a37 --- /dev/null +++ b/dash-pipeline/dpapp/README.md @@ -0,0 +1,84 @@ +# DPAPP - Data Plane Application +## Overview +DPAPP fulfills [VPP](https://fd.io/) plugin mechanism to implement the slow (exception) path of +packet processing. It works with [DASH pipeline BMv2](https://github.com/sonic-net/DASH/tree/main/dash-pipeline/bmv2), +serving fast path, to consist of a complete DASH data plane. Refer to the doc +[bmv2 data plane app](https://github.com/sonic-net/DASH/blob/main/documentation/dataplane/dash-bmv2-data-plane-app.md) +for design details. + +## Usage +1. build dpapp +``` +DASH/dash-pipeline$ make dpapp +``` +2. Run dpapp +``` +DASH/dash-pipeline$ make run-dpapp +``` + +## Debug +VPP CLI `vppctl` provides lots of commands for debugging. Use the command `docker exec -it dash-dpapp-${USER} vppctl` +to launch it. + +- Check dpapp interfaces and the counters +``` +vpp# show interface + Name Idx State MTU (L3/IP4/IP6/MPLS) Counter Count +host-veth5 1 up 9000/0/0/0 rx packets 39 + rx bytes 10764 + tx packets 39 + tx bytes 7628 +local0 0 down 0/0/0/0 + +vpp# show hardware-interfaces + Name Idx Link Hardware +host-veth5 1 up host-veth5 + Link speed: unknown + RX Queues: + queue thread mode + 0 vpp_wk_0 (1) interrupt + TX Queues: + TX Hash: [name: hash-eth-l34 priority: 50 description: Hash ethernet L34 headers] + queue shared thread(s) + 0 yes 0-1 + Ethernet address 02:fe:23:f0:88:99 + Linux PACKET socket interface v3 + FEATURES: + qdisc-bpass-enabled + cksum-gso-enabled + RX Queue 0: + block size:65536 nr:160 frame size:2048 nr:5120 next block:39 + TX Queue 0: + block size:69206016 nr:1 frame size:67584 nr:1024 next frame:39 + available:1024 request:0 sending:0 wrong:0 total:1024 +local0 0 down local0 + Link speed: unknown + local + +``` + +- Check flow table in dpapp +``` +vpp# show dash flow + 1: eni 00:cc:cc:cc:cc:cc, vnet_id 343, proto 17, 10.1.1.10 1234 -> 10.1.2.50 80 + common data - version 0, direction 1, actions 0x9, timeout 28 + +vpp# clear dash flow 1 +``` + +- Check packet processing trace in dpapp +``` +vpp# trace add af-packet-input 100 +vpp# show trace +``` + +On behalf of BMv2 switch, script `tools/send_p2a_pkt.py` can send packet with dash header to verify basic flow +functionality of dpapp. + +## Test +By default, flow lookup is not enabled in DASH pipeline. The decorator `@use_flow` will enable it and then involve dpapp +for slow path. If test cases are verified to support flow, aka stateful packet processing, use the decorator to mark +the test class. + +PTF test script [saidashdpapp_sanity.py](https://github.com/sonic-net/DASH/blob/main/test/test-cases/functional/ptf/saidashdpapp_sanity.py) +provides sanity check for dpapp. Refer to it as a sample for new flow tests. diff --git a/dash-pipeline/dpapp/dash/CMakeLists.txt b/dash-pipeline/dpapp/dash/CMakeLists.txt new file mode 100644 index 000000000..a56c568ef --- /dev/null +++ b/dash-pipeline/dpapp/dash/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright (c) 2018 Cisco and/or its affiliates. +# 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. + +include_directories(${CMAKE_SOURCE_DIR}) + +# for generated API headers: +include_directories(${CMAKE_BINARY_DIR}) + +add_vpp_plugin(dash + SOURCES + dash.c + dash_node.c + flow.c + saiapi.c + + MULTIARCH_SOURCES + dash_node.c + + API_FILES + dash.api + + API_TEST_SOURCES + dash_test.c + + COMPONENT vpp-plugin-dash +) diff --git a/dash-pipeline/dpapp/dash/dash.api b/dash-pipeline/dpapp/dash/dash.api new file mode 100644 index 000000000..426fec369 --- /dev/null +++ b/dash-pipeline/dpapp/dash/dash.api @@ -0,0 +1,18 @@ +/* Define a simple binary API to control the feature */ + +option version = "0.1.0"; +import "vnet/interface_types.api"; + +autoreply define dash_enable_disable { + /* Client identifier, set from api_main.my_client_index */ + u32 client_index; + + /* Arbitrary context, so client can match reply to request */ + u32 context; + + /* Enable / disable the feature */ + bool enable_disable; + + /* Interface handle */ + vl_api_interface_index_t sw_if_index; +}; diff --git a/dash-pipeline/dpapp/dash/dash.c b/dash-pipeline/dpapp/dash/dash.c new file mode 100644 index 000000000..c796f1d81 --- /dev/null +++ b/dash-pipeline/dpapp/dash/dash.c @@ -0,0 +1,194 @@ +/** + * @file + * @brief Dash Plugin, plugin API / trace / CLI handling. + */ + +#include +#include +#include + +#include +#include + +#include +#include + +#define REPLY_MSG_ID_BASE sm->msg_id_base +#include + +/* *INDENT-OFF* */ +VLIB_PLUGIN_REGISTER () = { + .version = DASH_PLUGIN_BUILD_VER, + .description = "Dash of VPP Plugin", +}; +/* *INDENT-ON* */ + +VLIB_REGISTER_LOG_CLASS (dash_log) = { + .class_name = "dash", +}; + +dash_main_t dash_main; + +/** + * @brief Enable/disable the dash plugin. + * + * Action function shared between message handler and debug CLI. + */ + +int dash_enable_disable (dash_main_t * sm, u32 sw_if_index, + int enable_disable) +{ + vnet_sw_interface_t * sw; + int rv = 0; + + /* Utterly wrong? */ + if (pool_is_free_index (sm->vnet_main->interface_main.sw_interfaces, + sw_if_index)) + return VNET_API_ERROR_INVALID_SW_IF_INDEX; + + /* Not a physical port? */ + sw = vnet_get_sw_interface (sm->vnet_main, sw_if_index); + if (sw->type != VNET_SW_INTERFACE_TYPE_HARDWARE) + return VNET_API_ERROR_INVALID_SW_IF_INDEX; + + vnet_feature_enable_disable ("dash-pipeline", "dash-pipeline-input", + sw_if_index, enable_disable, 0, 0); + + return rv; +} + +static clib_error_t * +dash_cmd_set_enable_disable_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + dash_main_t * sm = &dash_main; + u32 sw_if_index = ~0; + int enable_disable = 1; + int rv; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { + if (unformat (input, "disable")) + enable_disable = 0; + else if (unformat (input, "%U", unformat_vnet_sw_interface, + sm->vnet_main, &sw_if_index)) + ; + else + break; + } + + if (sw_if_index == ~0) + return clib_error_return (0, "Please specify an interface..."); + + rv = dash_enable_disable (sm, sw_if_index, enable_disable); + + switch(rv) { + case 0: + break; + + case VNET_API_ERROR_INVALID_SW_IF_INDEX: + return clib_error_return + (0, "Invalid interface, only works on physical ports"); + break; + + case VNET_API_ERROR_UNIMPLEMENTED: + return clib_error_return (0, "Device driver doesn't support redirection"); + break; + + default: + return clib_error_return (0, "dash_enable_disable returned %d", + rv); + } + return 0; +} + +/** + * @brief CLI command to enable/disable the dash plugin. + */ +VLIB_CLI_COMMAND (dash_set_command, static) = { + .path = "set dash", + .short_help = + "set dash [disable]", + .function = dash_cmd_set_enable_disable_fn, +}; + +/** + * @brief Plugin API message handler. + */ +static void vl_api_dash_enable_disable_t_handler +(vl_api_dash_enable_disable_t * mp) +{ + vl_api_dash_enable_disable_reply_t * rmp; + dash_main_t * sm = &dash_main; + int rv; + + rv = dash_enable_disable (sm, ntohl(mp->sw_if_index), + (int) (mp->enable_disable)); + + REPLY_MACRO(VL_API_DASH_ENABLE_DISABLE_REPLY); +} + +/* API definitions */ +#include + +/** + * @brief Initialize the dash plugin. + */ +static clib_error_t * dash_init (vlib_main_t * vm) +{ + dash_main_t * sm = &dash_main; + + sm->vnet_main = vnet_get_main (); + + /* Add our API messages to the global name_crc hash table */ + sm->msg_id_base = setup_message_id_table (); + + /* Reuse SECURE_DATA (0x876D) for dash metadata */ + ethernet_register_input_type (vm, ETHERNET_TYPE_SECURE_DATA, dash_node.index); + + dash_flow_table_init(dash_flow_table_get()); + + dash_sai_init(); + + return 0; +} + +VLIB_INIT_FUNCTION (dash_init); + +/** + * @brief Hook the dash plugin into the VPP graph hierarchy. + */ +VNET_FEATURE_ARC_INIT (dash_pipeline, static) = +{ + .arc_name = "dash-pipeline", + .start_nodes = VNET_FEATURES ("dash-pipeline-input"), + .last_in_arc = "error-drop", + .arc_index_ptr = &dash_main.feature_arc_index, +}; + +static uword +dash_timer_process (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_frame_t * f) +{ + f64 sleep_duration = 1.0; + + while (1) + { + /* + * Notify the first worker thread to scan flow table + */ + vlib_node_set_interrupt_pending (vlib_get_main_by_index(1), + dash_flow_scan_node.index); + + vlib_process_suspend (vm, sleep_duration); + } + return 0; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dash_timer_node,static) = { + .function = dash_timer_process, + .name = "dash-timer-process", + .type = VLIB_NODE_TYPE_PROCESS, +}; +/* *INDENT-ON* */ + diff --git a/dash-pipeline/dpapp/dash/dash.h b/dash-pipeline/dpapp/dash/dash.h new file mode 100644 index 000000000..d14ee65e7 --- /dev/null +++ b/dash-pipeline/dpapp/dash/dash.h @@ -0,0 +1,65 @@ +#ifndef __included_dash_h__ +#define __included_dash_h__ + +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +typedef struct { + /* API message ID base */ + u16 msg_id_base; + + /* convenience */ + vnet_main_t * vnet_main; + + /* dash pipeline arc index */ + u8 feature_arc_index; +} dash_main_t; + +extern dash_main_t dash_main; + +extern vlib_node_registration_t dash_node; + +extern vlib_log_class_registration_t dash_log; + +#define dash_log_err(fmt, ...) \ + vlib_log_err (dash_log.class, fmt, ##__VA_ARGS__) + +#define dash_log_warn(fmt, ...) \ + vlib_log_warn (dash_log.class, fmt, ##__VA_ARGS__) + +#define dash_log_notice(fmt, ...) \ + vlib_log_notice (dash_log.class, fmt, ##__VA_ARGS__) + +#define dash_log_info(fmt, ...) \ + vlib_log_info (dash_log.class, fmt, ##__VA_ARGS__) + +#define dash_log_debug(fmt, ...) \ + vlib_log_debug (dash_log.class, fmt, ##__VA_ARGS__) + + +#define ASSERT_MSG(expr, message) \ + do { \ + if (!(expr)) { \ + dash_log_err("Assertion failed: (%s), %s:%d %s", \ + #expr, __FILE__, __LINE__, message);\ + abort(); \ + } \ + } while (0) + +void dash_sai_init (); +sai_status_t dash_sai_create_flow_entry (const dash_flow_entry_t *flow); +sai_status_t dash_sai_remove_flow_entry (const dash_flow_entry_t *flow); + +#define DASH_PLUGIN_BUILD_VER "1.0" + +#endif /* __included_dash_h__ */ diff --git a/dash-pipeline/dpapp/dash/dash_node.c b/dash-pipeline/dpapp/dash/dash_node.c new file mode 100644 index 000000000..e574ecad3 --- /dev/null +++ b/dash-pipeline/dpapp/dash/dash_node.c @@ -0,0 +1,194 @@ +#include + +#include +#include +#include +#include +#include +#include + + +extern vlib_node_registration_t dash_node; + +#define foreach_dash_error \ +_(OK, "packets process OK") \ +_(FAILED, "packets process FAILED") + +typedef enum +{ +#define _(sym,str) DASH_ERROR_##sym, + foreach_dash_error +#undef _ + DASH_N_ERROR, +} dash_error_t; + +static char *dash_error_strings[] = { +#define _(sym,string) string, + foreach_dash_error +#undef _ +}; + +typedef struct +{ + u32 next_index; + dash_error_t error; +} dash_trace_t; + + +/* packet trace format function */ +static u8 * +format_dash_trace (u8 * s, va_list * args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + dash_trace_t *t = va_arg (*args, dash_trace_t *); + + s = format (s, "DASH: next index %d, error %d\n", + t->next_index, t->error); + + return s; +} + +typedef enum +{ + DASH_NEXT_INTERFACE_OUTPUT, + DASH_NEXT_DROP, + DASH_N_NEXT, +} dash_next_t; + +static inline void swap_ether_mac(ethernet_header_t *ether) +{ + u8 tmp[6]; + + clib_memcpy_fast (tmp, ether->src_address, sizeof (tmp)); + clib_memcpy_fast (ether->src_address, ether->dst_address, sizeof (tmp)); + clib_memcpy_fast (ether->dst_address, tmp, sizeof (tmp)); +} + +static inline dash_error_t process_one_buffer(vlib_buffer_t *buffer) +{ + u32 if_index; + ethernet_header_t *ether; + dash_header_t *dh = vlib_buffer_get_current (buffer); + + if (dash_flow_process(dash_flow_table_get(), dh) != 0) { + return DASH_ERROR_FAILED; + } + + /* Update dash header */ + dh->packet_meta.packet_source = DPAPP; + if (dh->packet_meta.packet_subtype != FLOW_DELETE) { + /* Only keep packet_meta and flow_key in dash_header_t */ + u16 length0 = ntohs(dh->packet_meta.length); + dh->packet_meta.length = htons(offsetof(dash_header_t, flow_data)); + /* Move customer packet after dash header */ + clib_memmove((u8*)&dh->flow_data, (u8*)dh + length0, + vlib_buffer_get_tail(buffer) - (u8*)dh - length0); + buffer->current_length -= length0 - offsetof(dash_header_t, flow_data); + } + + vlib_buffer_reset (buffer); + + /* Update ethernet header via swap src and dst mac */ + ether = vlib_buffer_get_current (buffer); + swap_ether_mac(ether); + + /* Send pkt back out the RX interface */ + if_index = vnet_buffer (buffer)->sw_if_index[VLIB_RX]; + vnet_buffer (buffer)->sw_if_index[VLIB_TX] = if_index; + + return DASH_ERROR_OK; +} + +VLIB_NODE_FN (dash_node) (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + u32 current_pkt_vector_size, *current_pkt_vector, *next_pkt_vector; + dash_next_t next_index; + dash_error_t error; + u32 pkts_counter[DASH_N_ERROR] = { 0 }; + + current_pkt_vector = vlib_frame_vector_args (frame); + current_pkt_vector_size = frame->n_vectors; + next_index = node->cached_next_index; + + while (current_pkt_vector_size > 0) + { + u32 next_pkt_vector_left_size; + + vlib_get_next_frame (vm, node, next_index, next_pkt_vector, next_pkt_vector_left_size); + + while (current_pkt_vector_size > 0 && next_pkt_vector_left_size > 0) + { + u32 buffer_index; + vlib_buffer_t *buffer; + u32 dst_next_index = DASH_NEXT_INTERFACE_OUTPUT; + + /* speculatively enqueue buffer to the current next frame */ + buffer_index = current_pkt_vector[0]; + next_pkt_vector[0] = buffer_index; + current_pkt_vector += 1; + next_pkt_vector += 1; + current_pkt_vector_size -= 1; + next_pkt_vector_left_size -= 1; + + buffer = vlib_get_buffer (vm, buffer_index); + error = process_one_buffer (buffer); + if (error != DASH_ERROR_OK) { + dst_next_index = DASH_NEXT_DROP; + } + + pkts_counter[error] += 1; + + if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) && + (buffer->flags & VLIB_BUFFER_IS_TRACED))) + { + dash_trace_t *t = vlib_add_trace (vm, node, buffer, sizeof (*t)); + t->next_index = dst_next_index; + t->error = error; + } + + /* verify speculative enqueue, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + next_pkt_vector, next_pkt_vector_left_size, + buffer_index, dst_next_index); + } + + vlib_put_next_frame (vm, node, next_index, next_pkt_vector_left_size); + } + + for (error = 0; error < DASH_N_ERROR; error++) { + vlib_node_increment_counter (vm, dash_node.index, + error, pkts_counter[error]); + } + return frame->n_vectors; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dash_node) = +{ + .name = "dash-pipeline-input", + .vector_size = sizeof (u32), + .format_trace = format_dash_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = ARRAY_LEN(dash_error_strings), + .error_strings = dash_error_strings, + + .n_next_nodes = DASH_N_NEXT, + + /* edit / add dispositions here */ + .next_nodes = { + [DASH_NEXT_INTERFACE_OUTPUT] = "interface-output", + [DASH_NEXT_DROP] = "error-drop" + }, +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/dash-pipeline/dpapp/dash/dash_test.c b/dash-pipeline/dpapp/dash/dash_test.c new file mode 100644 index 000000000..04466b0bc --- /dev/null +++ b/dash-pipeline/dpapp/dash/dash_test.c @@ -0,0 +1,71 @@ +/* + *------------------------------------------------------------------ + * dash_test.c - test harness plugin + *------------------------------------------------------------------ + */ + +#include +#include +#include +#include + +#define __plugin_msg_base dash_test_main.msg_id_base +#include + +uword unformat_sw_if_index (unformat_input_t * input, va_list * args); + +/* Declare message IDs */ +#include +#include + +typedef struct { + /* API message ID base */ + u16 msg_id_base; + vat_main_t *vat_main; +} dash_test_main_t; + +dash_test_main_t dash_test_main; + +static int api_dash_enable_disable (vat_main_t * vam) +{ + unformat_input_t * i = vam->input; + int enable_disable = 1; + u32 sw_if_index = ~0; + vl_api_dash_enable_disable_t * mp; + int ret; + + /* Parse args required to build the message */ + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) { + if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index)) + ; + else if (unformat (i, "sw_if_index %d", &sw_if_index)) + ; + else if (unformat (i, "disable")) + enable_disable = 0; + else + break; + } + + if (sw_if_index == ~0) { + errmsg ("missing interface name / explicit sw_if_index number \n"); + return -99; + } + + /* Construct the API message */ + M(DASH_ENABLE_DISABLE, mp); + mp->sw_if_index = ntohl (sw_if_index); + mp->enable_disable = enable_disable; + + /* send it... */ + S(mp); + + /* Wait for a reply... */ + W (ret); + return ret; +} + +/* + * List of messages that the api test plugin sends, + * and that the data plane plugin processes + */ +#include diff --git a/dash-pipeline/dpapp/dash/flow.c b/dash-pipeline/dpapp/dash/flow.c new file mode 100644 index 000000000..f51a0e5cc --- /dev/null +++ b/dash-pipeline/dpapp/dash/flow.c @@ -0,0 +1,404 @@ + +#include + +#include +#include + +#define DASH_FLOW_NUM (1 << 12) /* 4K */ +#define DASH_FLOW_NUM_BUCKETS (DASH_FLOW_NUM / BIHASH_KVP_PER_PAGE) +#define DASH_FLOW_MEMORY_SIZE (DASH_FLOW_NUM * 32) + +static dash_flow_table_t dash_flow_table; + +dash_flow_table_t* +dash_flow_table_get (void) +{ + return &dash_flow_table; +} + +dash_flow_entry_t* +dash_flow_alloc() +{ + dash_flow_entry_t *flow; + dash_flow_table_t *flow_table = dash_flow_table_get(); + + pool_get (flow_table->flow_pool, flow); + clib_memset (flow, 0, sizeof (*flow)); + flow->index = flow - flow_table->flow_pool; + flow->timeout = DASH_FLOW_TIMEOUT; + flow->access_time = (u64)unix_time_now(); + return flow; +} + +void +dash_flow_free(dash_flow_entry_t *flow) +{ + dash_flow_table_t *flow_table = dash_flow_table_get(); + + if (flow != NULL) + pool_put(flow_table->flow_pool, flow); +} + +dash_flow_entry_t* +dash_flow_get_by_index (u32 index) +{ + dash_flow_table_t *flow_table = dash_flow_table_get(); + dash_flow_entry_t *flow = pool_elt_at_index(flow_table->flow_pool, index); + + return flow; +} + +static int +dash_flow_create (dash_flow_table_t *flow_table, const dash_header_t *dh) +{ + int r; + sai_status_t status; + + ASSERT(flow_table && dh); + + u16 length = ntohs(dh->packet_meta.length); + ASSERT_MSG(length >= offsetof(dash_header_t, flow_overlay_data), "dash header not enough"); + + dash_flow_entry_t* flow = dash_flow_alloc(); + + clib_memcpy_fast(&flow->key, &dh->flow_key, sizeof(dh->flow_key)); + + clib_memcpy_fast(&flow->flow_data, &dh->flow_data, sizeof(dh->flow_data)); + + /* FIXME + * Assume overlay_data, u0_encap_data, u1_encap_data in order if exists + * Need to add their offset in generic. + */ + if (flow->flow_data.actions != 0) { + ASSERT_MSG(length >= offsetof(dash_header_t, flow_u0_encap_data), + "dash header not enough"); + clib_memcpy_fast(&flow->flow_overlay_data, &dh->flow_overlay_data, sizeof(dh->flow_overlay_data)); + } + + if (flow->flow_data.actions & htonl(SAI_DASH_FLOW_ACTION_ENCAP_U0)) { + ASSERT_MSG(length >= offsetof(dash_header_t, flow_u1_encap_data), + "dash header not enough"); + clib_memcpy_fast(&flow->flow_u0_encap_data, &dh->flow_u0_encap_data, sizeof(dh->flow_u0_encap_data)); + } + + if (flow->flow_data.actions & htonl(SAI_DASH_FLOW_ACTION_ENCAP_U1)) { + ASSERT_MSG((u8*)(&dh->flow_u1_encap_data + 1) <= (u8*)dh + length, + "dash header not enough"); + clib_memcpy_fast(&flow->flow_u1_encap_data, &dh->flow_u1_encap_data, sizeof(dh->flow_u1_encap_data)); + } + + r = dash_flow_table_add_entry (flow_table, flow); + if (r != 0) goto table_add_entry_fail; + + status = dash_sai_create_flow_entry(flow); + if (status != SAI_STATUS_SUCCESS) goto sai_create_flow_fail; + + flow_table->flow_stats.create_ok++; + flow->timer_handle = TW (tw_timer_start) (&flow_table->flow_tw, flow->index, 0, flow->timeout); + return 0; + +sai_create_flow_fail: + flow_table->flow_stats.create_fail++; + dash_flow_table_delete_entry (flow_table, flow); + dash_flow_free(flow); + return -1; + +table_add_entry_fail: + dash_flow_free(flow); + return r; +} + +static int +dash_flow_update (dash_flow_table_t *flow_table, const dash_header_t *dh) +{ + return -1; /* TODO later */ +} + +static int +dash_flow_remove (dash_flow_table_t *flow_table, const dash_header_t *dh) +{ + int r = -1; + sai_status_t status; + dash_flow_hash_key_t flow_hash_key; + dash_flow_entry_t* flow; + + ASSERT(flow_table && dh); + + u16 length = ntohs(dh->packet_meta.length); + ASSERT(length >= offsetof(dash_header_t, flow_key)); + + bzero(&flow_hash_key, sizeof(flow_hash_key)); + clib_memcpy_fast(&flow_hash_key, &dh->flow_key, sizeof(dh->flow_key)); + + flow = dash_flow_table_lookup_entry(flow_table, &flow_hash_key.key); + if (!flow) goto flow_not_found; + + status = dash_sai_remove_flow_entry(flow); + if (status != SAI_STATUS_SUCCESS) goto sai_remove_flow_fail; + + TW (tw_timer_stop) (&flow_table->flow_tw, flow->timer_handle); + + r = dash_flow_table_delete_entry (flow_table, flow); + ASSERT(r == 0); + dash_flow_free(flow); + + flow_table->flow_stats.remove_ok++; + return 0; + +sai_remove_flow_fail: + flow_table->flow_stats.remove_fail++; + +flow_not_found: + return -1; +} + +typedef int (*dash_flow_cmd_handler) (dash_flow_table_t *flow_table, const dash_header_t *dh); + +static dash_flow_cmd_handler flow_cmd_funs[] = { + [FLOW_CREATE] = dash_flow_create, + [FLOW_UPDATE] = dash_flow_update, + [FLOW_DELETE] = dash_flow_remove, +}; + + +int +dash_flow_process (dash_flow_table_t *flow_table, const dash_header_t *dh) +{ + ASSERT(dh->packet_meta.packet_type == REGULAR); + ASSERT(dh->packet_meta.packet_subtype >= FLOW_CREATE); + ASSERT(dh->packet_meta.packet_subtype <= FLOW_DELETE); + + return flow_cmd_funs[dh->packet_meta.packet_subtype](flow_table, dh); +} + +static void +dash_flow_expired_timer_callback (u32 * expired_timers) +{ + int i; + u32 index; + sai_status_t status; + dash_flow_table_t *flow_table = dash_flow_table_get(); + + for (i = 0; i < vec_len (expired_timers); i++) + { + index = expired_timers[i] & 0x7FFFFFFF; + dash_flow_entry_t *flow = dash_flow_get_by_index(index); + status = dash_sai_remove_flow_entry(flow); + if (status != SAI_STATUS_SUCCESS) { + dash_log_err("dash_sai_remove_flow_entry fail: %d", status); + continue; + } + + if (dash_flow_table_delete_entry (flow_table, flow) == 0) { + dash_flow_free(flow); + } else { + ASSERT(0); + } + } +} + +void +dash_flow_table_init (dash_flow_table_t *flow_table) +{ + BV(clib_bihash_init) (&flow_table->hash_table, "flow hash table", + DASH_FLOW_NUM_BUCKETS, DASH_FLOW_MEMORY_SIZE); + + pool_init_fixed (flow_table->flow_pool, DASH_FLOW_NUM); + + bzero(&flow_table->flow_stats, sizeof(flow_table->flow_stats)); + + TW (tw_timer_wheel_init) (&flow_table->flow_tw, + dash_flow_expired_timer_callback, + 1.0 /* timer interval */, 1024); +} + +int +dash_flow_table_add_entry (dash_flow_table_t *flow_table, dash_flow_entry_t *flow) +{ + BVT (clib_bihash_kv) kv; + + clib_memcpy_fast (kv.key, &flow->key, sizeof(kv.key)); + kv.value = (u64)(uintptr_t)&flow->flow_data; + return BV (clib_bihash_add_del) (&flow_table->hash_table, &kv, 1 /* is_add */ ); +} + +int +dash_flow_table_delete_entry (dash_flow_table_t *flow_table, dash_flow_entry_t *flow) +{ + BVT (clib_bihash_kv) kv; + + clib_memcpy_fast (kv.key, &flow->key, sizeof(kv.key)); + return BV (clib_bihash_add_del) (&flow_table->hash_table, &kv, 0 /* is_del */ ); +} + +dash_flow_entry_t* +dash_flow_table_lookup_entry (dash_flow_table_t *flow_table, flow_key_t *flow_key) +{ + BVT (clib_bihash_kv) kv; + flow_data_t *flow_data; + + clib_memcpy_fast (kv.key, flow_key, sizeof(kv.key)); + if (BV (clib_bihash_search) (&flow_table->hash_table, &kv, &kv)) + return NULL; + + flow_data = (flow_data_t *)(uintptr_t)kv.value; + return (dash_flow_entry_t*)((u8*)flow_data - offsetof(dash_flow_entry_t, flow_data)); +} + +static uword +dash_flow_scan (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_frame_t * f) +{ + dash_flow_table_t *flow_table = dash_flow_table_get(); + TW (tw_timer_expire_timers) (&flow_table->flow_tw, vlib_time_now(vm)); + return 0; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dash_flow_scan_node) = { + .function = dash_flow_scan, + .name = "dash-flow-scan", + .type = VLIB_NODE_TYPE_INPUT, + .state = VLIB_NODE_STATE_INTERRUPT, +}; +/* *INDENT-ON* */ + +static u8 * +dash_flow_format (u8 * s, va_list * args) +{ + dash_flow_entry_t *flow = va_arg(*args, dash_flow_entry_t*); + + s = format (s, "eni %U, vnet_id %d, proto %d, ", + format_mac_address, flow->key.eni_mac, + clib_net_to_host_u16 (flow->key.vnet_id), + flow->key.ip_proto); + + if (flow->key.is_ip_v6) { + s = format (s, "%U %d -> %U %d\n", + format_ip6_address, &flow->key.src_ip.ip6, + clib_net_to_host_u16 (flow->key.src_port), + format_ip6_address, &flow->key.dst_ip.ip6, + clib_net_to_host_u16 (flow->key.dst_port)); + } else { + s = format (s, "%U %d -> %U %d\n", + format_ip4_address, &flow->key.src_ip.ip4, + clib_net_to_host_u16 (flow->key.src_port), + format_ip4_address, &flow->key.dst_ip.ip4, + clib_net_to_host_u16 (flow->key.dst_port)); + } + + s = format (s, " common data - version %u, direction %u, actions 0x%x", + clib_net_to_host_u32 (flow->flow_data.version), + clib_net_to_host_u16 (flow->flow_data.direction), + clib_net_to_host_u32 (flow->flow_data.actions)); + s = format (s, ", timeout %lu\n", + flow->access_time + flow->timeout - (u64)unix_time_now()); + + return s; +} + +typedef struct dash_flow_show_walk_ctx_t_ +{ + u8 verbose; + vlib_main_t *vm; +} dash_flow_show_walk_ctx_t; + +static int +dash_flow_show_walk_cb (BVT (clib_bihash_kv) * kvp, void *arg) +{ + dash_flow_show_walk_ctx_t *ctx = arg; + flow_data_t *flow_data = (flow_data_t *)(uintptr_t)kvp->value; + dash_flow_entry_t *flow = (dash_flow_entry_t*)((u8*)flow_data - + offsetof(dash_flow_entry_t, flow_data)); + + vlib_cli_output (ctx->vm, "%6u: %U", flow->index, dash_flow_format, flow); + + return BIHASH_WALK_CONTINUE; +} + +static clib_error_t * +dash_cmd_show_flow_fn (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + clib_error_t *error = 0; + dash_flow_show_walk_ctx_t ctx = { + .vm = vm, + }; + dash_flow_table_t *flow_table = dash_flow_table_get(); + + BV (clib_bihash_foreach_key_value_pair) + (&flow_table->hash_table, dash_flow_show_walk_cb, &ctx); + + return error; +} + +VLIB_CLI_COMMAND (dash_show_flow_command, static) = { + .path = "show dash flow", + .short_help = "show dash flow [src-addr IP]", + .function = dash_cmd_show_flow_fn, +}; + +static clib_error_t * +dash_cmd_clear_flow_fn (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + clib_error_t *error = 0; + dash_flow_table_t *flow_table = dash_flow_table_get(); + u32 index; + + if (!unformat (input, "%u", &index)) + { + error = clib_error_return (0, "expected flow index"); + goto done; + } + + + dash_flow_entry_t *flow = dash_flow_get_by_index(index); + sai_status_t status = dash_sai_remove_flow_entry(flow); + if (status != SAI_STATUS_SUCCESS) { + error = clib_error_return (0, "dash_sai_remove_flow_entry fail: %d", status); + } + + TW (tw_timer_stop) (&flow_table->flow_tw, flow->timer_handle); + + if (dash_flow_table_delete_entry (flow_table, flow) == 0) { + dash_flow_free(flow); + } else { + ASSERT(0); + } + +done: + return error; +} + +VLIB_CLI_COMMAND (dash_clear_flow_command, static) = { + .path = "clear dash flow", + .short_help = "clear dash flow ", + .function = dash_cmd_clear_flow_fn, +}; + + +static clib_error_t * +dash_cmd_show_flow_stats_fn (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + clib_error_t *error = 0; + dash_flow_table_t *flow_table = dash_flow_table_get(); + + vlib_cli_output (vm, "%12s: %u", "create_ok", + flow_table->flow_stats.create_ok); + vlib_cli_output (vm, "%12s: %u", "create_fail", + flow_table->flow_stats.create_fail); + vlib_cli_output (vm, "%12s: %u", "remove_ok", + flow_table->flow_stats.remove_ok); + vlib_cli_output (vm, "%12s: %u", "remove_fail", + flow_table->flow_stats.remove_fail); + + return error; +} + +VLIB_CLI_COMMAND (dash_show_flow_stats_command, static) = { + .path = "show dash flow stats", + .short_help = "show dash flow [src-addr IP]", + .function = dash_cmd_show_flow_stats_fn, +}; + diff --git a/dash-pipeline/dpapp/dash/flow.h b/dash-pipeline/dpapp/dash/flow.h new file mode 100644 index 000000000..752564cce --- /dev/null +++ b/dash-pipeline/dpapp/dash/flow.h @@ -0,0 +1,193 @@ +#ifndef __included_flow_h__ +#define __included_flow_h__ + +#include + +#include +#include +#include + +#include + +/* Default timeout in seconds */ +#define DASH_FLOW_TIMEOUT 30 + +typedef enum _dash_packet_source_t { + EXTERNAL = 0, // Packets from external sources. + PIPELINE = 1, // Packets from P4 pipeline. + DPAPP = 2, // Packets from data plane app. + PEER = 3 // Packets from the paired DPU. +} dash_packet_source_t; + +typedef enum _dash_packet_type_t { + REGULAR = 0, // Regular packets from external sources. + FLOW_SYNC_REQ = 1, // Flow sync request packet. + FLOW_SYNC_ACK = 2, // Flow sync ack packet. + DP_PROBE_REQ = 3, // Data plane probe packet. + DP_PROBE_ACK = 4 // Data plane probe ack packet. +} dash_packet_type_t; + +typedef enum _dash_packet_subtype_t { + NONE = 0, // no op + FLOW_CREATE = 1, // New flow creation. + FLOW_UPDATE = 2, // Flow resimulation or any other reason causing existing flow to be updated. + FLOW_DELETE = 3 // Flow deletion. +} dash_packet_subtype_t; + +typedef struct dash_packet_meta { + u8 packet_source; +#if defined(__LITTLE_ENDIAN_BITFIELD) + u8 packet_subtype :4; + u8 packet_type :4; +#elif defined (__BIG_ENDIAN_BITFIELD) + u8 packet_type :4; + u8 packet_subtype :4; +#else +#error "Please fix " +#endif + u16 length; +} __clib_packed dash_packet_meta_t; + +/* + * If sizeof flow_key_t > 48, update the use of bihash_xx + */ +typedef struct flow_key { + u8 eni_mac[6]; + u16 vnet_id; + ip46_address_t src_ip; + ip46_address_t dst_ip; + u16 src_port; + u16 dst_port; + u8 ip_proto; +#if defined(__LITTLE_ENDIAN_BITFIELD) + u8 is_ip_v6 :1; + u8 reserved :7; +#elif defined (__BIG_ENDIAN_BITFIELD) + u8 reserved :7; + u8 is_ip_v6 :1; +#else +#error "Please fix " +#endif +} __clib_packed flow_key_t; + +typedef union { + flow_key_t key; + u64 bihash_key[6]; /* bihash_48_8 */ +} dash_flow_hash_key_t; + +typedef struct flow_data { +#if defined(__LITTLE_ENDIAN_BITFIELD) + u8 is_unidirectional :1; + u8 reserved :7; +#elif defined (__BIG_ENDIAN_BITFIELD) + u8 reserved :7; + u8 is_unidirectional :1; +#else +#error "Please fix " +#endif + u16 direction; + u32 version; + u32 actions; + u32 meter_class; +} __clib_packed flow_data_t; + +typedef struct encap_data { + u16 vni_high; + u8 vni_low; + u8 reserved; + ip4_address_t underlay_sip; + ip4_address_t underlay_dip; + u8 underlay_smac[6]; + u8 underlay_dmac[6]; + u16 dash_encapsulation; +} __clib_packed encap_data_t; + +typedef struct overlay_rewrite_data { + u8 dmac[6]; + ip46_address_t sip; + ip46_address_t dip; + ip6_address_t sip_mask; + ip6_address_t dip_mask; +#if defined(__LITTLE_ENDIAN_BITFIELD) + u8 is_ipv6 :1; + u8 reserved :7; +#elif defined (__BIG_ENDIAN_BITFIELD) + u8 reserved :7; + u8 is_ipv6 :1; +#else +#error "Please fix " +#endif +} __clib_packed overlay_rewrite_data_t; + + +typedef struct dash_header { + dash_packet_meta_t packet_meta; + union { + struct { + flow_key_t flow_key; + flow_data_t flow_data; // flow common data + overlay_rewrite_data_t flow_overlay_data; + encap_data_t flow_u0_encap_data; + encap_data_t flow_u1_encap_data; + }; + u8 data[0]; + }; +} +__clib_packed dash_header_t; + + +typedef struct dash_flow_entry { + union { + flow_key_t key; + u64 bihash_key[6]; /* bihash_48_8 */ + }; + + struct { + flow_data_t flow_data; + overlay_rewrite_data_t flow_overlay_data; + encap_data_t flow_u0_encap_data; + encap_data_t flow_u1_encap_data; + }; + + u32 index; + + /* timers */ + u32 timer_handle; /* index in the timer pool */ + u32 timeout; /* in seconds */ + u64 access_time; /* in seconds */ +} dash_flow_entry_t; + +typedef struct dash_flow_stats { + u32 create_ok; + u32 create_fail; + + u32 remove_ok; + u32 remove_fail; +} dash_flow_stats_t; + +typedef struct dash_flow_table { + /* hashtable */ + BVT(clib_bihash) hash_table; + + dash_flow_entry_t *flow_pool; + dash_flow_stats_t flow_stats; + + TWT (tw_timer_wheel) flow_tw; +} dash_flow_table_t; + +dash_flow_table_t* dash_flow_table_get (void); +dash_flow_entry_t* dash_flow_alloc(); +void dash_flow_free(dash_flow_entry_t *flow); +dash_flow_entry_t* dash_flow_get_by_index (u32 index); + +void dash_flow_table_init (dash_flow_table_t *flow_table); +int dash_flow_table_add_entry (dash_flow_table_t *flow_table, dash_flow_entry_t *flow); +int dash_flow_table_delete_entry (dash_flow_table_t *flow_table, dash_flow_entry_t *flow); +dash_flow_entry_t* dash_flow_table_lookup_entry (dash_flow_table_t *flow_table, flow_key_t *flow_key); + + +int dash_flow_process (dash_flow_table_t *flow_table, const dash_header_t *dh); + +extern vlib_node_registration_t dash_flow_scan_node; + +#endif /* __included_flow_h__ */ diff --git a/dash-pipeline/dpapp/dash/saiapi.c b/dash-pipeline/dpapp/dash/saiapi.c new file mode 100644 index 000000000..00963b11d --- /dev/null +++ b/dash-pipeline/dpapp/dash/saiapi.c @@ -0,0 +1,245 @@ + +#include + +#include +#include + +#include +#include + +static sai_object_id_t dash_switch_id = SAI_NULL_OBJECT_ID; +static sai_dash_flow_api_t *dash_flow_api = NULL; + +void +dash_sai_init () +{ + sai_status_t status; + + status = sai_api_initialize(0, NULL); + ASSERT_MSG(status == SAI_STATUS_SUCCESS, "Failed to initialize SAI api"); + + sai_switch_api_t *switch_api; + status = sai_api_query((sai_api_t)SAI_API_SWITCH, (void**)&switch_api); + ASSERT_MSG(status == SAI_STATUS_SUCCESS, "Failed to query SAI_API_SWITCH"); + + status = switch_api->create_switch(&dash_switch_id, 0, NULL); + ASSERT_MSG(status == SAI_STATUS_SUCCESS, "Failed to create switch"); + + status = sai_api_query((sai_api_t)SAI_API_DASH_FLOW, (void**)&dash_flow_api); + ASSERT_MSG(status == SAI_STATUS_SUCCESS, "Failed to query SAI_API_DASH_FLOW"); + + dash_log_info("Succeeded to init dash sai api"); +} + +sai_status_t +dash_sai_create_flow_entry (const dash_flow_entry_t *flow) +{ + sai_flow_entry_t flow_entry; + u32 count = 0; + sai_attribute_t attrs[SAI_FLOW_ENTRY_ATTR_END]; + const flow_key_t *flow_key = &flow->key; + const flow_data_t *flow_data = &flow->flow_data; + const overlay_rewrite_data_t *flow_overlay_data = &flow->flow_overlay_data; + const encap_data_t *flow_u0_encap_data = &flow->flow_u0_encap_data; + const encap_data_t *flow_u1_encap_data = &flow->flow_u1_encap_data; + + /* + * Fill sai_flow_entry_t, sai_attribute_t, whose values need host order + * ip4/6 address in network order + */ + flow_entry.switch_id = dash_switch_id; + clib_memcpy_fast(flow_entry.eni_mac, flow_key->eni_mac, sizeof(flow_entry.eni_mac)); + flow_entry.vnet_id = ntohs(flow_key->vnet_id); + if (flow_key->is_ip_v6) { + flow_entry.src_ip.addr_family = SAI_IP_ADDR_FAMILY_IPV6; + clib_memcpy_fast(flow_entry.src_ip.addr.ip6, &flow_key->src_ip.ip6, sizeof(sai_ip6_t)); + flow_entry.dst_ip.addr_family = SAI_IP_ADDR_FAMILY_IPV6; + clib_memcpy_fast(flow_entry.dst_ip.addr.ip6, &flow_key->dst_ip.ip6, sizeof(sai_ip6_t)); + } else { + flow_entry.src_ip.addr_family = SAI_IP_ADDR_FAMILY_IPV4; + flow_entry.src_ip.addr.ip4 = flow_key->src_ip.ip4.as_u32; + flow_entry.dst_ip.addr_family = SAI_IP_ADDR_FAMILY_IPV4; + flow_entry.dst_ip.addr.ip4 = flow_key->dst_ip.ip4.as_u32; + } + flow_entry.src_port = ntohs(flow_key->src_port); + flow_entry.dst_port = ntohs(flow_key->dst_port); + flow_entry.ip_proto = flow_key->ip_proto; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_ACTION; + attrs[count++].value.u32 = SAI_FLOW_ENTRY_ACTION_SET_FLOW_ENTRY_ATTR; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_VERSION; + attrs[count++].value.u32 = ntohl(flow_data->version); + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_DASH_DIRECTION; + attrs[count++].value.u16 = ntohs(flow_data->direction); + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_DASH_FLOW_ACTION; + attrs[count++].value.u32 = ntohl(flow_data->actions); + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_METER_CLASS; + attrs[count++].value.u32 = ntohl(flow_data->meter_class); + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_IS_UNIDIRECTIONAL_FLOW; + attrs[count++].value.booldata = flow_data->is_unidirectional; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_DASH_FLOW_SYNC_STATE; + attrs[count++].value.u8 = SAI_DASH_FLOW_SYNC_STATE_FLOW_CREATED; + + /* FIXME: Attrs for reverse flow key */ + { + u8 mac[6] = {0}; + attrs[count].id = SAI_FLOW_ENTRY_ATTR_REVERSE_FLOW_ENI_MAC; + clib_memcpy_fast(attrs[count].value.mac, mac, sizeof(ethernet_header_t)); + count++; + } + + /* Attrs for overlay rewrite data */ + if (flow_data->actions != 0) { + attrs[count].id = SAI_FLOW_ENTRY_ATTR_DST_MAC; + clib_memcpy_fast(attrs[count].value.mac, flow_overlay_data->dmac, sizeof(ethernet_header_t)); + count++; + + if (flow_overlay_data->is_ipv6) { + attrs[count].id = SAI_FLOW_ENTRY_ATTR_SIP; + attrs[count].value.ipaddr.addr_family = SAI_IP_ADDR_FAMILY_IPV6; + clib_memcpy_fast(attrs[count].value.ipaddr.addr.ip6, + &flow_overlay_data->sip.ip6, sizeof(sai_ip6_t)); + count++; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_DIP; + attrs[count].value.ipaddr.addr_family = SAI_IP_ADDR_FAMILY_IPV6; + clib_memcpy_fast(attrs[count].value.ipaddr.addr.ip6, + &flow_overlay_data->dip.ip6, sizeof(sai_ip6_t)); + count++; + } else { + attrs[count].id = SAI_FLOW_ENTRY_ATTR_SIP; + attrs[count].value.ipaddr.addr_family = SAI_IP_ADDR_FAMILY_IPV4; + attrs[count++].value.ipaddr.addr.ip4 = flow_overlay_data->sip.ip4.as_u32; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_DIP; + attrs[count].value.ipaddr.addr_family = SAI_IP_ADDR_FAMILY_IPV4; + attrs[count++].value.ipaddr.addr.ip4 = flow_overlay_data->dip.ip4.as_u32; + } + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_SIP_MASK; + attrs[count].value.ipaddr.addr_family = SAI_IP_ADDR_FAMILY_IPV6; + clib_memcpy_fast(attrs[count].value.ipaddr.addr.ip6, + &flow_overlay_data->sip_mask, sizeof(sai_ip6_t)); + count++; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_DIP_MASK; + attrs[count].value.ipaddr.addr_family = SAI_IP_ADDR_FAMILY_IPV6; + clib_memcpy_fast(attrs[count].value.ipaddr.addr.ip6, + &flow_overlay_data->dip_mask, sizeof(sai_ip6_t)); + count++; + } else { + u8 mac[6] = {0}; + /* set default value for bmv2 table */ + attrs[count].id = SAI_FLOW_ENTRY_ATTR_DST_MAC; + clib_memcpy_fast(attrs[count].value.mac, mac, sizeof(ethernet_header_t)); + count++; + } + + /* Attrs for encap data of underlay 0 */ + if (flow_data->actions & htonl(SAI_DASH_FLOW_ACTION_ENCAP_U0)) { + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY0_VNET_ID; + attrs[count++].value.u32 = ntohs(flow_u0_encap_data->vni_high) << 8 | flow_u0_encap_data->vni_low; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY0_SIP; + attrs[count].value.ipaddr.addr_family = SAI_IP_ADDR_FAMILY_IPV4; + attrs[count++].value.ipaddr.addr.ip4 = flow_u0_encap_data->underlay_sip.as_u32; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY0_DIP; + attrs[count].value.ipaddr.addr_family = SAI_IP_ADDR_FAMILY_IPV4; + attrs[count++].value.ipaddr.addr.ip4 = flow_u0_encap_data->underlay_dip.as_u32; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY0_SMAC; + clib_memcpy_fast(attrs[count].value.mac, flow_u0_encap_data->underlay_smac, sizeof(ethernet_header_t)); + count++; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY0_DMAC; + clib_memcpy_fast(attrs[count].value.mac, flow_u0_encap_data->underlay_dmac, sizeof(ethernet_header_t)); + count++; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY0_DASH_ENCAPSULATION; + attrs[count++].value.s32 = ntohs(flow_u0_encap_data->dash_encapsulation); + } else { + u8 mac[6] = {0}; + /* set default value for bmv2 table */ + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY0_SMAC; + clib_memcpy_fast(attrs[count].value.mac, mac, sizeof(ethernet_header_t)); + count++; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY0_DMAC; + clib_memcpy_fast(attrs[count].value.mac, mac, sizeof(ethernet_header_t)); + count++; + } + + /* Attrs for encap data of underlay 1 */ + if (flow_data->actions & htonl(SAI_DASH_FLOW_ACTION_ENCAP_U1)) { + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY1_VNET_ID; + attrs[count++].value.u32 = ntohs(flow_u1_encap_data->vni_high) << 8 | flow_u1_encap_data->vni_low; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY1_SIP; + attrs[count].value.ipaddr.addr_family = SAI_IP_ADDR_FAMILY_IPV4; + attrs[count++].value.ipaddr.addr.ip4 = flow_u1_encap_data->underlay_sip.as_u32; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY1_DIP; + attrs[count].value.ipaddr.addr_family = SAI_IP_ADDR_FAMILY_IPV4; + attrs[count++].value.ipaddr.addr.ip4 = flow_u1_encap_data->underlay_dip.as_u32; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY1_SMAC; + clib_memcpy_fast(attrs[count].value.mac, flow_u1_encap_data->underlay_smac, sizeof(ethernet_header_t)); + count++; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY1_DMAC; + clib_memcpy_fast(attrs[count].value.mac, flow_u1_encap_data->underlay_dmac, sizeof(ethernet_header_t)); + count++; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY1_DASH_ENCAPSULATION; + attrs[count++].value.s32 = ntohs(flow_u1_encap_data->dash_encapsulation); + } else { + u8 mac[6] = {0}; + /* set default value for bmv2 table */ + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY1_SMAC; + clib_memcpy_fast(attrs[count].value.mac, mac, sizeof(ethernet_header_t)); + count++; + + attrs[count].id = SAI_FLOW_ENTRY_ATTR_UNDERLAY1_DMAC; + clib_memcpy_fast(attrs[count].value.mac, mac, sizeof(ethernet_header_t)); + count++; + } + + return dash_flow_api->create_flow_entry(&flow_entry, count, attrs); +} + +sai_status_t +dash_sai_remove_flow_entry (const dash_flow_entry_t *flow) +{ + sai_flow_entry_t flow_entry; + const flow_key_t *flow_key = &flow->key; + + flow_entry.switch_id = dash_switch_id; + clib_memcpy_fast(flow_entry.eni_mac, flow_key->eni_mac, sizeof(flow_entry.eni_mac)); + flow_entry.vnet_id = ntohs(flow_key->vnet_id); + + if (flow_key->is_ip_v6) { + flow_entry.src_ip.addr_family = SAI_IP_ADDR_FAMILY_IPV6; + clib_memcpy_fast(flow_entry.src_ip.addr.ip6, &flow_key->src_ip.ip6, sizeof(sai_ip6_t)); + flow_entry.dst_ip.addr_family = SAI_IP_ADDR_FAMILY_IPV6; + clib_memcpy_fast(flow_entry.dst_ip.addr.ip6, &flow_key->dst_ip.ip6, sizeof(sai_ip6_t)); + } else { + flow_entry.src_ip.addr_family = SAI_IP_ADDR_FAMILY_IPV4; + flow_entry.src_ip.addr.ip4 = flow_key->src_ip.ip4.as_u32; + flow_entry.dst_ip.addr_family = SAI_IP_ADDR_FAMILY_IPV4; + flow_entry.dst_ip.addr.ip4 = flow_key->dst_ip.ip4.as_u32; + } + + flow_entry.src_port = ntohs(flow_key->src_port); + flow_entry.dst_port = ntohs(flow_key->dst_port); + flow_entry.ip_proto = flow_key->ip_proto; + + return dash_flow_api->remove_flow_entry(&flow_entry); +} + diff --git a/dash-pipeline/dpapp/dpapp.sh b/dash-pipeline/dpapp/dpapp.sh new file mode 100755 index 000000000..5e6514c84 --- /dev/null +++ b/dash-pipeline/dpapp/dpapp.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -m + +[ -d /var/log/vpp ] || mkdir -p /var/log/vpp + +sysctl vm.nr_hugepages=32 + +/usr/bin/vpp -c ${1:-/etc/vpp/startup.conf} & +sleep 5 + +# Create a host interface which connects p4 bmv2 simple_switch +HOST_INTERFACE=${HOST_INTERFACE:-veth5} +HOST_INTERFACE_MAC=`cat /sys/class/net/$HOST_INTERFACE/address` +vppctl create host-interface name $HOST_INTERFACE hw-addr $HOST_INTERFACE_MAC +vppctl set interface state host-$HOST_INTERFACE up + +# Move vpp to foreground +fg %1 diff --git a/dash-pipeline/dpapp/startup.conf b/dash-pipeline/dpapp/startup.conf new file mode 100644 index 000000000..248442fb3 --- /dev/null +++ b/dash-pipeline/dpapp/startup.conf @@ -0,0 +1,37 @@ +unix { + nodaemon + log /var/log/vpp/vpp.log + full-coredump + cli-listen /run/vpp/cli.sock + gid vpp +} + +api-trace { + on +} + +api-segment { + gid vpp +} + +socksvr { + default +} + +logging { + default-log-level info + default-syslog-log-level info +} + +cpu { + main-core 1 + corelist-workers 2 +} + +plugins { + add-path /dash/dash-pipeline/dpapp/build/lib/vpp_plugins + plugin default {disable} + plugin af_packet_plugin.so {enable} + plugin dash_plugin.so {enable} +} + diff --git a/dash-pipeline/dpapp/tools/send_p2a_pkt.py b/dash-pipeline/dpapp/tools/send_p2a_pkt.py new file mode 100755 index 000000000..49a31c691 --- /dev/null +++ b/dash-pipeline/dpapp/tools/send_p2a_pkt.py @@ -0,0 +1,127 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from scapy.all import * +import argparse + +class DASH_PACKET_META(Packet): + name = "DASH_PACKET_META" + fields_desc = [ ByteField("packet_source", 0), + BitField("packet_type", 0, 4), + BitField("packet_subtype", 1, 4), + ShortField("length", 4), + ] + +class DASH_FLOW_KEY(Packet): + name = "DASH_FLOW_KEY" + fields_desc = [ MACField("eni_mac", "0:0:0:0:0:0"), + ShortField("vnet_id", 2), + IP6Field("src_ip", "::1.1.1.1"), + IP6Field("dst_ip", "::2.2.2.2"), + XShortField("src_port", 0x5566), + XShortField("dst_port", 0x6677), + ByteEnumField("ip_proto", IP_PROTOS.udp, IP_PROTOS), + BitField("reserved", 0, 7), + BitField("is_ip_v6", 0, 1), + ] + +class DASH_FLOW_DATA(Packet): + name = "DASH_FLOW_DATA" + fields_desc = [ + BitField("reserved", 0, 7), + BitField("is_unidirectional", 0, 1), + ShortEnumField("direction", 1, { 1: "OUTBOUND", 2: "INBOUND" }), + IntField("version", 0), + IntField("actions", 0), + IntField("meter_class", 0), + ] + +class DASH_OVERLAY_DATA(Packet): + name = "DASH_OVERLAY_DATA" + fields_desc = [ MACField("dmac", 0), + IP6Field("sip", "::"), + IP6Field("dip", "::"), + IP6Field("sip_mask", "::"), + IP6Field("dip_mask", "::"), + BitField("reserved", 0, 7), + BitField("is_ipv6", 0, 1), + ] + +class DASH_ENCAP_DATA(Packet): + name = "DASH_ENCAP_DATA" + fields_desc = [ BitField("vni", 1, 24), + BitField("reserved", 0, 8), + IPField("underlay_sip", "1.1.1.1"), + IPField("underlay_dip", "2.2.2.2"), + MACField("underlay_smac", "0:0:0:0:0:0"), + MACField("underlay_dmac", "0:0:0:0:0:0"), + ShortField("dash_encapsulation", 1), + ] + + +def get_mac(interface): + try: + mac = open('/sys/class/net/'+interface+'/address').readline().strip() + except: + mac = "00:00:00:00:00:00" + return mac + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Packet generator on behalf of DASH pipeline") + parser.add_argument("--flow-action", type=str, default="CREATE", + help="Flow action, CREATE|UPDATE|DELETE") + parser.add_argument("--flow-key", type=str, + help="Flow key, string style eni_mac=,vnet_id=,src_ip=,dst_ip=,...") + parser.add_argument("--from-port", type=str, default="veth4", + help="DASH pipeline port name") + parser.add_argument("--to-port", type=str, default="veth5", + help="cpu port name") + args = parser.parse_args() + + dpappEther = Ether(dst=get_mac(args.to_port), + src=get_mac(args.from_port), type=0x876D) + + action_dic = { "CREATE":1, "UPDATE":2, "DELETE":3 } + try: + flow_action = action_dic[args.flow_action] + except KeyError: + print(f"Invalid flow action name: {args.flow_action}") + exit(1) + + if args.flow_key: + flow_key = dict(kv.split("=") for kv in args.flow_key.split(",")) + if "vnet_id" in flow_key: + flow_key["vnet_id"] = int(flow_key["vnet_id"]) + if "src_port" in flow_key: + flow_key["src_port"] = int(flow_key["src_port"]) + if "dst_port" in flow_key: + flow_key["dst_port"] = int(flow_key["dst_port"]) + if "ip_proto" in flow_key: + flow_key["ip_proto"] = int(flow_key["ip_proto"]) + if "is_ip_v6" in flow_key: + flow_key["is_ip_v6"] = int(flow_key["is_ip_v6"]) + else: + flow_key = {} + + packetMeta = DASH_PACKET_META(packet_subtype = flow_action) + flowKey = DASH_FLOW_KEY(**flow_key) + flowData = DASH_FLOW_DATA() + packetMeta.length = len(packetMeta) + len(flowKey) + len(flowData) + dashMeta = packetMeta/flowKey/flowData + + if flowKey.is_ip_v6: + L3 = IPv6(src = flowKey.src_ip, dst = flowKey.dst_ip) + else: + L3 = IP(src = flowKey.src_ip.lstrip("::"), dst = flowKey.dst_ip.lstrip("::")) + + if flowKey.ip_proto == IP_PROTOS.tcp: + L4 = TCP(sport=flowKey.src_port, dport=flowKey.dst_port) + else: + L4 = UDP(sport=flowKey.src_port, dport=flowKey.dst_port) + + customerPacket = Ether(dst="00:02:02:02:02:02") / L3 / L4 / ("a"*16) + + pkt = dpappEther/dashMeta/customerPacket + sendp(pkt, iface=args.from_port, count=1) + diff --git a/test/test-cases/functional/ptf/p4_dash_utils.py b/test/test-cases/functional/ptf/p4_dash_utils.py index bc93b7c79..1e87da321 100644 --- a/test/test-cases/functional/ptf/p4_dash_utils.py +++ b/test/test-cases/functional/ptf/p4_dash_utils.py @@ -1,6 +1,8 @@ import grpc from p4.v1 import p4runtime_pb2 from p4.v1 import p4runtime_pb2_grpc +from ipaddress import ip_address +from scapy.all import * def get_mac(interface): @@ -175,3 +177,110 @@ def tearDown(self, *args, **kwargs): setattr(cls, "tearDown", tearDown) return cls + +class P4Table(): + def __init__(self): + channel = grpc.insecure_channel('localhost:9559') + self.stub = p4runtime_pb2_grpc.P4RuntimeStub(channel) + self.p4info = P4info(self.stub) + + def read(self, table_id, match_list = None): + entry = p4runtime_pb2.TableEntry() + entry.table_id = table_id + if match_list: + entry.match.extend(match_list) + + req = p4runtime_pb2.ReadRequest() + req.device_id = 0 + entity = req.entities.add() + entity.table_entry.CopyFrom(entry) + for response in self.stub.Read(req): + for entity in response.entities: + yield entity.table_entry + + +class P4FlowTable(P4Table): + def __init__(self): + super(P4FlowTable, self).__init__() + self.p4info_table_flow = self.p4info.get_table("dash_ingress.conntrack_lookup_stage.flow_entry") + + def print_flow_table(self): + for entry in self.read(self.p4info_table_flow.preamble.id): + print(entry) + + def get_flow_entry(self, eni_mac, vnet_id, + src_ip, dst_ip, + src_port, dst_port, ip_proto): + match_list = [] + + match = p4runtime_pb2.FieldMatch() + match.field_id = 1 + match.exact.value = mac_in_bytes(eni_mac) + match_list.append(match) + + match = p4runtime_pb2.FieldMatch() + match.field_id = 2 + match.exact.value = vnet_id.to_bytes(2, byteorder='big') + match_list.append(match) + + match = p4runtime_pb2.FieldMatch() + match.field_id = 3 + match.exact.value = ip_address(src_ip).packed + match_list.append(match) + + match = p4runtime_pb2.FieldMatch() + match.field_id = 4 + match.exact.value = ip_address(dst_ip).packed + match_list.append(match) + + match = p4runtime_pb2.FieldMatch() + match.field_id = 5 + match.exact.value = src_port.to_bytes(2, byteorder='big') + match_list.append(match) + + match = p4runtime_pb2.FieldMatch() + match.field_id = 6 + match.exact.value = dst_port.to_bytes(2, byteorder='big') + match_list.append(match) + + match = p4runtime_pb2.FieldMatch() + match.field_id = 7 + match.exact.value = ip_proto.to_bytes(1, byteorder='big') + match_list.append(match) + + match = p4runtime_pb2.FieldMatch() + match.field_id = 8 + match.exact.value = b'\x00' if ip_address(src_ip).version == 4 else b'\x01' + match_list.append(match) + + for entry in self.read(self.p4info_table_flow.preamble.id, match_list): + return entry + + return None + + +def verify_flow(eni_mac, vnet_id, packet, existed = True): + if packet.haslayer(TCP): + sport = packet['TCP'].sport + dport = packet['TCP'].dport + elif packet.haslayer(UDP): + sport = packet['UDP'].sport + dport = packet['UDP'].dport + else: # TODO: later for other ip proto + assert False, "Not TCP/UDP packet" + + flow_table = P4FlowTable() + flow = flow_table.get_flow_entry(eni_mac, vnet_id, + packet['IP'].src, + packet['IP'].dst, + sport, + dport, + packet['IP'].proto) + if existed: + assert flow, "flow not found" + else: + assert not flow, "flow still found" + + +def verify_no_flow(eni_mac, vnet_id, packet): + verify_flow(eni_mac, vnet_id, packet, existed = False) diff --git a/test/test-cases/functional/ptf/saidashdpapp_sanity.py b/test/test-cases/functional/ptf/saidashdpapp_sanity.py new file mode 100644 index 000000000..f03b59af7 --- /dev/null +++ b/test/test-cases/functional/ptf/saidashdpapp_sanity.py @@ -0,0 +1,327 @@ +from sai_thrift.sai_headers import * +from sai_base_test import * +from p4_dash_utils import * + +@use_flow +class SaiThriftDpappPktTest(SaiHelperSimplified): + """ Test saithrift vnet outbound towards dpapp""" + + def setUp(self): + super(SaiThriftDpappPktTest, self).setUp() + self.switch_id = 5 + self.outbound_vni = 60 + self.vnet_vni = 100 + self.eni_mac = "00:cc:cc:cc:cc:cc" + self.our_mac = "00:00:02:03:04:05" + self.dst_ca_mac = "00:dd:dd:dd:dd:dd" + self.vip = "172.16.1.100" + self.ca_prefix_addr = "10.1.0.0" + self.ca_prefix_mask = "255.255.0.0" + self.dst_ca_ip = "10.1.2.50" + self.dst_pa_ip = "172.16.1.20" + self.src_vm_pa_ip = "172.16.1.1" + self.dpapp_port = 2 + + # SAI attribute name + self.ip_addr_family_attr = 'ip4' + # SAI address family + self.sai_ip_addr_family = SAI_IP_ADDR_FAMILY_IPV4 + + # Flag to indicate whether configureVnet were successful or not. + self.configured = False + + def configureVnet(self): + """Create VNET configuration""" + + vip = sai_thrift_ip_address_t(addr_family=SAI_IP_ADDR_FAMILY_IPV4, + addr=sai_thrift_ip_addr_t(ip4=self.vip)) + self.vpe = sai_thrift_vip_entry_t(switch_id=self.switch_id, vip=vip) + + status = sai_thrift_create_vip_entry(self.client, self.vpe, + action=SAI_VIP_ENTRY_ACTION_ACCEPT) + assert(status == SAI_STATUS_SUCCESS) + + self.dle = sai_thrift_direction_lookup_entry_t(switch_id=self.switch_id, vni=self.outbound_vni) + status = sai_thrift_create_direction_lookup_entry(self.client, self.dle, + action=SAI_DIRECTION_LOOKUP_ENTRY_ACTION_SET_OUTBOUND_DIRECTION) + assert(status == SAI_STATUS_SUCCESS) + + self.in_acl_group_id = sai_thrift_create_dash_acl_group(self.client, + ip_addr_family=self.sai_ip_addr_family) + assert (self.in_acl_group_id != SAI_NULL_OBJECT_ID) + self.out_acl_group_id = sai_thrift_create_dash_acl_group(self.client, + ip_addr_family=self.sai_ip_addr_family) + assert (self.out_acl_group_id != SAI_NULL_OBJECT_ID) + + self.vnet = sai_thrift_create_vnet(self.client, vni=self.vnet_vni) + assert (self.vnet != SAI_NULL_OBJECT_ID) + + self.outbound_routing_group = sai_thrift_create_outbound_routing_group(self.client, disabled=False) + assert (self.outbound_routing_group != SAI_NULL_OBJECT_ID) + + vm_underlay_dip = sai_thrift_ip_address_t(addr_family=SAI_IP_ADDR_FAMILY_IPV4, + addr=sai_thrift_ip_addr_t(ip4=self.src_vm_pa_ip)) + pl_sip_mask = sai_thrift_ip_address_t(addr_family=SAI_IP_ADDR_FAMILY_IPV6, + addr=sai_thrift_ip_addr_t(ip6="2001:0db8:85a3:0000:0000:0000:0000:0000")) + pl_sip = sai_thrift_ip_address_t(addr_family=SAI_IP_ADDR_FAMILY_IPV6, + addr=sai_thrift_ip_addr_t(ip6="2001:0db8:85a3:0000:0000:8a2e:0370:7334")) + pl_underlay_sip = sai_thrift_ip_address_t(addr_family=SAI_IP_ADDR_FAMILY_IPV4, + addr=sai_thrift_ip_addr_t(ip4="10.0.0.18")) + self.eni = sai_thrift_create_eni(self.client, cps=10000, + pps=100000, flows=100000, + admin_state=True, + ha_scope_id=0, + vm_underlay_dip=vm_underlay_dip, + vm_vni=9, + vnet_id=self.vnet, + pl_sip = pl_sip, + pl_sip_mask = pl_sip_mask, + pl_underlay_sip = pl_underlay_sip, + v4_meter_policy_id = 0, + v6_meter_policy_id = 0, + dash_tunnel_dscp_mode=SAI_DASH_TUNNEL_DSCP_MODE_PRESERVE_MODEL, + dscp=0, + # TODO: Enable ACL rule + #inbound_v4_stage1_dash_acl_group_id = self.in_acl_group_id, + #inbound_v4_stage2_dash_acl_group_id = self.in_acl_group_id, + #inbound_v4_stage3_dash_acl_group_id = self.in_acl_group_id, + #inbound_v4_stage4_dash_acl_group_id = self.in_acl_group_id, + #inbound_v4_stage5_dash_acl_group_id = self.in_acl_group_id, + #outbound_v4_stage1_dash_acl_group_id = self.out_acl_group_id, + #outbound_v4_stage2_dash_acl_group_id = self.out_acl_group_id, + #outbound_v4_stage3_dash_acl_group_id = self.out_acl_group_id, + #outbound_v4_stage4_dash_acl_group_id = self.out_acl_group_id, + #outbound_v4_stage5_dash_acl_group_id = self.out_acl_group_id, + inbound_v4_stage1_dash_acl_group_id = 0, + inbound_v4_stage2_dash_acl_group_id = 0, + inbound_v4_stage3_dash_acl_group_id = 0, + inbound_v4_stage4_dash_acl_group_id = 0, + inbound_v4_stage5_dash_acl_group_id = 0, + outbound_v4_stage1_dash_acl_group_id = 0, + outbound_v4_stage2_dash_acl_group_id = 0, + outbound_v4_stage3_dash_acl_group_id = 0, + outbound_v4_stage4_dash_acl_group_id = 0, + outbound_v4_stage5_dash_acl_group_id = 0, + inbound_v6_stage1_dash_acl_group_id = 0, + inbound_v6_stage2_dash_acl_group_id = 0, + inbound_v6_stage3_dash_acl_group_id = 0, + inbound_v6_stage4_dash_acl_group_id = 0, + inbound_v6_stage5_dash_acl_group_id = 0, + outbound_v6_stage1_dash_acl_group_id = 0, + outbound_v6_stage2_dash_acl_group_id = 0, + outbound_v6_stage3_dash_acl_group_id = 0, + outbound_v6_stage4_dash_acl_group_id = 0, + outbound_v6_stage5_dash_acl_group_id = 0, + disable_fast_path_icmp_flow_redirection = 0, + full_flow_resimulation_requested=False, + max_resimulated_flow_per_second=0, + outbound_routing_group_id=self.outbound_routing_group) + + self.eam = sai_thrift_eni_ether_address_map_entry_t(switch_id=self.switch_id, address = self.eni_mac) + status = sai_thrift_create_eni_ether_address_map_entry(self.client, + eni_ether_address_map_entry=self.eam, + eni_id=self.eni) + assert(status == SAI_STATUS_SUCCESS) + + dip = sai_thrift_ip_address_t(addr_family=self.sai_ip_addr_family, + addr=sai_thrift_ip_addr_t(**{self.ip_addr_family_attr: self.dst_ca_ip})) + + # TODO: Enable ACL rule for IPv6 + if self.sai_ip_addr_family == SAI_IP_ADDR_FAMILY_IPV4: + self.out_acl_rule_id = sai_thrift_create_dash_acl_rule(self.client, dash_acl_group_id=self.out_acl_group_id, priority=10, + action=SAI_DASH_ACL_RULE_ACTION_PERMIT) + assert(status == SAI_STATUS_SUCCESS) + + ca_prefix = sai_thrift_ip_prefix_t(addr_family=self.sai_ip_addr_family, + addr=sai_thrift_ip_addr_t(**{self.ip_addr_family_attr: self.ca_prefix_addr}), + mask=sai_thrift_ip_addr_t(**{self.ip_addr_family_attr: self.ca_prefix_mask})) + self.ore = sai_thrift_outbound_routing_entry_t(switch_id=self.switch_id, outbound_routing_group_id=self.outbound_routing_group, destination=ca_prefix) + status = sai_thrift_create_outbound_routing_entry(self.client, self.ore, + action=SAI_OUTBOUND_ROUTING_ENTRY_ACTION_ROUTE_VNET, + dst_vnet_id=self.vnet, + meter_class_or=0, meter_class_and=-1, + dash_tunnel_id=0, routing_actions_disabled_in_flow_resimulation = 0) + assert(status == SAI_STATUS_SUCCESS) + + underlay_dip = sai_thrift_ip_address_t(addr_family=SAI_IP_ADDR_FAMILY_IPV4, + addr=sai_thrift_ip_addr_t(ip4=self.dst_pa_ip)) + self.ocpe = sai_thrift_outbound_ca_to_pa_entry_t(switch_id=self.switch_id, dst_vnet_id=self.vnet, dip=dip) + status = sai_thrift_create_outbound_ca_to_pa_entry(self.client, self.ocpe, action=SAI_OUTBOUND_CA_TO_PA_ENTRY_ACTION_SET_TUNNEL_MAPPING, + underlay_dip = underlay_dip, + overlay_dmac=self.dst_ca_mac, use_dst_vnet_vni = True, + meter_class_or=0, flow_resimulation_requested = False, dash_tunnel_id=0, + routing_actions_disabled_in_flow_resimulation = 0) + assert(status == SAI_STATUS_SUCCESS) + + + print(f"\n{self.__class__.__name__} configureVnet OK\n") + self.configured = True + + def trafficUdpTest(self): + + src_vm_ip = "10.1.1.10" + outer_smac = "00:00:05:06:06:06" + + # check forwarding + inner_pkt = simple_udp_packet(eth_dst="02:02:02:02:02:02", + eth_src=self.eni_mac, + ip_dst=self.dst_ca_ip, + ip_src=src_vm_ip) + vxlan_pkt = simple_vxlan_packet(eth_dst=self.our_mac, + eth_src=outer_smac, + ip_dst=self.vip, + ip_src=self.src_vm_pa_ip, + udp_sport=11638, + with_udp_chksum=False, + vxlan_vni=self.outbound_vni, + inner_frame=inner_pkt) + + inner_exp_pkt = simple_udp_packet(eth_dst=self.dst_ca_mac, + eth_src=self.eni_mac, + ip_dst=self.dst_ca_ip, + ip_src=src_vm_ip) + vxlan_exp_pkt = simple_vxlan_packet(eth_dst="00:00:00:00:00:00", + eth_src="00:00:00:00:00:00", + ip_dst=self.dst_pa_ip, + ip_src=self.vip, + udp_sport=0, # TODO: Fix sport in pipeline + with_udp_chksum=False, + vxlan_vni=self.vnet_vni, + inner_frame=inner_exp_pkt) + + self.pkt_exp = vxlan_exp_pkt + print("\tSending outbound udp packet...") + send_packet(self, 0, vxlan_pkt) + print("\tVerifying packet...") + verify_packet(self, self.pkt_exp, 0) + print("\tVerifying flow created...") + verify_flow(self.eni_mac, self.vnet & 0xffff, inner_pkt) + print(f"{self.__class__.__name__} trafficUdpTest OK\n") + + def trafficTcpTest(self): + + src_vm_ip = "10.1.1.10" + outer_smac = "00:00:05:06:06:06" + tcp_src_port = 0x1234 + tcp_dst_port = 0x50 + + # customer packet: tcp SYN + inner_pkt = simple_tcp_packet(eth_dst="02:02:02:02:02:02", + eth_src=self.eni_mac, + ip_dst=self.dst_ca_ip, + ip_src=src_vm_ip, + tcp_sport=tcp_src_port, + tcp_dport=tcp_dst_port, + tcp_flags="S") + vxlan_pkt = simple_vxlan_packet(eth_dst=self.our_mac, + eth_src=outer_smac, + ip_dst=self.vip, + ip_src=self.src_vm_pa_ip, + udp_sport=11638, + with_udp_chksum=False, + vxlan_vni=self.outbound_vni, + inner_frame=inner_pkt) + + inner_exp_pkt = simple_tcp_packet(eth_dst=self.dst_ca_mac, + eth_src=self.eni_mac, + ip_dst=self.dst_ca_ip, + ip_src=src_vm_ip, + tcp_sport=tcp_src_port, + tcp_dport=tcp_dst_port, + tcp_flags="S") + vxlan_exp_pkt = simple_vxlan_packet(eth_dst="00:00:00:00:00:00", + eth_src="00:00:00:00:00:00", + ip_dst=self.dst_pa_ip, + ip_src=self.vip, + udp_sport=0, # TODO: Fix sport in pipeline + with_udp_chksum=False, + vxlan_vni=self.vnet_vni, + inner_frame=inner_exp_pkt) + + self.pkt_exp = vxlan_exp_pkt + print("\tSending outbound packet TCP SYN ...") + send_packet(self, 0, vxlan_pkt) + print("\tVerifying packet...") + verify_packet(self, self.pkt_exp, 0) + print("\tVerifying flow created...") + verify_flow(self.eni_mac, self.vnet & 0xffff, inner_pkt) + + # customer packet: tcp FIN + inner_pkt = simple_tcp_packet(eth_dst="02:02:02:02:02:02", + eth_src=self.eni_mac, + ip_dst=self.dst_ca_ip, + ip_src=src_vm_ip, + tcp_sport=tcp_src_port, + tcp_dport=tcp_dst_port, + tcp_flags="F") + vxlan_pkt = simple_vxlan_packet(eth_dst=self.our_mac, + eth_src=outer_smac, + ip_dst=self.vip, + ip_src=self.src_vm_pa_ip, + udp_sport=11638, + with_udp_chksum=False, + vxlan_vni=self.outbound_vni, + inner_frame=inner_pkt) + + inner_exp_pkt = simple_tcp_packet(eth_dst=self.dst_ca_mac, + eth_src=self.eni_mac, + ip_dst=self.dst_ca_ip, + ip_src=src_vm_ip, + tcp_sport=tcp_src_port, + tcp_dport=tcp_dst_port, + tcp_flags="F") + vxlan_exp_pkt = simple_vxlan_packet(eth_dst="00:00:00:00:00:00", + eth_src="00:00:00:00:00:00", + ip_dst=self.dst_pa_ip, + ip_src=self.vip, + udp_sport=0, # TODO: Fix sport in pipeline + with_udp_chksum=False, + vxlan_vni=self.vnet_vni, + inner_frame=inner_exp_pkt) + + self.pkt_exp = vxlan_exp_pkt + print("\tSending outbound packet TCP FIN ...") + send_packet(self, 0, vxlan_pkt) + print("\tVerifying packet...") + verify_packet(self, self.pkt_exp, 0) + print("\tVerifying flow deleted...") + verify_no_flow(self.eni_mac, self.vnet & 0xffff, inner_pkt) + + print(f"{self.__class__.__name__} trafficTcpTest OK\n") + + def runTest(self): + + self.configureVnet() + self.trafficUdpTest() + self.trafficTcpTest() + + def tearDown(self): + + status = True + try: + # Delete entries in the reverse order + status &= sai_thrift_remove_outbound_ca_to_pa_entry(self.client, self.ocpe) + status &= sai_thrift_remove_outbound_routing_entry(self.client, self.ore) + if self.sai_ip_addr_family == SAI_IP_ADDR_FAMILY_IPV4: + status &= sai_thrift_remove_dash_acl_rule(self.client, self.out_acl_rule_id) + status &= sai_thrift_remove_eni_ether_address_map_entry(self.client, self.eam) + status &= sai_thrift_remove_eni(self.client, self.eni) + status &= sai_thrift_remove_vnet(self.client, self.vnet) + status &= sai_thrift_remove_dash_acl_group(self.client, self.out_acl_group_id) + status &= sai_thrift_remove_dash_acl_group(self.client, self.in_acl_group_id) + status &= sai_thrift_remove_direction_lookup_entry(self.client, self.dle) + status &= sai_thrift_remove_vip_entry(self.client, self.vpe) + status &= sai_thrift_remove_route_entry(self.client, self.pa_route_entry) + if self.configured: + # Skip remove status verification if the configuration creation failed + self.assertEqual(status, SAI_STATUS_SUCCESS) + print(f"{self.__class__.__name__} tearDown OK") + except: + # Ignore errors if configuration were unsuccessful + if self.configured: + raise + finally: + # Run standard PTF teardown + super(SaiThriftDpappPktTest, self).tearDown() + + diff --git a/test/test-cases/functional/ptf/saidashvnet_sanity.py b/test/test-cases/functional/ptf/saidashvnet_sanity.py index 5080b5baa..c001e1702 100644 --- a/test/test-cases/functional/ptf/saidashvnet_sanity.py +++ b/test/test-cases/functional/ptf/saidashvnet_sanity.py @@ -2,6 +2,7 @@ from sai_base_test import * from p4_dash_utils import * +@use_flow class SaiThriftVnetOutboundUdpPktTest(SaiHelperSimplified): """ Test saithrift vnet outbound"""