From 97a108f852b2a5a50ebd90c9953206db5cbe73b8 Mon Sep 17 00:00:00 2001 From: Akhilesh Samineni <47657796+AkhileshSamineni@users.noreply.github.com> Date: Thu, 5 Aug 2021 23:49:44 +0530 Subject: [PATCH] Code changes to support IPv6 Link local enhancements (#1463) * Code changes to support IPv6 Link local enchancements - Changes to handle the "ipv6_use_link_local_only" option on a interface which is added/deleted based on Global Config or Interface config. - Adds/Deletes the RIF for an interface in the absence of IPv6 global address on a interface based on "ipv6_use_link_local_only". - Allow the ipv6 link-local address as neighbors. - Allow adding the IPv6 routes with link-local nexthops. - Skip Ipv4LL neighbor add in Orchagent for RFC5549. Signed-off-by: Akhilesh Samineni --- cfgmgr/intfmgr.cpp | 24 +++- neighsyncd/neighsync.cpp | 68 ++++++++++- neighsyncd/neighsync.h | 5 +- neighsyncd/neighsyncd.cpp | 3 +- orchagent/neighorch.cpp | 13 +++ orchagent/routeorch.cpp | 25 ++++- orchagent/routeorch.h | 7 +- tests/test_interface.py | 230 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 358 insertions(+), 17 deletions(-) diff --git a/cfgmgr/intfmgr.cpp b/cfgmgr/intfmgr.cpp index b40f55692a..3e0ed862be 100644 --- a/cfgmgr/intfmgr.cpp +++ b/cfgmgr/intfmgr.cpp @@ -476,6 +476,7 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, string proxy_arp = ""; string grat_arp = ""; string mpls = ""; + string ipv6_link_local_mode = ""; for (auto idx : data) { @@ -506,11 +507,14 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, { mpls = value; } - - if (field == "nat_zone") + else if (field == "nat_zone") { nat_zone = value; } + else if (field == "ipv6_use_link_local_only") + { + ipv6_link_local_mode = value; + } } if (op == SET_COMMAND) @@ -551,6 +555,7 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, FieldValueTuple fvTuple("nat_zone", nat_zone); data.push_back(fvTuple); } + /* Set mpls */ if (!setIntfMpls(alias, mpls)) { @@ -562,6 +567,13 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, FieldValueTuple fvTuple("mpls", mpls); data.push_back(fvTuple); } + + /* Set ipv6 mode */ + if (!ipv6_link_local_mode.empty()) + { + FieldValueTuple fvTuple("ipv6_use_link_local_only", ipv6_link_local_mode); + data.push_back(fvTuple); + } } if (!parentAlias.empty()) @@ -731,8 +743,8 @@ bool IntfMgr::doIntfAddrTask(const vector& keys, std::vector fvVector; FieldValueTuple f("family", ip_prefix.isV4() ? IPV4_NAME : IPV6_NAME); - // Don't send link local config to AppDB and Orchagent - if (ip_prefix.getIp().getAddrScope() != IpAddress::AddrScope::LINK_SCOPE) + // Don't send ipv4 link local config to AppDB and Orchagent + if ((ip_prefix.isV4() == false) || (ip_prefix.getIp().getAddrScope() != IpAddress::AddrScope::LINK_SCOPE)) { FieldValueTuple s("scope", "global"); fvVector.push_back(s); @@ -745,8 +757,8 @@ bool IntfMgr::doIntfAddrTask(const vector& keys, { setIntfIp(alias, "del", ip_prefix); - // Don't send link local config to AppDB and Orchagent - if (ip_prefix.getIp().getAddrScope() != IpAddress::AddrScope::LINK_SCOPE) + // Don't send ipv4 link local config to AppDB and Orchagent + if ((ip_prefix.isV4() == false) || (ip_prefix.getIp().getAddrScope() != IpAddress::AddrScope::LINK_SCOPE)) { m_appIntfTableProducer.del(appKey); m_stateIntfTable.del(keys[0] + state_db_key_delimiter + keys[1]); diff --git a/neighsyncd/neighsync.cpp b/neighsyncd/neighsync.cpp index 054f13a470..6b1abc235f 100644 --- a/neighsyncd/neighsync.cpp +++ b/neighsyncd/neighsync.cpp @@ -13,13 +13,17 @@ #include "neighsync.h" #include "warm_restart.h" +#include using namespace std; using namespace swss; -NeighSync::NeighSync(RedisPipeline *pipelineAppDB, DBConnector *stateDb) : +NeighSync::NeighSync(RedisPipeline *pipelineAppDB, DBConnector *stateDb, DBConnector *cfgDb) : m_neighTable(pipelineAppDB, APP_NEIGH_TABLE_NAME), - m_stateNeighRestoreTable(stateDb, STATE_NEIGH_RESTORE_TABLE_NAME) + m_stateNeighRestoreTable(stateDb, STATE_NEIGH_RESTORE_TABLE_NAME), + m_cfgInterfaceTable(cfgDb, CFG_INTF_TABLE_NAME), + m_cfgLagInterfaceTable(cfgDb, CFG_LAG_INTF_TABLE_NAME), + m_cfgVlanInterfaceTable(cfgDb, CFG_VLAN_INTF_TABLE_NAME) { m_AppRestartAssist = new AppRestartAssist(pipelineAppDB, "neighsyncd", "swss", DEFAULT_NEIGHSYNC_WARMSTART_TIMER); if (m_AppRestartAssist) @@ -57,6 +61,7 @@ void NeighSync::onMsg(int nlmsg_type, struct nl_object *obj) struct rtnl_neigh *neigh = (struct rtnl_neigh *)obj; string key; string family; + string intfName; if ((nlmsg_type != RTM_NEWNEIGH) && (nlmsg_type != RTM_GETNEIGH) && (nlmsg_type != RTM_DELNEIGH)) @@ -70,12 +75,18 @@ void NeighSync::onMsg(int nlmsg_type, struct nl_object *obj) return; key+= LinkCache::getInstance().ifindexToName(rtnl_neigh_get_ifindex(neigh)); + intfName = key; key+= ":"; nl_addr2str(rtnl_neigh_get_dst(neigh), ipStr, MAX_ADDR_SIZE); - /* Ignore IPv6 link-local addresses as neighbors */ + /* Ignore IPv6 link-local addresses as neighbors, if ipv6 link local mode is disabled */ if (family == IPV6_NAME && IN6_IS_ADDR_LINKLOCAL(nl_addr_get_binary_addr(rtnl_neigh_get_dst(neigh)))) - return; + { + if (isLinkLocalEnabled(intfName) == false) + { + return; + } + } /* Ignore IPv6 multicast link-local addresses as neighbors */ if (family == IPV6_NAME && IN6_IS_ADDR_MC_LINKLOCAL(nl_addr_get_binary_addr(rtnl_neigh_get_dst(neigh)))) return; @@ -124,3 +135,52 @@ void NeighSync::onMsg(int nlmsg_type, struct nl_object *obj) m_neighTable.set(key, fvVector); } } + +/* To check the ipv6 link local is enabled on a given port */ +bool NeighSync::isLinkLocalEnabled(const string &port) +{ + vector values; + + if (!port.compare(0, strlen("Vlan"), "Vlan")) + { + if (!m_cfgVlanInterfaceTable.get(port, values)) + { + SWSS_LOG_INFO("IPv6 Link local is not enabled on %s", port.c_str()); + return false; + } + } + else if (!port.compare(0, strlen("PortChannel"), "PortChannel")) + { + if (!m_cfgLagInterfaceTable.get(port, values)) + { + SWSS_LOG_INFO("IPv6 Link local is not enabled on %s", port.c_str()); + return false; + } + } + else if (!port.compare(0, strlen("Ethernet"), "Ethernet")) + { + if (!m_cfgInterfaceTable.get(port, values)) + { + SWSS_LOG_INFO("IPv6 Link local is not enabled on %s", port.c_str()); + return false; + } + } + else + { + SWSS_LOG_INFO("IPv6 Link local is not supported for %s ", port.c_str()); + return false; + } + + auto it = std::find_if(values.begin(), values.end(), [](const FieldValueTuple& t){ return t.first == "ipv6_use_link_local_only";}); + if (it != values.end()) + { + if (it->second == "enable") + { + SWSS_LOG_INFO("IPv6 Link local is enabled on %s", port.c_str()); + return true; + } + } + + SWSS_LOG_INFO("IPv6 Link local is not enabled on %s", port.c_str()); + return false; +} diff --git a/neighsyncd/neighsync.h b/neighsyncd/neighsync.h index 387c849f30..49a17ee6b6 100644 --- a/neighsyncd/neighsync.h +++ b/neighsyncd/neighsync.h @@ -23,7 +23,7 @@ class NeighSync : public NetMsg public: enum { MAX_ADDR_SIZE = 64 }; - NeighSync(RedisPipeline *pipelineAppDB, DBConnector *stateDb); + NeighSync(RedisPipeline *pipelineAppDB, DBConnector *stateDb, DBConnector *cfgDb); ~NeighSync(); virtual void onMsg(int nlmsg_type, struct nl_object *obj); @@ -39,6 +39,9 @@ class NeighSync : public NetMsg Table m_stateNeighRestoreTable; ProducerStateTable m_neighTable; AppRestartAssist *m_AppRestartAssist; + Table m_cfgVlanInterfaceTable, m_cfgLagInterfaceTable, m_cfgInterfaceTable; + + bool isLinkLocalEnabled(const std::string &port); }; } diff --git a/neighsyncd/neighsyncd.cpp b/neighsyncd/neighsyncd.cpp index 99e86b2ef9..a0882c28e2 100644 --- a/neighsyncd/neighsyncd.cpp +++ b/neighsyncd/neighsyncd.cpp @@ -18,8 +18,9 @@ int main(int argc, char **argv) DBConnector appDb("APPL_DB", 0); RedisPipeline pipelineAppDB(&appDb); DBConnector stateDb("STATE_DB", 0); + DBConnector cfgDb("CONFIG_DB", 0); - NeighSync sync(&pipelineAppDB, &stateDb); + NeighSync sync(&pipelineAppDB, &stateDb, &cfgDb); NetDispatcher::getInstance().registerMessageHandler(RTM_NEWNEIGH, &sync); NetDispatcher::getInstance().registerMessageHandler(RTM_DELNEIGH, &sync); diff --git a/orchagent/neighorch.cpp b/orchagent/neighorch.cpp index b9870a24eb..f792c24621 100644 --- a/orchagent/neighorch.cpp +++ b/orchagent/neighorch.cpp @@ -671,6 +671,19 @@ void NeighOrch::doTask(Consumer &consumer) IpAddress ip_address(key.substr(found+1)); + /* Verify Ipv4 LinkLocal and skip neighbor entry added for RFC5549 */ + if ((ip_address.getAddrScope() == IpAddress::LINK_SCOPE) && (ip_address.isV4())) + { + /* Check if this prefix is not a configured ip, if so allow */ + IpPrefix ipll_prefix(ip_address.getV4Addr(), 16); + if (!m_intfsOrch->isPrefixSubnet (ipll_prefix, alias)) + { + SWSS_LOG_NOTICE("Skip IPv4LL neighbor %s, Intf:%s op: %s ", ip_address.to_string().c_str(), alias.c_str(), op.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + } + NeighborEntry neighbor_entry = { ip_address, alias }; if (op == SET_COMMAND) diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index 0f688a64d0..3cf490a5fc 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -127,15 +127,15 @@ RouteOrch::RouteOrch(DBConnector *db, vector &tableNames, IpPrefix linklocal_prefix = getLinkLocalEui64Addr(); addLinkLocalRouteToMe(gVirtualRouterId, linklocal_prefix); + SWSS_LOG_NOTICE("Created link local ipv6 route %s to cpu", linklocal_prefix.to_string().c_str()); /* Add fe80::/10 subnet route to forward all link-local packets * destined to us, to CPU */ IpPrefix default_link_local_prefix("fe80::/10"); addLinkLocalRouteToMe(gVirtualRouterId, default_link_local_prefix); + SWSS_LOG_NOTICE("Created link local ipv6 route %s to cpu", default_link_local_prefix.to_string().c_str()); - /* TODO: Add the link-local fe80::/10 route to cpu in every VRF created from - * vrforch::addOperation. */ } std::string RouteOrch::getLinkLocalEui64Addr(void) @@ -205,6 +205,27 @@ void RouteOrch::addLinkLocalRouteToMe(sai_object_id_t vrf_id, IpPrefix linklocal SWSS_LOG_NOTICE("Created link local ipv6 route %s to cpu", linklocal_prefix.to_string().c_str()); } +void RouteOrch::delLinkLocalRouteToMe(sai_object_id_t vrf_id, IpPrefix linklocal_prefix) +{ + sai_route_entry_t unicast_route_entry; + unicast_route_entry.switch_id = gSwitchId; + unicast_route_entry.vr_id = vrf_id; + copy(unicast_route_entry.destination, linklocal_prefix); + subnet(unicast_route_entry.destination, unicast_route_entry.destination); + + sai_status_t status = sai_route_api->remove_route_entry(&unicast_route_entry); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to delete link local ipv6 route %s to cpu, rv:%d", + linklocal_prefix.getIp().to_string().c_str(), status); + return; + } + + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); + + SWSS_LOG_NOTICE("Deleted link local ipv6 route %s to cpu", linklocal_prefix.to_string().c_str()); +} + bool RouteOrch::hasNextHopGroup(const NextHopGroupKey& nexthops) const { return m_syncdNextHopGroups.find(nexthops) != m_syncdNextHopGroups.end(); diff --git a/orchagent/routeorch.h b/orchagent/routeorch.h index aca68624f6..20e79699d5 100644 --- a/orchagent/routeorch.h +++ b/orchagent/routeorch.h @@ -152,6 +152,10 @@ class RouteOrch : public Orch, public Subject bool createFineGrainedNextHopGroup(sai_object_id_t &next_hop_group_id, vector &nhg_attrs); bool removeFineGrainedNextHopGroup(sai_object_id_t &next_hop_group_id); + void addLinkLocalRouteToMe(sai_object_id_t vrf_id, IpPrefix linklocal_prefix); + void delLinkLocalRouteToMe(sai_object_id_t vrf_id, IpPrefix linklocal_prefix); + std::string getLinkLocalEui64Addr(void); + private: SwitchOrch *m_switchOrch; NeighOrch *m_neighOrch; @@ -188,9 +192,6 @@ class RouteOrch : public Orch, public Subject bool addLabelRoutePost(const LabelRouteBulkContext& ctx, const NextHopGroupKey &nextHops); bool removeLabelRoutePost(const LabelRouteBulkContext& ctx); - std::string getLinkLocalEui64Addr(void); - void addLinkLocalRouteToMe(sai_object_id_t vrf_id, IpPrefix linklocal_prefix); - void doTask(Consumer& consumer); void doLabelTask(Consumer& consumer); }; diff --git a/tests/test_interface.py b/tests/test_interface.py index ac1446481a..a57970b1e5 100644 --- a/tests/test_interface.py +++ b/tests/test_interface.py @@ -1964,6 +1964,236 @@ def test_LoopbackInterfaceIpv4AddressWithVrf(self, dvs, testlog): assert False + def create_ipv6_link_local(self, interface): + if interface.startswith("PortChannel"): + tbl_name = "PORTCHANNEL_INTERFACE" + elif interface.startswith("Vlan"): + tbl_name = "VLAN_INTERFACE" + else: + tbl_name = "INTERFACE" + + fvs = swsscommon.FieldValuePairs([("ipv6_use_link_local_only", "enable")]) + tbl = swsscommon.Table(self.cdb, tbl_name) + tbl.set(interface, fvs) + time.sleep(1) + + def remove_ipv6_link_local(self, interface): + if interface.startswith("PortChannel"): + tbl_name = "PORTCHANNEL_INTERFACE" + elif interface.startswith("Vlan"): + tbl_name = "VLAN_INTERFACE" + else: + tbl_name = "INTERFACE" + tbl = swsscommon.Table(self.cdb, tbl_name) + tbl._del(interface) + time.sleep(1) + + def test_InterfaceIpv6LinkLocalOnly(self, dvs, testlog): + # Check enable/disable ipv6-link-local mode for physical interface + self.setup_db(dvs) + + # create ipv6 link local interface + self.create_ipv6_link_local("Ethernet8") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Ethernet8") + assert status == True + for fv in fvs: + if fv[0] == "ipv6_use_link_local_only": + ipv6_link_local_found = True + assert fv[1] == "enable" + + assert ipv6_link_local_found + + # bring up interface + self.set_admin_status(dvs, "Ethernet8", "up") + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one port based router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) >= 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # the default MTU without any configuration is 9100 + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "9100" + + # remove ipv6 link local interface + self.remove_ipv6_link_local("Ethernet8") + + # bring down interface + self.set_admin_status(dvs, "Ethernet8", "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet8") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Ethernet8" + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface + assert len(intf_entries) == 1 + + def test_LagInterfaceIpv6LinkLocalOnly(self, dvs, testlog): + # Check enable/disable ipv6-link-local mode for lag interface + self.setup_db(dvs) + + # create port channel + self.create_port_channel("PortChannel001") + + # bring up interface + self.set_admin_status(dvs, "PortChannel001", "up") + + # create ipv6 link local interface + self.create_ipv6_link_local("PortChannel001") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("PortChannel001") + assert status == True + for fv in fvs: + if fv[0] == "ipv6_use_link_local_only": + ipv6_link_local_found = True + assert fv[1] == "enable" + + assert ipv6_link_local_found + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one port based router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) >= 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # the default MTU without any configuration is 9100 + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "9100" + + # remove ipv6 link local interface + self.remove_ipv6_link_local("PortChannel001") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "PortChannel001" + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface + assert len(intf_entries) == 1 + + # remove port channel + self.remove_port_channel("PortChannel001") + + + def test_VLanInterfaceIpv6LinkLocalOnly(self, dvs, testlog): + # Check enable/disable ipv6-link-local mode for vlan interface + self.setup_db(dvs) + + # create vlan + self.create_vlan("10") + + # add vlan member + self.create_vlan_member("10", "Ethernet0") + + # bring up interface + self.set_admin_status(dvs, "Ethernet0", "up") + self.set_admin_status(dvs, "Vlan10", "up") + + # create ipv6 link local interface + self.create_ipv6_link_local("Vlan10") + + # check asic database and get vlan_oid + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + vlan_entries = [k for k in tbl.getKeys() if k != dvs.asicdb.default_vlan_id] + assert len(vlan_entries) == 1 + vlan_oid = vlan_entries[0] + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Vlan10") + assert status == True + for fv in fvs: + if fv[0] == "ipv6_use_link_local_only": + ipv6_link_local_found = True + assert fv[1] == "enable" + + assert ipv6_link_local_found + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one vlan router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + if len(fvs) >= 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_VLAN" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VLAN_ID": + assert fv[1] == vlan_oid + + + # remove ipv6 link local interface + self.remove_ipv6_link_local("Vlan10") + + # remove vlan member + self.remove_vlan_member("10", "Ethernet0") + + # remove vlan + self.remove_vlan("10") + + # bring down interface + self.set_admin_status(dvs, "Ethernet0", "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Vlan10" + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface + assert len(intf_entries) == 1 + + # 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():