diff --git a/vslib/Makefile.am b/vslib/Makefile.am index f7f2f60587..8058d63d7e 100644 --- a/vslib/Makefile.am +++ b/vslib/Makefile.am @@ -90,6 +90,7 @@ libSaiVS_a_SOURCES +=\ vpp/SwitchVppSRv6.cpp \ vpp/SwitchVppMirror.cpp \ vpp/SwitchVppSflow.cpp \ + vpp/SwitchVppMpls.cpp \ vpp/TunnelManager.cpp \ vpp/SaiVppLog.cpp \ vpp/vppxlate/SaiVppXlate.c \ diff --git a/vslib/vpp/SwitchVpp.cpp b/vslib/vpp/SwitchVpp.cpp index b17a84e1e6..7397e38744 100644 --- a/vslib/vpp/SwitchVpp.cpp +++ b/vslib/vpp/SwitchVpp.cpp @@ -1393,6 +1393,11 @@ sai_status_t SwitchVpp::create( return status; } + if (object_type == SAI_OBJECT_TYPE_INSEG_ENTRY) + { + return addMplsRoute(serializedObjectId, switch_id, attr_count, attr_list); + } + if (object_type == SAI_OBJECT_TYPE_MY_SID_ENTRY) { sai_status_t status = m_tunnel_mgr_srv6.create_my_sid_entry(serializedObjectId, switch_id, attr_count, attr_list); @@ -1777,6 +1782,11 @@ sai_status_t SwitchVpp::remove( m_routeCounterStatsCarryMap.erase(objectId); } + if (object_type == SAI_OBJECT_TYPE_INSEG_ENTRY) + { + return removeMplsRoute(serializedObjectId); + } + if (object_type == SAI_OBJECT_TYPE_MY_SID_ENTRY) { sai_status_t status = m_tunnel_mgr_srv6.remove_my_sid_entry(serializedObjectId); diff --git a/vslib/vpp/SwitchVpp.h b/vslib/vpp/SwitchVpp.h index cbe4edb603..f21d6f487b 100644 --- a/vslib/vpp/SwitchVpp.h +++ b/vslib/vpp/SwitchVpp.h @@ -754,6 +754,22 @@ namespace saivs sai_status_t removeIpRoute( _In_ const std::string &serializedObjectId); + sai_status_t addMplsRoute( + _In_ const std::string &serializedObjectId, + _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list); + sai_status_t removeMplsRoute( + _In_ const std::string &serializedObjectId); + sai_status_t MplsRouteAddRemove( + _In_ const SaiObject *inseg_obj, + _In_ const std::string &serializedObjectId, + _In_ bool is_add); + sai_status_t fillMplsNexthop( + _In_ const SaiObject *nh_obj, + _Out_ vpp_mpls_nexthop_t *vnh); + sai_status_t ensureMplsTable(); + sai_status_t IpRouteNexthopEntry( _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list, @@ -1292,6 +1308,7 @@ namespace saivs // SRv6 object tracking for CRM constexpr static const int m_maxMySidEntries = 1000; uint32_t m_srv6_my_sid_count = 0; + bool m_mpls_table_created = false; std::shared_ptr m_realObjectIdManager; diff --git a/vslib/vpp/SwitchVppMpls.cpp b/vslib/vpp/SwitchVppMpls.cpp new file mode 100644 index 0000000000..3deda64c10 --- /dev/null +++ b/vslib/vpp/SwitchVppMpls.cpp @@ -0,0 +1,312 @@ +/* + * VPP MPLS backend for SAI INSEG (MPLS in-segment / local label) entries. + * Issue: sonic-buildimage#25782 - enable MPLS data plane testing on VPP. + * + * Translates SAI_OBJECT_TYPE_INSEG_ENTRY into VPP MPLS FIB programming via + * mpls_route_add_del(). Pop (IP next-hop, no out-labels) and swap/push + * (SAI_NEXT_HOP_TYPE_MPLS next-hop carrying an out-label stack) are supported. + */ +#include "SwitchVpp.h" +#include "SwitchVppUtils.h" + +#include "meta/sai_serialize.h" + +#include "swss/logger.h" + +#include "vppxlate/SaiVppXlate.h" + +#include +#include +#include +#include + +using namespace saivs; + +/* VPP MPLS FIB table used for all SONiC in-segment entries. */ +#define MPLS_TABLE_ID 0 +/* Reserved MPLS implicit-null label (RFC 3032): pop and forward on the payload. */ +#define MPLS_IMPLICIT_NULL_LABEL 3 + +sai_status_t SwitchVpp::ensureMplsTable() +{ + SWSS_LOG_ENTER(); + + if (m_mpls_table_created) { + return SAI_STATUS_SUCCESS; + } + + int ret = mpls_table_add_del(MPLS_TABLE_ID, true); + if (ret != 0) { + SWSS_LOG_ERROR("Failed to create VPP MPLS table %u: %d", MPLS_TABLE_ID, ret); + return SAI_STATUS_FAILURE; + } + + m_mpls_table_created = true; + SWSS_LOG_NOTICE("Created VPP MPLS table %u", MPLS_TABLE_ID); + + return SAI_STATUS_SUCCESS; +} + +sai_status_t SwitchVpp::fillMplsNexthop( + _In_ const SaiObject *nh_obj, + _Out_ vpp_mpls_nexthop_t *vnh) +{ + SWSS_LOG_ENTER(); + + sai_attribute_t attr; + + memset(vnh, 0, sizeof(*vnh)); + vnh->sw_if_index = (uint32_t)~0; /* let VPP resolve egress recursively by IP */ + vnh->hwif_name = NULL; + vnh->weight = 1; + vnh->preference = 0; + vnh->type = VPP_NEXTHOP_NORMAL; + vnh->n_labels = 0; + + attr.id = SAI_NEXT_HOP_ATTR_TYPE; + CHECK_STATUS_W_MSG(nh_obj->get_attr(attr), "MPLS path nexthop missing TYPE"); + int32_t nh_type = attr.value.s32; + + if (nh_type != SAI_NEXT_HOP_TYPE_IP && nh_type != SAI_NEXT_HOP_TYPE_MPLS) { + SWSS_LOG_ERROR("Unsupported MPLS path nexthop type %d", nh_type); + return SAI_STATUS_NOT_IMPLEMENTED; + } + + attr.id = SAI_NEXT_HOP_ATTR_IP; + CHECK_STATUS_W_MSG(nh_obj->get_attr(attr), "MPLS path nexthop missing IP"); + sai_ip_address_t_to_vpp_ip_addr_t(attr.value.ipaddr, vnh->addr); + + if (nh_type == SAI_NEXT_HOP_TYPE_MPLS) { + /* + * LABELSTACK is a list attribute: the caller must supply the output + * buffer (list pointer + count) before calling get_attr, otherwise the + * read fails and the out labels are silently dropped (turning a + * swap/push into a bare pop). + */ + uint32_t label_buf[VPP_MPLS_MAX_LABELS]; + attr.id = SAI_NEXT_HOP_ATTR_LABELSTACK; + attr.value.u32list.count = VPP_MPLS_MAX_LABELS; + attr.value.u32list.list = label_buf; + + sai_status_t label_status = nh_obj->get_attr(attr); + + /* + * A stack deeper than the buffer yields SAI_STATUS_BUFFER_OVERFLOW with + * count set to the required size and nothing copied. Fail explicitly: + * falling through with zero labels would turn a swap/push into a bare + * pop and silently misforward traffic. + */ + if (label_status == SAI_STATUS_BUFFER_OVERFLOW) { + SWSS_LOG_ERROR("MPLS out-label stack of %u labels exceeds maximum %u", + attr.value.u32list.count, VPP_MPLS_MAX_LABELS); + return SAI_STATUS_NOT_SUPPORTED; + } + + /* + * Any other failure means the nexthop carries no label stack, which is + * a valid pop/disposition path. + */ + if (label_status == SAI_STATUS_SUCCESS && attr.value.u32list.count > 0) { + uint32_t cnt = attr.value.u32list.count; + vnh->n_labels = (uint8_t)cnt; + for (uint32_t i = 0; i < cnt; i++) { + vnh->label_stack[i].label = attr.value.u32list.list[i]; + vnh->label_stack[i].ttl = MPLS_DEFAULT_OUT_TTL; + vnh->label_stack[i].exp = 0; + vnh->label_stack[i].is_uniform = 1; + } + } + } + + return SAI_STATUS_SUCCESS; +} + +sai_status_t SwitchVpp::MplsRouteAddRemove( + _In_ const SaiObject *inseg_obj, + _In_ const std::string &serializedObjectId, + _In_ bool is_add) +{ + SWSS_LOG_ENTER(); + + sai_inseg_entry_t inseg_entry; + sai_deserialize_inseg_entry(serializedObjectId, inseg_entry); + + sai_attribute_t attr; + int32_t action = SAI_PACKET_ACTION_FORWARD; + attr.id = SAI_INSEG_ENTRY_ATTR_PACKET_ACTION; + if (inseg_obj->get_attr(attr) == SAI_STATUS_SUCCESS) { + action = attr.value.s32; + } + if (action != SAI_PACKET_ACTION_FORWARD) { + SWSS_LOG_NOTICE("Ignoring inseg label %u: packet action %d is not forward", + inseg_entry.label, action); + return SAI_STATUS_SUCCESS; + } + + auto nh_obj = inseg_obj->get_linked_object(SAI_OBJECT_TYPE_NEXT_HOP, + SAI_INSEG_ENTRY_ATTR_NEXT_HOP_ID); + if (!nh_obj) { + SWSS_LOG_NOTICE("Ignoring inseg label %u: no resolvable nexthop", inseg_entry.label); + return SAI_STATUS_SUCCESS; + } + + CHECK_STATUS(ensureMplsTable()); + + vpp_mpls_route_t *route = (vpp_mpls_route_t *) + calloc(1, sizeof(vpp_mpls_route_t) + sizeof(vpp_mpls_nexthop_t)); + if (!route) { + return SAI_STATUS_FAILURE; + } + route->table_id = MPLS_TABLE_ID; + route->label = inseg_entry.label; + route->is_multipath = false; + route->nexthop_cnt = 1; + route->eos_proto_af = AF_INET; + + sai_status_t status = fillMplsNexthop(nh_obj.get(), &route->nexthop[0]); + if (status != SAI_STATUS_SUCCESS) { + free(route); + return status; + } + + bool has_outlabels = (route->nexthop[0].n_labels > 0); + + /* + * Resolve the next hop's router interface to its VPP egress hwif so the + * path is programmed as *attached* rather than recursive. This is required + * for both forwarding cases: + * - pop (disposition): VPP only inserts the MPLS disposition (and thus + * honours the uniform LSP mode) for an attached next hop; a recursive + * next hop collapses to a plain IP forward and ignores the mode. + * - swap/push (imposition): a recursive labelled path fails to resolve + * and the packet is dropped at the MPLS DROP DPO. + * If resolution fails we fall back to the recursive (~0) path. + */ + std::string egress_hwif; + { + sai_attribute_t rif_attr; + rif_attr.id = SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID; + if (nh_obj->get_attr(rif_attr) == SAI_STATUS_SUCCESS) { + sai_object_id_t rif_id = rif_attr.value.oid; + sai_attribute_t port_attr; + port_attr.id = SAI_ROUTER_INTERFACE_ATTR_PORT_ID; + if (get(SAI_OBJECT_TYPE_ROUTER_INTERFACE, rif_id, 1, &port_attr) == SAI_STATUS_SUCCESS && + vpp_get_hwif_name(port_attr.value.oid, 0, egress_hwif)) { + route->nexthop[0].hwif_name = egress_hwif.c_str(); + } + } + } + + if (!has_outlabels) { + /* + * Pop/disposition case (no out labels). Inject an implicit-null out + * label with uniform LSP mode so VPP builds a UNIFORM-mode MPLS + * disposition: the popped inner IP TTL is derived from the popped MPLS + * TTL (mpls_ttl - 1). This matches the SAI default POP TTL mode + * (SAI_INSEG_ENTRY_POP_TTL_MODE_UNIFORM) and typical hardware. Without + * it VPP defaults the disposition to PIPE (inner TTL left untouched and + * only decremented once by the IP forwarding stage). + */ + route->nexthop[0].n_labels = 1; + route->nexthop[0].label_stack[0].label = MPLS_IMPLICIT_NULL_LABEL; + route->nexthop[0].label_stack[0].ttl = 0; + route->nexthop[0].label_stack[0].exp = 0; + route->nexthop[0].label_stack[0].is_uniform = 1; + } + + /* + * The SAI INSEG entry carries a single local label with no End-of-Stack + * qualifier, but the VPP MPLS FIB is keyed by {label, eos}. Always program + * the eos=1 entry (single-label / disposition case). For swap/push (out + * labels present) also program eos=0 so a non-bottom label in a stack is + * handled (e.g. test_swap_labelstack). + */ + uint8_t eos_list[2]; + int n_eos = 0; + if (has_outlabels) { + eos_list[n_eos++] = 0; + } + eos_list[n_eos++] = 1; + + int ret = 0; + int programmed = 0; + for (int e = 0; e < n_eos; e++) { + route->eos = eos_list[e]; + ret = mpls_route_add_del(route, is_add); + if (ret != 0) { + break; + } + programmed++; + } + + /* + * A multi-EOS add that fails part way through would leave an orphaned FIB + * entry behind that no SAI object refers to, because the caller aborts + * before recording the route. Undo the entries that did get programmed. + */ + if (ret != 0 && is_add) { + for (int e = 0; e < programmed; e++) { + route->eos = eos_list[e]; + mpls_route_add_del(route, false); + } + } + + /* + * Report the SAI-visible out-label count, not route->nexthop[0].n_labels: + * the pop case injects an implicit-null label and sets n_labels to 1, which + * would otherwise log a pop as having one out-label. + */ + SWSS_LOG_NOTICE("%s inseg label %u out_labels %u status %d", + (is_add ? "Add" : "Remove"), inseg_entry.label, + (has_outlabels ? route->nexthop[0].n_labels : 0), ret); + + free(route); + + return (ret == 0) ? SAI_STATUS_SUCCESS : SAI_STATUS_FAILURE; +} + +sai_status_t SwitchVpp::addMplsRoute( + _In_ const std::string &serializedObjectId, + _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list) +{ + SWSS_LOG_ENTER(); + + SaiCachedObject inseg_obj(this, SAI_OBJECT_TYPE_INSEG_ENTRY, serializedObjectId, attr_count, attr_list); + + bool route_programmed = false; + + if (is_ip_nbr_active() == true) { + CHECK_STATUS(MplsRouteAddRemove(&inseg_obj, serializedObjectId, true)); + route_programmed = true; + } + + sai_status_t status = create_internal(SAI_OBJECT_TYPE_INSEG_ENTRY, serializedObjectId, switch_id, attr_count, attr_list); + if (status != SAI_STATUS_SUCCESS) + { + // The entry was not committed, so undo the VPP programming to keep the + // MPLS FIB in sync with the SAI object database. + if (route_programmed) { + MplsRouteAddRemove(&inseg_obj, serializedObjectId, false); + } + return status; + } + + return SAI_STATUS_SUCCESS; +} + +sai_status_t SwitchVpp::removeMplsRoute( + _In_ const std::string &serializedObjectId) +{ + SWSS_LOG_ENTER(); + + auto inseg_obj = get_sai_object(SAI_OBJECT_TYPE_INSEG_ENTRY, serializedObjectId); + if (inseg_obj && is_ip_nbr_active() == true) { + CHECK_STATUS(MplsRouteAddRemove(inseg_obj.get(), serializedObjectId, false)); + } + + CHECK_STATUS(remove_internal(SAI_OBJECT_TYPE_INSEG_ENTRY, serializedObjectId)); + + return SAI_STATUS_SUCCESS; +} diff --git a/vslib/vpp/SwitchVppNexthop.cpp b/vslib/vpp/SwitchVppNexthop.cpp index f6aaac7d83..5ab5286900 100644 --- a/vslib/vpp/SwitchVppNexthop.cpp +++ b/vslib/vpp/SwitchVppNexthop.cpp @@ -174,7 +174,8 @@ SwitchVpp::fillNHGrpMember(nexthop_grp_member_t *nxt_grp_member, sai_object_id_t attr.id = SAI_NEXT_HOP_ATTR_TYPE; CHECK_STATUS_QUIET(nh_obj->get_mandatory_attr(attr)); int32_t next_hop_type = attr.value.s32; - if (next_hop_type != SAI_NEXT_HOP_TYPE_IP && next_hop_type != SAI_NEXT_HOP_TYPE_TUNNEL_ENCAP) { + if (next_hop_type != SAI_NEXT_HOP_TYPE_IP && next_hop_type != SAI_NEXT_HOP_TYPE_TUNNEL_ENCAP && + next_hop_type != SAI_NEXT_HOP_TYPE_MPLS) { return SAI_STATUS_NOT_IMPLEMENTED; } @@ -187,13 +188,66 @@ SwitchVpp::fillNHGrpMember(nexthop_grp_member_t *nxt_grp_member, sai_object_id_t nxt_grp_member->weight = next_hop_weight; nxt_grp_member->seq_id = next_hop_sequence; nxt_grp_member->sw_if_index = ~0; + nxt_grp_member->n_labels = 0; + + bool have_rif = false; switch (next_hop_type) { + case SAI_NEXT_HOP_TYPE_MPLS: case SAI_NEXT_HOP_TYPE_IP: attr.id = SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID; - if (get(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_oid, 1, &attr) == SAI_STATUS_SUCCESS) { + have_rif = (get(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_oid, 1, &attr) == SAI_STATUS_SUCCESS); + if (have_rif) { nxt_grp_member->rif_oid = attr.value.oid; } + if (next_hop_type == SAI_NEXT_HOP_TYPE_MPLS) { + /* + * Read the imposed (push) label stack. LABELSTACK is a list + * attribute, so the output buffer must be supplied before the get. + */ + uint32_t lbuf[VPP_MPLS_MAX_LABELS]; + attr.id = SAI_NEXT_HOP_ATTR_LABELSTACK; + attr.value.u32list.count = VPP_MPLS_MAX_LABELS; + attr.value.u32list.list = lbuf; + + sai_status_t label_status = get(SAI_OBJECT_TYPE_NEXT_HOP, next_hop_oid, 1, &attr); + + /* + * A stack deeper than the buffer yields SAI_STATUS_BUFFER_OVERFLOW + * with count set to the required size and nothing copied. Fail + * explicitly rather than programming a bare IP nexthop, which would + * silently drop the imposed labels. + */ + if (label_status == SAI_STATUS_BUFFER_OVERFLOW) { + SWSS_LOG_ERROR("MPLS out-label stack of %u labels exceeds maximum %u", + attr.value.u32list.count, VPP_MPLS_MAX_LABELS); + return SAI_STATUS_NOT_SUPPORTED; + } + + if (label_status == SAI_STATUS_SUCCESS && attr.value.u32list.count > 0) { + uint32_t cnt = attr.value.u32list.count; + nxt_grp_member->n_labels = (uint8_t)cnt; + for (uint32_t li = 0; li < cnt; li++) { + nxt_grp_member->label_stack[li] = attr.value.u32list.list[li]; + } + } + /* + * Program an attached path (resolve the router interface to its VPP + * egress hwif) so VPP actually imposes the label; a recursive + * labelled path is dropped at the MPLS DROP DPO. + */ + std::string mpls_hwif; + sai_attribute_t port_attr; + port_attr.id = SAI_ROUTER_INTERFACE_ATTR_PORT_ID; + if (have_rif && + get(SAI_OBJECT_TYPE_ROUTER_INTERFACE, nxt_grp_member->rif_oid, 1, &port_attr) == SAI_STATUS_SUCCESS && + vpp_get_hwif_name(port_attr.value.oid, 0, mpls_hwif)) { + int idx = get_sw_if_idx(mpls_hwif.c_str()); + if (idx >= 0) { + nxt_grp_member->sw_if_index = (uint32_t)idx; + } + } + } break; case SAI_NEXT_HOP_TYPE_TUNNEL_ENCAP: { u_int32_t sw_if_index; diff --git a/vslib/vpp/SwitchVppNexthop.h b/vslib/vpp/SwitchVppNexthop.h index aa6cbc2302..03857478b1 100644 --- a/vslib/vpp/SwitchVppNexthop.h +++ b/vslib/vpp/SwitchVppNexthop.h @@ -1,5 +1,7 @@ #pragma once +#include "vppxlate/SaiVppXlate.h" + #ifdef __cplusplus extern "C" { #endif @@ -10,6 +12,8 @@ typedef struct nexthop_grp_member_ { uint32_t weight; uint32_t seq_id; uint32_t sw_if_index; + uint8_t n_labels; + uint32_t label_stack[VPP_MPLS_MAX_LABELS]; } nexthop_grp_member_t; typedef struct nexthop_grp_config_ { diff --git a/vslib/vpp/SwitchVppRif.cpp b/vslib/vpp/SwitchVppRif.cpp index 454d557c5a..1ef577774c 100644 --- a/vslib/vpp/SwitchVppRif.cpp +++ b/vslib/vpp/SwitchVppRif.cpp @@ -1812,6 +1812,28 @@ sai_status_t SwitchVpp::vpp_create_router_interface( vpp_set_interface_mtu(obj_id, vlan_id, attr_type_mtu->value.u32); } + auto attr_type_mpls = sai_metadata_get_attr_by_id(SAI_ROUTER_INTERFACE_ATTR_ADMIN_MPLS_STATE, attr_count, attr_list); + + if (attr_type_mpls != NULL) + { + std::string mpls_hwif_name; + if (vpp_get_hwif_name(obj_id, vlan_id, mpls_hwif_name)) + { + CHECK_STATUS(ensureMplsTable()); + int mpls_ret = sw_interface_set_mpls_enable(mpls_hwif_name.c_str(), attr_type_mpls->value.booldata); + if (mpls_ret != 0) + { + SWSS_LOG_ERROR("Failed to %s MPLS on router interface %s: %d", + attr_type_mpls->value.booldata ? "enable" : "disable", + mpls_hwif_name.c_str(), mpls_ret); + return SAI_STATUS_FAILURE; + } + SWSS_LOG_NOTICE("MPLS %s on router interface %s", + attr_type_mpls->value.booldata ? "enabled" : "disabled", + mpls_hwif_name.c_str()); + } + } + bool v4_is_up = false, v6_is_up = false; auto attr_type_v4 = sai_metadata_get_attr_by_id(SAI_ROUTER_INTERFACE_ATTR_ADMIN_V4_STATE, attr_count, attr_list); @@ -1909,6 +1931,28 @@ sai_status_t SwitchVpp::vpp_update_router_interface( vpp_set_interface_mtu(obj_id, vlan_id, attr_type_mtu->value.u32); } + auto attr_type_mpls = sai_metadata_get_attr_by_id(SAI_ROUTER_INTERFACE_ATTR_ADMIN_MPLS_STATE, attr_count, attr_list); + + if (attr_type_mpls != NULL) + { + std::string mpls_hwif_name; + if (vpp_get_hwif_name(obj_id, vlan_id, mpls_hwif_name)) + { + CHECK_STATUS(ensureMplsTable()); + int mpls_ret = sw_interface_set_mpls_enable(mpls_hwif_name.c_str(), attr_type_mpls->value.booldata); + if (mpls_ret != 0) + { + SWSS_LOG_ERROR("Failed to %s MPLS on router interface %s: %d", + attr_type_mpls->value.booldata ? "enable" : "disable", + mpls_hwif_name.c_str(), mpls_ret); + return SAI_STATUS_FAILURE; + } + SWSS_LOG_NOTICE("MPLS %s on router interface %s", + attr_type_mpls->value.booldata ? "enabled" : "disabled", + mpls_hwif_name.c_str()); + } + } + bool v4_is_up = false, v6_is_up = false; auto attr_type_v4 = sai_metadata_get_attr_by_id(SAI_ROUTER_INTERFACE_ATTR_ADMIN_V4_STATE, attr_count, attr_list); diff --git a/vslib/vpp/SwitchVppRoute.cpp b/vslib/vpp/SwitchVppRoute.cpp index a33e6eedc9..ed6139c5ac 100644 --- a/vslib/vpp/SwitchVppRoute.cpp +++ b/vslib/vpp/SwitchVppRoute.cpp @@ -101,6 +101,10 @@ void create_vpp_nexthop_entry ( vpp_nexthop->sw_if_index = nxt_grp_member->sw_if_index; vpp_nexthop->weight = (uint8_t) nxt_grp_member->weight; vpp_nexthop->preference = 0; + vpp_nexthop->n_labels = nxt_grp_member->n_labels; + for (uint8_t li = 0; li < nxt_grp_member->n_labels && li < VPP_MPLS_MAX_LABELS; li++) { + vpp_nexthop->label_stack[li] = nxt_grp_member->label_stack[li]; + } } const char* SwitchVpp::resolveNexthopMemberHwif( diff --git a/vslib/vpp/vppxlate/SaiVppXlate.c b/vslib/vpp/vppxlate/SaiVppXlate.c index e1146dff56..7ff70c82aa 100644 --- a/vslib/vpp/vppxlate/SaiVppXlate.c +++ b/vslib/vpp/vppxlate/SaiVppXlate.c @@ -73,6 +73,8 @@ #include #include +#include +#include #include #include @@ -209,6 +211,24 @@ #include #undef vl_api_version +/* MPLS API inclusion */ + +#define vl_typedefs +#include +#undef vl_typedefs + +#define vl_endianfun +#include +#undef vl_endianfun + +#define vl_calcsizefun +#include +#undef vl_calcsizefun + +#define vl_api_version(n, v) static u32 mpls_api_version = v; +#include +#undef vl_api_version + /* ipv4 API inclusion */ #define vl_typedefs @@ -1775,6 +1795,7 @@ static u16 l2_msg_id_base, vxlan_msg_id_base, ipip_msg_id_base; static u16 tunterm_msg_id_base; static u16 bfd_msg_id_base; static u16 sr_msg_id_base; +static u16 mpls_msg_id_base; static u16 bond_msg_id_base; static u16 span_msg_id_base; static u16 classify_msg_id_base; @@ -1951,6 +1972,26 @@ vl_api_acl_interface_add_del_reply_t_handler(vl_api_acl_interface_add_del_reply_ set_reply_status(retval); } +static void +vl_api_sw_interface_set_mpls_enable_reply_t_handler (vl_api_sw_interface_set_mpls_enable_reply_t *msg) +{ + int retval = (int)ntohl((uint32_t)msg->retval); + set_reply_status(retval); +} + +static void +vl_api_mpls_table_add_del_reply_t_handler (vl_api_mpls_table_add_del_reply_t *msg) +{ + int retval = (int)ntohl((uint32_t)msg->retval); + set_reply_status(retval); +} + +static void +vl_api_mpls_route_add_del_reply_t_handler (vl_api_mpls_route_add_del_reply_t *msg) +{ + int retval = (int)ntohl((uint32_t)msg->retval); + set_reply_status(retval); +} #define LCP_MSG_ID(id) \ (VL_API_##id + lcp_msg_id_base) @@ -1970,6 +2011,9 @@ vl_api_acl_interface_add_del_reply_t_handler(vl_api_acl_interface_add_del_reply_ #define SR_MSG_ID(id) \ (VL_API_##id + sr_msg_id_base) +#define MPLS_MSG_ID(id) \ + (VL_API_##id + mpls_msg_id_base) + #define foreach_vpe_plugin_api_reply_msg \ _(LCP_MSG_ID(LCP_ITF_PAIR_ADD_DEL_REPLY), lcp_itf_pair_add_del_reply) \ _(LCP_MSG_ID(LCP_ETHERTYPE_ENABLE_REPLY), lcp_ethertype_enable_reply) \ @@ -1989,7 +2033,10 @@ vl_api_acl_interface_add_del_reply_t_handler(vl_api_acl_interface_add_del_reply_ _(SFLOW_MSG_ID(SFLOW_ENABLE_DISABLE_REPLY), sflow_enable_disable_reply) \ _(SFLOW_MSG_ID(SFLOW_SAMPLING_RATE_SET_REPLY), sflow_sampling_rate_set_reply) \ _(IPIP_MSG_ID(IPIP_ADD_TUNNEL_REPLY), ipip_add_tunnel_reply) \ - _(IPIP_MSG_ID(IPIP_DEL_TUNNEL_REPLY), ipip_del_tunnel_reply) + _(IPIP_MSG_ID(IPIP_DEL_TUNNEL_REPLY), ipip_del_tunnel_reply) \ + _(MPLS_MSG_ID(SW_INTERFACE_SET_MPLS_ENABLE_REPLY), sw_interface_set_mpls_enable_reply) \ + _(MPLS_MSG_ID(MPLS_TABLE_ADD_DEL_REPLY), mpls_table_add_del_reply) \ + _(MPLS_MSG_ID(MPLS_ROUTE_ADD_DEL_REPLY), mpls_route_add_del_reply) static void vpp_plugin_vpe_init(void) { @@ -2047,6 +2094,10 @@ static void get_base_msg_id() msg_base_lookup_name = format (0, "sr_%08x%c", sr_api_version, 0); sr_msg_id_base = vl_client_get_first_plugin_msg_id ((char *) msg_base_lookup_name); assert(sr_msg_id_base != (u16) ~0); + msg_base_lookup_name = format (0, "mpls_%08x%c", mpls_api_version, 0); + mpls_msg_id_base = vl_client_get_first_plugin_msg_id ((char *) msg_base_lookup_name); + assert(mpls_msg_id_base != (u16) ~0); + memclnt_msg_id_base = 0; @@ -2874,6 +2925,172 @@ int ip6_nbr_add_del (const char *hwif_name, uint32_t sw_if_index, struct sockadd return ip_nbr_add_del(hwif_name, sw_if_index, (struct sockaddr *) addr, is_static, no_fib_entry, mac, is_add); } +int sw_interface_set_mpls_enable (const char *hwif_name, bool enable) +{ + vat_main_t *vam = &vat_main; + vl_api_sw_interface_set_mpls_enable_t *mp; + u32 sw_if_index; + int ret; + + VPP_LOCK(); + + sw_if_index = get_swif_idx(vam, hwif_name); + if (sw_if_index == (u32) -1) { + SAIVPP_ERROR("%s: unknown interface %s", __func__, hwif_name); + VPP_UNLOCK(); + return -EINVAL; + } + + __plugin_msg_base = mpls_msg_id_base; + + M (SW_INTERFACE_SET_MPLS_ENABLE, mp); + mp->sw_if_index = htonl(sw_if_index); + mp->enable = enable; + + S (mp); + WR (ret); + + ret = vpp_normalize_ret(ret, false, __func__); + + if (ret) { SAIVPP_ERROR("%s failed(%d) intf %s enable %d", __func__, ret, hwif_name, enable); } + else { SAIVPP_INFO("%s intf %s enable %d", __func__, hwif_name, enable); } + + VPP_UNLOCK(); + + return ret; +} + +int mpls_table_add_del (uint32_t table_id, bool is_add) +{ + vat_main_t *vam = &vat_main; + vl_api_mpls_table_add_del_t *mp; + int ret; + + VPP_LOCK(); + + __plugin_msg_base = mpls_msg_id_base; + + M (MPLS_TABLE_ADD_DEL, mp); + mp->mt_is_add = is_add; + mp->mt_table.mt_table_id = htonl(table_id); + + S (mp); + WR (ret); + + ret = vpp_normalize_ret(ret, !is_add, __func__); + + if (ret) { SAIVPP_ERROR("%s failed(%d) table %u is_add %d", __func__, ret, table_id, is_add); } + else { SAIVPP_INFO("%s table %u is_add %d", __func__, table_id, is_add); } + + VPP_UNLOCK(); + + return ret; +} + +int mpls_route_add_del (vpp_mpls_route_t *route, bool is_add) +{ + u32 idx, path_count; + vat_main_t *vam = &vat_main; + vl_api_mpls_route_t *mr; + vl_api_mpls_route_add_del_t *mp; + int ret; + + VPP_LOCK(); + + path_count = route->nexthop_cnt; + + /* + * Validate every path before allocating the API message: the message is + * only freed once it is sent, so an early return afterwards would leak it. + */ + for (unsigned int i = 0; i < path_count; i++) { + if (route->nexthop[i].addr.sa_family != AF_INET && + route->nexthop[i].addr.sa_family != AF_INET6) { + VPP_UNLOCK(); + return -EINVAL; + } + if (route->nexthop[i].n_labels > VPP_MPLS_MAX_LABELS) { + VPP_UNLOCK(); + return -EINVAL; + } + } + + __plugin_msg_base = mpls_msg_id_base; + + M22 (MPLS_ROUTE_ADD_DEL, mp, sizeof (vl_api_fib_path_t) * path_count); + mr = &mp->mr_route; + + mr->mr_table_id = htonl(route->table_id); + mr->mr_label = htonl(route->label); + mr->mr_eos = route->eos; + mr->mr_eos_proto = (u8)((route->eos_proto_af == AF_INET6) ? + FIB_API_PATH_NH_PROTO_IP6 : FIB_API_PATH_NH_PROTO_IP4); + mr->mr_is_multicast = false; + mr->mr_n_paths = (u8)path_count; + + for (unsigned int i = 0; i < path_count; i++) { + vpp_mpls_nexthop_t *nexthop = &route->nexthop[i]; + vl_api_fib_path_t *fib_path = &mr->mr_paths[i]; + vl_api_address_union_t *nh_addr = &fib_path->nh.address; + vpp_ip_addr_t *addr = &nexthop->addr; + + memset(fib_path, 0, sizeof(*fib_path)); + + if (nexthop->sw_if_index != (u32) -1) { + fib_path->sw_if_index = htonl(nexthop->sw_if_index); + } else if (nexthop->hwif_name) { + idx = get_swif_idx(vam, nexthop->hwif_name); + fib_path->sw_if_index = htonl(idx != (u32) -1 ? idx : (uint32_t)~0); + } else { + fib_path->sw_if_index = htonl((uint32_t)~0); + } + + if (addr->sa_family == AF_INET) { + struct sockaddr_in *ip4 = &addr->addr.ip4; + memcpy(nh_addr->ip4, &ip4->sin_addr.s_addr, sizeof(nh_addr->ip4)); + fib_path->proto = htonl(FIB_API_PATH_NH_PROTO_IP4); + } else if (addr->sa_family == AF_INET6) { + struct sockaddr_in6 *ip6 = &addr->addr.ip6; + memcpy(nh_addr->ip6, &ip6->sin6_addr.s6_addr, sizeof(nh_addr->ip6)); + fib_path->proto = htonl(FIB_API_PATH_NH_PROTO_IP6); + } + + if (nexthop->type == VPP_NEXTHOP_LOCAL) { + fib_path->type = htonl(FIB_API_PATH_TYPE_LOCAL); + } else { + fib_path->type = htonl(FIB_API_PATH_TYPE_NORMAL); + } + + fib_path->table_id = 0; + fib_path->rpf_id = htonl((uint32_t)~0); + fib_path->weight = nexthop->weight; + fib_path->preference = nexthop->preference; + + fib_path->n_labels = nexthop->n_labels; + for (uint8_t l = 0; l < nexthop->n_labels && l < VPP_MPLS_MAX_LABELS; l++) { + fib_path->label_stack[l].label = htonl(nexthop->label_stack[l].label); + fib_path->label_stack[l].ttl = nexthop->label_stack[l].ttl; + fib_path->label_stack[l].exp = nexthop->label_stack[l].exp; + fib_path->label_stack[l].is_uniform = nexthop->label_stack[l].is_uniform; + } + } + + mp->mr_is_add = is_add; + mp->mr_is_multipath = route->is_multipath; + + S (mp); + WR (ret); + + ret = vpp_normalize_ret(ret, !is_add, __func__); + + if (ret) { SAIVPP_ERROR("%s failed(%d) label %u eos %u is_add %d", __func__, ret, route->label, route->eos, is_add); } + else { SAIVPP_INFO("%s label %u eos %u paths %u is_add %d", __func__, route->label, route->eos, path_count, is_add); } + + VPP_UNLOCK(); + + return ret; +} + int ip_route_add_del_get_stats (vpp_ip_route_t *prefix, bool is_add, uint32_t *stats_index) { u32 idx, path_count = 1; @@ -2974,7 +3191,20 @@ int ip_route_add_del_get_stats (vpp_ip_route_t *prefix, bool is_add, uint32_t *s fib_path->rpf_id = htonl((uint32_t)~0); fib_path->weight = nexthop->weight; fib_path->preference = nexthop->preference; - fib_path->n_labels = 0; + /* + * Clamp before assigning: only VPP_MPLS_MAX_LABELS entries are ever + * populated below, so an unclamped n_labels would tell VPP the message + * carries more labels than it actually does. + */ + uint8_t n_labels = nexthop->n_labels > VPP_MPLS_MAX_LABELS ? + VPP_MPLS_MAX_LABELS : nexthop->n_labels; + fib_path->n_labels = n_labels; + for (uint8_t l = 0; l < n_labels; l++) { + fib_path->label_stack[l].label = htonl(nexthop->label_stack[l]); + fib_path->label_stack[l].ttl = MPLS_DEFAULT_OUT_TTL; + fib_path->label_stack[l].exp = 0; + fib_path->label_stack[l].is_uniform = 1; + } } ip_route->table_id = htonl(prefix->vrf_id); diff --git a/vslib/vpp/vppxlate/SaiVppXlate.h b/vslib/vpp/vppxlate/SaiVppXlate.h index c52e9a178d..7d56a3d7c7 100644 --- a/vslib/vpp/vppxlate/SaiVppXlate.h +++ b/vslib/vpp/vppxlate/SaiVppXlate.h @@ -27,7 +27,12 @@ extern "C" { VPP_NEXTHOP_LOCAL = 2 } vpp_nexthop_type_e; - typedef struct vpp_ip_addr_ { + /* Maximum MPLS label stack depth carried on a single fib path (VPP API limit). */ +#define VPP_MPLS_MAX_LABELS 16 + /* Default TTL for an imposed label; SAI carries no per-outsegment TTL. */ +#define MPLS_DEFAULT_OUT_TTL 64 + +typedef struct vpp_ip_addr_ { int sa_family; union { struct sockaddr_in ip4; @@ -43,6 +48,14 @@ extern "C" { uint8_t preference; vpp_nexthop_type_e type; uint32_t flags; + uint8_t n_labels; + /* + * Labels imposed on an IP route. Only the label value is carried here: + * the TTL of an imposed label on an IP path is derived from the IP + * header (uniform mode), so unlike vpp_mpls_nexthop_t below there is no + * per-label ttl/exp to express. + */ + uint32_t label_stack[VPP_MPLS_MAX_LABELS]; } vpp_ip_nexthop_t; typedef struct vpp_ip_route_ { @@ -180,6 +193,35 @@ extern "C" { vpp_prefix_t prefix; } vpp_sr_steer_t; + typedef struct vpp_mpls_label_ { + uint32_t label; + uint8_t ttl; + uint8_t exp; + uint8_t is_uniform; + } vpp_mpls_label_t; + + typedef struct vpp_mpls_nexthop_ { + vpp_ip_addr_t addr; + uint32_t sw_if_index; + const char *hwif_name; + uint8_t weight; + uint8_t preference; + vpp_nexthop_type_e type; + uint8_t n_labels; + vpp_mpls_label_t label_stack[VPP_MPLS_MAX_LABELS]; + } vpp_mpls_nexthop_t; + + typedef struct vpp_mpls_route_ { + uint32_t table_id; + uint32_t label; + uint8_t eos; + int eos_proto_af; /* AF_INET / AF_INET6 for post-pop lookup proto */ + bool is_multipath; + unsigned int nexthop_cnt; + vpp_mpls_nexthop_t nexthop[0]; + } vpp_mpls_route_t; + + typedef struct vpp_event_info_ { struct vpp_event_info_ *next; vpp_event_type_e type; @@ -430,6 +472,9 @@ typedef enum { bool is_input); extern int vpp_add_node_next(const char *node_name, const char *next_name, uint32_t *next_index); + extern int sw_interface_set_mpls_enable(const char *hwif_name, bool enable); + extern int mpls_table_add_del(uint32_t table_id, bool is_add); + extern int mpls_route_add_del(vpp_mpls_route_t *route, bool is_add); #ifdef __cplusplus } #endif