diff --git a/fpmsyncd/fpmlink.cpp b/fpmsyncd/fpmlink.cpp index d90ea8aed6b..0a7f1021ac8 100644 --- a/fpmsyncd/fpmlink.cpp +++ b/fpmsyncd/fpmlink.cpp @@ -281,11 +281,21 @@ void FpmLink::processFpmMessage(fpm_msg_hdr_t* hdr) /* EVPN Type5 Add route processing */ processRawMsg(nl_hdr); } - else if(nl_hdr->nlmsg_type == RTM_NEWNEXTHOP || nl_hdr->nlmsg_type == RTM_DELNEXTHOP) + else if(nl_hdr->nlmsg_type == RTM_NEWSRV6VPNROUTE || nl_hdr->nlmsg_type == RTM_DELSRV6VPNROUTE) + { + /* rtnl api dont support RTM_NEWSRV6VPNROUTE/RTM_DELSRV6VPNROUTE yet. Processing as raw message*/ + processRawMsg(nl_hdr); + } + else if(nl_hdr->nlmsg_type == RTM_NEWNEXTHOP || nl_hdr->nlmsg_type == RTM_DELNEXTHOP) { /* rtnl api dont support RTM_NEWNEXTHOP/RTM_DELNEXTHOP yet. Processing as raw message*/ processRawMsg(nl_hdr); } + else if(nl_hdr->nlmsg_type == RTM_NEWPICCONTEXT || nl_hdr->nlmsg_type == RTM_DELPICCONTEXT) + { + /* rtnl api dont support RTM_NEWPICCONTEXT/RTM_DELPICCONTEXT yet. Processing as raw message*/ + processRawMsg(nl_hdr); + } else { NetDispatcher::getInstance().onNetlinkMessage(msg); diff --git a/fpmsyncd/fpmlink.h b/fpmsyncd/fpmlink.h index dffa823e901..cedeaa967c8 100644 --- a/fpmsyncd/fpmlink.h +++ b/fpmsyncd/fpmlink.h @@ -17,6 +17,10 @@ #define RTM_NEWSRV6LOCALSID 1000 #define RTM_DELSRV6LOCALSID 1001 +#define RTM_NEWPICCONTEXT 2000 +#define RTM_DELPICCONTEXT 2001 +#define RTM_NEWSRV6VPNROUTE 3000 +#define RTM_DELSRV6VPNROUTE 3001 namespace swss { diff --git a/fpmsyncd/routesync.cpp b/fpmsyncd/routesync.cpp index fa2c47b8d7d..014a85ece69 100644 --- a/fpmsyncd/routesync.cpp +++ b/fpmsyncd/routesync.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include using namespace std; using namespace swss; @@ -111,6 +113,8 @@ enum { ROUTE_ENCAP_SRV6_UNSPEC = 0, ROUTE_ENCAP_SRV6_VPN_SID = 1, ROUTE_ENCAP_SRV6_ENCAP_SRC_ADDR = 2, + ROUTE_ENCAP_SRV6_PIC_ID = 3, + ROUTE_ENCAP_SRV6_NH_ID = 4, }; #define MAX_MULTIPATH_NUM 514 @@ -151,6 +155,7 @@ RouteSync::RouteSync(RedisPipeline *pipeline) : m_routeTable(createProducerStateTable(pipeline, APP_ROUTE_TABLE_NAME, true, m_zmqClient)), m_nexthop_groupTable(pipeline, APP_NEXTHOP_GROUP_TABLE_NAME, true), m_label_routeTable(createProducerStateTable(pipeline, APP_LABEL_ROUTE_TABLE_NAME, true, m_zmqClient)), + m_pic_context_groupTable(pipeline, APP_PIC_CONTEXT_TABLE_NAME, true), 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.get(), APP_ROUTE_TABLE_NAME, "bgp", "bgp"), @@ -328,6 +333,32 @@ const char *RouteSync::mySidAction2Str(uint32_t action) } } +bool RouteSync::parseEncapSrv6VpnRoute(struct rtattr *tb, uint32_t &pic_id, + uint32_t &nhg_id) +{ + struct rtattr *tb_encap[256] = {}; + + parseRtAttrNested(tb_encap, 256, tb); + + if (tb_encap[ROUTE_ENCAP_SRV6_PIC_ID]) + pic_id = *((uint32_t *)RTA_DATA(tb_encap[ROUTE_ENCAP_SRV6_PIC_ID])); + else { + SWSS_LOG_ERROR("Failed to find rtattr ROUTE_ENCAP_SRV6_PIC_ID"); + return false; + } + + if (tb_encap[ROUTE_ENCAP_SRV6_NH_ID]) + nhg_id = *((uint32_t *)RTA_DATA(tb_encap[ROUTE_ENCAP_SRV6_NH_ID])); + else { + SWSS_LOG_ERROR("Failed to find rtattr ROUTE_ENCAP_SRV6_NH_ID"); + return false; + } + + SWSS_LOG_INFO("pic_id:%d nhg_id:%d ", pic_id, nhg_id); + + return true; +} + /** * @parseSrv6MySidFormat() - Parses srv6 MySid format * @tb: Pointer to rtattr to look for nested items in. @@ -919,6 +950,39 @@ bool RouteSync::getSrv6SteerRouteNextHop(struct nlmsghdr *h, int received_bytes, return true; } +bool RouteSync::getSrv6VpnRouteNextHop(struct nlmsghdr *h, int received_bytes, + struct rtattr *tb[], uint32_t &pic_id, + uint32_t &nhg_id) +{ + 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) + { + return parseEncapSrv6VpnRoute(tb[RTA_ENCAP], pic_id, nhg_id); + } + + SWSS_LOG_DEBUG("Rx MsgType:%d encap:%d pic_id:%d nhg_id:%d", + h->nlmsg_type, encap, pic_id, + nhg_id); + } + else + { + /* This is a multipath route */ + SWSS_LOG_NOTICE("Multipath SRv6 routes aren't supported"); + return false; + } + + return false; +} vector RouteTableFieldValueTupleWrapper::fieldValueTupleVector() { @@ -1456,6 +1520,235 @@ void RouteSync::onSrv6MySidMsg(struct nlmsghdr *h, int len) return; } + + +void RouteSync::onSrv6VpnRouteMsg(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_NEWSRV6VPNROUTE ? "add" : "del"); + + if (nlmsg_type != RTM_NEWSRV6VPNROUTE && nlmsg_type != RTM_DELSRV6VPNROUTE) + { + 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; + } + + uint32_t pic_id; + uint32_t nhg_id; + bool ret; + + ret = getSrv6VpnRouteNextHop(h, len, tb, pic_id, nhg_id); + if(!ret){ + return ; + } + + if (nlmsg_type == RTM_DELSRV6VPNROUTE) + { + SWSS_LOG_INFO("RouteTable del msg: %s", routeTableKey); + delWithWarmRestart( + RouteTableFieldValueTupleWrapper{std::move(routeTableKey), std::string()}, + *m_routeTable); + return; + } + else if (nlmsg_type == RTM_NEWSRV6VPNROUTE) + { + auto nhg_it = m_nh_groups.find(nhg_id); + auto pic_it = m_nh_groups.find(pic_id); + if(nhg_it == m_nh_groups.end() || pic_it == m_nh_groups.end()) + { + SWSS_LOG_ERROR("Can not find pic or nexthop for vpn route :%s", routeTableKey); + return ; + } + + NextHopGroup &nhg = nhg_it->second; + NextHopGroup &pic = pic_it->second; + if(nhg.group.size() == 0) + { + vector fvVector; + struct NextHopField nhField; + getPicContextGroupFields(pic, nhField); + FieldValueTuple nh("nexthop", nhField.nexthops.c_str()); + FieldValueTuple vpn_sid("vpn_sid", nhField.vpn_sid.c_str()); + FieldValueTuple seg_srcs("seg_src", nhField.seg_srcs.c_str()); + FieldValueTuple pic_context_id("pic_context_id", ""); + FieldValueTuple nexthop_group("nexthop_group", ""); + fvVector.push_back(nh); + fvVector.push_back(vpn_sid); + fvVector.push_back(seg_srcs); + fvVector.push_back(pic_context_id); + fvVector.push_back(nexthop_group); + //Using route-table only for single next-hop + string nexthops, ifnames, weights; + getNextHopGroupFields(nhg, nexthops, ifnames, weights); + FieldValueTuple intf("ifname", ifnames.c_str()); + fvVector.push_back(intf); + if(!weights.empty()) + { + FieldValueTuple wg("weight", weights.c_str()); + fvVector.push_back(wg); + } + m_routeTable->set(routeTableKey, fvVector); + + SWSS_LOG_DEBUG("NextHop group id %d is a single nexthop address. Filling the route table %s with nexthop and ifname", nhg_id, destipprefix); + } + else{ + vector fvVectorVpnRoute; + FieldValueTuple pic_context_id("pic_context_id", getNextHopGroupKeyAsString(pic_id)); + fvVectorVpnRoute.push_back(pic_context_id); + + vector fvVector; + struct NextHopField nhField; + string key = getNextHopGroupKeyAsString(nhg_id); + getPicContextGroupFields(pic, nhField); + FieldValueTuple seg_srcs("seg_src", nhField.seg_srcs.c_str()); + fvVector.push_back(seg_srcs); + m_nexthop_groupTable.set(key.c_str(), fvVector); + + FieldValueTuple nexthop_group("nexthop_group", getNextHopGroupKeyAsString(nhg_id)); + fvVectorVpnRoute.push_back(nexthop_group); + + FieldValueTuple nh("nexthop", ""); + FieldValueTuple vpn_sid("vpn_sid", ""); + FieldValueTuple seg_srcs_route("seg_src", ""); + FieldValueTuple intf("ifname", ""); + fvVectorVpnRoute.push_back(nh); + fvVectorVpnRoute.push_back(vpn_sid); + fvVectorVpnRoute.push_back(seg_srcs_route); + fvVectorVpnRoute.push_back(intf); + m_routeTable->set(routeTableKey, fvVectorVpnRoute); + } + } + + return; +} uint16_t RouteSync::getEncapType(struct nlmsghdr *h) { int len; @@ -1535,17 +1828,24 @@ void RouteSync::onMsgRaw(struct nlmsghdr *h) if ((h->nlmsg_type != RTM_NEWROUTE) && (h->nlmsg_type != RTM_DELROUTE) - && (h->nlmsg_type != RTM_NEWSRV6LOCALSID) - && (h->nlmsg_type != RTM_DELSRV6LOCALSID) && (h->nlmsg_type != RTM_NEWNEXTHOP) && (h->nlmsg_type != RTM_DELNEXTHOP) - ) + && (h->nlmsg_type != RTM_NEWPICCONTEXT) + && (h->nlmsg_type != RTM_DELPICCONTEXT) + && (h->nlmsg_type != RTM_NEWSRV6VPNROUTE) + && (h->nlmsg_type != RTM_DELSRV6VPNROUTE) + && (h->nlmsg_type != RTM_NEWSRV6LOCALSID) + && (h->nlmsg_type != RTM_DELSRV6LOCALSID)) return; if(h->nlmsg_type == RTM_NEWNEXTHOP || h->nlmsg_type == RTM_DELNEXTHOP) { len = (int)(h->nlmsg_len - NLMSG_LENGTH(sizeof(struct nhmsg))); } + else if(h->nlmsg_type == RTM_NEWPICCONTEXT || h->nlmsg_type == RTM_DELPICCONTEXT) + { + len = (int)(h->nlmsg_len - NLMSG_LENGTH(sizeof(struct nhmsg))); + } else { len = (int)(h->nlmsg_len - NLMSG_LENGTH(sizeof(struct ndmsg))); @@ -1564,7 +1864,17 @@ void RouteSync::onMsgRaw(struct nlmsghdr *h) onNextHopMsg(h, len); return; } - + if(h->nlmsg_type == RTM_NEWPICCONTEXT || h->nlmsg_type == RTM_DELPICCONTEXT) + { + onPicContextMsg(h, len); + return; + } + if ((h->nlmsg_type == RTM_NEWSRV6VPNROUTE) + || (h->nlmsg_type == RTM_DELSRV6VPNROUTE)) + { + onSrv6VpnRouteMsg(h, len); + return; + } if ((h->nlmsg_type == RTM_NEWSRV6LOCALSID) || (h->nlmsg_type == RTM_DELSRV6LOCALSID)) { @@ -1751,16 +2061,17 @@ void RouteSync::onRouteMsg(int nlmsg_type, struct nl_object *obj, char *vrf) NextHopGroup& nhg = itg->second; if(nhg.group.size() == 0) { - // Using route-table only for single next-hop - string nexthops = nhg.nexthop.empty() ? (rtnl_route_get_family(route_obj) == AF_INET ? "0.0.0.0" : "::") : nhg.nexthop; - string ifnames, weights; - - getNextHopGroupFields(nhg, nexthops, ifnames, weights, rtnl_route_get_family(route_obj)); + // Using route-table only for single next-hop + string nexthops = nhg.nexthop.empty() ? (rtnl_route_get_family(route_obj) == AF_INET ? "0.0.0.0" : "::") : nhg.nexthop; + string ifnames, weights; - fvw.nexthop = std::move(nexthops); - fvw.ifname = std::move(ifnames); + getNextHopGroupFields(nhg, nexthops, ifnames, weights, rtnl_route_get_family(route_obj)); + fvw.nexthop = std::move(nexthops); + fvw.ifname = std::move(ifnames); + if (!weights.empty()) + fvw.weight = std::move(weights); - SWSS_LOG_DEBUG("NextHop group id %d is a single nexthop address. Filling the route table %s with nexthop and ifname", nhg_id, destipprefix); + SWSS_LOG_DEBUG("NextHop group id %d is a single nexthop address. Filling the route table %s with nexthop and ifname", nhg_id, destipprefix); } else { @@ -1906,14 +2217,13 @@ void RouteSync::onNextHopMsg(struct nlmsghdr *h, int len) { NextHopGroup &nhg = it->second; nhg.group = group; - if (nhg.installed) - { - updateNextHopGroupDb(nhg); - } + updateNextHopGroupDb(nhg); } else { + NextHopGroup nhg = NextHopGroup(id, group); m_nh_groups.insert({id, NextHopGroup(id, group)}); + updateNextHopGroupDb(nhg); } } else @@ -1966,6 +2276,219 @@ void RouteSync::onNextHopMsg(struct nlmsghdr *h, int len) return; } +void netlink_parse_rtattr_nested(struct rtattr **tb, int max, + const struct rtattr *rta) +{ + netlink_parse_rtattr(tb, max, (struct rtattr *)RTA_DATA(rta), (int)RTA_PAYLOAD(rta)); +} + +int RouteSync::parse_encap_seg6(const struct rtattr *tb, struct in6_addr *segs, + struct in6_addr *src) +{ + struct rtattr *tb_encap[256] = {}; + struct seg6_iptunnel_encap_pri *ipt = NULL; + struct in6_addr *segments = NULL; + + netlink_parse_rtattr_nested(tb_encap, 256, tb); + + if (tb_encap[SEG6_IPTUNNEL_SRH]) { + ipt = (struct seg6_iptunnel_encap_pri *) + RTA_DATA(tb_encap[SEG6_IPTUNNEL_SRH]); + segments = ipt->srh[0].segments; + *segs = segments[0]; + *src = ipt->src; + return 1; + } + + return 0; +} + +void RouteSync::onPicContextMsg(struct nlmsghdr *h, int len) +{ + int nlmsg_type = h->nlmsg_type; + uint32_t id = 0; + unsigned char addr_family; + int32_t ifindex = -1, grp_count = 0; + string ifname; + struct nhmsg *nhm = NULL; + struct rtattr *tb[NHA_MAX + 1] = {}; + struct nexthop_grp grp[MAX_MULTIPATH_NUM]; + struct in_addr ipv4 = {0}; + struct in6_addr ipv6 = {0}; + char gateway[INET6_ADDRSTRLEN] = {0}; + char seg6[INET6_ADDRSTRLEN] = {0}; + char seg6_srcs[INET6_ADDRSTRLEN] = {0}; + char ifname_unknown[IFNAMSIZ] = "unknown"; + uint16_t encap_type; + vector fvVector; + + SWSS_LOG_INFO("type %d len %d", nlmsg_type, len); + if ((nlmsg_type != RTM_NEWPICCONTEXT) + && (nlmsg_type != RTM_DELPICCONTEXT)) + { + return; + } + + nhm = (struct nhmsg *)NLMSG_DATA(h); + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-align" + struct rtattr* rta = NHA_RTA(nhm); + #pragma GCC diagnostic pop + + netlink_parse_rtattr(tb, NHA_MAX, rta, len); + + if (!tb[NHA_ID]) { + SWSS_LOG_ERROR( + "Nexthop group without an ID received from the zebra"); + return; + } + + /* We use the ID key'd nhg table for kernel updates */ + id = *((uint32_t *)RTA_DATA(tb[NHA_ID])); + + addr_family = nhm->nh_family; + + if (nlmsg_type == RTM_NEWPICCONTEXT) + { + if(tb[NHA_GROUP]) + { + SWSS_LOG_INFO("New nexthop group message!"); + fvVector.emplace_back("nexthop_type", "pic_context_group"); + struct nexthop_grp *nha_grp = (struct nexthop_grp *)RTA_DATA(tb[NHA_GROUP]); + grp_count = (int)(RTA_PAYLOAD(tb[NHA_GROUP]) / sizeof(*nha_grp)); + + if(grp_count > MAX_MULTIPATH_NUM) + grp_count = MAX_MULTIPATH_NUM; + + fvVector.emplace_back("nexthop_count", to_string(grp_count)); + string nhid_list; + string weight_list; + for (int i = 0; i < grp_count; i++) { + nhid_list += to_string(nha_grp[i].id); + grp[i].id = nha_grp[i].id; + /* + The minimum weight value is 1, but kernel store it as zero (https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/iproute.c?h=v5.19.0#n1028). + Adding one to weight to write the right value to the database. + */ + weight_list += to_string(nha_grp[i].weight + 1); + grp[i].weight = nha_grp[i].weight + 1; + if (i + 1 < grp_count) + { + nhid_list += NHG_DELIMITER; + weight_list += NHG_DELIMITER; + } + } + fvVector.emplace_back("nh_id", nhid_list); + fvVector.emplace_back("weight", weight_list); + } + else + { + if (tb[NHA_GATEWAY]) + { + if (addr_family == AF_INET) + { + memcpy(&ipv4, (void *)RTA_DATA(tb[NHA_GATEWAY]), 4); + inet_ntop(AF_INET, &ipv4, gateway, INET_ADDRSTRLEN); + } + else if (addr_family == AF_INET6) + { + memcpy(&ipv6, (void *)RTA_DATA(tb[NHA_GATEWAY]), 16); + inet_ntop(AF_INET6, &ipv6, gateway, INET6_ADDRSTRLEN); + } + else + { + SWSS_LOG_ERROR( + "Unexpected nexthop address family"); + return; + } + fvVector.emplace_back("nexthop", string(gateway)); + } + + if(tb[NHA_OIF]) + { + ifindex = *((int32_t *)RTA_DATA(tb[NHA_OIF])); + char if_name[IFNAMSIZ] = {0}; + if (!getIfName(ifindex, if_name, IFNAMSIZ)) + { + strcpy(if_name, ifname_unknown); + } + ifname = string(if_name); + if (ifname == "eth0" || ifname == "docker0") + { + SWSS_LOG_DEBUG("Skip routes to inteface: %s id[%d]", ifname.c_str(), id); + return; + } + fvVector.emplace_back("ifname", ifname); + } + if(tb[NHA_ENCAP] && tb[NHA_ENCAP_TYPE]) + { + struct in6_addr seg6_segs = {0}; + struct in6_addr seg6_src = {0}; + encap_type = *((uint16_t *)RTA_DATA(tb[NHA_ENCAP_TYPE])); + switch (encap_type) + { + case LWTUNNEL_ENCAP_SEG6: + fvVector.emplace_back("nexthop_type", "srv6"); + parse_encap_seg6(tb[NHA_ENCAP], &seg6_segs, &seg6_src); + inet_ntop(AF_INET6, &seg6_segs, seg6, INET6_ADDRSTRLEN); + inet_ntop(AF_INET6, &seg6_src, seg6_srcs, INET6_ADDRSTRLEN); + fvVector.emplace_back("vpn_sid", seg6); + fvVector.emplace_back("seg_src", seg6_srcs); + + break; + default: + SWSS_LOG_ERROR("unknown encap type: %d id[%d]", encap_type, id); + } + + SWSS_LOG_INFO("seg6:%s seg6_srcs:%s", seg6, seg6_srcs); + } + else + { + fvVector.emplace_back("nexthop_type", "nh"); + fvVector.emplace_back("vpn_sid", ""); + fvVector.emplace_back("seg_src", ""); + } + + } + + if(grp_count) + { + vector> group; + for(int i = 0; i < grp_count; i++) + { + group.push_back(std::make_pair(grp[i].id, grp[i].weight)); + } + auto it = m_nh_groups.find(id); + if(it != m_nh_groups.end()) + { + NextHopGroup &nhg = it->second; + nhg.group = group; + updatePicContextGroupDb(nhg); + } + else + { + NextHopGroup nhg = NextHopGroup(id, group); + m_nh_groups.insert({id, nhg}); + updatePicContextGroupDb(nhg); + } + } + else + { + SWSS_LOG_DEBUG("Received: id[%d], if[%d/%s] address[%s]", id, ifindex, ifname.c_str(), gateway); + NextHopGroup nhg = NextHopGroup(id, string(gateway), ifname, seg6, seg6_srcs); + m_nh_groups.insert({id, nhg}); + } + } + else if (nlmsg_type == RTM_DELPICCONTEXT) + { + SWSS_LOG_DEBUG("NextHopGroup del event: %d", id); + deletePicContextGroup(id); + } + + return; +} + /* * Handle label route * @arg nlmsg_type Netlink message type @@ -2700,6 +3223,26 @@ void RouteSync::deleteNextHopGroup(uint32_t nh_id) m_nh_groups.erase(git); } +void RouteSync::deletePicContextGroup(uint32_t nh_id) +{ + auto git = m_nh_groups.find(nh_id); + if(git == m_nh_groups.end()) + { + SWSS_LOG_INFO("Nexthop not found: %d", nh_id); + return; + } + + NextHopGroup& nhg = git->second; + + if(nhg.installed) + { + string key = getNextHopGroupKeyAsString(nh_id); + m_pic_context_groupTable.del(key.c_str()); + SWSS_LOG_DEBUG("NextHopGroup table del: key [%s]", key.c_str()); + } + m_nh_groups.erase(git); +} + /* * update the nexthop group table in database * @arg nhg the nexthop group @@ -2727,6 +3270,29 @@ void RouteSync::updateNextHopGroupDb(const NextHopGroup& nhg) setTable(fvw, m_nexthop_groupTable); } +void RouteSync::updatePicContextGroupDb(const NextHopGroup& nhg) +{ + vector fvVector; + struct NextHopField nhField; + string key = getNextHopGroupKeyAsString(nhg.id); + getPicContextGroupFields(nhg, nhField); + + FieldValueTuple nh("nexthop", nhField.nexthops.c_str()); + FieldValueTuple ifname("ifname", nhField.ifnames.c_str()); + FieldValueTuple vpn_sid("vpn_sid", nhField.vpn_sid.c_str()); + FieldValueTuple seg_srcs("seg_src", nhField.seg_srcs.c_str()); + FieldValueTuple wg("weight", nhField.weights.c_str()); + fvVector.push_back(nh); + fvVector.push_back(ifname); + fvVector.push_back(vpn_sid); + fvVector.push_back(seg_srcs); + fvVector.push_back(wg); + + //TODO: Take care of warm reboot + m_pic_context_groupTable.set(key.c_str(), fvVector); +} + + /* * generate the database fields. * @arg nhg the nexthop group @@ -2774,3 +3340,61 @@ void RouteSync::getNextHopGroupFields(const NextHopGroup& nhg, string& nexthops, } } } + + +/* + * generate the database fields. + * @arg pic context + * + */ +void RouteSync::getPicContextGroupFields(const NextHopGroup& nhg, struct NextHopField& nhField, uint8_t af /*= AF_INET*/) +{ + if(nhg.group.size() == 0) + { + if(!nhg.nexthop.empty()) + { + nhField.nexthops = nhg.nexthop; + } + else + { + nhField.nexthops = af == AF_INET ? "0.0.0.0" : "::"; + } + nhField.ifnames = nhg.intf; + nhField.vni_label += nhg.vni_label.empty() ? ("") : nhg.vni_label; + nhField.vpn_sid += nhg.vpn_sid.empty() ? ("") : nhg.vpn_sid; + nhField.seg_srcs += nhg.seg_src.empty() ? ("") : nhg.seg_src; + } + else + { + int i = 0; + for(const auto &nh : nhg.group) + { + uint32_t id = nh.first; + auto itr = m_nh_groups.find(id); + if(itr == m_nh_groups.end()) + { + SWSS_LOG_INFO("NextHop group is incomplete: %d", nhg.id); + return; + } + + NextHopGroup& nhgr = itr->second; + string weight = to_string(nh.second); + if(i) + { + nhField.nexthops += NHG_DELIMITER; + nhField.ifnames += NHG_DELIMITER; + nhField.vni_label += NHG_DELIMITER; + nhField.vpn_sid += NHG_DELIMITER; + nhField.weights += NHG_DELIMITER; + nhField.seg_srcs += NHG_DELIMITER; + } + nhField.nexthops += nhgr.nexthop.empty() ? (af == AF_INET ? "0.0.0.0" : "::") : nhgr.nexthop; + nhField.ifnames += nhgr.intf.empty() ? ("") : nhgr.intf; + nhField.vni_label += nhgr.vni_label.empty() ? ("") : nhgr.vni_label; + nhField.vpn_sid += nhgr.vpn_sid.empty() ? ("") : nhgr.vpn_sid; + nhField.weights += weight; + nhField.seg_srcs += nhgr.seg_src.empty() ? ("") : nhgr.seg_src; + ++i; + } + } +} diff --git a/fpmsyncd/routesync.h b/fpmsyncd/routesync.h index 9582d005dc2..956a59b3184 100644 --- a/fpmsyncd/routesync.h +++ b/fpmsyncd/routesync.h @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -26,6 +27,7 @@ using namespace std; /* Parse the Raw netlink msg */ extern void netlink_parse_rtattr(struct rtattr **tb, int max, struct rtattr *rta, int len); +extern void netlink_parse_rtattr_nested(struct rtattr **tb, int max, const struct rtattr *rta); namespace swss { @@ -35,8 +37,21 @@ struct NextHopGroup { string nexthop; string intf; bool installed; + string vni_label; + string vpn_sid; + string seg_src; NextHopGroup(uint32_t id, const string& nexthop, const string& interface) : installed(false), id(id), nexthop(nexthop), intf(interface) {}; NextHopGroup(uint32_t id, const vector>& group) : installed(false), id(id), group(group) {}; + NextHopGroup(uint32_t id, const string& nexthop, const string& interface, + const string& vpnsid, const string& segsrc) : installed(false), id(id), nexthop(nexthop), intf(interface), vpn_sid(vpnsid), seg_src(segsrc) {}; +}; + + +struct seg6_iptunnel_encap_pri { + int mode; + char segment_name[64]; + struct in6_addr src; + struct ipv6_sr_hdr srh[0]; }; /* Path to protocol name database provided by iproute2 */ @@ -248,6 +263,7 @@ class RouteSync : public NetMsg struct nl_sock *m_nl_sock; /* nexthop group table */ ProducerStateTable m_nexthop_groupTable; + ProducerStateTable m_pic_context_groupTable; map m_nh_groups; /* SID list to refcount */ map m_srv6_sidlist_refcnt; @@ -264,6 +280,7 @@ 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 parseEncapSrv6VpnRoute(struct rtattr *tb, uint32_t &pic_id, uint32_t &nhg_id); bool parseSrv6MySid(struct rtattr *tb[], string &block_len, string &node_len, string &func_len, @@ -289,6 +306,9 @@ class RouteSync : public NetMsg /* Handle SRv6 MySID */ void onSrv6MySidMsg(struct nlmsghdr *h, int len); + /* Handle vpn route */ + void onSrv6VpnRouteMsg(struct nlmsghdr *h, int len); + /* Handle vnet route */ void onVnetRouteMsg(int nlmsg_type, struct nl_object *obj, string vnet); @@ -312,6 +332,8 @@ class RouteSync : public NetMsg bool getSrv6SteerRouteNextHop(struct nlmsghdr *h, int received_bytes, struct rtattr *tb[], string &vpn_sid, string &src_addr); + bool getSrv6VpnRouteNextHop(struct nlmsghdr *h, int received_bytes, + struct rtattr *tb[], uint32_t &pic_id,uint32_t &nhg_id); /* Get next hop list */ void getNextHopList(struct rtnl_route *route_obj, string& gw_list, @@ -342,12 +364,27 @@ class RouteSync : public NetMsg /* Handle Nexthop message */ void onNextHopMsg(struct nlmsghdr *h, int len); + void onPicContextMsg(struct nlmsghdr *h, int len); + int parse_encap_seg6(const struct rtattr *tb, struct in6_addr *segs, struct in6_addr *src); /* Get next hop group key */ const string getNextHopGroupKeyAsString(uint32_t id) const; void installNextHopGroup(uint32_t nh_id); void deleteNextHopGroup(uint32_t nh_id); + void deletePicContextGroup(uint32_t nh_id); void updateNextHopGroupDb(const NextHopGroup& nhg); + void updatePicContextGroupDb(const NextHopGroup& nhg); void getNextHopGroupFields(const NextHopGroup& nhg, string& nexthops, string& ifnames, string& weights, uint8_t af = AF_INET); + void getPicContextGroupFields(const NextHopGroup& nhg, struct NextHopField& nhField, uint8_t af = AF_INET); + +}; +struct NextHopField { + string nexthops; + string ifnames; + string vni_label; + string vpn_sid; + string mpls_nh; + string weights; + string seg_srcs; }; } diff --git a/tests/mock_tests/fpmsyncd/test_routesync.cpp b/tests/mock_tests/fpmsyncd/test_routesync.cpp index 7e73ce66f32..00931ab5a5e 100644 --- a/tests/mock_tests/fpmsyncd/test_routesync.cpp +++ b/tests/mock_tests/fpmsyncd/test_routesync.cpp @@ -5,6 +5,7 @@ #include "mock_table.h" #define private public #include "fpmsyncd/routesync.h" +#include "fpmsyncd/fpmlink.h" #undef private #include @@ -12,11 +13,14 @@ #include #include #include +#include +#include #include using namespace swss; using namespace testing; +using namespace ut_fpmsyncd; #define MAX_PAYLOAD 1024 @@ -631,12 +635,13 @@ TEST_F(FpmSyncdResponseTest, TestDeleteNextHopGroup) EXPECT_EQ(m_mockRouteSync.m_nh_groups.find(999), m_mockRouteSync.m_nh_groups.end()); } -struct nlmsghdr* createNewNextHopMsgHdr(const vector>& group_members, uint32_t id) { +struct nlmsghdr* createNewNextHopMsgHdr(const vector>& group_members, uint32_t id, + uint32_t nlmsg_type = RTM_NEWNEXTHOP) { struct nlmsghdr *nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD)); memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD)); // Set header - nlh->nlmsg_type = RTM_NEWNEXTHOP; + nlh->nlmsg_type = static_cast(nlmsg_type); nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE; nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)); @@ -1207,7 +1212,6 @@ TEST_F(WarmRestartRouteSyncTest, TestRouteHandlingWarmRestartInProgress) Table routeTable(m_db.get(), APP_ROUTE_TABLE_NAME); vector result; EXPECT_FALSE(routeTable.get("192.168.10.0/24", result)); - } TEST_F(WarmRestartRouteSyncTest, TestVrfRouteHandlingWarmRestartInProgress) @@ -1251,4 +1255,985 @@ TEST_F(WarmRestartRouteSyncTest, TestRouteDeleteHandlingWarmRestartInProgress) // Verify: Route should still be in table (deletion handled by warm restart helper) EXPECT_TRUE(routeTable.get("192.168.12.0/24", result)); + +} + + +TEST_F(FpmSyncdResponseTest, TestSrv6VpnRoute_Add_NHG) +{ + std::string dst_prefix = "2001:db8::/64"; + std::string encap_src = "2001:db8::1"; + std::string vpn_sid = "2001:db8::2"; + uint16_t vrf_table_id = 100; + uint32_t pic_id = 67; + uint32_t nhg_id = 12; + + /* Create IpAddress and IpPrefix Object */ + IpAddress _encap_src_obj = IpAddress(encap_src); + IpAddress _vpn_sid_obj = IpAddress(vpn_sid); + IpPrefix _dst_obj = IpPrefix(dst_prefix); + + /* Create Srv6 Vpn route netlink msg */ + struct nlmsg *nl_obj = create_srv6_vpn_route_nlmsg( + RTM_NEWSRV6VPNROUTE, + &_dst_obj, + &_encap_src_obj, + &_vpn_sid_obj, + vrf_table_id, + 64, + AF_INET6, + RTN_UNICAST, + nhg_id, + pic_id); + if (!nl_obj) { + ADD_FAILURE() << "Failed to create SRv6 VPN Route message"; + return; + } + + /* Mock using getIfName to return vrfname */ + EXPECT_CALL(m_mockRouteSync, getIfName(vrf_table_id, _, _)) + .Times(2) + .WillRepeatedly(DoAll( + [](int32_t, char* ifname, size_t size) { + strncpy(ifname, "Vrf100", size); + ifname[size-1] = '\0'; + }, + Return(true) + )); + + /* for case: not found pic_it or nhg_it + * nothing to check and would return. + */ + m_mockRouteSync.onSrv6VpnRouteMsg(&nl_obj->n, nl_obj->n.nlmsg_len); + + /* Construct PIC Group */ + NextHopGroup pic_group(pic_id, encap_src, "sr0"); + pic_group.vpn_sid = vpn_sid; + pic_group.seg_src = encap_src; + m_mockRouteSync.m_nh_groups.insert({pic_id, pic_group}); + + /* Construct NHG */ + vector> nhg_data; + nhg_data.push_back(make_pair(1, 1)); + NextHopGroup nh_group(nhg_id, nhg_data); + nh_group.nexthop = "fe80::1"; + nh_group.intf = "eth0"; + m_mockRouteSync.m_nh_groups.insert({nhg_id, nh_group}); + + /* Call the target function */ + m_mockRouteSync.onSrv6VpnRouteMsg(&nl_obj->n, nl_obj->n.nlmsg_len); + + // Check whether use the m_routeTable.set + Table route_table(m_db.get(), APP_ROUTE_TABLE_NAME); + std::vector fvs; + std::string key = "Vrf100:" + dst_prefix; + + /* Check the results */ + bool found = route_table.get(key, fvs); + EXPECT_TRUE(found); + // Check each attr value + for (const auto& fv : fvs) { + if (fvField(fv) == "pic_context_id") { + EXPECT_EQ(fvValue(fv), "67"); + } else if (fvField(fv) == "nexthop_group") { + EXPECT_EQ(fvValue(fv), "12"); + } + } + + /* Check whether use the m_nexthop_groupTable.set */ + Table nhg_table(m_db.get(), APP_NEXTHOP_GROUP_TABLE_NAME); + std::vector fvs_nhg; + std::string key_nhg = m_mockRouteSync.getNextHopGroupKeyAsString(nhg_id); + + /* Check the result */ + bool found_nhg = nhg_table.get(key_nhg.c_str(), fvs_nhg); + EXPECT_TRUE(found_nhg); + // Check each attr value + for (const auto& fv_nhg : fvs_nhg) { + if (fvField(fv_nhg) == "seg_src") { + EXPECT_EQ(fvValue(fv_nhg), "2001:db8::1"); + } + } + + free(nl_obj); } + +TEST_F(FpmSyncdResponseTest, TestSrv6VpnRoute_NH) +{ + std::string dst_prefix = "2001:db8:1::/64"; + std::string encap_src = "2001:db8:1::1"; + std::string vpn_sid = "2001:db8:1::2"; + uint16_t vrf_table_id = 101; + uint32_t pic_id = 89; + uint32_t nhg_id = 34; + + /* Create IpAddress and IpPrefix Object */ + IpAddress _encap_src_obj = IpAddress(encap_src); + IpAddress _vpn_sid_obj = IpAddress(vpn_sid); + IpPrefix _dst_obj = IpPrefix(dst_prefix); + + /* Mock using getIfName to return vrfname */ + EXPECT_CALL(m_mockRouteSync, getIfName(vrf_table_id, _, _)) + .Times(11) + .WillRepeatedly(DoAll( + [](int32_t, char* ifname, size_t size) { + strncpy(ifname, "Vrf101", size); + ifname[size-1] = '\0'; + }, + Return(true) + )); + + /*-----------------------------------------------*/ + /* Test 1: Create and process ADD message for NH */ + /*-----------------------------------------------*/ + { + /* Create Srv6 Vpn route netlink msg with ADD cmd */ + struct nlmsg *nl_obj = create_srv6_vpn_route_nlmsg( + RTM_NEWSRV6VPNROUTE, + &_dst_obj, + &_encap_src_obj, + &_vpn_sid_obj, + vrf_table_id, + 64, + AF_INET6, + RTN_UNICAST, + nhg_id, + pic_id); + if (!nl_obj) { + ADD_FAILURE() << "Failed to create SRv6 VPN Route message"; + return; + } + + /* Construct PIC Group */ + NextHopGroup pic_group(pic_id, encap_src, "sr0"); + pic_group.vpn_sid = vpn_sid; + pic_group.seg_src = encap_src; + m_mockRouteSync.m_nh_groups.insert({pic_id, pic_group}); + + /* Construct NHG with no group */ + NextHopGroup nh_group(nhg_id, "fe80::2", "eth1"); + nh_group.nexthop = "fe80::2"; + nh_group.intf = "eth1"; + m_mockRouteSync.m_nh_groups.insert({nhg_id, nh_group}); + + /* Call the target function */ + m_mockRouteSync.onSrv6VpnRouteMsg(&nl_obj->n, nl_obj->n.nlmsg_len); + + /* Check whether use the m_routeTable.set */ + Table route_table(m_db.get(), APP_ROUTE_TABLE_NAME); + std::vector fvs; + std::string key = "Vrf101:" + dst_prefix; + + /* Check the result */ + bool found = route_table.get(key, fvs); + EXPECT_TRUE(found); + // Check each attr value + for (const auto& fv : fvs) { + if (fvField(fv) == "nexthop") { + EXPECT_EQ(fvValue(fv), "2001:db8:1::1"); + } else if (fvField(fv) == "vpn_sid") { + EXPECT_EQ(fvValue(fv), "2001:db8:1::2"); + } else if (fvField(fv) == "seg_src") { + EXPECT_EQ(fvValue(fv), "2001:db8:1::1"); + } else if (fvField(fv) == "ifname") { + EXPECT_EQ(fvValue(fv), "eth1"); + } + } + + /* Free the memory */ + free(nl_obj); + } + + /*-----------------------------------------------*/ + /* Test 2: Create and process DEL message for NH */ + /*-----------------------------------------------*/ + { + /* Create Srv6 Vpn route netlink msg with DEL cmd */ + struct nlmsg *del_nl_obj = create_srv6_vpn_route_nlmsg( + RTM_DELSRV6VPNROUTE, + &_dst_obj, + &_encap_src_obj, + &_vpn_sid_obj, + vrf_table_id, + 64, + AF_INET6, + RTN_UNICAST, + nhg_id, + pic_id); + if (!del_nl_obj) { + ADD_FAILURE() << "Failed to create SRv6 DEL Route message"; + return; + } + + /* Call the target function for DEL */ + m_mockRouteSync.onSrv6VpnRouteMsg(&del_nl_obj->n, del_nl_obj->n.nlmsg_len); + + /* Check whether use the m_routeTable.set */ + Table route_table(m_db.get(), APP_ROUTE_TABLE_NAME); + std::vector fvs; + std::string key = "Vrf101:" + dst_prefix; + + /* Check whether the route was deleted */ + bool found = route_table.get(key, fvs); + EXPECT_FALSE(found); + + free(del_nl_obj); + } + + /*-----------------------------*/ + /* Test 3: Test other branches */ + /*-----------------------------*/ + // Case 1: no DST + { + /* Create a route message without RTA_DST */ + struct nlmsg *nl_obj_no_dst = create_srv6_vpn_route_nlmsg( + RTM_NEWSRV6VPNROUTE, + nullptr, + &_encap_src_obj, + &_vpn_sid_obj, + vrf_table_id, + 64, + AF_INET6, + RTN_UNICAST, + nhg_id, + pic_id); + if (!nl_obj_no_dst) { + ADD_FAILURE() << "Failed to create SRv6 VPN Route message"; + return; + } + + /* Call the target function */ + m_mockRouteSync.onSrv6VpnRouteMsg(&nl_obj_no_dst->n, nl_obj_no_dst->n.nlmsg_len); + + free(nl_obj_no_dst); + } + + // Case 2: AF_INET6 with too large dst bitlen + { + /* Create a route message with too large dst bitlen */ + struct nlmsg *nl_obj_large_bitlen = create_srv6_vpn_route_nlmsg( + RTM_NEWSRV6VPNROUTE, + &_dst_obj, + &_encap_src_obj, + &_vpn_sid_obj, + vrf_table_id, + 130, + AF_INET6, + RTN_UNICAST, + nhg_id, + pic_id); + if (!nl_obj_large_bitlen) { + ADD_FAILURE() << "Failed to create SRv6 VPN Route message"; + return; + } + + /* Call the target function */ + m_mockRouteSync.onSrv6VpnRouteMsg(&nl_obj_large_bitlen->n, nl_obj_large_bitlen->n.nlmsg_len); + + free(nl_obj_large_bitlen); + } + + // Case 3: AF_INET6 with max dst bitlen + { + /* Create a route message with max dst bitlen */ + struct nlmsg *nl_obj_max_bitlen = create_srv6_vpn_route_nlmsg( + RTM_NEWSRV6VPNROUTE, + &_dst_obj, + &_encap_src_obj, + &_vpn_sid_obj, + vrf_table_id, + 128, + AF_INET6, + RTN_UNICAST, + nhg_id, + pic_id); + if (!nl_obj_max_bitlen) { + ADD_FAILURE() << "Failed to create SRv6 VPN Route message"; + return; + } + + /* Call the target function */ + m_mockRouteSync.onSrv6VpnRouteMsg(&nl_obj_max_bitlen->n, nl_obj_max_bitlen->n.nlmsg_len); + + free(nl_obj_max_bitlen); + } + + // Case 4: wrong nlmsg_type, neither RTM_NEWSRV6VPNROUTE nor RTM_DELSRV6VPNROUTE + { + /* Create a route message with wrong nlmsg_type */ + struct nlmsg *nl_obj_wrong_nlmsg_type = create_srv6_vpn_route_nlmsg( + RTM_NEWSRV6LOCALSID, + &_dst_obj, + &_encap_src_obj, + &_vpn_sid_obj, + vrf_table_id, + 64, + AF_INET6, + RTN_UNICAST, + nhg_id, + pic_id); + if (!nl_obj_wrong_nlmsg_type) { + ADD_FAILURE() << "Failed to create SRv6 VPN Route message"; + return; + } + + /* Call the target function */ + m_mockRouteSync.onSrv6VpnRouteMsg(&nl_obj_wrong_nlmsg_type->n, nl_obj_wrong_nlmsg_type->n.nlmsg_len); + + free(nl_obj_wrong_nlmsg_type); + } + + // Case 5: wrong rtm_type + { + /* List of rtm_types to test */ + int types_to_test[] = { + RTN_BLACKHOLE, + RTN_UNREACHABLE, + RTN_PROHIBIT, + RTN_MULTICAST, + RTN_BROADCAST, + RTN_LOCAL, + __RTN_MAX // default case + }; + + for (int rtm_type : types_to_test) { + struct nlmsg *nl_obj_wrong_rtm_type = create_srv6_vpn_route_nlmsg( + RTM_NEWSRV6VPNROUTE, + &_dst_obj, + &_encap_src_obj, + &_vpn_sid_obj, + vrf_table_id, + 64, + AF_INET6, + static_cast(rtm_type), + nhg_id, + pic_id); + if (!nl_obj_wrong_rtm_type) { + ADD_FAILURE() << "Failed to create SRv6 VPN Route message with type " << rtm_type; + continue; + } + + /* Call the target function */ + m_mockRouteSync.onSrv6VpnRouteMsg(&nl_obj_wrong_rtm_type->n, nl_obj_wrong_rtm_type->n.nlmsg_len); + + free(nl_obj_wrong_rtm_type); + } + } + + // Case 6: invalid rtm_family + { + /* Create a route message with invalid rtm_family */ + struct nlmsg *nl_obj_invalid_rtm_family = create_srv6_vpn_route_nlmsg( + RTM_NEWSRV6VPNROUTE, + &_dst_obj, + &_encap_src_obj, + &_vpn_sid_obj, + vrf_table_id, + 64, + AF_LOCAL, + RTN_UNICAST, + nhg_id, + pic_id); + if (!nl_obj_invalid_rtm_family) { + ADD_FAILURE() << "Failed to create SRv6 VPN Route message"; + return; + } + + /* Call the target function */ + m_mockRouteSync.onSrv6VpnRouteMsg(&nl_obj_invalid_rtm_family->n, nl_obj_invalid_rtm_family->n.nlmsg_len); + + free(nl_obj_invalid_rtm_family); + } + + // Case 7: create RTA_TABLE + { + /* Create a route message with RTA_TABLE */ + struct nlmsg *nl_obj_RTA_TABLE = create_srv6_vpn_route_nlmsg( + RTM_NEWSRV6VPNROUTE, + &_dst_obj, + &_encap_src_obj, + &_vpn_sid_obj, + 257, // set vrf_table_id > 256 + 64, + AF_INET6, + RTN_UNICAST, + nhg_id, + pic_id); + if (!nl_obj_RTA_TABLE) { + ADD_FAILURE() << "Failed to create SRv6 VPN Route message"; + return; + } + + /* Mock using getIfName to return vrfname, vrf_table_id == 257 */ + EXPECT_CALL(m_mockRouteSync, getIfName(257, _, _)) + .WillOnce(DoAll( + [](int32_t, char* ifname, size_t size) { + strncpy(ifname, "Vrf257", size); + ifname[size-1] = '\0'; + }, + Return(true) + )); + + /* Call the target function */ + m_mockRouteSync.onSrv6VpnRouteMsg(&nl_obj_RTA_TABLE->n, nl_obj_RTA_TABLE->n.nlmsg_len); + + free(nl_obj_RTA_TABLE); + } +} + + +/* Add UT for onPicContextMsg */ +struct nlmsghdr* createPicContextMsgHdr(uint16_t msg_type, uint32_t id = 0, const char *gateway = nullptr, + int32_t ifindex = 0, unsigned char nh_family = AF_INET, + const char *seg6_sid = nullptr, + const char *seg6_src = nullptr, + uint32_t encap_type = LWTUNNEL_ENCAP_SEG6) +{ + struct nlmsghdr *nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD)); + memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD)); + + // Set header + nlh->nlmsg_type = msg_type; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE; + nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)); + + // Set nhmsg + struct nhmsg *nhm = (struct nhmsg *)NLMSG_DATA(nlh); + nhm->nh_family = nh_family; + + // Prepare the rta + struct rtattr *rta; + // Add NHA_ID + if (id) { + rta = (struct rtattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len)); + rta->rta_type = NHA_ID; + rta->rta_len = RTA_LENGTH(sizeof(uint32_t)); + *(uint32_t *)RTA_DATA(rta) = id; + nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(rta->rta_len); + } + + // Add NHA_OIF + if (ifindex) { + rta = (struct rtattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len)); + rta->rta_type = NHA_OIF; + rta->rta_len = RTA_LENGTH(sizeof(int32_t)); + *(int32_t *)RTA_DATA(rta) = ifindex; + nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(rta->rta_len); + } + + // Add NHA_GATEWAY + if (gateway) { + rta = (struct rtattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len)); + rta->rta_type = NHA_GATEWAY; + if (nh_family == AF_INET6) { + struct in6_addr gw_addr6; + inet_pton(AF_INET6, gateway, &gw_addr6); + rta->rta_len = RTA_LENGTH(sizeof(struct in6_addr)); + memcpy(RTA_DATA(rta), &gw_addr6, sizeof(struct in6_addr)); + } + else { + struct in_addr gw_addr; + inet_pton(AF_INET, gateway, &gw_addr); + rta->rta_len = RTA_LENGTH(sizeof(struct in_addr)); + memcpy(RTA_DATA(rta), &gw_addr, sizeof(struct in_addr)); + } + nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(rta->rta_len); + } + + // Add Srv6 Encap info if provided + if (seg6_sid && seg6_src) { + // Add NHA_ENCAP_TYPE tlv, type of value is int. Similar with rta_type, change it to uint16_t + rta = (struct rtattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len)); + rta->rta_type = NHA_ENCAP_TYPE; + rta->rta_len = RTA_LENGTH(sizeof(uint16_t)); + *(uint16_t *)RTA_DATA(rta) = static_cast(encap_type); + nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(rta->rta_len); + + /* + * Prepare work: Calculate the nested tlv length, + * we need it to fill the value of outside len. + */ + size_t num_segments = 1; + size_t value_size = sizeof(struct seg6_iptunnel_encap_pri) + + num_segments * sizeof(struct ipv6_sr_hdr) + + num_segments * sizeof(struct in6_addr); + size_t nested_size = sizeof(struct rtattr) + value_size; + + // Add type and len of NHA_ENCAP + rta = (struct rtattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len)); + rta->rta_type = NHA_ENCAP; + rta->rta_len = static_cast(RTA_LENGTH(nested_size)); + + // Add nested SEG6_IPTUNNEL_SRH as ENCAP's payload + struct rtattr *sub_rta = (struct rtattr *)(RTA_DATA(rta)); + // Add type and len of SEG6_IPTUNNEL_SRH + sub_rta->rta_type = SEG6_IPTUNNEL_SRH; + sub_rta->rta_len = static_cast(RTA_LENGTH(value_size)); + + // Prepare the value we truly need + struct seg6_iptunnel_encap_pri *encap_data = (struct seg6_iptunnel_encap_pri *)malloc(value_size); + if (!encap_data) { + free(nlh); + return NULL; + } + memset(encap_data, 0, value_size); + + // Set the src + struct in6_addr src; + inet_pton(AF_INET6, seg6_src, &src); + encap_data->src = src; + + // Aquire srh pointer + struct ipv6_sr_hdr *srh = encap_data->srh; + // Set segments Address + struct in6_addr sid; + inet_pton(AF_INET6, seg6_sid, &sid); + memcpy(srh->segments, &sid, sizeof(sid)); + + // Copy the entire data into the Netlink message + memcpy(RTA_DATA(sub_rta), encap_data, value_size); + nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(rta->rta_len); + + free(encap_data); + } + + return nlh; +} + +TEST_F(FpmSyncdResponseTest, TestPicContext_NH) +{ + uint16_t msg_type = RTM_NEWPICCONTEXT; + uint32_t id = 100; + const char *gateway = "2001:db8::1"; + int32_t ifindex = 101; + unsigned char nh_family = AF_INET6; + const char *seg6_sid = "2001:db8::2"; + const char *seg6_src = "2001:db8::3"; + + + /*-------------------------------------------*/ + /* Test 1: Create and process ADD msg for NH */ + /*-------------------------------------------*/ + { + /* Create netlink msg header with ADD cmd */ + struct nlmsghdr *nlh = createPicContextMsgHdr( + msg_type, + id, + gateway, + ifindex, + nh_family, + seg6_sid, + seg6_src + ); + if (!nlh) { + ADD_FAILURE() << "Failed to create Pic Context nlmsghdr"; + return; + } + int expected_length = (int)(nlh->nlmsg_len - NLMSG_LENGTH(sizeof(struct nhmsg))); + + /* Construct the IfName */ + EXPECT_CALL(m_mockRouteSync, getIfName(ifindex, _, _)) + .WillOnce(DoAll( + [](int32_t, char *ifname, size_t size) { + strncpy(ifname, "Ethernet1", size); + ifname[size-1] = '\0'; + }, + Return(true) + )); + + /* Call the target function */ + m_mockRouteSync.onPicContextMsg(nlh, expected_length); + + /* Check the results */ + auto it = m_mockRouteSync.m_nh_groups.find(id); + ASSERT_NE(it, m_mockRouteSync.m_nh_groups.end()) << "Failed to add new Pic Context"; + + /* Check other attrs */ + const NextHopGroup &nhg = it->second; + // Check each value of attr + EXPECT_EQ(nhg.nexthop, gateway); // Check gateway + EXPECT_EQ(nhg.intf, "Ethernet1"); // Check interface name + EXPECT_EQ(nhg.vpn_sid, seg6_sid); // Check sid + EXPECT_EQ(nhg.seg_src, seg6_src); // Check seg_src + + free(nlh); + } + + /*-------------------------------------------*/ + /* Test 2: Create and process DEL msg for NH */ + /*-------------------------------------------*/ + { + /* Create netlink msg header with DEL cmd */ + struct nlmsghdr *nlh_del = createPicContextMsgHdr(RTM_DELPICCONTEXT, id); + if (!nlh_del) { + ADD_FAILURE() << "Failed to create Pic Context nlmsghdr"; + return; + } + int expected_length = (int)(nlh_del->nlmsg_len - NLMSG_LENGTH(sizeof(struct nhmsg))); + + /* Call the target function */ + m_mockRouteSync.onPicContextMsg(nlh_del, expected_length); + + /* Check the result */ + auto it = m_mockRouteSync.m_nh_groups.find(id); + ASSERT_EQ(it, m_mockRouteSync.m_nh_groups.end()) << "Failed to remove Pic Context"; + + free(nlh_del); + } + + /*-------------------------------*/ + /* Test 3: Other branches for NH */ + /*-------------------------------*/ + // Case 1: nlmsg_type is nothing to do with PIC + { + /* Create netlink msg header with wrong nlmsg_type */ + struct nlmsghdr *nlh_no_pic = createPicContextMsgHdr(RTM_NEWSRV6VPNROUTE); + if (!nlh_no_pic) { + ADD_FAILURE() << "Failed to create Pic Context nlmsghdr"; + return; + } + int expected_length = (int)(nlh_no_pic->nlmsg_len - NLMSG_LENGTH(sizeof(struct nhmsg))); + + /* Call the target function */ + m_mockRouteSync.onPicContextMsg(nlh_no_pic, expected_length); + + /* Check the result */ + auto it = m_mockRouteSync.m_nh_groups.find(id); + ASSERT_EQ(it, m_mockRouteSync.m_nh_groups.end()) << "We should've find nothing for no pic case"; + + free(nlh_no_pic); + } + + // Case 2: missing NHA_ID + { + /* Create netlink msg header without NHA_ID */ + struct nlmsghdr *nlh_no_id = createPicContextMsgHdr(msg_type, 0); + if (!nlh_no_id) { + ADD_FAILURE() << "Failed to create Pic Context nlmsghdr"; + return; + } + int expected_length = (int)(nlh_no_id->nlmsg_len - NLMSG_LENGTH(sizeof(struct nhmsg))); + + /* Call the target function */ + m_mockRouteSync.onPicContextMsg(nlh_no_id, expected_length); + + /* Check the result */ + auto it = m_mockRouteSync.m_nh_groups.find(id); + ASSERT_EQ(it, m_mockRouteSync.m_nh_groups.end()) << "We should've find nothing for no id case"; + + free(nlh_no_id); + } + + // Case 3: has NHA_ENCAP & NHA_ENCAP_TYPE but not LWTUNNEL_ENCAP_SEG6 + { + /* Create netlink msg header with other encap_type */ + struct nlmsghdr *nlh_wrong_encap = createPicContextMsgHdr( + msg_type, + 200, + gateway, + ifindex, + nh_family, + seg6_sid, + seg6_src, + __LWTUNNEL_ENCAP_MAX + ); + if (!nlh_wrong_encap) { + ADD_FAILURE() << "Failed to create Pic Context nlmsghdr"; + return; + } + int expected_length = (int)(nlh_wrong_encap->nlmsg_len - NLMSG_ALIGN(sizeof(struct nhmsg))); + + /* Construct the IfName */ + EXPECT_CALL(m_mockRouteSync, getIfName(ifindex, _, _)) + .WillOnce(DoAll( + [](int32_t, char *ifname, size_t size) { + strncpy(ifname, "Ethernet1", size); + ifname[size-1] = '\0'; + }, + Return(true) + )); + + /* Call the target fuction */ + m_mockRouteSync.onPicContextMsg(nlh_wrong_encap, expected_length); + + /* Check the results */ + auto it = m_mockRouteSync.m_nh_groups.find(200); + ASSERT_NE(it, m_mockRouteSync.m_nh_groups.end()) << "Failed to add new Pic Context"; + + /* Check other attrs */ + const NextHopGroup &nhg = it->second; + // Check the values + EXPECT_EQ(nhg.vpn_sid, ""); + EXPECT_EQ(nhg.seg_src, ""); + + free(nlh_wrong_encap); + } + + // Case 4: addr_family == AF_INET + { + /* Create netlink msg header for AF_INET case */ + struct nlmsghdr *nlh_ipv4 = createPicContextMsgHdr( + msg_type, + 300, + "192.168.0.1", + ifindex + ); + if (!nlh_ipv4) { + ADD_FAILURE() << "Failed to create Pic Context nlmsghdr"; + return; + } + int expected_length = (int)(nlh_ipv4->nlmsg_len - NLMSG_ALIGN(sizeof(struct nhmsg))); + + /* Construct the IfName */ + EXPECT_CALL(m_mockRouteSync, getIfName(ifindex, _, _)) + .WillOnce(DoAll( + [](int32_t, char *ifname, size_t size) { + strncpy(ifname, "Ethernet1", size); + ifname[size-1] = '\0'; + }, + Return(true) + )); + + /* Call the target function */ + m_mockRouteSync.onPicContextMsg(nlh_ipv4, expected_length); + + /* Check the results */ + auto it = m_mockRouteSync.m_nh_groups.find(300); + ASSERT_NE(it, m_mockRouteSync.m_nh_groups.end()) << "Failed to add new Pic Context for ipv4"; + + /* Check other attrs */ + const NextHopGroup &nhg = it->second; + // Check each value of attr + EXPECT_EQ(nhg.nexthop, "192.168.0.1"); + EXPECT_EQ(nhg.intf, "Ethernet1"); + + free(nlh_ipv4); + } + + // case 5: unknown addr_family type + { + /* Create netlink msg header with unknown addr_family type */ + struct nlmsghdr *nlh_unknown_af = createPicContextMsgHdr( + msg_type, + id, + gateway, + ifindex, + AF_UNSPEC + ); + if (!nlh_unknown_af) { + ADD_FAILURE() << "Failed to create Pic Context nlmsghdr"; + return; + } + int expected_length = (int)(nlh_unknown_af->nlmsg_len - NLMSG_LENGTH(sizeof(struct nhmsg))); + + /* Call the target function */ + m_mockRouteSync.onPicContextMsg(nlh_unknown_af, expected_length); + + /* Check the result */ + auto it = m_mockRouteSync.m_nh_groups.find(id); + ASSERT_EQ(it, m_mockRouteSync.m_nh_groups.end()) << "We should not process the unknown af"; + + free(nlh_unknown_af); + } + + // case 6: ifName does not exist + { + /* Create netlink msg header with ADD cmd */ + struct nlmsghdr *nlh_unknown_intf = createPicContextMsgHdr( + msg_type, + 400, + gateway, + ifindex, + nh_family + ); + if (!nlh_unknown_intf) { + ADD_FAILURE() << "Failed to create Pic Context nlmsghdr"; + return; + } + int expected_length = (int)(nlh_unknown_intf->nlmsg_len - NLMSG_LENGTH(sizeof(struct nhmsg))); + + /* Construct the IfName, mock unknown case */ + EXPECT_CALL(m_mockRouteSync, getIfName(ifindex, _, _)) + .WillOnce(Return(false)); + + /* Call the target function */ + m_mockRouteSync.onPicContextMsg(nlh_unknown_intf, expected_length); + + /* Check the results */ + auto it = m_mockRouteSync.m_nh_groups.find(400); + ASSERT_NE(it, m_mockRouteSync.m_nh_groups.end()) << "Failed to add new Pic Context"; + + /* Check other attrs */ + const NextHopGroup &nhg = it->second; + // Check each value of attr + EXPECT_EQ(nhg.nexthop, gateway); + EXPECT_EQ(nhg.intf, "unknown"); + + free(nlh_unknown_intf); + } + + // case 7: ifName is docker0 + { + /* Create netlink msg header with ifName "docker0" */ + struct nlmsghdr *nlh_docker0 = createPicContextMsgHdr( + msg_type, + id, + gateway, + ifindex, + nh_family + ); + if (!nlh_docker0) { + ADD_FAILURE() << "Failed to create Pic Context nlmsghdr"; + return; + } + int expected_length = (int)(nlh_docker0->nlmsg_len - NLMSG_LENGTH(sizeof(struct nhmsg))); + + /* Construct the IfName, docker0 */ + EXPECT_CALL(m_mockRouteSync, getIfName(ifindex, _, _)) + .WillOnce(DoAll( + [](int32_t, char *ifname, size_t size) { + strncpy(ifname, "docker0", size); + ifname[size-1] = '\0'; + }, + Return(true) + )); + + /* Call the target function */ + m_mockRouteSync.onPicContextMsg(nlh_docker0, expected_length); + + /* Check the results */ + auto it = m_mockRouteSync.m_nh_groups.find(id); + ASSERT_EQ(it, m_mockRouteSync.m_nh_groups.end()) << "Failed to skip docker0 case"; + + free(nlh_docker0); + } +} + +TEST_F(FpmSyncdResponseTest, TestPicContext_NHG) +{ + // Prepare the information of nexthops + uint16_t msg_type = RTM_NEWPICCONTEXT; + uint32_t nh1_id = 1; + uint32_t nh2_id = 2; + uint32_t nh3_id = 3; + const char *gateway_1 = "2001:db8::1"; + const char *gateway_2 = "2002:db8::1"; + const char *gateway_3 = "2003:db8::1"; + uint32_t ifindex_1 = 1; + uint32_t ifindex_2 = 2; + uint32_t ifindex_3 = 3; + unsigned char nh_family = AF_INET6; + const char *seg6_sid_1 = "2001:db8::2"; + const char *seg6_sid_2 = "2002:db8::2"; + const char *seg6_sid_3 = "2003:db8::2"; + const char *seg6_src_1 = "2001:db8::3"; + const char *seg6_src_2 = "2002:db8::3"; + const char *seg6_src_3 = "2003:db8::3"; + + /* First, we need to add the nexthops to m_nh_groups */ + struct nlmsghdr *nlh_1 = createPicContextMsgHdr(msg_type, nh1_id, gateway_1, ifindex_1, + nh_family, seg6_sid_1, seg6_src_1); + struct nlmsghdr *nlh_2 = createPicContextMsgHdr(msg_type, nh2_id, gateway_2, ifindex_2, + nh_family, seg6_sid_2, seg6_src_2); + struct nlmsghdr *nlh_3 = createPicContextMsgHdr(msg_type, nh3_id, gateway_3, ifindex_3, + nh_family, seg6_sid_3, seg6_src_3); + if (!nlh_1 || !nlh_2 || !nlh_3) { + ADD_FAILURE() << "Failed to create Pic Context nlmsghdr"; + return; + } + + // Construct the IfName + EXPECT_CALL(m_mockRouteSync, getIfName(ifindex_1, _, _)) + .WillOnce(DoAll( + [](int32_t, char *ifname, size_t size) { + strncpy(ifname, "Ethernet1", size); + ifname[size-1] = '\0'; + }, + Return(true) + )); + EXPECT_CALL(m_mockRouteSync, getIfName(ifindex_2, _, _)) + .WillOnce(DoAll( + [](int32_t, char *ifname, size_t size) { + strncpy(ifname, "Ethernet2", size); + ifname[size-1] = '\0'; + }, + Return(true) + )); + EXPECT_CALL(m_mockRouteSync, getIfName(ifindex_3, _, _)) + .WillOnce(DoAll( + [](int32_t, char *ifname, size_t size) { + strncpy(ifname, "Ethernet3", size); + ifname[size-1] = '\0'; + }, + Return(true) + )); + // Call onPicContextMsg to insert these nexthops + m_mockRouteSync.onPicContextMsg(nlh_1, (int)(nlh_1->nlmsg_len - NLMSG_LENGTH(sizeof(struct nhmsg)))); + m_mockRouteSync.onPicContextMsg(nlh_2, (int)(nlh_2->nlmsg_len - NLMSG_LENGTH(sizeof(struct nhmsg)))); + m_mockRouteSync.onPicContextMsg(nlh_3, (int)(nlh_3->nlmsg_len - NLMSG_LENGTH(sizeof(struct nhmsg)))); + + /* Create a nexthop group with these nexthops */ + uint32_t group_id = 100; + vector> group_members = { + {nh1_id, 1}, // id=1, weight=1 + {nh2_id, 2}, // id=2, weight=2 + {nh3_id, 3}, // id=3, weight=3 + }; + + // Create group_nlh + struct nlmsghdr* group_nlh = createNewNextHopMsgHdr(group_members, group_id, RTM_NEWPICCONTEXT); + ASSERT_NE(group_nlh, nullptr) << "Failed to create group nexthop message"; + + // Call the target function + m_mockRouteSync.onPicContextMsg(group_nlh, (int)(group_nlh->nlmsg_len - NLMSG_LENGTH(sizeof(struct nhmsg)))); + + // Verify the group was added correctly + auto it = m_mockRouteSync.m_nh_groups.find(group_id); + ASSERT_NE(it, m_mockRouteSync.m_nh_groups.end()) << "Failed to add nexthop group"; + + // Verify group members + const auto& group = it->second.group; + ASSERT_EQ(group.size(), 3) << "Wrong number of group members"; + + // Check each member's ID and weight + EXPECT_EQ(group[0].first, nh1_id); + EXPECT_EQ(group[0].second, 1); + EXPECT_EQ(group[1].first, nh2_id); + EXPECT_EQ(group[1].second, 2); + EXPECT_EQ(group[2].first, nh3_id); + EXPECT_EQ(group[2].second, 3); + + // Check values in PIC table + Table pic_context_group_table(m_db.get(), APP_PIC_CONTEXT_TABLE_NAME); + vector fieldValues; + string key = to_string(group_id); + pic_context_group_table.get(key, fieldValues); + + ASSERT_EQ(fieldValues.size(), 5) << "Wrong number of fields in DB"; + + // Verify the DB fields + string nexthops, ifnames, sids, srcs, weights; + for (const auto& fv : fieldValues) { + if (fvField(fv) == "nexthop") { + nexthops = fvValue(fv); + } else if (fvField(fv) == "ifname") { + ifnames = fvValue(fv); + } else if (fvField(fv) == "vpn_sid") { + sids = fvValue(fv); + } else if (fvField(fv) == "seg_src") { + srcs = fvValue(fv); + } else if (fvField(fv) == "weight") { + weights = fvValue(fv); + } + } + EXPECT_EQ(nexthops, "2001:db8::1,2002:db8::1,2003:db8::1"); + EXPECT_EQ(ifnames, "Ethernet1,Ethernet2,Ethernet3"); + EXPECT_EQ(sids, "2001:db8::2,2002:db8::2,2003:db8::2"); + EXPECT_EQ(srcs, "2001:db8::3,2002:db8::3,2003:db8::3"); + EXPECT_EQ(weights, "1,2,3"); + + free(nlh_1); + free(nlh_2); + free(nlh_3); + free(group_nlh); +} \ 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 index 92ba3af15aa..75d251cca90 100644 --- a/tests/mock_tests/fpmsyncd/ut_helpers_fpmsyncd.cpp +++ b/tests/mock_tests/fpmsyncd/ut_helpers_fpmsyncd.cpp @@ -106,7 +106,9 @@ namespace ut_fpmsyncd uint16_t table_id, uint8_t prefixlen, uint8_t address_family, - uint8_t rtm_type) + uint8_t rtm_type, + uint32_t nhg_id, + uint32_t pic_id) { struct rtattr *nest; @@ -191,6 +193,16 @@ namespace ut_fpmsyncd vpn_sid->getV6Addr(), 16)) return NULL; + /* Add PIC_ID */ + if (!nl_attr_put32(&nl_obj->n, sizeof(*nl_obj), ROUTE_ENCAP_SRV6_PIC_ID, + pic_id)) + return NULL; + + /* Add NHG_ID */ + if (!nl_attr_put32(&nl_obj->n, sizeof(*nl_obj), ROUTE_ENCAP_SRV6_NH_ID, + nhg_id)) + return NULL; + nl_attr_nest_end(&nl_obj->n, nest); return nl_obj; diff --git a/tests/mock_tests/fpmsyncd/ut_helpers_fpmsyncd.h b/tests/mock_tests/fpmsyncd/ut_helpers_fpmsyncd.h index 71316999d36..f99113099b5 100644 --- a/tests/mock_tests/fpmsyncd/ut_helpers_fpmsyncd.h +++ b/tests/mock_tests/fpmsyncd/ut_helpers_fpmsyncd.h @@ -19,6 +19,8 @@ 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, + ROUTE_ENCAP_SRV6_PIC_ID = 3, + ROUTE_ENCAP_SRV6_NH_ID = 4, }; enum srv6_localsid_action { /* Values copied from fpmsyncd/routesync.cpp */ @@ -97,7 +99,8 @@ namespace ut_fpmsyncd /* 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, uint8_t prefixlen = 0, - uint8_t address_family = 0, uint8_t rtm_type = 0); + uint8_t address_family = 0, uint8_t rtm_type = 0, + uint32_t nhg_id = 0, uint32_t pic_id = 0); /* Build a Netlink object containing an SRv6 My SID */ struct nlmsg *create_srv6_mysid_nlmsg(uint16_t cmd, IpAddress *mysid, int8_t block_len, int8_t node_len, int8_t func_len, int8_t arg_len,