From 98225dcb74b5075e2b6e8d77b77617951d9c1b15 Mon Sep 17 00:00:00 2001 From: Carmine Scarpitta Date: Fri, 15 Mar 2024 12:53:02 -0500 Subject: [PATCH] [fpmsyncd]: Add support for SRv6 * Extend fpmsyncd to process SRv6 routes and local SIDs received from FRR * Add test cases to verify SRv6 functionality Signed-off-by: Carmine Scarpitta --- fpmsyncd/fpmlink.cpp | 5 + fpmsyncd/fpmlink.h | 3 + fpmsyncd/routesync.cpp | 881 +++++++++++++++++- fpmsyncd/routesync.h | 31 +- tests/mock_tests/Makefile.am | 7 +- .../fpmsyncd/receive_srv6_localsids_ut.cpp | 261 ++++++ .../fpmsyncd/receive_srv6_steer_routes_ut.cpp | 130 +++ .../fpmsyncd/ut_helpers_fpmsyncd.cpp | 359 +++++++ .../mock_tests/fpmsyncd/ut_helpers_fpmsyncd.h | 109 +++ tests/test_srv6.py | 585 ++++++++++++ 10 files changed, 2366 insertions(+), 5 deletions(-) create mode 100644 tests/mock_tests/fpmsyncd/receive_srv6_localsids_ut.cpp create mode 100644 tests/mock_tests/fpmsyncd/receive_srv6_steer_routes_ut.cpp create mode 100644 tests/mock_tests/fpmsyncd/ut_helpers_fpmsyncd.cpp create mode 100644 tests/mock_tests/fpmsyncd/ut_helpers_fpmsyncd.h diff --git a/fpmsyncd/fpmlink.cpp b/fpmsyncd/fpmlink.cpp index 13d170a805..1ed888d292 100644 --- a/fpmsyncd/fpmlink.cpp +++ b/fpmsyncd/fpmlink.cpp @@ -43,6 +43,11 @@ bool FpmLink::isRawProcessing(struct nlmsghdr *h) rtm = (struct rtmsg *)NLMSG_DATA(h); + if (h->nlmsg_type == RTM_NEWSRV6LOCALSID || h->nlmsg_type == RTM_DELSRV6LOCALSID) + { + return true; + } + if (h->nlmsg_type != RTM_NEWROUTE && h->nlmsg_type != RTM_DELROUTE) { return false; diff --git a/fpmsyncd/fpmlink.h b/fpmsyncd/fpmlink.h index c025750edf..dffa823e90 100644 --- a/fpmsyncd/fpmlink.h +++ b/fpmsyncd/fpmlink.h @@ -15,6 +15,9 @@ #include "fpmsyncd/fpminterface.h" #include "fpmsyncd/routesync.h" +#define RTM_NEWSRV6LOCALSID 1000 +#define RTM_DELSRV6LOCALSID 1001 + namespace swss { class FpmLink : public FpmInterface { diff --git a/fpmsyncd/routesync.cpp b/fpmsyncd/routesync.cpp index 0f6ee41188..6158d58e04 100644 --- a/fpmsyncd/routesync.cpp +++ b/fpmsyncd/routesync.cpp @@ -23,6 +23,7 @@ using namespace swss; #define MGMT_VRF_PREFIX "mgmt" #define NHG_DELIMITER ',' +#define MY_SID_KEY_DELIMITER ':' #ifndef ETH_ALEN #define ETH_ALEN 6 @@ -37,6 +38,10 @@ using namespace swss; #define VXLAN_RMAC 1 #define NH_ENCAP_VXLAN 100 +#define NH_ENCAP_SRV6_ROUTE 101 + +#define RTM_NEWSRV6LOCALSID 1000 +#define RTM_DELSRV6LOCALSID 1001 #define IPV4_MAX_BYTE 4 #define IPV6_MAX_BYTE 16 @@ -45,6 +50,65 @@ using namespace swss; #define ETHER_ADDR_STRLEN (3*ETH_ALEN) +#define DEFAULT_SRV6_LOCALSID_BLOCK_LEN "32" +#define DEFAULT_SRV6_LOCALSID_NODE_LEN "16" +#define DEFAULT_SRV6_LOCALSID_FUNC_LEN "16" +#define DEFAULT_SRV6_LOCALSID_ARG_LEN "0" + +enum srv6_localsid_action { + SRV6_LOCALSID_ACTION_UNSPEC = 0, + SRV6_LOCALSID_ACTION_END = 1, + SRV6_LOCALSID_ACTION_END_X = 2, + SRV6_LOCALSID_ACTION_END_T = 3, + SRV6_LOCALSID_ACTION_END_DX2 = 4, + SRV6_LOCALSID_ACTION_END_DX6 = 5, + SRV6_LOCALSID_ACTION_END_DX4 = 6, + SRV6_LOCALSID_ACTION_END_DT6 = 7, + SRV6_LOCALSID_ACTION_END_DT4 = 8, + SRV6_LOCALSID_ACTION_END_DT46 = 9, + SRV6_LOCALSID_ACTION_B6_ENCAPS = 10, + SRV6_LOCALSID_ACTION_B6_ENCAPS_RED = 11, + SRV6_LOCALSID_ACTION_B6_INSERT = 12, + SRV6_LOCALSID_ACTION_B6_INSERT_RED = 13, + SRV6_LOCALSID_ACTION_UN = 14, + SRV6_LOCALSID_ACTION_UA = 15, + SRV6_LOCALSID_ACTION_UDX2 = 16, + SRV6_LOCALSID_ACTION_UDX6 = 17, + SRV6_LOCALSID_ACTION_UDX4 = 18, + SRV6_LOCALSID_ACTION_UDT6 = 19, + SRV6_LOCALSID_ACTION_UDT4 = 20, + SRV6_LOCALSID_ACTION_UDT46 = 21, +}; + +enum { + SRV6_LOCALSID_UNSPEC = 0, + SRV6_LOCALSID_SID_VALUE = 1, + SRV6_LOCALSID_FORMAT = 2, + SRV6_LOCALSID_ACTION = 3, + SRV6_LOCALSID_VRFNAME = 4, + SRV6_LOCALSID_NH6 = 5, + SRV6_LOCALSID_NH4 = 6, + SRV6_LOCALSID_IIF = 7, + SRV6_LOCALSID_OIF = 8, + SRV6_LOCALSID_BPF = 9, + SRV6_LOCALSID_SIDLIST = 10, + SRV6_LOCALSID_ENCAP_SRC_ADDR = 11, +}; + +enum { + SRV6_LOCALSID_FORMAT_UNSPEC = 0, + SRV6_LOCALSID_FORMAT_BLOCK_LEN = 1, + SRV6_LOCALSID_FORMAT_NODE_LEN = 2, + SRV6_LOCALSID_FORMAT_FUNC_LEN = 3, + SRV6_LOCALSID_FORMAT_ARG_LEN = 4, +}; + +enum { + ROUTE_ENCAP_SRV6_UNSPEC = 0, + ROUTE_ENCAP_SRV6_VPN_SID = 1, + ROUTE_ENCAP_SRV6_ENCAP_SRC_ADDR = 2, +}; + /* Returns name of the protocol passed number represents */ static string getProtocolString(int proto) { @@ -81,6 +145,8 @@ RouteSync::RouteSync(RedisPipeline *pipeline) : m_vnet_routeTable(pipeline, APP_VNET_RT_TABLE_NAME, true), m_vnet_tunnelTable(pipeline, APP_VNET_RT_TUNNEL_TABLE_NAME, true), m_warmStartHelper(pipeline, &m_routeTable, APP_ROUTE_TABLE_NAME, "bgp", "bgp"), + m_srv6LocalSidTable(pipeline, APP_SRV6_MY_SID_TABLE_NAME, true), + m_srv6SidListTable(pipeline, APP_SRV6_SID_LIST_TABLE_NAME, true), m_nl_sock(NULL), m_link_cache(NULL) { m_nl_sock = nl_socket_alloc(); @@ -144,6 +210,229 @@ void RouteSync::parseEncap(struct rtattr *tb, uint32_t &encap_value, string &rma return; } +/** + * @parseEncapSrv6SteerRoute() - Parses encapsulated SRv6 attributes + * @tb: Pointer to rtattr to look for nested items in. + * @vpn_sid: (output) VPN SID. + * @src_addr: (output) source address for SRv6 encapsulation + * + * Return: void. + */ +void RouteSync::parseEncapSrv6SteerRoute(struct rtattr *tb, string &vpn_sid, + string &src_addr) +{ + struct rtattr *tb_encap[256] = {}; + char vpn_sid_buf[MAX_ADDR_SIZE + 1] = {0}; + char src_addr_buf[MAX_ADDR_SIZE + 1] = {0}; + + parseRtAttrNested(tb_encap, 256, tb); + + if (tb_encap[ROUTE_ENCAP_SRV6_VPN_SID]) + { + vpn_sid += inet_ntop(AF_INET6, RTA_DATA(tb_encap[ROUTE_ENCAP_SRV6_VPN_SID]), + vpn_sid_buf, MAX_ADDR_SIZE); + } + + if (tb_encap[ROUTE_ENCAP_SRV6_ENCAP_SRC_ADDR]) + { + src_addr += + inet_ntop(AF_INET6, RTA_DATA(tb_encap[ROUTE_ENCAP_SRV6_ENCAP_SRC_ADDR]), + src_addr_buf, MAX_ADDR_SIZE); + } + + SWSS_LOG_INFO("Rx vpn_sid:%s src_addr:%s ", vpn_sid.c_str(), + src_addr.c_str()); + + return; +} + +const char *RouteSync::localSidAction2Str(uint32_t action) +{ + switch (action) + { + case SRV6_LOCALSID_ACTION_UNSPEC: + return "unspec"; + case SRV6_LOCALSID_ACTION_END: + return "end"; + case SRV6_LOCALSID_ACTION_END_X: + return "end.x"; + case SRV6_LOCALSID_ACTION_END_T: + return "end.t"; + case SRV6_LOCALSID_ACTION_END_DX6: + return "end.dx6"; + case SRV6_LOCALSID_ACTION_END_DX4: + return "end.dx4"; + case SRV6_LOCALSID_ACTION_END_DT6: + return "end.dt6"; + case SRV6_LOCALSID_ACTION_END_DT4: + return "end.dt4"; + case SRV6_LOCALSID_ACTION_END_DT46: + return "end.dt46"; + case SRV6_LOCALSID_ACTION_UN: + return "un"; + case SRV6_LOCALSID_ACTION_UA: + return "ua"; + case SRV6_LOCALSID_ACTION_UDX6: + return "udx6"; + case SRV6_LOCALSID_ACTION_UDX4: + return "udx4"; + case SRV6_LOCALSID_ACTION_UDT6: + return "udt6"; + case SRV6_LOCALSID_ACTION_UDT4: + return "udt4"; + case SRV6_LOCALSID_ACTION_UDT46: + return "udt46"; + default: + return "unknown"; + } +} + +/** + * @parseSrv6LocalSidFormat() - Parses srv6 localsid format + * @tb: Pointer to rtattr to look for nested items in. + * @block_len: (output) locator block length + * @node_len: (output) locator node length + * @func_len: (output) function length + * @arg_len: (output) argument length + * + * Return: true on success, false otherwise. + */ +bool RouteSync::parseSrv6LocalSidFormat(struct rtattr *tb, + string &block_len, + string &node_len, string &func_len, + string &arg_len) +{ + struct rtattr *tb_localsid_format[256] = {}; + uint8_t block_len_buf, node_len_buf, func_len_buf, arg_len_buf; + + parseRtAttrNested(tb_localsid_format, 4, tb); + + if (tb_localsid_format[SRV6_LOCALSID_FORMAT_BLOCK_LEN]) + { + block_len_buf = *(uint8_t *)RTA_DATA( + tb_localsid_format[SRV6_LOCALSID_FORMAT_BLOCK_LEN]); + block_len += to_string(block_len_buf); + } + else + { + SWSS_LOG_ERROR("Invalid Srv6 localsid format: missing mandatory attribute block_len"); + return false; + } + + if (tb_localsid_format[SRV6_LOCALSID_FORMAT_NODE_LEN]) + { + node_len_buf = *(uint8_t *)RTA_DATA( + tb_localsid_format[SRV6_LOCALSID_FORMAT_NODE_LEN]); + node_len += to_string(node_len_buf); + } + else + { + SWSS_LOG_ERROR("Invalid Srv6 localsid format: missing mandatory attribute node_len"); + return false; + } + + if (tb_localsid_format[SRV6_LOCALSID_FORMAT_FUNC_LEN]) + { + func_len_buf = *(uint8_t *)RTA_DATA( + tb_localsid_format[SRV6_LOCALSID_FORMAT_FUNC_LEN]); + func_len += to_string(func_len_buf); + } + else + { + SWSS_LOG_ERROR("Invalid Srv6 localsid format: missing mandatory attribute func_len"); + return false; + } + + if (tb_localsid_format[SRV6_LOCALSID_FORMAT_ARG_LEN]) + { + arg_len_buf = *(uint8_t *)RTA_DATA( + tb_localsid_format[SRV6_LOCALSID_FORMAT_ARG_LEN]); + arg_len += to_string(arg_len_buf); + } + else + { + /* arg_len is optional, by default arg_len is 0 */ + arg_len += DEFAULT_SRV6_LOCALSID_ARG_LEN; + } + + SWSS_LOG_INFO("Rx Srv6 localsid block_len:%s node_len:%s func_len:%s arg_len:%s", + block_len.c_str(), node_len.c_str(), func_len.c_str(), + arg_len.c_str()); + + return true; +} + +/** + * @parseSrv6LocalSid() - Parses sRv6 localsid attributes + * @tb: Pointer to rtattr to look for nested items in. + * @block_len: (output) locator block length + * @node_len: (output) locator node length + * @func_len: (output) function length + * @arg_len: (output) argument length + * @action: (output) behavior defined for the local SID. + * @vrf: (output) VRF name. + * @adj: (output) adjacency. + * + * Return: true on success, false otherwise. + */ +bool RouteSync::parseSrv6LocalSid(struct rtattr *tb[], string &block_len, + string &node_len, string &func_len, + string &arg_len, string &action, + string &vrf, string &adj) +{ + uint32_t action_buf = SRV6_LOCALSID_ACTION_UNSPEC; + char vrf_buf[IFNAMSIZ + 1] = {0}; + char adj_buf[MAX_ADDR_SIZE + 1] = {0}; + + if (tb[SRV6_LOCALSID_FORMAT]) + { + if (!parseSrv6LocalSidFormat(tb[SRV6_LOCALSID_FORMAT], block_len, + node_len, func_len, arg_len)) + { + SWSS_LOG_ERROR("Invalid Srv6 localsid format"); + return false; + } + } + + if (tb[SRV6_LOCALSID_ACTION]) + { + action_buf = *(uint32_t *)RTA_DATA(tb[SRV6_LOCALSID_ACTION]); + } + + if (tb[SRV6_LOCALSID_NH6]) + { + struct in6_addr *nh6 = + (struct in6_addr *)RTA_DATA(tb[SRV6_LOCALSID_NH6]); + + inet_ntop(AF_INET6, nh6, adj_buf, MAX_ADDR_SIZE); + } + + if (tb[SRV6_LOCALSID_NH4]) + { + struct in_addr *nh4 = + (struct in_addr *)RTA_DATA(tb[SRV6_LOCALSID_NH4]); + + inet_ntop(AF_INET, nh4, adj_buf, MAX_ADDR_SIZE); + } + + if (tb[SRV6_LOCALSID_VRFNAME]) + { + memcpy(vrf_buf, (char *)RTA_DATA(tb[SRV6_LOCALSID_VRFNAME]), + strlen((char *)RTA_DATA(tb[SRV6_LOCALSID_VRFNAME]))); + } + + action = localSidAction2Str(action_buf); + vrf = vrf_buf; + adj = adj_buf; + + SWSS_LOG_INFO("Rx block_len:%s node_len:%s func_len:%s arg_len:%s " + "action:%s vrf:%s adj:%s", + block_len.c_str(), node_len.c_str(), func_len.c_str(), + arg_len.c_str(), action.c_str(), vrf.c_str(), adj.c_str()); + + return true; +} + void RouteSync::getEvpnNextHopSep(string& nexthops, string& vni_list, string& mac_list, string& intf_list) { @@ -584,13 +873,576 @@ void RouteSync::onEvpnRouteMsg(struct nlmsghdr *h, int len) return; } +bool RouteSync::getSrv6SteerRouteNextHop(struct nlmsghdr *h, int received_bytes, + struct rtattr *tb[], string &vpn_sid, + string &src_addr) +{ + uint16_t encap = 0; + + if (!tb[RTA_MULTIPATH]) + { + if (tb[RTA_ENCAP_TYPE]) + { + encap = *(uint16_t *)RTA_DATA(tb[RTA_ENCAP_TYPE]); + } + + if (tb[RTA_ENCAP] && tb[RTA_ENCAP_TYPE] && + *(uint16_t *)RTA_DATA(tb[RTA_ENCAP_TYPE]) == + NH_ENCAP_SRV6_ROUTE) + { + parseEncapSrv6SteerRoute(tb[RTA_ENCAP], vpn_sid, src_addr); + } + SWSS_LOG_DEBUG("Rx MsgType:%d encap:%d vpn_sid:%s src_addr:%s", + h->nlmsg_type, encap, vpn_sid.c_str(), + src_addr.c_str()); + + if (vpn_sid.empty()) + { + return false; + } + } + else + { + /* This is a multipath route */ + SWSS_LOG_NOTICE("Multipath SRv6 routes aren't supported"); + return false; + } + + return true; +} + +void RouteSync::onSrv6SteerRouteMsg(struct nlmsghdr *h, int len) +{ + struct rtmsg *rtm; + struct rtattr *tb[RTA_MAX + 1]; + void *dest = NULL; + char dstaddr[IPV6_MAX_BYTE] = {0}; + int dst_len = 0; + char destipprefix[MAX_ADDR_SIZE + 1] = {0}; + char routeTableKey[IFNAMSIZ + MAX_ADDR_SIZE + 2] = {0}; + int nlmsg_type = h->nlmsg_type; + unsigned int vrf_index; + + rtm = (struct rtmsg *)NLMSG_DATA(h); + + /* Parse attributes and extract fields of interest. */ + memset(tb, 0, sizeof(tb)); + netlink_parse_rtattr(tb, RTA_MAX, RTM_RTA(rtm), len); + + if (!tb[RTA_DST]) + { + SWSS_LOG_ERROR( + "Received an invalid SRv6 route: missing RTA_DST attribute"); + return; + } + + dest = RTA_DATA(tb[RTA_DST]); + + if (rtm->rtm_family == AF_INET) + { + if (rtm->rtm_dst_len > IPV4_MAX_BITLEN) + { + SWSS_LOG_ERROR( + "Received an invalid SRv6 route: prefix len %d is out of range", + rtm->rtm_dst_len); + return; + } + memcpy(dstaddr, dest, IPV4_MAX_BYTE); + dst_len = rtm->rtm_dst_len; + } + else if (rtm->rtm_family == AF_INET6) + { + if (rtm->rtm_dst_len > IPV6_MAX_BITLEN) + { + SWSS_LOG_ERROR( + "Received an invalid SRv6 route: prefix len %d is out of range", + rtm->rtm_dst_len); + return; + } + memcpy(dstaddr, dest, IPV6_MAX_BYTE); + dst_len = rtm->rtm_dst_len; + } + else + { + SWSS_LOG_ERROR( + "Received an invalid SRv6 route: invalid address family %d", + rtm->rtm_family); + return; + } + + inet_ntop(rtm->rtm_family, dstaddr, destipprefix, MAX_ADDR_SIZE); + + SWSS_LOG_DEBUG("Rx MsgType:%d Family:%d Prefix:%s/%d", nlmsg_type, + rtm->rtm_family, destipprefix, dst_len); + + /* Table corresponding to route. */ + if (tb[RTA_TABLE]) + { + vrf_index = *(int *)RTA_DATA(tb[RTA_TABLE]); + } + else + { + vrf_index = rtm->rtm_table; + } + + if (vrf_index) + { + if (!getIfName(vrf_index, routeTableKey, IFNAMSIZ)) + { + SWSS_LOG_ERROR("Fail to get the VRF name (ifindex %u)", vrf_index); + return; + } + /* + * Now vrf device name is required to start with VRF_PREFIX + */ + if (memcmp(routeTableKey, VRF_PREFIX, strlen(VRF_PREFIX))) + { + SWSS_LOG_ERROR("Invalid VRF name %s (ifindex %u)", routeTableKey, + vrf_index); + return; + } + routeTableKey[strlen(routeTableKey)] = ':'; + } + + if ((rtm->rtm_family == AF_INET && dst_len == IPV4_MAX_BITLEN) || + (rtm->rtm_family == AF_INET6 && dst_len == IPV6_MAX_BITLEN)) + { + snprintf(routeTableKey + strlen(routeTableKey), + sizeof(routeTableKey) - strlen(routeTableKey), "%s", + destipprefix); + } + else + { + snprintf(routeTableKey + strlen(routeTableKey), + sizeof(routeTableKey) - strlen(routeTableKey), "%s/%u", + destipprefix, dst_len); + } + + SWSS_LOG_INFO("Received route message dest ip prefix: %s Op:%s", + destipprefix, nlmsg_type == RTM_NEWROUTE ? "add" : "del"); + + if (nlmsg_type != RTM_NEWROUTE && nlmsg_type != RTM_DELROUTE) + { + SWSS_LOG_ERROR("Unknown message-type: %d for %s", nlmsg_type, + destipprefix); + return; + } + + switch (rtm->rtm_type) + { + case RTN_BLACKHOLE: + case RTN_UNREACHABLE: + case RTN_PROHIBIT: + SWSS_LOG_ERROR( + "RTN_BLACKHOLE route not expected (%s)", destipprefix); + return; + case RTN_UNICAST: + break; + + case RTN_MULTICAST: + case RTN_BROADCAST: + case RTN_LOCAL: + SWSS_LOG_NOTICE( + "BUM routes aren't supported yet (%s)", destipprefix); + return; + + default: + return; + } + + /* Get nexthop lists */ + string vpn_sid_str; + string src_addr_str; + bool ret; + + ret = getSrv6SteerRouteNextHop(h, len, tb, vpn_sid_str, src_addr_str); + if (ret == false) + { + SWSS_LOG_NOTICE( + "SRv6 Route issue with RouteTable msg: %s vpn_sid:%s src_addr:%s", + destipprefix, vpn_sid_str.c_str(), src_addr_str.c_str()); + return; + } + + if (vpn_sid_str.empty()) + { + SWSS_LOG_NOTICE("SRv6 IP Prefix: %s vpn_sid is empty", destipprefix); + return; + } + + bool warmRestartInProgress = m_warmStartHelper.inProgress(); + + if (nlmsg_type == RTM_DELROUTE) + { + string srv6SidListTableKey = routeTableKey; + + if (!warmRestartInProgress) + { + m_routeTable.del(routeTableKey); + m_srv6SidListTable.del(srv6SidListTableKey); + return; + } + else + { + SWSS_LOG_INFO("Warm-Restart mode: Receiving delete msg: %s", + routeTableKey); + + vector fvVector; + const KeyOpFieldsValuesTuple kfv = std::make_tuple(routeTableKey, + DEL_COMMAND, + fvVector); + m_warmStartHelper.insertRefreshMap(kfv); + return; + } + } + else if (nlmsg_type == RTM_NEWROUTE) + { + /* Write SID list to SRV6_SID_LIST_TABLE */ + + string srv6SidListTableKey = routeTableKey; + + vector fvVectorSidList; + + FieldValueTuple path("path", vpn_sid_str); + fvVectorSidList.push_back(path); + + m_srv6SidListTable.set(srv6SidListTableKey, fvVectorSidList); + SWSS_LOG_DEBUG("Srv6SidListTable set msg: %s path: %s", + srv6SidListTableKey.c_str(), vpn_sid_str.c_str()); + + /* Write route to ROUTE_TABLE */ + + vector fvVectorRoute; + + FieldValueTuple vpn_sid("segment", srv6SidListTableKey); + fvVectorRoute.push_back(vpn_sid); + + if (!src_addr_str.empty()) + { + FieldValueTuple seg_src("seg_src", src_addr_str); + fvVectorRoute.push_back(seg_src); + } + if (!warmRestartInProgress) + { + m_routeTable.set(routeTableKey, fvVectorRoute); + SWSS_LOG_DEBUG("RouteTable set msg: %s vpn_sid: %s src_addr:%s", + routeTableKey, vpn_sid_str.c_str(), + src_addr_str.c_str()); + } + + /* + * During routing-stack restarting scenarios route-updates will be + * temporarily put on hold by warm-reboot logic. + */ + else + { + SWSS_LOG_INFO( + "Warm-Restart mode: RouteTable set msg: %s vpn_sid:%s src_addr:%s", + routeTableKey, vpn_sid_str.c_str(), src_addr_str.c_str()); + + const KeyOpFieldsValuesTuple kfv = + std::make_tuple(routeTableKey, SET_COMMAND, fvVectorRoute); + m_warmStartHelper.insertRefreshMap(kfv); + } + } + + return; +} + +void RouteSync::onSrv6LocalSidMsg(struct nlmsghdr *h, int len) +{ + struct rtmsg *rtm; + struct rtattr *tb[RTA_MAX + 1]; + void *sid_value_tmp = NULL; + char sid_value[IPV6_MAX_BYTE] = {0}; + char sid_value_str[MAX_ADDR_SIZE]; + int nlmsg_type = h->nlmsg_type; + + rtm = (struct rtmsg *)NLMSG_DATA(h); + + /* Parse attributes and extract fields of interest. */ + memset(tb, 0, sizeof(tb)); + netlink_parse_rtattr(tb, RTA_MAX, RTM_RTA(rtm), len); + + if (!tb[SRV6_LOCALSID_SID_VALUE]) + { + SWSS_LOG_ERROR( + "Received an invalid localsid route: missing SRV6_LOCALSID_SID_VALUE attribute"); + return; + } + + sid_value_tmp = RTA_DATA(tb[SRV6_LOCALSID_SID_VALUE]); + + /* + * Only AF_INET6 is allowed for mylocalsid routes + */ + if (rtm->rtm_family == AF_INET) + { + SWSS_LOG_ERROR( + "AF_INET address family is not allowed for localsid"); + return; + } + else if (rtm->rtm_family == AF_INET6) + { + if (rtm->rtm_dst_len > IPV6_MAX_BITLEN) + { + SWSS_LOG_ERROR("Received an invalid localsid: prefix len %d " + "is out of range", + rtm->rtm_dst_len); + return; + } + memcpy(sid_value, sid_value_tmp, IPV6_MAX_BYTE); + } + else + { + SWSS_LOG_ERROR( + "Received an invalid localsid route: invalid address family %d", + rtm->rtm_family); + return; + } + + inet_ntop(AF_INET6, sid_value, sid_value_str, MAX_ADDR_SIZE); + + SWSS_LOG_INFO("Rx MsgType:%d SidValue:%s", nlmsg_type, + sid_value_str); + + if (nlmsg_type != RTM_NEWSRV6LOCALSID && nlmsg_type != RTM_DELSRV6LOCALSID) + { + SWSS_LOG_ERROR("Unknown message-type: %d for %s", nlmsg_type, + sid_value_str); + return; + } + + /* Get nexthop lists */ + string block_len_str; + string node_len_str; + string func_len_str; + string arg_len_str; + string action_str; + string vrf_str; + string adj_str; + string my_sid_table_key; + + if (!parseSrv6LocalSid(tb, block_len_str, node_len_str, + func_len_str, arg_len_str, action_str, vrf_str, + adj_str)) + { + SWSS_LOG_ERROR("Invalid Srv6 localsid"); + return; + } + + if (block_len_str.empty()) + { + block_len_str = DEFAULT_SRV6_LOCALSID_BLOCK_LEN; + } + + if (node_len_str.empty()) + { + node_len_str = DEFAULT_SRV6_LOCALSID_NODE_LEN; + } + + if (func_len_str.empty()) + { + func_len_str = DEFAULT_SRV6_LOCALSID_FUNC_LEN; + } + + if (arg_len_str.empty()) + { + arg_len_str = DEFAULT_SRV6_LOCALSID_ARG_LEN; + } + + my_sid_table_key += block_len_str + MY_SID_KEY_DELIMITER; + my_sid_table_key += node_len_str + MY_SID_KEY_DELIMITER; + my_sid_table_key += func_len_str + MY_SID_KEY_DELIMITER; + my_sid_table_key += arg_len_str + MY_SID_KEY_DELIMITER; + my_sid_table_key += sid_value_str; + + if (nlmsg_type == RTM_DELSRV6LOCALSID) + { + m_srv6LocalSidTable.del(my_sid_table_key); + return; + } + + if (action_str.empty() || !(action_str.compare("unspec")) || + !(action_str.compare("unknown"))) + { + SWSS_LOG_NOTICE("Localsid IP Prefix: %s act is empty or invalid", + sid_value_str); + return; + } + + if (!(action_str.compare("end.dt6")) && vrf_str.empty()) + { + SWSS_LOG_NOTICE("Localsid End.DT6 IP Prefix: %s vrf is empty", + sid_value_str); + return; + } + + if (!(action_str.compare("end.dt4")) && vrf_str.empty()) + { + SWSS_LOG_NOTICE("Localsid End.DT4 IP Prefix: %s vrf is empty", + sid_value_str); + return; + } + + if (!(action_str.compare("end.dt46")) && vrf_str.empty()) + { + SWSS_LOG_NOTICE("Localsid End.DT46 IP Prefix: %s vrf is empty", + sid_value_str); + return; + } + + if (!(action_str.compare("udt6")) && vrf_str.empty()) + { + SWSS_LOG_NOTICE("Localsid uDT6 IP Prefix: %s vrf is empty", + sid_value_str); + return; + } + + if (!(action_str.compare("udt4")) && vrf_str.empty()) + { + SWSS_LOG_NOTICE("Localsid uDT4 IP Prefix: %s vrf is empty", + sid_value_str); + return; + } + + if (!(action_str.compare("udt46")) && vrf_str.empty()) + { + SWSS_LOG_NOTICE("Localsid uDT46 IP Prefix: %s vrf is empty", + sid_value_str); + return; + } + + if (!(action_str.compare("end.t")) && vrf_str.empty()) + { + SWSS_LOG_NOTICE("Localsid End.T IP Prefix: %s vrf is empty", + sid_value_str); + return; + } + + if (!(action_str.compare("end.x")) && adj_str.empty()) + { + SWSS_LOG_NOTICE("Localsid End.X IP Prefix: %s adj is empty", + sid_value_str); + return; + } + + if (!(action_str.compare("end.dx6")) && adj_str.empty()) + { + SWSS_LOG_NOTICE("Localsid End.DX6 IP Prefix: %s adj is empty", + sid_value_str); + return; + } + + if (!(action_str.compare("end.dx4")) && adj_str.empty()) + { + SWSS_LOG_NOTICE("Localsid End.DX4 IP Prefix: %s adj is empty", + sid_value_str); + return; + } + + vector fvVector; + FieldValueTuple act("action", action_str); + fvVector.push_back(act); + if (!vrf_str.empty()) + { + FieldValueTuple vrf("vrf", vrf_str); + fvVector.push_back(vrf); + } + if (!adj_str.empty()) + { + FieldValueTuple adj("adj", adj_str); + fvVector.push_back(adj); + } + + m_srv6LocalSidTable.set(my_sid_table_key, fvVector); + + return; +} + +uint16_t RouteSync::getEncapType(struct nlmsghdr *h) +{ + int len; + uint16_t encap_type = 0; + struct rtmsg *rtm; + struct rtattr *tb[RTA_MAX + 1]; + + rtm = (struct rtmsg *)NLMSG_DATA(h); + + if (h->nlmsg_type != RTM_NEWROUTE && h->nlmsg_type != RTM_DELROUTE) + { + return 0; + } + + len = (int)(h->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg))); + if (len < 0) + { + return 0; + } + + memset(tb, 0, sizeof(tb)); + netlink_parse_rtattr(tb, RTA_MAX, RTM_RTA(rtm), len); + + if (!tb[RTA_MULTIPATH]) + { + if (tb[RTA_ENCAP_TYPE]) + { + encap_type = *(short *)RTA_DATA(tb[RTA_ENCAP_TYPE]); + } + } + else + { + /* This is a multipath route */ + int len; + struct rtnexthop *rtnh = + (struct rtnexthop *)RTA_DATA(tb[RTA_MULTIPATH]); + len = (int)RTA_PAYLOAD(tb[RTA_MULTIPATH]); + struct rtattr *subtb[RTA_MAX + 1]; + + for (;;) + { + if (len < (int)sizeof(*rtnh) || rtnh->rtnh_len > len) + { + break; + } + + if (rtnh->rtnh_len > sizeof(*rtnh)) + { + memset(subtb, 0, sizeof(subtb)); + netlink_parse_rtattr(subtb, RTA_MAX, RTNH_DATA(rtnh), + (int)(rtnh->rtnh_len - sizeof(*rtnh))); + if (subtb[RTA_ENCAP_TYPE]) + { + encap_type = *(uint16_t *)RTA_DATA(subtb[RTA_ENCAP_TYPE]); + break; + } + } + + if (rtnh->rtnh_len == 0) + { + break; + } + + len -= NLMSG_ALIGN(rtnh->rtnh_len); + rtnh = RTNH_NEXT(rtnh); + } + } + + SWSS_LOG_INFO("Rx MsgType:%d Encap:%d", h->nlmsg_type, encap_type); + + return encap_type; +} + void RouteSync::onMsgRaw(struct nlmsghdr *h) { int len; if ((h->nlmsg_type != RTM_NEWROUTE) - && (h->nlmsg_type != RTM_DELROUTE)) + && (h->nlmsg_type != RTM_DELROUTE) + && (h->nlmsg_type != RTM_NEWSRV6LOCALSID) + && (h->nlmsg_type != RTM_DELSRV6LOCALSID)) return; + /* Length validity. */ len = (int)(h->nlmsg_len - NLMSG_LENGTH(sizeof(struct ndmsg))); if (len < 0) @@ -600,7 +1452,32 @@ void RouteSync::onMsgRaw(struct nlmsghdr *h) (size_t)NLMSG_LENGTH(sizeof(struct ndmsg))); return; } - onEvpnRouteMsg(h, len); + + if ((h->nlmsg_type == RTM_NEWSRV6LOCALSID) + || (h->nlmsg_type == RTM_DELSRV6LOCALSID)) + { + onSrv6LocalSidMsg(h, len); + return; + } + + switch (getEncapType(h)) + { + case NH_ENCAP_SRV6_ROUTE: + onSrv6SteerRouteMsg(h, len); + break; + default: + /* + * Currently only SRv6 route, SRv6 Local SID, and EVPN + * encapsulation types are supported. If the encapsulation + * type is not SRv6 route or SRv6 Local SID, we fall back + * to EVPN. The onEvpnRouteMsg() handler will verify that the + * route is actually an EVPN route. If it is not, this handler + * will reject the route. + */ + onEvpnRouteMsg(h, len); + break; + } + return; } diff --git a/fpmsyncd/routesync.h b/fpmsyncd/routesync.h index eb07eb8f15..1e91a3f431 100644 --- a/fpmsyncd/routesync.h +++ b/fpmsyncd/routesync.h @@ -74,7 +74,11 @@ class RouteSync : public NetMsg /* vnet route table */ ProducerStateTable m_vnet_routeTable; /* vnet vxlan tunnel table */ - ProducerStateTable m_vnet_tunnelTable; + ProducerStateTable m_vnet_tunnelTable; + /* srv6 local sid table */ + ProducerStateTable m_srv6LocalSidTable; + /* srv6 sid list table */ + ProducerStateTable m_srv6SidListTable; struct nl_cache *m_link_cache; struct nl_sock *m_nl_sock; @@ -89,6 +93,17 @@ class RouteSync : public NetMsg void parseEncap(struct rtattr *tb, uint32_t &encap_value, string &rmac); + void parseEncapSrv6SteerRoute(struct rtattr *tb, string &vpn_sid, string &src_addr); + + bool parseSrv6LocalSid(struct rtattr *tb[], string &block_len, + string &node_len, string &func_len, + string &arg_len, string &action, string &vrf, + string &adj); + + bool parseSrv6LocalSidFormat(struct rtattr *tb, string &block_len, + string &node_len, string &func_len, + string &arg_len); + void parseRtAttrNested(struct rtattr **tb, int max, struct rtattr *rta); @@ -98,6 +113,12 @@ class RouteSync : public NetMsg /* Handle prefix route */ void onEvpnRouteMsg(struct nlmsghdr *h, int len); + /* Handle routes containing an SRv6 nexthop */ + void onSrv6SteerRouteMsg(struct nlmsghdr *h, int len); + + /* Handle SRv6 Local SID */ + void onSrv6LocalSidMsg(struct nlmsghdr *h, int len); + /* Handle vnet route */ void onVnetRouteMsg(int nlmsg_type, struct nl_object *obj, string vnet); @@ -119,6 +140,9 @@ class RouteSync : public NetMsg string& nexthops, string& vni_list, string& mac_list, string& intf_list); + bool getSrv6SteerRouteNextHop(struct nlmsghdr *h, int received_bytes, + struct rtattr *tb[], string &vpn_sid, string &src_addr); + /* Get next hop list */ void getNextHopList(struct rtnl_route *route_obj, string& gw_list, string& mpls_list, string& intf_list); @@ -140,6 +164,11 @@ class RouteSync : public NetMsg /* Sends FPM message with RTM_F_OFFLOAD flag set for all routes in the table */ void sendOffloadReply(swss::DBConnector& db, const std::string& table); + + /* Get encap type */ + uint16_t getEncapType(struct nlmsghdr *h); + + const char *localSidAction2Str(uint32_t action); }; } diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index c3a305b1eb..2454ea5bbc 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -229,6 +229,9 @@ tests_teammgrd_LDADD = $(LDADD_GTEST) $(LDADD_SAI) -lnl-genl-3 -lhiredis -lhired tests_fpmsyncd_SOURCES = fpmsyncd/test_fpmlink.cpp \ fpmsyncd/test_routesync.cpp \ + fpmsyncd/receive_srv6_steer_routes_ut.cpp \ + fpmsyncd/receive_srv6_localsids_ut.cpp \ + fpmsyncd/ut_helpers_fpmsyncd.cpp \ fake_netlink.cpp \ fake_warmstarthelper.cpp \ fake_producerstatetable.cpp \ @@ -239,7 +242,8 @@ tests_fpmsyncd_SOURCES = fpmsyncd/test_fpmlink.cpp \ $(top_srcdir)/fpmsyncd/fpmlink.cpp \ $(top_srcdir)/fpmsyncd/routesync.cpp -tests_fpmsyncd_INCLUDES = $(tests_INCLUDES) -I$(top_srcdir)/tests_fpmsyncd -I$(top_srcdir)/lib -I$(top_srcdir)/warmrestart +tests_fpmsyncd_INCLUDES = $(tests_INCLUDES) -I$(top_srcdir)/tests_fpmsyncd -I$(top_srcdir)/lib -I$(top_srcdir)/warmrestart -I$(top_srcdir)/fpmsyncd +tests_fpmsyncd_CXXFLAGS = -Wl,-wrap,rtnl_link_i2name tests_fpmsyncd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) tests_fpmsyncd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) $(tests_fpmsyncd_INCLUDES) tests_fpmsyncd_LDADD = $(LDADD_GTEST) $(LDADD_SAI) -lnl-genl-3 -lhiredis -lhiredis \ @@ -261,4 +265,3 @@ tests_response_publisher_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CF tests_response_publisher_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(CFLAGS_SAI) $(tests_response_publisher_INCLUDES) tests_response_publisher_LDADD = $(LDADD_GTEST) $(LDADD_SAI) -lnl-genl-3 -lhiredis -lhiredis \ -lswsscommon -lswsscommon -lgtest -lgtest_main -lzmq -lnl-3 -lnl-route-3 -lpthread - diff --git a/tests/mock_tests/fpmsyncd/receive_srv6_localsids_ut.cpp b/tests/mock_tests/fpmsyncd/receive_srv6_localsids_ut.cpp new file mode 100644 index 0000000000..89fdb58930 --- /dev/null +++ b/tests/mock_tests/fpmsyncd/receive_srv6_localsids_ut.cpp @@ -0,0 +1,261 @@ +#include "ut_helpers_fpmsyncd.h" +#include "gtest/gtest.h" +#include +#include "mock_table.h" +#include +#include +#include +#include "ipaddress.h" + +#define private public // Need to modify internal cache +#include "fpmlink.h" +#include "routesync.h" +#undef private + +using namespace swss; +using namespace testing; + +/* +Test Fixture +*/ +namespace ut_fpmsyncd +{ + struct FpmSyncdSRv6LocalSIDsTest : public ::testing::Test + { + std::shared_ptr m_app_db; + std::shared_ptr pipeline; + std::shared_ptr m_routeSync; + std::shared_ptr m_fpmLink; + std::shared_ptr m_srv6LocalSidTable; + + virtual void SetUp() override + { + testing_db::reset(); + + m_app_db = std::make_shared("APPL_DB", 0); + + /* Construct dependencies */ + + /* 1) RouteSync */ + pipeline = std::make_shared(m_app_db.get()); + m_routeSync = std::make_shared(pipeline.get()); + + /* 2) FpmLink */ + m_fpmLink = std::make_shared(m_routeSync.get()); + + /* 3) SRV6_MY_SID_TABLE in APP_DB */ + m_srv6LocalSidTable = std::make_shared(m_app_db.get(), APP_SRV6_MY_SID_TABLE_NAME); + } + + virtual void TearDown() override + { + } + }; +} + +namespace ut_fpmsyncd +{ + /* Test Receiving a route containing an SRv6 Local SID nexthop bound to the End.DT4 behavior */ + TEST_F(FpmSyncdSRv6LocalSIDsTest, RecevingRouteWithSRv6LocalSIDEndDT4) + { + ASSERT_NE(m_routeSync, nullptr); + + /* Create a Netlink object containing an SRv6 Local SID */ + IpAddress _localsid = IpAddress("fc00:0:1:1::"); + uint8_t _block_len = 32; + uint8_t _node_len = 16; + uint8_t _func_len = 16; + uint8_t _arg_len = 0; + uint32_t _action = SRV6_LOCALSID_ACTION_END_DT4; + char *_vrf = "Vrf10"; + + struct nlmsg *nl_obj = create_srv6_localsid_nlmsg(RTM_NEWSRV6LOCALSID, &_localsid, _block_len, _node_len, _func_len, _arg_len, _action, _vrf); + if (!nl_obj) + printf("Error\n\n"); + + /* Send the Netlink object to the FpmLink */ + m_fpmLink->processRawMsg(&nl_obj->n); + + /* Check that fpmsyncd created the correct entries in APP_DB */ + std::string action; + ASSERT_EQ(m_srv6LocalSidTable->hget("32:16:16:0:fc00:0:1:1::", "action", action), true); + ASSERT_EQ(action, "end.dt4"); + + std::string vrf; + ASSERT_EQ(m_srv6LocalSidTable->hget("32:16:16:0:fc00:0:1:1::", "vrf", vrf), true); + ASSERT_EQ(vrf, "Vrf10"); + + /* Destroy the Netlink object and free the memory */ + free_nlobj(nl_obj); + } + + /* Test Receiving a route containing an SRv6 Local SID nexthop bound to the End.DT6 behavior */ + TEST_F(FpmSyncdSRv6LocalSIDsTest, RecevingRouteWithSRv6LocalSIDEndDT6) + { + ASSERT_NE(m_routeSync, nullptr); + + /* Create a Netlink object containing an SRv6 Local SID */ + IpAddress _localsid = IpAddress("fc00:0:1:1::"); + uint8_t _block_len = 32; + uint8_t _node_len = 16; + uint8_t _func_len = 16; + uint8_t _arg_len = 0; + uint32_t _action = SRV6_LOCALSID_ACTION_END_DT6; + char *_vrf = "Vrf10"; + + struct nlmsg *nl_obj = create_srv6_localsid_nlmsg(RTM_NEWSRV6LOCALSID, &_localsid, _block_len, _node_len, _func_len, _arg_len, _action, _vrf); + if (!nl_obj) + printf("Error\n\n"); + + /* Send the Netlink object to the FpmLink */ + m_fpmLink->processRawMsg(&nl_obj->n); + + /* Check that fpmsyncd created the correct entries in APP_DB */ + std::string action; + ASSERT_EQ(m_srv6LocalSidTable->hget("32:16:16:0:fc00:0:1:1::", "action", action), true); + ASSERT_EQ(action, "end.dt6"); + + std::string vrf; + ASSERT_EQ(m_srv6LocalSidTable->hget("32:16:16:0:fc00:0:1:1::", "vrf", vrf), true); + ASSERT_EQ(vrf, "Vrf10"); + + /* Destroy the Netlink object and free the memory */ + free_nlobj(nl_obj); + } + + /* Test Receiving a route containing an SRv6 Local SID nexthop bound to the End.DT46 behavior */ + TEST_F(FpmSyncdSRv6LocalSIDsTest, RecevingRouteWithSRv6LocalSIDEndDT46) + { + ASSERT_NE(m_routeSync, nullptr); + + /* Create a Netlink object containing an SRv6 Local SID */ + IpAddress _localsid = IpAddress("fc00:0:1:1::"); + uint8_t _block_len = 32; + uint8_t _node_len = 16; + uint8_t _func_len = 16; + uint8_t _arg_len = 0; + uint32_t _action = SRV6_LOCALSID_ACTION_END_DT46; + char *_vrf = "Vrf10"; + + struct nlmsg *nl_obj = create_srv6_localsid_nlmsg(RTM_NEWSRV6LOCALSID, &_localsid, _block_len, _node_len, _func_len, _arg_len, _action, _vrf); + if (!nl_obj) + printf("Error\n\n"); + + /* Send the Netlink object to the FpmLink */ + m_fpmLink->processRawMsg(&nl_obj->n); + + /* Check that fpmsyncd created the correct entries in APP_DB */ + std::string action; + ASSERT_EQ(m_srv6LocalSidTable->hget("32:16:16:0:fc00:0:1:1::", "action", action), true); + ASSERT_EQ(action, "end.dt46"); + + std::string vrf; + ASSERT_EQ(m_srv6LocalSidTable->hget("32:16:16:0:fc00:0:1:1::", "vrf", vrf), true); + ASSERT_EQ(vrf, "Vrf10"); + + /* Destroy the Netlink object and free the memory */ + free_nlobj(nl_obj); + } + + /* Test Receiving a route containing an SRv6 Local SID nexthop bound to the uDT4 behavior */ + TEST_F(FpmSyncdSRv6LocalSIDsTest, RecevingRouteWithSRv6LocalSIDUDT4) + { + ASSERT_NE(m_routeSync, nullptr); + + /* Create a Netlink object containing an SRv6 Local SID */ + IpAddress _localsid = IpAddress("fc00:0:1:1::"); + uint8_t _block_len = 32; + uint8_t _node_len = 16; + uint8_t _func_len = 16; + uint8_t _arg_len = 0; + uint32_t _action = SRV6_LOCALSID_ACTION_UDT4; + char *_vrf = "Vrf10"; + + struct nlmsg *nl_obj = create_srv6_localsid_nlmsg(RTM_NEWSRV6LOCALSID, &_localsid, _block_len, _node_len, _func_len, _arg_len, _action, _vrf); + if (!nl_obj) + printf("Error\n\n"); + + /* Send the Netlink object to the FpmLink */ + m_fpmLink->processRawMsg(&nl_obj->n); + + /* Check that fpmsyncd created the correct entries in APP_DB */ + std::string action; + ASSERT_EQ(m_srv6LocalSidTable->hget("32:16:16:0:fc00:0:1:1::", "action", action), true); + ASSERT_EQ(action, "udt4"); + + std::string vrf; + ASSERT_EQ(m_srv6LocalSidTable->hget("32:16:16:0:fc00:0:1:1::", "vrf", vrf), true); + ASSERT_EQ(vrf, "Vrf10"); + + /* Destroy the Netlink object and free the memory */ + free_nlobj(nl_obj); + } + + /* Test Receiving a route containing an SRv6 Local SID nexthop bound to the uDT6 behavior */ + TEST_F(FpmSyncdSRv6LocalSIDsTest, RecevingRouteWithSRv6LocalSIDUDT6) + { + ASSERT_NE(m_routeSync, nullptr); + + /* Create a Netlink object containing an SRv6 Local SID */ + IpAddress _localsid = IpAddress("fc00:0:1:1::"); + uint8_t _block_len = 32; + uint8_t _node_len = 16; + uint8_t _func_len = 16; + uint8_t _arg_len = 0; + uint32_t _action = SRV6_LOCALSID_ACTION_UDT6; + char *_vrf = "Vrf10"; + + struct nlmsg *nl_obj = create_srv6_localsid_nlmsg(RTM_NEWSRV6LOCALSID, &_localsid, _block_len, _node_len, _func_len, _arg_len, _action, _vrf); + if (!nl_obj) + printf("Error\n\n"); + + /* Send the Netlink object to the FpmLink */ + m_fpmLink->processRawMsg(&nl_obj->n); + + /* Check that fpmsyncd created the correct entries in APP_DB */ + std::string action; + ASSERT_EQ(m_srv6LocalSidTable->hget("32:16:16:0:fc00:0:1:1::", "action", action), true); + ASSERT_EQ(action, "udt6"); + + std::string vrf; + ASSERT_EQ(m_srv6LocalSidTable->hget("32:16:16:0:fc00:0:1:1::", "vrf", vrf), true); + ASSERT_EQ(vrf, "Vrf10"); + + /* Destroy the Netlink object and free the memory */ + free_nlobj(nl_obj); + } + + /* Test Receiving a route containing an SRv6 Local SID nexthop bound to the uDT46 behavior */ + TEST_F(FpmSyncdSRv6LocalSIDsTest, RecevingRouteWithSRv6LocalSIDUDT46) + { + ASSERT_NE(m_routeSync, nullptr); + + /* Create a Netlink object containing an SRv6 Local SID */ + IpAddress _localsid = IpAddress("fc00:0:1:1::"); + uint8_t _block_len = 32; + uint8_t _node_len = 16; + uint8_t _func_len = 16; + uint8_t _arg_len = 0; + uint32_t _action = SRV6_LOCALSID_ACTION_UDT46; + char *_vrf = "Vrf10"; + + struct nlmsg *nl_obj = create_srv6_localsid_nlmsg(RTM_NEWSRV6LOCALSID, &_localsid, _block_len, _node_len, _func_len, _arg_len, _action, _vrf); + if (!nl_obj) + printf("Error\n\n"); + + /* Send the Netlink object to the FpmLink */ + m_fpmLink->processRawMsg(&nl_obj->n); + + /* Check that fpmsyncd created the correct entries in APP_DB */ + std::string action; + ASSERT_EQ(m_srv6LocalSidTable->hget("32:16:16:0:fc00:0:1:1::", "action", action), true); + ASSERT_EQ(action, "udt46"); + + std::string vrf; + ASSERT_EQ(m_srv6LocalSidTable->hget("32:16:16:0:fc00:0:1:1::", "vrf", vrf), true); + ASSERT_EQ(vrf, "Vrf10"); + + /* Destroy the Netlink object and free the memory */ + free_nlobj(nl_obj); + } +} \ No newline at end of file diff --git a/tests/mock_tests/fpmsyncd/receive_srv6_steer_routes_ut.cpp b/tests/mock_tests/fpmsyncd/receive_srv6_steer_routes_ut.cpp new file mode 100644 index 0000000000..92d4e394b7 --- /dev/null +++ b/tests/mock_tests/fpmsyncd/receive_srv6_steer_routes_ut.cpp @@ -0,0 +1,130 @@ +#include "ut_helpers_fpmsyncd.h" +#include "gtest/gtest.h" +#include +#include "mock_table.h" +#include +#include +#include +#include "ipaddress.h" +#include "ipprefix.h" + +#define private public // Need to modify internal cache +#include "fpmlink.h" +#include "routesync.h" +#undef private + +using namespace swss; +using namespace testing; + +/* +Test Fixture +*/ +namespace ut_fpmsyncd +{ + struct FpmSyncdSRv6RoutesTest : public ::testing::Test + { + std::shared_ptr m_app_db; + std::shared_ptr pipeline; + std::shared_ptr m_routeSync; + std::shared_ptr m_fpmLink; + std::shared_ptr m_routeTable; + std::shared_ptr m_srv6SidListTable; + + virtual void SetUp() override + { + testing_db::reset(); + + m_app_db = std::make_shared("APPL_DB", 0); + + /* Construct dependencies */ + + /* 1) RouteSync */ + pipeline = std::make_shared(m_app_db.get()); + m_routeSync = std::make_shared(pipeline.get()); + + /* 2) FpmLink */ + m_fpmLink = std::make_shared(m_routeSync.get()); + + /* 3) ROUTE_TABLE in APP_DB */ + m_routeTable = std::make_shared(m_app_db.get(), APP_ROUTE_TABLE_NAME); + + /* 4) SRV6_SID_LIST_TABLE in APP_DB */ + m_srv6SidListTable = std::make_shared(m_app_db.get(), APP_SRV6_SID_LIST_TABLE_NAME); + } + + virtual void TearDown() override + { + } + }; +} + +namespace ut_fpmsyncd +{ + /* Test Receiving an SRv6 VPN Route (with an IPv4 prefix) */ + TEST_F(FpmSyncdSRv6RoutesTest, RecevingSRv6VpnRoutesWithIPv4Prefix) + { + ASSERT_NE(m_routeSync, nullptr); + + /* Create a Netlink object containing an SRv6 VPN Route */ + IpPrefix _dst = IpPrefix("192.168.6.0/24"); + IpAddress _vpn_sid = IpAddress("fc00:0:2:1::"); + IpAddress _encap_src_addr = IpAddress("fc00:0:1:1::1"); + + struct nlmsg *nl_obj = create_srv6_vpn_route_nlmsg(RTM_NEWROUTE, &_dst, &_encap_src_addr, &_vpn_sid); + if (!nl_obj) + throw std::runtime_error("SRv6 VPN Route creation failed"); + + /* Send the Netlink object to the FpmLink */ + m_fpmLink->processRawMsg(&nl_obj->n); + + /* Check that fpmsyncd created the correct entries in APP_DB */ + std::string path; + ASSERT_EQ(m_srv6SidListTable->hget("Vrf10:192.168.6.0/24", "path", path), true); + ASSERT_EQ(path, _vpn_sid.to_string()); + + std::string segment; + ASSERT_EQ(m_routeTable->hget("Vrf10:192.168.6.0/24", "segment", segment), true); + ASSERT_EQ(segment, "Vrf10:192.168.6.0/24"); + + std::string seg_src; + ASSERT_EQ(m_routeTable->hget("Vrf10:192.168.6.0/24", "seg_src", seg_src), true); + ASSERT_EQ(seg_src, _encap_src_addr.to_string()); + + /* Destroy the Netlink object and free the memory */ + free_nlobj(nl_obj); + } + + /* Test Receiving an SRv6 VPN Route (with an IPv6 prefix) */ + TEST_F(FpmSyncdSRv6RoutesTest, RecevingSRv6VpnRoutesWithIPv6Prefix) + { + ASSERT_NE(m_routeSync, nullptr); + + /* Create a Netlink object containing an SRv6 VPN Route */ + IpPrefix _dst = IpPrefix("fd00:0:21::/64"); + IpAddress _vpn_sid = IpAddress("fc00:0:2:1::"); + IpAddress _encap_src_addr = IpAddress("fc00:0:1:1::1"); + + struct nlmsg *nl_obj = create_srv6_vpn_route_nlmsg(RTM_NEWROUTE, &_dst, &_encap_src_addr, &_vpn_sid); + if (!nl_obj) + throw std::runtime_error("SRv6 VPN Route creation failed"); + + /* Send the Netlink object to the FpmLink */ + m_fpmLink->processRawMsg(&nl_obj->n); + + /* Check that fpmsyncd created the correct entries in APP_DB */ + std::string path; + ASSERT_EQ(m_srv6SidListTable->hget("Vrf10:fd00:0:21::/64", "path", path), true); + ASSERT_EQ(path, _vpn_sid.to_string()); + + std::string segment; + ASSERT_EQ(m_routeTable->hget("Vrf10:fd00:0:21::/64", "segment", segment), true); + ASSERT_EQ(segment, "Vrf10:fd00:0:21::/64"); + + std::string seg_src; + ASSERT_EQ(m_routeTable->hget("Vrf10:fd00:0:21::/64", "seg_src", seg_src), true); + ASSERT_EQ(seg_src, _encap_src_addr.to_string()); + + /* Destroy the Netlink object and free the memory */ + free_nlobj(nl_obj); + } +} \ No newline at end of file diff --git a/tests/mock_tests/fpmsyncd/ut_helpers_fpmsyncd.cpp b/tests/mock_tests/fpmsyncd/ut_helpers_fpmsyncd.cpp new file mode 100644 index 0000000000..f89caa6b15 --- /dev/null +++ b/tests/mock_tests/fpmsyncd/ut_helpers_fpmsyncd.cpp @@ -0,0 +1,359 @@ +#include "ut_helpers_fpmsyncd.h" +#include "ipaddress.h" +#include "ipprefix.h" +#include +#include + +#define IPV6_MAX_BYTE 16 +#define IPV6_MAX_BITLEN 128 + +/* + * Mock rtnl_link_i2name() call + * We simulate the existence of a VRF called Vrf10 with ifindex 10. + * Calling rtnl_link_i2name(_, 10, _, _) will return the name of the VRF (i.e., "Vrf10" string) + */ +extern "C" { +char *__wrap_rtnl_link_i2name(struct nl_cache *cache, int ifindex, char *dst, size_t len) +{ + switch (ifindex) + { + case 10: + strncpy(dst, "Vrf10", 6); + return dst; + default: + return NULL; + } +} +} + +namespace ut_fpmsyncd +{ + /* Add a unspecific attribute to netlink message */ + bool nl_attr_put(struct nlmsghdr *n, unsigned int maxlen, int type, + const void *data, unsigned int alen) + { + int len; + struct rtattr *rta; + + len = (int)RTA_LENGTH(alen); + + if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) + return false; + + rta = reinterpret_cast(static_cast(((char *)n) + NLMSG_ALIGN(n->nlmsg_len))); + rta->rta_type = (uint16_t)type; + rta->rta_len = (uint16_t)len; + + if (data) + memcpy(RTA_DATA(rta), data, alen); + else + assert(alen == 0); + + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + + return true; + } + + /* Add 8 bit integer attribute to netlink message */ + bool nl_attr_put8(struct nlmsghdr *n, unsigned int maxlen, int type, + uint16_t data) + { + return nl_attr_put(n, maxlen, type, &data, sizeof(uint8_t)); + } + + /* Add 16 bit integer attribute to netlink message */ + bool nl_attr_put16(struct nlmsghdr *n, unsigned int maxlen, int type, + uint16_t data) + { + return nl_attr_put(n, maxlen, type, &data, sizeof(uint16_t)); + } + + /* Add 32 bit integer attribute to netlink message */ + bool nl_attr_put32(struct nlmsghdr *n, unsigned int maxlen, int type, + uint32_t data) + { + return nl_attr_put(n, maxlen, type, &data, sizeof(uint32_t)); + } + + /* Start a new level of nested attributes */ + struct rtattr *nl_attr_nest(struct nlmsghdr *n, unsigned int maxlen, int type) + { + struct rtattr *nest = NLMSG_TAIL(n); + + if (!nl_attr_put(n, maxlen, type, NULL, 0)) + return NULL; + + nest->rta_type |= NLA_F_NESTED; + return nest; + } + + /* Finalize nesting of attributes */ + int nl_attr_nest_end(struct nlmsghdr *n, struct rtattr *nest) + { + nest->rta_len = (uint16_t)((uint8_t *)NLMSG_TAIL(n) - (uint8_t *)nest); + return n->nlmsg_len; + } + + /* Build a Netlink object containing an SRv6 VPN Route */ + struct nlmsg *create_srv6_vpn_route_nlmsg( + uint16_t cmd, + IpPrefix *dst, + IpAddress *encap_src_addr, + IpAddress *vpn_sid, + uint16_t table_id) + { + struct rtattr *nest; + + /* Allocate memory for the Netlink objct */ + struct nlmsg *nl_obj = (struct nlmsg *)calloc(1, sizeof(struct nlmsg)); + if (!nl_obj) + throw std::runtime_error("netlink: nlmsg object allocation failed"); + + nl_obj->n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + nl_obj->n.nlmsg_flags = NLM_F_CREATE | NLM_F_REQUEST; + + if (cmd == RTM_NEWROUTE && + dst->isV4()) + nl_obj->n.nlmsg_flags |= NLM_F_REPLACE; + + nl_obj->n.nlmsg_type = cmd; + + nl_obj->n.nlmsg_pid = 100; + + nl_obj->r.rtm_family = dst->getIp().getIp().family; + nl_obj->r.rtm_dst_len = (unsigned char)(dst->getMaskLength()); + nl_obj->r.rtm_scope = RT_SCOPE_UNIVERSE; + + nl_obj->r.rtm_protocol = 11; // ZEBRA protocol + + if (cmd != RTM_DELROUTE) + nl_obj->r.rtm_type = RTN_UNICAST; + + /* Add the destination address */ + if (dst->isV4()) + { + if (!nl_attr_put32(&nl_obj->n, sizeof(*nl_obj), + RTA_DST, dst->getIp().getV4Addr())) + return NULL; + } + else + { + if (!nl_attr_put(&nl_obj->n, sizeof(*nl_obj), + RTA_DST, dst->getIp().getV6Addr(), IPV6_MAX_BYTE)) + return NULL; + } + + /* Add the table ID */ + if (table_id < 256) + nl_obj->r.rtm_table = (unsigned char)table_id; + else + { + nl_obj->r.rtm_table = RT_TABLE_UNSPEC; + if (!nl_attr_put32(&nl_obj->n, sizeof(*nl_obj), RTA_TABLE, table_id)) + return NULL; + } + + /* If the Netlink message is a Delete Route message, we have done */ + if (cmd == RTM_DELROUTE) + { + NLMSG_ALIGN(nl_obj->n.nlmsg_len); + return nl_obj; + } + + /* Add encapsulation type NH_ENCAP_SRV6_ROUTE (SRv6 Route) */ + if (!nl_attr_put16(&nl_obj->n, sizeof(*nl_obj), RTA_ENCAP_TYPE, + NH_ENCAP_SRV6_ROUTE)) + return NULL; + + /* Add encapsulation information */ + nest = nl_attr_nest(&nl_obj->n, sizeof(*nl_obj), RTA_ENCAP); + if (!nest) + return NULL; + + /* Add source address for SRv6 encapsulation */ + if (!nl_attr_put( + &nl_obj->n, sizeof(*nl_obj), ROUTE_ENCAP_SRV6_ENCAP_SRC_ADDR, + encap_src_addr->getV6Addr(), 16)) + return NULL; + + /* Add the VPN SID */ + if (!nl_attr_put(&nl_obj->n, sizeof(*nl_obj), ROUTE_ENCAP_SRV6_VPN_SID, + vpn_sid->getV6Addr(), 16)) + return NULL; + + nl_attr_nest_end(&nl_obj->n, nest); + + return nl_obj; + } + + /* Build a Netlink object containing an SRv6 Local SID */ + struct nlmsg *create_srv6_localsid_nlmsg( + uint16_t cmd, + IpAddress *localsid, + uint8_t block_len, + uint8_t node_len, + uint8_t func_len, + uint8_t arg_len, + uint32_t action, + char *vrf, + uint16_t table_id) + { + struct rtattr *nest; + + /* Allocate memory for the Netlink object */ + struct nlmsg *nl_obj = (struct nlmsg *)malloc(sizeof(struct nlmsg)); + if (!nl_obj) + throw std::runtime_error("netlink: nlmsg object allocation failed"); + + memset(nl_obj, 0, sizeof(*nl_obj)); + + nl_obj->n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + nl_obj->n.nlmsg_flags = NLM_F_CREATE | NLM_F_REQUEST; + + nl_obj->n.nlmsg_type = cmd; + + nl_obj->n.nlmsg_pid = 100; + + nl_obj->r.rtm_family = AF_INET6; + nl_obj->r.rtm_dst_len = IPV6_MAX_BITLEN; + nl_obj->r.rtm_scope = RT_SCOPE_UNIVERSE; + + nl_obj->r.rtm_protocol = 11; // Protocol ZEBRA + + if (cmd != RTM_DELROUTE) + nl_obj->r.rtm_type = RTN_UNICAST; + + /* Add local SID address */ + if (localsid->isV4()) + { + throw std::runtime_error("SRv6 local SID cannot be an IPv4 address"); + } + else + { + if (!nl_attr_put(&nl_obj->n, sizeof(*nl_obj), + RTA_DST, localsid->getV6Addr(), 16)) + return NULL; + } + + /* Add table ID */ + if (table_id < 256) + nl_obj->r.rtm_table = (unsigned char)table_id; + else + { + nl_obj->r.rtm_table = RT_TABLE_UNSPEC; + if (!nl_attr_put32(&nl_obj->n, sizeof(*nl_obj), RTA_TABLE, table_id)) + return NULL; + } + + /* Add SID format information */ + nest = + nl_attr_nest(&nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_FORMAT); + + /* Add block bits length */ + if (!nl_attr_put8( + &nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_FORMAT_BLOCK_LEN, + block_len)) + return NULL; + + /* Add node bits length */ + if (!nl_attr_put8( + &nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_FORMAT_NODE_LEN, + node_len)) + return NULL; + + /* Add function bits length */ + if (!nl_attr_put8( + &nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_FORMAT_FUNC_LEN, + func_len)) + return NULL; + + /* Add argument bits length */ + if (!nl_attr_put8( + &nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_FORMAT_ARG_LEN, + arg_len)) + return NULL; + + nl_attr_nest_end(&nl_obj->n, nest); + + /* If the Netlink message is a Delete Route message, we have done */ + if (cmd == RTM_DELROUTE) + { + NLMSG_ALIGN(nl_obj->n.nlmsg_len); + return nl_obj; + } + + /* Add local SID behavior (action and parameters) */ + switch (action) + { + case SRV6_LOCALSID_ACTION_END_DT4: + if (!nl_attr_put32(&nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_ACTION, + SRV6_LOCALSID_ACTION_END_DT4)) + return NULL; + if (!nl_attr_put(&nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_VRFNAME, + vrf, (uint32_t)strlen(vrf))) + return NULL; + break; + case SRV6_LOCALSID_ACTION_END_DT6: + if (!nl_attr_put32(&nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_ACTION, + SRV6_LOCALSID_ACTION_END_DT6)) + return NULL; + if (!nl_attr_put(&nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_VRFNAME, + vrf, (uint32_t)strlen(vrf))) + return NULL; + break; + case SRV6_LOCALSID_ACTION_END_DT46: + if (!nl_attr_put32(&nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_ACTION, + SRV6_LOCALSID_ACTION_END_DT46)) + return NULL; + if (!nl_attr_put(&nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_VRFNAME, + vrf, (uint32_t)strlen(vrf))) + return NULL; + break; + case SRV6_LOCALSID_ACTION_UDT4: + if (!nl_attr_put32(&nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_ACTION, + SRV6_LOCALSID_ACTION_UDT4)) + return NULL; + if (!nl_attr_put(&nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_VRFNAME, + vrf, (uint32_t)strlen(vrf))) + return NULL; + break; + case SRV6_LOCALSID_ACTION_UDT6: + if (!nl_attr_put32(&nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_ACTION, + SRV6_LOCALSID_ACTION_UDT6)) + return NULL; + if (!nl_attr_put(&nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_VRFNAME, + vrf, (uint32_t)strlen(vrf))) + return NULL; + break; + case SRV6_LOCALSID_ACTION_UDT46: + if (!nl_attr_put32(&nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_ACTION, + SRV6_LOCALSID_ACTION_UDT46)) + return NULL; + if (!nl_attr_put(&nl_obj->n, sizeof(*nl_obj), + SRV6_LOCALSID_VRFNAME, + vrf, (uint32_t)strlen(vrf))) + return NULL; + break; + default: + throw std::runtime_error("Unsupported localsid action\n"); + } + + return nl_obj; + } +} \ No newline at end of file diff --git a/tests/mock_tests/fpmsyncd/ut_helpers_fpmsyncd.h b/tests/mock_tests/fpmsyncd/ut_helpers_fpmsyncd.h new file mode 100644 index 0000000000..bdd4c599b1 --- /dev/null +++ b/tests/mock_tests/fpmsyncd/ut_helpers_fpmsyncd.h @@ -0,0 +1,109 @@ +#include "ipaddress.h" +#include "ipprefix.h" +#include +#include + +using namespace swss; + +#define NLMSG_TAIL(nmsg) \ + (reinterpret_cast(static_cast((((uint8_t *)nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))) + +/* Values copied from fpmsyncd/fpmlink.h */ +#define RTM_NEWSRV6LOCALSID 1000 +#define RTM_DELSRV6LOCALSID 1001 + +/* Values copied from fpmsyncd/routesync.cpp */ +#define NH_ENCAP_SRV6_ROUTE 101 + +enum { /* Values copied from fpmsyncd/routesync.cpp */ + ROUTE_ENCAP_SRV6_UNSPEC = 0, + ROUTE_ENCAP_SRV6_VPN_SID = 1, + ROUTE_ENCAP_SRV6_ENCAP_SRC_ADDR = 2, +}; + +enum srv6_localsid_action { /* Values copied from fpmsyncd/routesync.cpp */ + SRV6_LOCALSID_ACTION_UNSPEC = 0, + SRV6_LOCALSID_ACTION_END = 1, + SRV6_LOCALSID_ACTION_END_X = 2, + SRV6_LOCALSID_ACTION_END_T = 3, + SRV6_LOCALSID_ACTION_END_DX2 = 4, + SRV6_LOCALSID_ACTION_END_DX6 = 5, + SRV6_LOCALSID_ACTION_END_DX4 = 6, + SRV6_LOCALSID_ACTION_END_DT6 = 7, + SRV6_LOCALSID_ACTION_END_DT4 = 8, + SRV6_LOCALSID_ACTION_END_DT46 = 9, + SRV6_LOCALSID_ACTION_B6_ENCAPS = 10, + SRV6_LOCALSID_ACTION_B6_ENCAPS_RED = 11, + SRV6_LOCALSID_ACTION_B6_INSERT = 12, + SRV6_LOCALSID_ACTION_B6_INSERT_RED = 13, + SRV6_LOCALSID_ACTION_UN = 14, + SRV6_LOCALSID_ACTION_UA = 15, + SRV6_LOCALSID_ACTION_UDX2 = 16, + SRV6_LOCALSID_ACTION_UDX6 = 17, + SRV6_LOCALSID_ACTION_UDX4 = 18, + SRV6_LOCALSID_ACTION_UDT6 = 19, + SRV6_LOCALSID_ACTION_UDT4 = 20, + SRV6_LOCALSID_ACTION_UDT46 = 21, +}; + +enum { /* Values copied from fpmsyncd/routesync.cpp */ + SRV6_LOCALSID_UNSPEC = 0, + SRV6_LOCALSID_SID_VALUE = 1, + SRV6_LOCALSID_FORMAT = 2, + SRV6_LOCALSID_ACTION = 3, + SRV6_LOCALSID_VRFNAME = 4, + SRV6_LOCALSID_NH6 = 5, + SRV6_LOCALSID_NH4 = 6, + SRV6_LOCALSID_IIF = 7, + SRV6_LOCALSID_OIF = 8, + SRV6_LOCALSID_BPF = 9, + SRV6_LOCALSID_SIDLIST = 10, + SRV6_LOCALSID_ENCAP_SRC_ADDR = 11, +}; + +enum { /* Values copied from fpmsyncd/routesync.cpp */ + SRV6_LOCALSID_FORMAT_UNSPEC = 0, + SRV6_LOCALSID_FORMAT_BLOCK_LEN = 1, + SRV6_LOCALSID_FORMAT_NODE_LEN = 2, + SRV6_LOCALSID_FORMAT_FUNC_LEN = 3, + SRV6_LOCALSID_FORMAT_ARG_LEN = 4, +}; + +namespace ut_fpmsyncd +{ + struct nlmsg + { + struct nlmsghdr n; + struct rtmsg r; + char buf[512]; + }; + + /* Add a unspecific attribute to netlink message */ + bool nl_attr_put(struct nlmsghdr *n, unsigned int maxlen, int type, + const void *data, unsigned int alen); + /* Add 8 bit integer attribute to netlink message */ + bool nl_attr_put8(struct nlmsghdr *n, unsigned int maxlen, int type, + uint16_t data); + /* Add 16 bit integer attribute to netlink message */ + bool nl_attr_put16(struct nlmsghdr *n, unsigned int maxlen, int type, + uint16_t data); + /* Add 32 bit integer attribute to netlink message */ + bool nl_attr_put32(struct nlmsghdr *n, unsigned int maxlen, int type, + uint32_t data); + /* Start a new level of nested attributes */ + struct rtattr *nl_attr_nest(struct nlmsghdr *n, unsigned int maxlen, int type); + /* Finalize nesting of attributes */ + int nl_attr_nest_end(struct nlmsghdr *n, struct rtattr *nest); + /* Build a Netlink object containing an SRv6 VPN Route */ + struct nlmsg *create_srv6_vpn_route_nlmsg(uint16_t cmd, IpPrefix *dst, IpAddress *encap_src_addr, + IpAddress *vpn_sid, uint16_t table_id = 10); + /* Build a Netlink object containing an SRv6 Local SID */ + struct nlmsg *create_srv6_localsid_nlmsg(uint16_t cmd, IpAddress *localsid, uint8_t block_len, + uint8_t node_len, uint8_t func_len, uint8_t arg_len, + uint32_t action, char *vrf, uint16_t table_id = 10); + /* Free the memory allocated for a Netlink object */ + inline void free_nlobj(struct nlmsg *msg) + { + free(msg); + } +} \ No newline at end of file diff --git a/tests/test_srv6.py b/tests/test_srv6.py index 3ce19421b0..e16221d143 100644 --- a/tests/test_srv6.py +++ b/tests/test_srv6.py @@ -627,6 +627,591 @@ def test_srv6(self, dvs, testlog): assert nexthop_entries == get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") assert route_entries == get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + +class TestSrv6MySidFpmsyncd(object): + """ Functionality tests for Srv6 MySid handling in fpmsyncd """ + + def setup_db(self, dvs): + self.pdb = dvs.get_app_db() + self.adb = dvs.get_asic_db() + self.cdb = dvs.get_config_db() + + def create_vrf(self, vrf_name): + table = "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER" + existed_entries = get_exist_entries(self.adb.db_connection, table) + + self.cdb.create_entry("VRF", vrf_name, {"empty": "empty"}) + + self.adb.wait_for_n_keys(table, len(existed_entries) + 1) + return get_created_entry(self.adb.db_connection, table, existed_entries) + + def remove_vrf(self, vrf_name): + self.cdb.delete_entry("VRF", vrf_name) + + def add_ip_address(self, interface, ip): + self.cdb.create_entry("INTERFACE", interface + "|" + ip, {"NULL": "NULL"}) + + def remove_ip_address(self, interface, ip): + self.cdb.delete_entry("INTERFACE", interface + "|" + ip) + + def add_neighbor(self, interface, ip, mac, family): + table = "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY" + existed_entries = get_exist_entries(self.adb.db_connection, table) + + tbl = swsscommon.ProducerStateTable(self.pdb.db_connection, "NEIGH_TABLE") + fvs = swsscommon.FieldValuePairs([("neigh", mac), + ("family", family)]) + tbl.set(interface + ":" + ip, fvs) + + self.adb.wait_for_n_keys(table, len(existed_entries) + 1) + return get_created_entry(self.adb.db_connection, table, existed_entries) + + def remove_neighbor(self, interface, ip): + tbl = swsscommon.ProducerStateTable(self.pdb.db_connection, "NEIGH_TABLE") + tbl._del(interface + ":" + ip) + time.sleep(1) + + def create_l3_intf(self, interface, vrf_name): + table = "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE" + existed_entries = get_exist_entries(self.adb.db_connection, table) + + if len(vrf_name) == 0: + self.cdb.create_entry("INTERFACE", interface, {"NULL": "NULL"}) + else: + self.cdb.create_entry("INTERFACE", interface, {"vrf_name": vrf_name}) + + self.adb.wait_for_n_keys(table, len(existed_entries) + 1) + return get_created_entry(self.adb.db_connection, table, existed_entries) + + def remove_l3_intf(self, interface): + self.cdb.delete_entry("INTERFACE", interface) + + def get_nexthop_id(self, ip_address): + next_hop_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") + for next_hop_entry in next_hop_entries: + (status, fvs) = tbl.get(next_hop_entry) + + assert status == True + assert len(fvs) == 3 + + for fv in fvs: + if fv[0] == "SAI_NEXT_HOP_ATTR_IP" and fv[1] == ip_address: + return next_hop_entry + + return None + + def set_interface_status(self, dvs, interface, admin_status): + tbl_name = "PORT" + tbl = swsscommon.Table(self.cdb.db_connection, tbl_name) + fvs = swsscommon.FieldValuePairs([("admin_status", "up")]) + tbl.set(interface, fvs) + time.sleep(1) + + def setup_srv6(self, dvs): + self.setup_db(dvs) + + dvs.runcmd("sysctl -w net.vrf.strict_mode=1") + + # create interface + self.create_l3_intf("Ethernet104", "") + + # assign IP to interface + self.add_ip_address("Ethernet104", "2001::2/126") + self.add_ip_address("Ethernet104", "192.0.2.2/30") + + time.sleep(3) + + # bring up Ethernet104 + self.set_interface_status(dvs, "Ethernet104", "up") + + time.sleep(3) + + # save the initial number of entries in MySID table + self.initial_my_sid_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY") + + # save the initial number of entries in Nexthop table + self.initial_next_hop_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") + + # now, let's create the IPv6 neighbor + self.add_neighbor("Ethernet104", "2001::1", "00:00:00:01:02:04", "IPv6") + + # verify that the nexthop is created in the ASIC (i.e., we have the previous number of next hop entries + 1) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", len(self.initial_next_hop_entries) + 1) + + # get the new nexthop and nexthop ID, which will be used later to verify the MySID entry + next_hop_entry = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", self.initial_next_hop_entries) + assert next_hop_entry is not None + self.next_hop_ipv6_id = self.get_nexthop_id("2001::1") + assert self.next_hop_ipv6_id is not None + + # save the number of entries in Nexthop table, after adding the ipv6 neighbor + updated_next_hop_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") + + # now, let's create the IPv4 neighbor + self.add_neighbor("Ethernet104", "192.0.2.1", "00:00:00:01:02:05", "IPv4") + + # verify that the nexthop is created in the ASIC (i.e., we have the previous number of next hop entries + 1) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", len(updated_next_hop_entries) + 1) + + # get the new nexthop and nexthop ID, which will be used later to verify the MySID entry + next_hop_entry = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", updated_next_hop_entries) + assert next_hop_entry is not None + self.next_hop_ipv4_id = self.get_nexthop_id("192.0.2.1") + assert self.next_hop_ipv4_id is not None + + # create vrf + initial_vrf_entries = set(self.adb.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER")) + self.create_vrf("Vrf10") + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", len(initial_vrf_entries) + 1) + current_vrf_entries = set(self.adb.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER")) + self.vrf_id = list(current_vrf_entries - initial_vrf_entries)[0] + _, vrf_info = dvs.runcmd("ip --json -d link show Vrf10") + vrf_info_json = json.loads(vrf_info) + self.vrf_table_id = str(vrf_info_json[0]["linkinfo"]["info_data"]["table"]) + + # create dummy interface sr0 + dvs.runcmd("ip link add sr0 type dummy") + dvs.runcmd("ip link set sr0 up") + + def teardown_srv6(self, dvs): + # remove dummy interface sr0 + dvs.runcmd("ip link del sr0 type dummy") + + # remove vrf + initial_vrf_entries = set(self.adb.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER")) + self.remove_vrf("Vrf10") + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", len(initial_vrf_entries) - 1) + + # remove the IPv4 neighbor + initial_neighbor_entries = set(self.adb.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP")) + self.remove_neighbor("Ethernet104", "192.0.2.1") + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", len(initial_neighbor_entries) - 1) + + # remove the IPv6 neighbor + initial_neighbor_entries = set(self.adb.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP")) + self.remove_neighbor("Ethernet104", "2001::1") + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", len(initial_neighbor_entries) - 1) + + time.sleep(3) + + # put Ethernet104 down + self.set_interface_status(dvs, "Ethernet104", "down") + + time.sleep(3) + + # remove IP from interface + self.remove_ip_address("Ethernet104", "2001::2/126") + self.remove_ip_address("Ethernet104", "192.0.2.2/30") + + # remove interface + initial_interface_entries = set(self.adb.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE")) + self.remove_l3_intf("Ethernet104") + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE", len(initial_interface_entries) - 1) + + def test_AddRemoveSrv6MySidEnd(self, dvs, testlog): + + _, output = dvs.runcmd(f"vtysh -c 'show zebra dplane providers'") + if 'dplane_fpm_sonic' not in output: + pytest.skip("'dplane_fpm_sonic' required for this test is not available, skipping", allow_module_level=True) + + self.setup_srv6(dvs) + + # configure srv6 locator + dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"srv6\" -c \"locators\" -c \"locator loc1\" -c \"prefix fc00:0:0:1::/64 block-len 40 node-len 24 func-bits 16\"") + + # create srv6 mysid end behavior + dvs.runcmd("ip -6 route add fc00:0:0:1:64::/128 encap seg6local action End dev sr0") + + # check application database + self.pdb.wait_for_entry("SRV6_MY_SID_TABLE", "40:24:16:0:fc00:0:0:1:64::") + + # verify that the mysid has been programmed into the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", len(self.initial_my_sid_entries) + 1) + + # check ASIC SAI_OBJECT_TYPE_MY_SID_ENTRY database + my_sid = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", self.initial_my_sid_entries) + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY") + (status, fvs) = tbl.get(my_sid) + assert status == True + for fv in fvs: + if fv[0] == "SAI_MY_SID_ENTRY_ATTR_ENDPOINT_BEHAVIOR": + assert fv[1] == "SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_E" + + # remove srv6 mysid end behavior + dvs.runcmd("ip -6 route del fc00:0:0:1:64::/128 encap seg6local action End dev sr0") + + # check application database + self.pdb.wait_for_deleted_entry("SRV6_MY_SID_TABLE", "40:24:16:0:fc00:0:0:1:64::") + + # verify that the mysid has been removed from the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", len(self.initial_my_sid_entries)) + + # unconfigure srv6 locator + dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"no srv6\"") + + self.teardown_srv6(dvs) + + def test_AddRemoveSrv6MySidEndX(self, dvs, testlog): + + _, output = dvs.runcmd(f"vtysh -c 'show zebra dplane providers'") + if 'dplane_fpm_sonic' not in output: + pytest.skip("'dplane_fpm_sonic' required for this test is not available, skipping", allow_module_level=True) + + self.setup_srv6(dvs) + + # configure srv6 locator + dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"srv6\" -c \"locators\" -c \"locator loc1\" -c \"prefix fc00:0:0:1::/64 block-len 40 node-len 24 func-bits 16\"") + + # create srv6 mysid end.x behavior + dvs.runcmd("ip -6 route add fc00:0:0:1:65::/128 encap seg6local action End.X nh6 2001::1 dev sr0") + + # check application database + self.pdb.wait_for_entry("SRV6_MY_SID_TABLE", "40:24:16:0:fc00:0:0:1:65::") + + # verify that the mysid has been programmed into the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", len(self.initial_my_sid_entries) + 1) + + # check ASIC SAI_OBJECT_TYPE_MY_SID_ENTRY database + my_sid = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", self.initial_my_sid_entries) + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY") + (status, fvs) = tbl.get(my_sid) + assert status == True + for fv in fvs: + if fv[0] == "SAI_MY_SID_ENTRY_ATTR_ENDPOINT_BEHAVIOR": + assert fv[1] == "SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_X" + if fv[0] == "SAI_MY_SID_ENTRY_ATTR_NEXT_HOP_ID": + assert fv[1] == self.next_hop_ipv6_id + if fv[0] == "SAI_MY_SID_ENTRY_ATTR_ENDPOINT_BEHAVIOR_FLAVOR": + assert fv[1] == "SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_FLAVOR_PSP_AND_USD" + + # remove srv6 mysid end.x behavior + dvs.runcmd("ip -6 route del fc00:0:0:1:65::/128 encap seg6local action End.X nh6 2001::1 dev sr0") + + # check application database + self.pdb.wait_for_deleted_entry("SRV6_MY_SID_TABLE", "40:24:16:0:fc00:0:0:1:65::") + + # verify that the mysid has been removed from the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", len(self.initial_my_sid_entries)) + + # unconfigure srv6 locator + dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"no srv6\"") + + self.teardown_srv6(dvs) + + def test_AddRemoveSrv6MySidEndDT46(self, dvs, testlog): + + _, output = dvs.runcmd(f"vtysh -c 'show zebra dplane providers'") + if 'dplane_fpm_sonic' not in output: + pytest.skip("'dplane_fpm_sonic' required for this test is not available, skipping", allow_module_level=True) + + self.setup_srv6(dvs) + + # configure srv6 locator + dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"srv6\" -c \"locators\" -c \"locator loc1\" -c \"prefix fc00:0:0:1::/64 block-len 40 node-len 24 func-bits 16\"") + + # create srv6 mysid end.dt46 behavior + dvs.runcmd("ip -6 route add fc00:0:0:1:6b::/128 encap seg6local action End.DT46 vrftable {} dev sr0".format(self.vrf_table_id)) + + # check application database + self.pdb.wait_for_entry("SRV6_MY_SID_TABLE", "40:24:16:0:fc00:0:0:1:6b::") + + # verify that the mysid has been programmed into the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", len(self.initial_my_sid_entries) + 1) + + # check ASIC SAI_OBJECT_TYPE_MY_SID_ENTRY database + my_sid = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", self.initial_my_sid_entries) + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY") + (status, fvs) = tbl.get(my_sid) + assert status == True + for fv in fvs: + if fv[0] == "SAI_MY_SID_ENTRY_ATTR_ENDPOINT_BEHAVIOR": + assert fv[1] == "SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_DT46" + if fv[0] == "SAI_MY_SID_ENTRY_ATTR_VRF": + assert fv[1] == self.vrf_id + + # remove srv6 mysid end.dt46 behavior + dvs.runcmd("ip -6 route del fc00:0:0:1:6b::/128 encap seg6local action End.DT46 vrftable {} dev sr0".format(self.vrf_table_id)) + + # check application database + self.pdb.wait_for_deleted_entry("SRV6_MY_SID_TABLE", "40:24:16:0:fc00:0:0:1:6b::") + + # verify that the mysid has been removed from the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", len(self.initial_my_sid_entries)) + + # unconfigure srv6 locator + dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"no srv6\"") + + self.teardown_srv6(dvs) + + def test_AddRemoveSrv6MySidUN(self, dvs, testlog): + + _, output = dvs.runcmd(f"vtysh -c 'show zebra dplane providers'") + if 'dplane_fpm_sonic' not in output: + pytest.skip("'dplane_fpm_sonic' required for this test is not available, skipping", allow_module_level=True) + + self.setup_srv6(dvs) + + # configure srv6 usid locator + dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"srv6\" -c \"locators\" -c \"locator loc1\" -c \"prefix fc00:0:2::/48 block-len 32 node-len 16 func-bits 16\" -c \"behavior usid\"") + + # create srv6 mysid un behavior + dvs.runcmd("ip -6 route add fc00:0:2::/48 encap seg6local action End dev sr0") + # dvs.runcmd("ip -6 route add fc00:0:2::/48 encap seg6local action End flavors next-csid lblen 32 nflen 16 dev sr0") + + # check application database + self.pdb.wait_for_entry("SRV6_MY_SID_TABLE", "32:16:16:0:fc00:0:2::") + + # verify that the mysid has been programmed into the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", len(self.initial_my_sid_entries) + 1) + + # check ASIC SAI_OBJECT_TYPE_MY_SID_ENTRY database + my_sid = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", self.initial_my_sid_entries) + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY") + (status, fvs) = tbl.get(my_sid) + assert status == True + for fv in fvs: + if fv[0] == "SAI_MY_SID_ENTRY_ATTR_ENDPOINT_BEHAVIOR": + assert fv[1] == "SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_UN" + + # remove srv6 mysid un behavior + dvs.runcmd("ip -6 route del fc00:0:2::/48 encap seg6local action End dev sr0".format(self.vrf_table_id)) + # dvs.runcmd("ip -6 route del fc00:0:2::/48 encap seg6local action End flavors next-csid lblen 32 nflen 16 dev sr0".format(self.vrf_table_id)) + + # check application database + self.pdb.wait_for_deleted_entry("SRV6_MY_SID_TABLE", "32:16:16:0:fc00:0:2::") + + # verify that the mysid has been removed from the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", len(self.initial_my_sid_entries)) + + # unconfigure srv6 locator + dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"no srv6\"") + + self.teardown_srv6(dvs) + + def test_AddRemoveSrv6MySidUA(self, dvs, testlog): + + _, output = dvs.runcmd(f"vtysh -c 'show zebra dplane providers'") + if 'dplane_fpm_sonic' not in output: + pytest.skip("'dplane_fpm_sonic' required for this test is not available, skipping", allow_module_level=True) + + self.setup_srv6(dvs) + + # configure srv6 usid locator + dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"srv6\" -c \"locators\" -c \"locator loc1\" -c \"prefix fc00:0:2::/48 block-len 32 node-len 16 func-bits 16\" -c \"behavior usid\"") + + # create srv6 mysid ua behavior + dvs.runcmd("ip -6 route add fc00:0:2:ff00::/64 encap seg6local action End.X nh6 2001::1 dev sr0") + # dvs.runcmd("ip -6 route add fc00:0:2:ff00::/64 encap seg6local action End.X nh6 2001::1 flavors next-csid lblen 32 nflen 16 dev sr0") + + # check application database + self.pdb.wait_for_entry("SRV6_MY_SID_TABLE", "32:16:16:0:fc00:0:2:ff00::") + + # verify that the mysid has been programmed into the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", len(self.initial_my_sid_entries) + 1) + + # check ASIC SAI_OBJECT_TYPE_MY_SID_ENTRY database + my_sid = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", self.initial_my_sid_entries) + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY") + (status, fvs) = tbl.get(my_sid) + assert status == True + for fv in fvs: + if fv[0] == "SAI_MY_SID_ENTRY_ATTR_ENDPOINT_BEHAVIOR": + assert fv[1] == "SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_UA" + if fv[0] == "SAI_MY_SID_ENTRY_ATTR_NEXT_HOP_ID": + assert fv[1] == self.next_hop_ipv6_id + + # remove srv6 mysid ua behavior + dvs.runcmd("ip -6 route del fc00:0:2:ff00::/64 encap seg6local action End.DT6 nh6 2001::1 dev sr0") + # dvs.runcmd("ip -6 route del fc00:0:2:ff00::/64 encap seg6local action End.DT6 nh6 2001::1 flavors next-csid lblen 32 nflen 16 dev sr0") + + # check application database + self.pdb.wait_for_deleted_entry("SRV6_MY_SID_TABLE", "32:16:16:0:fc00:0:2:ff00::") + + # verify that the mysid has been removed from the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", len(self.initial_my_sid_entries)) + + # unconfigure srv6 locator + dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"no srv6\"") + + self.teardown_srv6(dvs) + + def test_AddRemoveSrv6MySidUDT46(self, dvs, testlog): + + _, output = dvs.runcmd(f"vtysh -c 'show zebra dplane providers'") + if 'dplane_fpm_sonic' not in output: + pytest.skip("'dplane_fpm_sonic' required for this test is not available, skipping", allow_module_level=True) + + self.setup_srv6(dvs) + + # configure srv6 usid locator + dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"srv6\" -c \"locators\" -c \"locator loc1\" -c \"prefix fc00:0:2::/48 block-len 32 node-len 16 func-bits 16\" -c \"behavior usid\"") + + # create srv6 mysid udt46 behavior + dvs.runcmd("ip -6 route add fc00:0:2:ff05::/128 encap seg6local action End.DT46 vrftable {} dev sr0".format(self.vrf_table_id)) + + # check application database + self.pdb.wait_for_entry("SRV6_MY_SID_TABLE", "32:16:16:0:fc00:0:2:ff05::") + + # verify that the mysid has been programmed into the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", len(self.initial_my_sid_entries) + 1) + + # check ASIC SAI_OBJECT_TYPE_MY_SID_ENTRY database + my_sid = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", self.initial_my_sid_entries) + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY") + (status, fvs) = tbl.get(my_sid) + assert status == True + for fv in fvs: + if fv[0] == "SAI_MY_SID_ENTRY_ATTR_ENDPOINT_BEHAVIOR": + assert fv[1] == "SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_DT46" + if fv[0] == "SAI_MY_SID_ENTRY_ATTR_VRF": + assert fv[1] == self.vrf_id + + # remove srv6 mysid udt46 behavior + dvs.runcmd("ip -6 route del fc00:0:2:ff05::/128 encap seg6local action End.DT46 vrftable {} dev sr0".format(self.vrf_table_id)) + + # check application database + self.pdb.wait_for_deleted_entry("SRV6_MY_SID_TABLE", "32:16:16:0:fc00:0:2:ff05::") + + # verify that the mysid has been removed from the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY", len(self.initial_my_sid_entries)) + + # unconfigure srv6 locator + dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"no srv6\"") + + self.teardown_srv6(dvs) + + +class TestSrv6VpnFpmsyncd: + """ Functionality tests for SRv6 VPN handling in fpmsyncd """ + + def setup_db(self, dvs): + self.pdb = dvs.get_app_db() + self.adb = dvs.get_asic_db() + self.cdb = dvs.get_config_db() + + def create_vrf(self, vrf_name): + table = "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER" + existed_entries = get_exist_entries(self.adb.db_connection, table) + + self.cdb.create_entry("VRF", vrf_name, {"empty": "empty"}) + + self.adb.wait_for_n_keys(table, len(existed_entries) + 1) + return get_created_entry(self.adb.db_connection, table, existed_entries) + + def remove_vrf(self, vrf_name): + self.cdb.delete_entry("VRF", vrf_name) + + def setup_srv6(self, dvs): + self.setup_db(dvs) + + # create vrf + initial_vrf_entries = set(self.adb.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER")) + self.create_vrf("Vrf10") + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", len(initial_vrf_entries) + 1) + + # create dummy interface sr0 + dvs.runcmd("ip link add sr0 type dummy") + dvs.runcmd("ip link set sr0 up") + + def teardown_srv6(self, dvs): + # remove dummy interface sr0 + dvs.runcmd("ip link del sr0 type dummy") + + # remove vrf + initial_vrf_entries = set(self.adb.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER")) + self.remove_vrf("Vrf10") + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", len(initial_vrf_entries) - 1) + + def test_AddRemoveSrv6VpnRouteIpv4(self, dvs, testlog): + + _, output = dvs.runcmd(f"vtysh -c 'show zebra dplane providers'") + if 'dplane_fpm_sonic' not in output: + pytest.skip("'dplane_fpm_sonic' required for this test is not available, skipping", allow_module_level=True) + + self.setup_srv6(dvs) + + dvs.runcmd("vtysh -c \"configure terminal\" -c \"interface lo\" -c \"ip address fc00:0:2::1/128\"") + + # configure srv6 usid locator + dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"srv6\" -c \"locators\" -c \"locator loc1\" -c \"prefix fc00:0:2::/48 block-len 32 node-len 16 func-bits 16\" -c \"behavior usid\"") + + # save exist asic db entries + tunnel_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL") + nexthop_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") + route_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + sidlist_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST") + + # create v4 route with vpn sid + dvs.runcmd("ip route add 192.0.2.0/24 encap seg6 mode encap segs fc00:0:1:e000:: dev sr0 vrf Vrf10") + + # check application database + self.pdb.wait_for_entry("ROUTE_TABLE", "Vrf10:192.0.2.0/24") + + # verify that the route has been programmed into the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", len(route_entries) + 1) + + # get created entries + route_key = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", route_entries) + nexthop_id = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nexthop_entries) + tunnel_id = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL", tunnel_entries) + sidlist_id = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST", sidlist_entries) + + # check ASIC SAI_OBJECT_TYPE_SRV6_SIDLIST database + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST") + (status, fvs) = tbl.get(sidlist_id) + assert status == True + for fv in fvs: + if fv[0] == "SAI_SRV6_SIDLIST_ATTR_SEGMENT_LIST": + assert fv[1] == "1:fc00:0:1:e000::" + elif fv[0] == "SAI_SRV6_SIDLIST_ATTR_TYPE": + assert fv[1] == "SAI_SRV6_SIDLIST_TYPE_ENCAPS_RED" + + # check ASIC SAI_OBJECT_TYPE_ROUTE_ENTRY database + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + (status, fvs) = tbl.get(route_key) + assert status == True + for fv in fvs: + if fv[0] == "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID": + assert fv[1] == nexthop_id + + # check ASIC SAI_OBJECT_TYPE_NEXT_HOP database + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") + (status, fvs) = tbl.get(nexthop_id) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEXT_HOP_ATTR_SRV6_SIDLIST_ID": + assert fv[1] == sidlist_id + elif fv[0] == "SAI_NEXT_HOP_ATTR_TUNNEL_ID": + assert fv[1] == tunnel_id + + # check ASIC SAI_OBJECT_TYPE_TUNNEL database + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL") + (status, fvs) = tbl.get(tunnel_id) + assert status == True + for fv in fvs: + if fv[0] == "SAI_TUNNEL_ATTR_TYPE": + assert fv[1] == "SAI_TUNNEL_TYPE_SRV6" + elif fv[0] == "SAI_TUNNEL_ATTR_ENCAP_SRC_IP": + assert fv[1] == "fc00:0:2::1" + + # remove v4 route with vpn sid + dvs.runcmd("ip route del 192.0.2.0/24 encap seg6 mode encap segs fc00:0:1:e000:: dev sr0 vrf Vrf10") + + # check application database + self.pdb.wait_for_deleted_entry("ROUTE_TABLE", "Vrf10:192.0.2.0/24") + + # verify that the route has been removed from the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", len(nexthop_entries)) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL", len(tunnel_entries)) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", len(route_entries)) + + # unconfigure srv6 locator + dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"no srv6\"") + + self.teardown_srv6(dvs) + + # Add Dummy always-pass test at end as workaroud # for issue when Flaky fail on final test it invokes module tear-down before retrying def test_nonflaky_dummy():