Skip to content

Commit

Permalink
[subnet decap] Support decap rule generation based on T0 VIP route (s…
Browse files Browse the repository at this point in the history
…onic-net#3183)

* [subnet decap] Support decap rule generation based on T0 VIP route
What I did
Support dynamic decap rule generation based on the T0 VIP route.

Microsoft ADO (number only): 28253947
Why I did it
To enable SONiC with the capability to decap IPinIP packets with dest IP as the VIP route received by T0.
  • Loading branch information
lolyu authored Jun 5, 2024
1 parent 9bcb9b6 commit 8f333b6
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 5 deletions.
65 changes: 64 additions & 1 deletion orchagent/routeorch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <algorithm>
#include "routeorch.h"
#include "nhgorch.h"
#include "tunneldecaporch.h"
#include "cbf/cbfnhgorch.h"
#include "logger.h"
#include "flowcounterrouteorch.h"
Expand All @@ -25,6 +26,7 @@ extern Directory<Orch*> gDirectory;
extern NhgOrch *gNhgOrch;
extern CbfNhgOrch *gCbfNhgOrch;
extern FlowCounterRouteOrch *gFlowCounterRouteOrch;
extern TunnelDecapOrch *gTunneldecapOrch;

extern size_t gMaxBulkSize;

Expand All @@ -44,7 +46,8 @@ RouteOrch::RouteOrch(DBConnector *db, vector<table_name_with_pri_t> &tableNames,
m_fgNhgOrch(fgNhgOrch),
m_nextHopGroupCount(0),
m_srv6Orch(srv6Orch),
m_resync(false)
m_resync(false),
m_appTunnelDecapTermProducer(db, APP_TUNNEL_DECAP_TERM_TABLE_NAME)
{
SWSS_LOG_ENTER();

Expand Down Expand Up @@ -2337,6 +2340,13 @@ bool RouteOrch::addRoutePost(const RouteBulkContext& ctx, const NextHopGroupKey

m_syncdRoutes[vrf_id][ipPrefix] = RouteNhg(nextHops, ctx.nhg_index);

/* add subnet decap term for VIP route */
const SubnetDecapConfig &config = gTunneldecapOrch->getSubnetDecapConfig();
if (config.enable && isVipRoute(ipPrefix, nextHops))
{
createVipRouteSubnetDecapTerm(ipPrefix);
}

// update routes to reflect mux state
if (mux_orch->isMuxNexthops(nextHops))
{
Expand Down Expand Up @@ -2567,6 +2577,10 @@ bool RouteOrch::removeRoutePost(const RouteBulkContext& ctx)
/* Publish removal status, removes route entry from APPL STATE DB */
publishRouteState(ctx);

/* Remove the VIP route subnet decap term */
removeVipRouteSubnetDecapTerm(ipPrefix);


if (ipPrefix.isDefaultRoute() && vrf_id == gVirtualRouterId)
{
it_route_table->second[ipPrefix] = RouteNhg();
Expand Down Expand Up @@ -2741,3 +2755,52 @@ void RouteOrch::publishRouteState(const RouteBulkContext& ctx, const ReturnCode&

m_publisher.publish(APP_ROUTE_TABLE_NAME, ctx.key, fvs, status, replace);
}

inline bool RouteOrch::isVipRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHops)
{
bool res = true;
/* Ensure all next hops are vlan devices */
for (const auto &nextHop : nextHops.getNextHops())
{
res &= (!nextHop.alias.compare(0, strlen(VLAN_PREFIX), VLAN_PREFIX));
}
/* Ensure the prefix is non-local */
if (nextHops.getSize() == 1)
{
res &= (!m_intfsOrch->isPrefixSubnet(ipPrefix, nextHops.getNextHops().begin()->alias));
}
return res;
}

inline void RouteOrch::createVipRouteSubnetDecapTerm(const IpPrefix &ipPrefix)
{
const SubnetDecapConfig &config = gTunneldecapOrch->getSubnetDecapConfig();
if (!config.enable || m_SubnetDecapTermsCreated.find(ipPrefix) != m_SubnetDecapTermsCreated.end())
{
return;
}
SWSS_LOG_NOTICE("Add subnet decap term for %s", ipPrefix.to_string().c_str());
static const vector<FieldValueTuple> data = {
{"term_type", "MP2MP"},
{"subnet_type", "vip"}
};
string tunnel_name = ipPrefix.isV4() ? config.tunnel : config.tunnel_v6;
string key = tunnel_name + ":" + ipPrefix.to_string();
m_appTunnelDecapTermProducer.set(key, data);
m_SubnetDecapTermsCreated.insert(ipPrefix);
}

inline void RouteOrch::removeVipRouteSubnetDecapTerm(const IpPrefix &ipPrefix)
{
auto it = m_SubnetDecapTermsCreated.find(ipPrefix);
if (it == m_SubnetDecapTermsCreated.end())
{
return;
}
const SubnetDecapConfig &config = gTunneldecapOrch->getSubnetDecapConfig();
SWSS_LOG_NOTICE("Remove subnet decap term for %s", ipPrefix.to_string().c_str());
string tunnel_name = ipPrefix.isV4() ? config.tunnel : config.tunnel_v6;
string key = tunnel_name + ":" + ipPrefix.to_string();
m_appTunnelDecapTermProducer.del(key);
m_SubnetDecapTermsCreated.erase(it);
}
8 changes: 8 additions & 0 deletions orchagent/routeorch.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#define EUI64_INTF_ID_LEN 8

#define LOOPBACK_PREFIX "Loopback"
#define VLAN_PREFIX "Vlan"

struct NextHopGroupMemberEntry
{
Expand Down Expand Up @@ -250,6 +251,9 @@ class RouteOrch : public Orch, public Subject
std::set<std::pair<NextHopGroupKey, sai_object_id_t>> m_bulkNhgReducedRefCnt;
/* m_bulkNhgReducedRefCnt: nexthop, vrf_id */

std::set<IpPrefix> m_SubnetDecapTermsCreated;
ProducerStateTable m_appTunnelDecapTermProducer;

NextHopObserverTable m_nextHopObservers;

EntityBulker<sai_route_api_t> gRouteBulker;
Expand Down Expand Up @@ -278,6 +282,10 @@ class RouteOrch : public Orch, public Subject
void decNhgRefCount(const std::string& nhg_index);

void publishRouteState(const RouteBulkContext& ctx, const ReturnCode& status = ReturnCode(SAI_STATUS_SUCCESS));

bool isVipRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHops);
void createVipRouteSubnetDecapTerm(const IpPrefix &ipPrefix);
void removeVipRouteSubnetDecapTerm(const IpPrefix &ipPrefix);
};

#endif /* SWSS_ROUTEORCH_H */
9 changes: 7 additions & 2 deletions tests/mock_tests/flowcounterrouteorch_ut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,16 +176,18 @@ namespace flowcounterrouteorch_test
ASSERT_EQ(gNeighOrch, nullptr);
gNeighOrch = new NeighOrch(m_app_db.get(), APP_NEIGH_TABLE_NAME, gIntfsOrch, gFdbOrch, gPortsOrch, m_chassis_app_db.get());

ASSERT_EQ(gTunneldecapOrch, nullptr);
vector<string> tunnel_tables = {
APP_TUNNEL_DECAP_TABLE_NAME,
APP_TUNNEL_DECAP_TERM_TABLE_NAME
};
auto* tunnel_decap_orch = new TunnelDecapOrch(m_app_db.get(), m_state_db.get(), m_config_db.get(), tunnel_tables);
gTunneldecapOrch = new TunnelDecapOrch(m_app_db.get(), m_state_db.get(), m_config_db.get(), tunnel_tables);

vector<string> mux_tables = {
CFG_MUX_CABLE_TABLE_NAME,
CFG_PEER_SWITCH_TABLE_NAME
};
auto* mux_orch = new MuxOrch(m_config_db.get(), mux_tables, tunnel_decap_orch, gNeighOrch, gFdbOrch);
auto* mux_orch = new MuxOrch(m_config_db.get(), mux_tables, gTunneldecapOrch, gNeighOrch, gFdbOrch);
gDirectory.set(mux_orch);

ASSERT_EQ(gFgNhgOrch, nullptr);
Expand Down Expand Up @@ -310,6 +312,9 @@ namespace flowcounterrouteorch_test
delete gNeighOrch;
gNeighOrch = nullptr;

delete gTunneldecapOrch;
gTunneldecapOrch = nullptr;

delete gFdbOrch;
gFdbOrch = nullptr;

Expand Down
1 change: 1 addition & 0 deletions tests/mock_tests/mock_orchagent_main.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ extern Srv6Orch *gSrv6Orch;
extern BfdOrch *gBfdOrch;
extern AclOrch *gAclOrch;
extern PolicerOrch *gPolicerOrch;
extern TunnelDecapOrch *gTunneldecapOrch;
extern Directory<Orch*> gDirectory;

extern sai_acl_api_t *sai_acl_api;
Expand Down
9 changes: 7 additions & 2 deletions tests/mock_tests/routeorch_ut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,16 +222,18 @@ namespace routeorch_test
ASSERT_EQ(gNeighOrch, nullptr);
gNeighOrch = new NeighOrch(m_app_db.get(), APP_NEIGH_TABLE_NAME, gIntfsOrch, gFdbOrch, gPortsOrch, m_chassis_app_db.get());

ASSERT_EQ(gTunneldecapOrch, nullptr);
vector<string> tunnel_tables = {
APP_TUNNEL_DECAP_TABLE_NAME,
APP_TUNNEL_DECAP_TERM_TABLE_NAME
};
TunnelDecapOrch *tunnel_decap_orch = new TunnelDecapOrch(m_app_db.get(), m_state_db.get(), m_config_db.get(), tunnel_tables);
gTunneldecapOrch = new TunnelDecapOrch(m_app_db.get(), m_state_db.get(), m_config_db.get(), tunnel_tables);

vector<string> mux_tables = {
CFG_MUX_CABLE_TABLE_NAME,
CFG_PEER_SWITCH_TABLE_NAME
};
MuxOrch *mux_orch = new MuxOrch(m_config_db.get(), mux_tables, tunnel_decap_orch, gNeighOrch, gFdbOrch);
MuxOrch *mux_orch = new MuxOrch(m_config_db.get(), mux_tables, gTunneldecapOrch, gNeighOrch, gFdbOrch);
gDirectory.set(mux_orch);

ASSERT_EQ(gFgNhgOrch, nullptr);
Expand Down Expand Up @@ -348,6 +350,9 @@ namespace routeorch_test
delete gNeighOrch;
gNeighOrch = nullptr;

delete gTunneldecapOrch;
gTunneldecapOrch = nullptr;

delete gFdbOrch;
gFdbOrch = nullptr;

Expand Down
145 changes: 145 additions & 0 deletions tests/test_route.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import time
import json
import pytest
import ipaddress

from swsscommon import swsscommon
from dvslib.dvs_common import wait_for_result
Expand Down Expand Up @@ -1113,6 +1114,150 @@ def check_offloaded():
# make sure route suppression is disabled
dvs.runcmd("config suppress-fib-pending disabled")


class TestSubnetDecapVipRoute(TestRouteBase):
VLAN_ID = "1000"
VLAN_INTF = "Vlan1000"
SERV_IPV4 = "192.168.0.100"
SERV_IPV6 = "fc02:1000::100"
CFG_SUBNET_DECAP_TABLE_NAME = "SUBNET_DECAP"
APP_TUNNEL_DECAP_TERM_TABLE_NAME = "TUNNEL_DECAP_TERM_TABLE"

def add_neighbor(self, dvs, ip, mac):
if ipaddress.ip_address(ip).version == 6:
dvs.runcmd("ip -6 neigh replace " + ip + " lladdr " + mac + " dev " + self.VLAN_INTF)
else:
dvs.runcmd("ip -4 neigh replace " + ip + " lladdr " + mac + " dev " + self.VLAN_INTF)

def add_route(self, dvs, ip_prefix):
if ipaddress.ip_network(ip_prefix).version == 4:
dvs.runcmd(
'vtysh -c "configure terminal" -c "ip route %s %s"' % (ip_prefix, self.SERV_IPV4)
)
else:
dvs.runcmd(
'vtysh -c "configure terminal" -c "ipv6 route %s %s"' % (ip_prefix, self.SERV_IPV6)
)

def remove_route(self, dvs, ip_prefix):
if ipaddress.ip_network(ip_prefix).version == 4:
dvs.runcmd(
'vtysh -c "configure terminal" -c "no ip route %s %s"' % (ip_prefix, self.SERV_IPV4)
)
else:
dvs.runcmd(
'vtysh -c "configure terminal" -c "no ipv6 route %s %s"' % (ip_prefix, self.SERV_IPV6)
)

def validate_subnet_decap_term(self, dvs, ip_prefix_list):
tunnel_decap_term_app_table = swsscommon.Table(dvs.pdb, self.APP_TUNNEL_DECAP_TERM_TABLE_NAME)
decap_term_list = tunnel_decap_term_app_table.getKeys()
decap_term_prefix_list = [decap_term.split(":", 1)[1] for decap_term in decap_term_list]

assert len(ip_prefix_list) == len(decap_term_prefix_list)
for ip_prefix in ip_prefix_list:
assert ip_prefix in decap_term_prefix_list

for decap_term in decap_term_list:
_, fvs = tunnel_decap_term_app_table.get(decap_term)
decap_term_attrs = dict(fvs)
assert decap_term_attrs["term_type"] == "MP2MP"
assert decap_term_attrs["subnet_type"] == "vip"

@pytest.fixture(scope="class", autouse=True)
def setup_vlan(self, dvs, dvs_vlan_manager, setup_subnet_decap):
dvs.setup_db()

subnet_decap_config = {
"status": "enable",
"src_ip": "10.10.10.0/24",
"src_ip_v6": "20c1:ba8::/64"
}
setup_subnet_decap(subnet_decap_config)

vlan = self.VLAN_ID
vlan_intf = self.VLAN_INTF
self.dvs_vlan.create_vlan(vlan)
vlan_oid = self.dvs_vlan.get_and_verify_vlan_ids(1)[0]
self.dvs_vlan.verify_vlan(vlan_oid, vlan)

dvs.port_admin_set("Ethernet0", "up")
self.dvs_vlan.create_vlan_member(vlan, "Ethernet0")
self.dvs_vlan.verify_vlan_member(vlan_oid, "Ethernet0")

dvs.add_ip_address(vlan_intf, "192.168.0.1/24")
dvs.add_ip_address(vlan_intf, "fc02:1000::1/64")

yield

dvs.remove_ip_address(vlan_intf, "192.168.0.1/24")
dvs.remove_ip_address(vlan_intf, "fc02:1000::1/64")

self.dvs_vlan.remove_vlan_member(vlan, "Ethernet0")
self.dvs_vlan.get_and_verify_vlan_member_ids(0)

time.sleep(2)
self.dvs_vlan.remove_vlan(vlan)
self.dvs_vlan.get_and_verify_vlan_ids(0)

@pytest.fixture(scope="class", autouse=True)
def setup_server_neighbor(self, dvs, setup_vlan):
self.add_neighbor(dvs, self.SERV_IPV4, "00:00:00:00:00:01")
self.add_neighbor(dvs, self.SERV_IPV6, "00:00:00:00:00:01")

@pytest.fixture(scope="class")
def setup_subnet_decap(self, dvs):

def _apply_subnet_decap_config(subnet_decap_config):
"""Apply subnet decap config to CONFIG_DB."""
fvs = swsscommon.FieldValuePairs(list(subnet_decap_config.items()))
subnet_decap_tbl.set("AZURE", fvs)

def _cleanup_subnet_decap_config():
"""Cleanup subnet decap config in CONFIG_DB."""
for key in subnet_decap_tbl.getKeys():
subnet_decap_tbl._del(key)

configdb = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0)
subnet_decap_tbl = swsscommon.Table(configdb, self.CFG_SUBNET_DECAP_TABLE_NAME)
_cleanup_subnet_decap_config()

yield _apply_subnet_decap_config

_cleanup_subnet_decap_config()

def test_vip_route(self, dvs, setup_subnet_decap):
self.add_route(dvs, "10.10.20.0/24")
self.add_route(dvs, "2001:506:28:9d::/64")

time.sleep(2)
self.validate_subnet_decap_term(dvs, ["10.10.20.0/24", "2001:506:28:9d::/64"])

self.remove_route(dvs, "10.10.20.0/24")
self.remove_route(dvs, "2001:506:28:9d::/64")

time.sleep(2)
self.validate_subnet_decap_term(dvs, [])

subnet_decap_config = {
"status": "disable",
"src_ip": "10.10.10.0/24",
"src_ip_v6": "20c1:ba8::/64"
}
setup_subnet_decap(subnet_decap_config)

self.add_route(dvs, "10.10.20.0/24")
self.add_route(dvs, "2001:506:28:9d::/64")

time.sleep(2)
self.validate_subnet_decap_term(dvs, [])

self.remove_route(dvs, "10.10.20.0/24")
self.remove_route(dvs, "2001:506:28:9d::/64")

time.sleep(2)
self.validate_subnet_decap_term(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():
Expand Down

0 comments on commit 8f333b6

Please sign in to comment.