From 7c68432231909e1937f3c95925ec051e186ff514 Mon Sep 17 00:00:00 2001 From: Alexandr Stepanov Date: Wed, 21 Jan 2026 11:45:36 +0300 Subject: [PATCH 1/3] Implement correct support for updating network interfaces for neighbors In the current version, when the configuration of the network interface list is changed, the internal network interface identifiers are changed. This led to the fact that when performing a reload configuration for a while, the data in neighbors became incorrect. In this commit, information about neighbors is also stored by network interface names. Added event handling from controlplane route_t::reload_after to dataplane. An additional command has also been added to view information about neighbors.: yanet-cli neighbor show cache --- cli/bus.h | 1 + cli/main.cpp | 1 + cli/neighbor.h | 12 + common/idataplane.h | 10 + common/idp.h | 12 + controlplane/route.cpp | 1 + dataplane/bus.cpp | 8 + dataplane/neighbor.cpp | 881 +++++++++++++++++++++++--------- dataplane/neighbor.h | 86 +++- dataplane/netlink.cpp | 55 +- dataplane/netlink.hpp | 30 +- dataplane/unittest/meson.build | 2 +- dataplane/unittest/neighbor.cpp | 64 +-- 13 files changed, 826 insertions(+), 337 deletions(-) diff --git a/cli/bus.h b/cli/bus.h index 801a913b..d4e5a17e 100644 --- a/cli/bus.h +++ b/cli/bus.h @@ -53,6 +53,7 @@ inline std::vector get_bus_requests(common::sdp::DataPlaneInSh {common::idp::requestType::dump_physical_port, "dump_physical_port"}, {common::idp::requestType::balancer_state_clear, "balancer_state_clear"}, {common::idp::requestType::neighbor_show, "neighbor_show"}, + {common::idp::requestType::neighbor_show_cache, "neighbor_show_cache"}, {common::idp::requestType::neighbor_insert, "neighbor_insert"}, {common::idp::requestType::neighbor_remove, "neighbor_remove"}, {common::idp::requestType::neighbor_clear, "neighbor_clear"}, diff --git a/cli/main.cpp b/cli/main.cpp index 46f14057..ce7957d7 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -82,6 +82,7 @@ std::vector(); } + auto neighbor_show_cache() const + { + return get(); + } + auto neighbor_insert(const common::idp::neighbor_insert::request& request) const { return get(request); @@ -250,6 +255,11 @@ class dataPlane return get(); } + auto neighbor_interfaces_switch() const + { + return get(); + } + protected: void connectToDataPlane() const { diff --git a/common/idp.h b/common/idp.h index cb616de8..61ae54da 100644 --- a/common/idp.h +++ b/common/idp.h @@ -74,6 +74,7 @@ enum class requestType : uint32_t dump_physical_port, balancer_state_clear, neighbor_show, + neighbor_show_cache, neighbor_insert, neighbor_remove, neighbor_clear, @@ -82,6 +83,7 @@ enum class requestType : uint32_t neighbor_stats, memory_manager_update, memory_manager_stats, + neighbor_interfaces_switch, size, // size should always be at the bottom of the list, this enum allows us to find out the size of the enum list }; @@ -965,6 +967,15 @@ namespace neighbor_show { using entry = std::tuple; ///< mac_address + +using response = std::vector; +} + +namespace neighbor_show_cache +{ +using entry = std::tuple, ///< last_update_timestamp @@ -1068,6 +1079,7 @@ using response = std::variant, get_shm_info::response, get_shm_tsc_info::response, neighbor_show::response, + neighbor_show_cache::response, neighbor_stats::response, memory_manager_stats::response>; } diff --git a/controlplane/route.cpp b/controlplane/route.cpp index d69f87cb..55f676f1 100644 --- a/controlplane/route.cpp +++ b/controlplane/route.cpp @@ -1024,6 +1024,7 @@ void route_t::reload_after() generations.switch_generation(); generations_neighbors.next_unlock(); generations.next_unlock(); + dataplane.neighbor_interfaces_switch(); } void route_t::prefix_flush_prefixes(common::idp::updateGlobalBase::request& globalbase) diff --git a/dataplane/bus.cpp b/dataplane/bus.cpp index 578495f6..4fd59581 100644 --- a/dataplane/bus.cpp +++ b/dataplane/bus.cpp @@ -318,6 +318,10 @@ void cBus::clientThread(int clientSocket) { response = dataPlane->neighbor.neighbor_show(); } + else if (type == common::idp::requestType::neighbor_show_cache) + { + response = dataPlane->neighbor.neighbor_show_cache(); + } else if (type == common::idp::requestType::neighbor_insert) { response = dataPlane->neighbor.neighbor_insert(std::get(std::get<1>(request))); @@ -350,6 +354,10 @@ void cBus::clientThread(int clientSocket) { response = dataPlane->memory_manager.memory_manager_stats(); } + else if (type == common::idp::requestType::neighbor_interfaces_switch) + { + response = dataPlane->neighbor.neighbor_interfaces_switch(); + } else { stats.errors[(uint32_t)common::idp::errorType::busParse]++; diff --git a/dataplane/neighbor.cpp b/dataplane/neighbor.cpp index 8a0525de..6d491503 100644 --- a/dataplane/neighbor.cpp +++ b/dataplane/neighbor.cpp @@ -7,6 +7,18 @@ #include "base.h" #include "neighbor.h" +#define NEIGHBOR_DEBUG_LEVEL 1 +#if NEIGHBOR_DEBUG_LEVEL >= 1 +#define NEIGHBOR_INFO(msg, args...) YANET_LOG_INFO(msg, ##args) +#else +#define NEIGHBOR_INFO(msg, args...) +#endif +#if NEIGHBOR_DEBUG_LEVEL >= 2 +#define NEIGHBOR_DEBUG(msg, args...) YANET_LOG_INFO(msg, ##args) +#else +#define NEIGHBOR_DEBUG(msg, args...) +#endif + namespace dataplane::neighbor { @@ -41,6 +53,8 @@ eResult module::init( remove_timeout_ = remove_timeout; resolve_removed_ = resolve_removed; + neighbor_cache_.Init(checks_interval, remove_timeout, resolve_removed); + generation_hashtable.fill([&](neighbor::generation_hashtable& hashtable) { for (const auto socket_id : socket_ids) { @@ -59,6 +73,7 @@ eResult module::init( void module::update_worker_base(const std::vector>& base_nexts) { + NEIGHBOR_DEBUG("update_worker_base\n"); auto lock = generation_hashtable.current_lock_guard(); for (auto& [socket_id, base_next] : base_nexts) { @@ -96,24 +111,10 @@ common::idp::neighbor_show::response module::neighbor_show() const const auto& [route_name, interface_name] = it->second; - std::optional last_update_timestamp; - if (!(value.flags & flag_is_static)) - { - last_update_timestamp = current_time_provider_() - value.last_update_timestamp; - } - - std::string last_remove_timestamp; - if (value.last_remove_timestamp != 0) - { - last_remove_timestamp = std::to_string(current_time_provider_() - value.last_remove_timestamp); - } - response.emplace_back(route_name, interface_name, common::ip_address_t(key.flags & flag_is_ipv6 ? 6 : 4, key.address.bytes), - common::mac_address_t(value.ether_address.addr_bytes), - last_update_timestamp, - last_remove_timestamp); + common::mac_address_t(value.ether_address.addr_bytes)); } } } @@ -121,28 +122,19 @@ common::idp::neighbor_show::response module::neighbor_show() const return response; } +common::idp::neighbor_show_cache::response module::neighbor_show_cache() const +{ + return neighbor_cache_.NeighborShow(current_time_provider_()); +} + eResult module::neighbor_insert(const common::idp::neighbor_insert::request& request) { const auto& [route_name, interface_name, ip_address, mac_address] = request; + NEIGHBOR_INFO("neighbor_insert interface_name=%s, ip_address=%s, mac_address=%s\n", interface_name.c_str(), ip_address.toString().c_str(), mac_address.toString().c_str()); GCC_BUG_UNUSED(route_name); ///< @todo - tInterfaceId interface_id = 0; - { - auto lock = generation_interface.current_lock_guard(); - - const auto& interface_name_to_id = generation_interface.current().interface_name_to_id; - auto it = interface_name_to_id.find(interface_name); - if (it == interface_name_to_id.end()) - { - return eResult::invalidInterfaceName; - } - - interface_id = it->second; - } - dataplane::neighbor::key key; memset(&key, 0, sizeof(key)); - key.interface_id = interface_id; key.flags = 0; if (ip_address.is_ipv4()) @@ -157,11 +149,15 @@ eResult module::neighbor_insert(const common::idp::neighbor_insert::request& req dataplane::neighbor::value value; memcpy(value.ether_address.addr_bytes, mac_address.data(), 6); - value.flags = 0; - value.flags |= flag_is_static; - value.last_update_timestamp = current_time_provider_(); - value.last_remove_timestamp = 0; - value.last_resolve_timestamp = 0; + + neighbor_cache_.Insert(interface_name, key.address, ip_address.is_ipv6(), value.ether_address, current_time_provider_(), true); + + std::optional interface_id = GetInterfaceId(interface_name); + if (!interface_id.has_value()) + { + return eResult::invalidInterfaceName; + } + key.interface_id = *interface_id; auto response = generation_hashtable.update([this, key, value](neighbor::generation_hashtable& hashtable) { eResult result = eResult::success; @@ -187,25 +183,11 @@ eResult module::neighbor_insert(const common::idp::neighbor_insert::request& req eResult module::neighbor_remove(const common::idp::neighbor_remove::request& request) { const auto& [route_name, interface_name, ip_address] = request; + NEIGHBOR_INFO("neighbor_remove interface_name=%s, ip_address=%s\n", interface_name.c_str(), ip_address.toString().c_str()); GCC_BUG_UNUSED(route_name); ///< @todo - tInterfaceId interface_id = 0; - { - auto lock = generation_interface.current_lock_guard(); - - const auto& interface_name_to_id = generation_interface.current().interface_name_to_id; - auto it = interface_name_to_id.find(interface_name); - if (it == interface_name_to_id.end()) - { - return eResult::invalidInterfaceName; - } - - interface_id = it->second; - } - dataplane::neighbor::key key; memset(&key, 0, sizeof(key)); - key.interface_id = interface_id; key.flags = 0; if (ip_address.is_ipv4()) @@ -218,6 +200,15 @@ eResult module::neighbor_remove(const common::idp::neighbor_remove::request& req key.flags |= flag_is_ipv6; } + neighbor_cache_.Remove(interface_name, key.address, ip_address.is_ipv6(), current_time_provider_(), true); + + std::optional interface_id = GetInterfaceId(interface_name); + if (!interface_id.has_value()) + { + return eResult::invalidInterfaceName; + } + key.interface_id = *interface_id; + auto response = generation_hashtable.update([this, key](neighbor::generation_hashtable& hashtable) { eResult result = eResult::success; for (auto& [socket_id, hashtable_updater] : hashtable.hashtable_updater) @@ -241,7 +232,15 @@ eResult module::neighbor_remove(const common::idp::neighbor_remove::request& req eResult module::neighbor_clear() { - return DumpOSNeighbors(); + NEIGHBOR_INFO("neighbor_clear\n"); + eResult result = DumpOSNeighbors(); + if (result != eResult::success) + { + return result; + } + UpdateFromCache(true); + + return eResult::success; } eResult module::neighbor_flush() @@ -256,18 +255,6 @@ void module::StartNetlinkMonitor() { neighbor_provider->StartMonitor( rcvbuf_size_, - [this](const char* ifname) -> std::optional { - auto interfaces_guard = generation_interface.current_lock_guard(); - auto& ids = generation_interface.current().interface_name_to_id; - if (auto it = ids.find(ifname); it != ids.end()) - { - return it->second; - } - else - { - return std::nullopt; - } - }, [this](auto... args) { return Upsert(args...); }, [this](auto... args) { return Remove(args...); }, [this](auto... args) { return UpdateTimestamp(args...); }); @@ -282,72 +269,41 @@ void module::StopNetlinkMonitor() eResult module::DumpOSNeighbors() { - std::vector dump; - std::vector> static_entries; - { - auto interfaces_guard = generation_interface.current_lock_guard(); - auto& new_interfaces = generation_interface.current(); - auto& old_interfaces = generation_interface.next(); - dump = neighbor_provider->GetHostDump(rcvbuf_size_, new_interfaces.interface_name_to_id); - - { - auto lock = generation_hashtable.current_lock_guard(); - for (auto it : generation_hashtable.current().hashtable_updater.begin()->second.range()) - { - if (!it.is_valid()) - { - continue; - } - - if (it.value()->flags & flag_is_static) - { - auto key = *it.key(); - auto to_name = old_interfaces.interface_id_to_name.find(key.interface_id); - if (to_name == old_interfaces.interface_id_to_name.cend()) - { - continue; - } - - auto to_id = new_interfaces.interface_name_to_id.find(std::get<1>(to_name->second)); - if (to_id == new_interfaces.interface_name_to_id.cend()) - { - continue; - } - - key.interface_id = to_id->second; - static_entries.emplace_back(key, *it.value()); - } - } - } - } + NEIGHBOR_INFO("DumpOSNeighbors\n"); + std::vector dump = neighbor_provider->GetHostDump(rcvbuf_size_); + neighbor_cache_.UpdateFromDump(dump, current_time_provider_()); + auto interfaces_guard = generation_interface.current_lock_guard(); + const auto& interface_name_to_id = generation_interface.current().interface_name_to_id; eResult res = generation_hashtable.update( [&dump, now = current_time_provider_(), - &static_entries, + interface_name_to_id, this]( neighbor::generation_hashtable& hashtable) { for (auto& [socket_id, hashtable_updater] : hashtable.hashtable_updater) { - hashtable_updater.get_pointer()->clear(); - - for (const auto& [key, value] : static_entries) - { - hashtable_updater.get_pointer()->insert_or_update(key, value); - } - - for (const auto& [iface, dst, mac, is_v6] : dump) + for (const auto& entry : dump) { + const auto& [iface_name, dst, mac, is_v6] = entry; if (!mac) { - YANET_LOG_INFO("No MAC address for neighbor in dump\n"); + NEIGHBOR_INFO("No MAC address for neighbor in dump\n"); continue; } + auto iter = interface_name_to_id.find(iface_name); + if (iter == interface_name_to_id.end()) + { + continue; + } + tInterfaceId iface = std::get<1>(iter->second); + NEIGHBOR_INFO("DumpOSNeighbors add iface=%d %s\n", iface, entry.toString().c_str()); + hashtable_updater.get_pointer() ->insert_or_update( dataplane::neighbor::key{iface, is_v6 ? flag_is_ipv6 : uint16_t{}, dst}, - dataplane::neighbor::value{mac.value(), 0, now, 0, 0, 0}); + dataplane::neighbor::value{mac.value()}); stats.netlink_neighbor_update++; } } @@ -362,26 +318,121 @@ eResult module::DumpOSNeighbors() return neighbor_flush(); } +template +bool HasNewKeys(const std::unordered_map& current, const std::unordered_map& update) +{ + for (const auto& iter : update) + { + if (current.find(iter.first) == current.end()) + { + return true; + } + } + return false; +} + +template +void CopyNewKeys(std::unordered_map& current, const std::unordered_map& update) +{ + for (const auto& iter : update) + { + if (current.find(iter.first) == current.end()) + { + current.insert(iter); + } + } +} + +template +bool MapsEqual(const std::unordered_map& first, const std::unordered_map& second) +{ + if (first.size() != second.size()) + { + return false; + } + for (const auto& iter_first : first) + { + const auto iter_second = second.find(iter_first.first); + if (iter_second == second.end() || iter_first.second != iter_second->second) + { + return false; + } + } + return true; +} + eResult module::neighbor_update_interfaces(const common::idp::neighbor_update_interfaces::request& request) { + YANET_LOG_INFO("neighbor_update_interfaces\n"); + + // build new maps for next generation + decltype(generation_interface.next().interface_name_to_id) interface_name_to_id; + decltype(generation_interface.next().interface_id_to_name) interface_id_to_name; + for (const auto& [interface_id, route_name, interface_name] : request) + { + NEIGHBOR_INFO("neighbor_update_interfaces %d %s\n", interface_id, interface_name.c_str()); + interface_name_to_id[interface_name] = {route_name, interface_id}; + interface_id_to_name[interface_id] = {route_name, interface_name}; + } + + { + // check if there are any new keys + auto lock = generation_interface.current_lock_guard(); + if (HasNewKeys(generation_interface.current().interface_name_to_id, interface_name_to_id) || + HasNewKeys(generation_interface.current().interface_id_to_name, interface_id_to_name)) + { + // prepare new tmp maps + auto tmp_interface_name_to_id = generation_interface.current().interface_name_to_id; + CopyNewKeys(tmp_interface_name_to_id, interface_name_to_id); + auto tmp_interface_id_to_name = generation_interface.current().interface_id_to_name; + CopyNewKeys(tmp_interface_id_to_name, interface_id_to_name); + lock.unlock(); + + // update by new tmp maps and switch generation + generation_interface.next_lock(); + generation_interface.next().interface_name_to_id = tmp_interface_name_to_id; + generation_interface.next().interface_id_to_name = tmp_interface_id_to_name; + generation_interface.next_unlock(); + generation_interface.switch_generation(); + } + } + + // update next generations generation_interface.next_lock(); auto& generation = generation_interface.next(); - generation.interface_name_to_id.clear(); - generation.interface_id_to_name.clear(); - for (const auto& [interface_id, - route_name, - interface_name] : request) + generation.interface_name_to_id = interface_name_to_id; + generation.interface_id_to_name = interface_id_to_name; + generation_interface.next_unlock(); + + return eResult::success; +} + +eResult module::neighbor_interfaces_switch() +{ + YANET_LOG_INFO("neighbor_interfaces_switch\n"); + + // check if there have been any changes + generation_interface.current_lock(); + generation_interface.next_lock(); + bool changed = !MapsEqual(generation_interface.current().interface_id_to_name, generation_interface.next().interface_id_to_name) || + !MapsEqual(generation_interface.current().interface_name_to_id, generation_interface.next().interface_name_to_id); + generation_interface.next_unlock(); + generation_interface.current_unlock(); + + // switch generation + generation_interface.switch_generation(); + + if (changed) { - generation.interface_name_to_id[interface_name] = interface_id; - generation.interface_id_to_name[interface_id] = {route_name, interface_name}; + NEIGHBOR_INFO("neighbor_interfaces_switch changed"); +#ifdef CONFIG_YADECAP_AUTOTEST + UpdateFromCache(true); +#else // CONFIG_YADECAP_AUTOTEST + time_del_unused_ = current_time_provider_() + 30; + UpdateFromCache(false); +#endif // CONFIG_YADECAP_AUTOTEST } - generation_interface.switch_generation(); - generation_interface.next_unlock(); - std::lock_guard guard(mutex_restart_monitor_); - StopNetlinkMonitor(); - DumpOSNeighbors(); - StartNetlinkMonitor(); return eResult::success; } @@ -406,9 +457,15 @@ void module::StartResolveJob() { resolve_.Run([this]() mutable { std::vector keys = keys_to_resolve_provider_(); + uint32_t timestamp = current_time_provider_(); for (const auto& key : keys) { - resolve(key); + std::optional interface_name = GetInterfaceName(key.interface_id); + if (interface_name.has_value() && neighbor_cache_.NeedResolve(*interface_name, key.address, key.flags & flag_is_ipv6, timestamp)) + { + common::ip_address_t ip_address(key.flags & flag_is_ipv6 ? 6 : 4, key.address.bytes); + resolve(*interface_name, ip_address); + } } neighbor_flush(); @@ -419,10 +476,24 @@ void module::StartResolveJob() YANET_LOG_INFO("Neighbor resolve job started\n"); } -void module::Upsert(tInterfaceId iface, const ipv6_address_t& dst, bool is_v6, const rte_ether_addr& mac) +void module::Upsert(std::string iface_name, const ipv6_address_t& dst, bool is_v6, const rte_ether_addr& mac) { - TransformHashtables([k = key{iface, is_v6 ? flag_is_ipv6 : uint16_t{}, dst}, - v = value{mac, 0, current_time_provider_(), 0, 0, 0}, + NEIGHBOR_INFO("Upsert %s\n", netlink::Entry{iface_name, dst, mac, is_v6}.toString().c_str()); + bool is_static = false; +#ifdef CONFIG_YADECAP_UNITTEST + is_static = true; +#endif + neighbor_cache_.Insert(iface_name, dst, is_v6, mac, current_time_provider_(), is_static); + + std::optional iface = GetInterfaceId(iface_name); + if (!iface.has_value()) + { + stats.hashtable_insert_error++; + return; + } + + TransformHashtables([k = key{*iface, is_v6 ? flag_is_ipv6 : uint16_t{}, dst}, + v = value{mac}, this](dataplane::neighbor::hashtable& hashtable) { if (!hashtable.insert_or_update(k, v)) { @@ -435,106 +506,96 @@ void module::Upsert(tInterfaceId iface, const ipv6_address_t& dst, bool is_v6, c }); } -void module::UpdateTimestamp(tInterfaceId iface, const ipv6_address_t& dst, bool is_v6) +void module::UpdateTimestamp(std::string iface_name, const ipv6_address_t& dst, bool is_v6) { - TransformHashtables([k = key{iface, is_v6 ? flag_is_ipv6 : uint16_t{}, dst}, - this](dataplane::neighbor::hashtable& hashtable) { - dataplane::neighbor::value* value; - hashtable.lookup(k, value); - if (value) - { - value->last_update_timestamp = current_time_provider_(); - value->last_remove_timestamp = 0; - value->last_resolve_timestamp = 0; - value->number_resolve_after_remove = 0; - stats.hashtable_insert_success++; - } - else - { - stats.hashtable_insert_error++; - } - }); + NEIGHBOR_INFO("UpdateTimestamp %s\n", netlink::Entry{iface_name, dst, std::nullopt, is_v6}.toString().c_str()); + if (neighbor_cache_.UpdateTimestamp(iface_name, dst, is_v6, current_time_provider_())) + { + stats.hashtable_insert_success++; + } + else + { + stats.hashtable_insert_error++; + } } -void module::Remove(tInterfaceId iface, const ipv6_address_t& dst, bool is_v6) +void module::Remove(std::string iface_name, const ipv6_address_t& dst, bool is_v6) { - TransformHashtables([k = key{iface, is_v6 ? flag_is_ipv6 : uint16_t{}, dst}, - this](dataplane::neighbor::hashtable& hashtable) { - dataplane::neighbor::value* value; - hashtable.lookup(k, value); - if (value) - { - value->last_remove_timestamp = current_time_provider_(); - value->number_resolve_after_remove = 0; - stats.hashtable_remove_success++; - } - else - { - stats.hashtable_remove_error++; - } - }); + NEIGHBOR_INFO("Remove %s\n", netlink::Entry{iface_name, dst, std::nullopt, is_v6}.toString().c_str()); + bool is_static = false; +#ifdef CONFIG_YADECAP_UNITTEST + is_static = true; + time_del_unused_ = 1; +#endif + if (neighbor_cache_.Remove(iface_name, dst, is_v6, current_time_provider_(), is_static)) + { + stats.hashtable_remove_success++; + } + else + { + stats.hashtable_remove_error++; + } } -bool module::resolve(const dataplane::neighbor::key& key) +bool module::resolve(const std::string& interface_name, const common::ip_address_t& ip_address) { stats.resolve++; - common::ip_address_t ip_address(key.flags & flag_is_ipv6 ? 6 : 4, key.address.bytes); - std::string interface_name; - { - auto lock = generation_interface.current_lock_guard(); - const auto& interface_id_to_name = generation_interface.current().interface_id_to_name; - auto it = interface_id_to_name.find(key.interface_id); - if (it == interface_id_to_name.end()) - { - YANET_LOG_ERROR("unknown interface_id: %u [ipv4_address: %s]\n", - key.interface_id, - ip_address.toString().data()); - return false; - } - - const auto& [it_route_name, it_interface_name] = it->second; - GCC_BUG_UNUSED(it_route_name); - - interface_name = it_interface_name; - } - + NEIGHBOR_DEBUG("neighbor resolve: %s, %s\n", interface_name.c_str(), ip_address.toString().c_str()); YANET_LOG_DEBUG("resolve: %s, %s\n", interface_name.data(), ip_address.toString().data()); bool result = true; #ifdef CONFIG_YADECAP_AUTOTEST - YANET_LOG_INFO("Mocking resolve: %s, %s\n", - interface_name.data(), - ip_address.toString().data()); + NEIGHBOR_INFO("Mocking resolve: %s, %s\n", + interface_name.data(), + ip_address.toString().data()); value value; value.ether_address.addr_bytes[0] = 44; value.ether_address.addr_bytes[1] = 44; - if (key.flags & flag_is_ipv6) + if (ip_address.is_ipv6()) { value.ether_address.addr_bytes[0] = 66; value.ether_address.addr_bytes[1] = 66; } + + dataplane::neighbor::key key; + memset(&key, 0, sizeof(key)); + if (ip_address.is_ipv4()) + { + key.address.mapped_ipv4_address = ipv4_address_t::convert(ip_address.get_ipv4()); + } + else + { + key.address = ipv6_address_t::convert(ip_address.get_ipv6()); + key.flags |= flag_is_ipv6; + } *((uint32_t*)&value.ether_address.addr_bytes[2]) = rte_hash_crc(key.address.bytes, 16, 0); - value.flags = 0 | flag_is_static; - value.last_update_timestamp = current_time_provider_(); - generation_hashtable.update([this, key, value](neighbor::generation_hashtable& hashtable) { - for (auto& [socket_id, hashtable_updater] : hashtable.hashtable_updater) - { - GCC_BUG_UNUSED(socket_id); - if (!hashtable_updater.get_pointer()->insert_or_update(key, value)) - { - stats.hashtable_insert_error++; - } - else + neighbor_cache_.Insert(interface_name, key.address, ip_address.is_ipv6(), value.ether_address, current_time_provider_(), false); + + std::optional interface_id = GetInterfaceId(interface_name); + if (interface_id.has_value()) + { + key.interface_id = *interface_id; + generation_hashtable.update([this, key, value](neighbor::generation_hashtable& hashtable) { + for (auto& [socket_id, hashtable_updater] : hashtable.hashtable_updater) { - stats.hashtable_insert_success++; + GCC_BUG_UNUSED(socket_id); + if (!hashtable_updater.get_pointer()->insert_or_update(key, value)) + { + stats.hashtable_insert_error++; + } + else + { + stats.hashtable_insert_success++; + } } - } - return eResult::success; - }); + return eResult::success; + }); + } + neighbor_flush(); #else // CONFIG_YADECAP_AUTOTEST @@ -542,7 +603,7 @@ bool module::resolve(const dataplane::neighbor::key& key) int family = AF_INET; int protocol = IPPROTO_ICMP; - if (key.flags & flag_is_ipv6) + if (ip_address.is_ipv6()) { family = AF_INET6; protocol = IPPROTO_ICMPV6; @@ -579,11 +640,11 @@ bool module::resolve(const dataplane::neighbor::key& key) socklen_t address_length = sizeof(address_v4); - if (key.flags & flag_is_ipv6) + if (ip_address.is_ipv6()) { address_v6.sin6_family = AF_INET6; address_v6.sin6_port = 0; - memcpy(address_v6.sin6_addr.__in6_u.__u6_addr8, key.address.bytes, 16); + memcpy(address_v6.sin6_addr.__in6_u.__u6_addr8, ip_address.get_ipv6().data(), 16); address_length = sizeof(address_v6); } @@ -591,7 +652,7 @@ bool module::resolve(const dataplane::neighbor::key& key) { address_v4.sin_family = AF_INET; address_v4.sin_port = 0; - address_v4.sin_addr.s_addr = key.address.mapped_ipv4_address.address; + address_v4.sin_addr.s_addr = ip_address.get_ipv6().get_mapped_ipv4_address(); } icmphdr header; @@ -625,37 +686,29 @@ void module::NeighborThreadAction(uint32_t current_time) } // find records to remove or resolve - std::vector keys_to_remove; - std::vector keys_to_resolve; - auto lock = generation_hashtable.current_lock_guard(); - const auto& hashtable_updater = generation_hashtable.current().hashtable_updater.begin()->second; - for (auto iter : hashtable_updater.range()) + auto [keys_to_remove, keys_to_resolve] = neighbor_cache_.GetKeysRemoveAndResolve(current_time_provider_()); + + // remove records + for (const key_cache& cur_key : keys_to_remove) { - if (!iter.is_valid()) - { - continue; - } - auto& key = *iter.key(); - auto& value = *iter.value(); - if ((value.last_remove_timestamp == 0) || (value.flags & flag_is_static)) + std::optional interface_id = GetInterfaceId(cur_key.iface_name); + if (!interface_id.has_value()) { + stats.hashtable_remove_error++; continue; } - else if (value.last_remove_timestamp + remove_timeout_ < current_time) - { - keys_to_remove.push_back(key); - } - else if (value.last_resolve_timestamp + checks_interval_ <= current_time && value.number_resolve_after_remove < resolve_removed_) + + dataplane::neighbor::key key_main; + memset(&key_main, 0, sizeof(key_main)); + key_main.interface_id = *interface_id; + key_main.address = cur_key.address; + if (cur_key.is_v6) { - keys_to_resolve.push_back(key); + key_main.flags |= flag_is_ipv6; } - } - // remove records - for (const key& cur_key : keys_to_remove) - { - TransformHashtables([cur_key, this](dataplane::neighbor::hashtable& hashtable) { - if (hashtable.remove(cur_key)) + TransformHashtables([key_main, this](dataplane::neighbor::hashtable& hashtable) { + if (hashtable.remove(key_main)) { stats.remove_final++; } @@ -667,27 +720,365 @@ void module::NeighborThreadAction(uint32_t current_time) } // resolve - for (const key& cur_key : keys_to_resolve) + for (const key_cache& cur_key : keys_to_resolve) { - if (!resolve(cur_key)) + common::ip_address_t ip_address(cur_key.is_v6 ? 6 : 4, cur_key.address.bytes); + if (resolve(cur_key.iface_name, ip_address)) { - continue; + neighbor_cache_.SetSentResolve(cur_key, current_time_provider_()); + } + } + + // delete unused records + if (time_del_unused_ != 0 && current_time >= time_del_unused_) + { + time_del_unused_ = 0; + UpdateFromCache(true); + } +} + +std::optional module::GetInterfaceId(const std::string& iface_name) +{ + auto lock = generation_interface.current_lock_guard(); + const auto& interface_name_to_id = generation_interface.current().interface_name_to_id; + auto it = interface_name_to_id.find(iface_name); + if (it == interface_name_to_id.end()) + { + return std::nullopt; + } + + return std::get<1>(it->second); +} + +std::optional module::GetInterfaceName(tInterfaceId iface_id) +{ + auto lock = generation_interface.current_lock_guard(); + const auto& interface_id_to_name = generation_interface.current().interface_id_to_name; + auto it = interface_id_to_name.find(iface_id); + if (it == interface_id_to_name.end()) + { + return std::nullopt; + } + + const auto& [it_route_name, it_interface_name] = it->second; + GCC_BUG_UNUSED(it_route_name); + + return it_interface_name; +} + +void module::UpdateFromCache(bool remove_old) +{ + std::lock_guard lock_cache = neighbor_cache_.LockGuard(); + std::map data = neighbor_cache_.GetData(); + + // insert or update all values + for (const auto& [cur_key, cur_value] : data) + { + std::optional iface_id = GetInterfaceId(cur_key.iface_name); + if (iface_id.has_value()) + { + NEIGHBOR_DEBUG("UpdateFromCache %s iface_id=%d\n", netlink::Entry{cur_key.iface_name, cur_key.address, cur_value.ether_address, cur_key.is_v6}.toString().c_str(), *iface_id); + TransformHashtables([k = key{*iface_id, cur_key.is_v6 ? flag_is_ipv6 : uint16_t{}, cur_key.address}, + v = value{cur_value.ether_address}, + this](dataplane::neighbor::hashtable& hashtable) { + if (!hashtable.insert_or_update(k, v)) + { + stats.hashtable_insert_error++; + } + else + { + stats.hashtable_insert_success++; + } + }); } - TransformHashtables([cur_key, current_time, this](dataplane::neighbor::hashtable& hashtable) { - dataplane::neighbor::value* value; - hashtable.lookup(cur_key, value); - if (value) + else + { + NEIGHBOR_DEBUG("UpdateFromCache %s iface_id=null\n", netlink::Entry{cur_key.iface_name, cur_key.address, cur_value.ether_address, cur_key.is_v6}.toString().c_str()); + stats.hashtable_insert_error++; + } + } + + // remove unused + if (remove_old) + { + std::set keys_del; + { + auto lock = generation_hashtable.current_lock_guard(); + for (auto it : generation_hashtable.current().hashtable_updater.begin()->second.range()) { - value->last_resolve_timestamp = current_time; - value->number_resolve_after_remove++; - stats.resolve_removed++; + if (it.is_valid()) + { + const key& cur_key = *it.key(); + bool exists = false; + std::optional iface_name = GetInterfaceName(cur_key.interface_id); + if (iface_name.has_value()) + { + key_cache check_key{*iface_name, (cur_key.flags & flag_is_ipv6) != 0, cur_key.address}; + if (data.find(check_key) != data.end()) + { + exists = true; + } + } + NEIGHBOR_DEBUG("UpdateFromCache key_exists=%d %s iface_id=%d\n", exists, netlink::Entry{"", cur_key.address, std::nullopt, true}.toString().c_str(), cur_key.interface_id); + + if (!exists) + { + keys_del.insert(cur_key); + } + } } - else + } + + for (const key& key_main : keys_del) + { + NEIGHBOR_DEBUG("UpdateFromCache %s iface_id=%d\n", netlink::Entry{"", key_main.address, std::nullopt, true}.toString().c_str(), key_main.interface_id); + TransformHashtables([key_main, this](dataplane::neighbor::hashtable& hashtable) { + if (hashtable.remove(key_main)) + { + stats.remove_final++; + } + else + { + stats.hashtable_remove_error++; + } + }); + } + } + + // flush + neighbor_flush(); +} + +void NeighborCache::Init(uint64_t checks_interval, uint64_t remove_timeout, uint64_t resolve_removed) +{ + checks_interval_ = checks_interval; + remove_timeout_ = remove_timeout; + resolve_removed_ = resolve_removed; +} + +bool NeighborCache::UpdateTimestamp(std::string iface_name, const ipv6_address_t& dst, bool is_v6, uint32_t timestamp) +{ + key_cache key; + key.iface_name = iface_name; + key.is_v6 = is_v6; + key.address = dst; + + std::lock_guard guard(mutex_); + auto iter = data_.find(key); + if (iter == data_.end()) + { + return false; + } + + dataplane::neighbor::value_cache& value = iter->second; + value.last_update_timestamp = timestamp; + value.last_remove_timestamp = 0; + value.last_resolve_timestamp = 0; + value.number_resolve_after_remove = 0; + + return true; +} + +void NeighborCache::Insert(const std::string& iface_name, const ipv6_address_t& dst, bool is_v6, const rte_ether_addr& mac, uint32_t timestamp, bool is_static) +{ + key_cache key; + key.iface_name = iface_name; + key.is_v6 = is_v6; + key.address = dst; + + std::lock_guard guard(mutex_); + if (!is_static) + { + auto iter = data_.find(key); + if (iter != data_.end() && iter->second.is_static) + { + return; + } + } + + dataplane::neighbor::value_cache value; + memcpy(value.ether_address.addr_bytes, mac.addr_bytes, 6); + value.is_static = is_static; + value.last_update_timestamp = timestamp; + value.last_remove_timestamp = 0; + value.last_resolve_timestamp = 0; + value.number_resolve_after_remove = 0; + + data_[key] = value; +} + +bool NeighborCache::Remove(std::string iface_name, const ipv6_address_t& dst, bool is_v6, uint32_t timestamp, bool is_static) +{ + key_cache key; + key.iface_name = iface_name; + key.is_v6 = is_v6; + key.address = dst; + + std::lock_guard guard(mutex_); + auto iter = data_.find(key); + if (iter == data_.end()) + { + return false; + } + +#ifdef CONFIG_YADECAP_AUTOTEST + data_.erase(key); +#else // CONFIG_YADECAP_AUTOTEST + if (is_static) + { + data_.erase(key); + } + else + { + dataplane::neighbor::value_cache& value = iter->second; + if (value.last_remove_timestamp == 0) + { + value.last_remove_timestamp = timestamp; + value.number_resolve_after_remove = 0; + } + } +#endif // CONFIG_YADECAP_AUTOTEST + + return true; +} + +common::idp::neighbor_show_cache::response NeighborCache::NeighborShow(uint32_t timestamp) const +{ + std::lock_guard guard(mutex_); + common::idp::neighbor_show_cache::response response; + for (const auto& [key, value] : data_) + { + std::optional last_update_timestamp; + if (!value.is_static) + { + last_update_timestamp = timestamp - value.last_update_timestamp; + } + + std::string last_remove_timestamp; + if (value.last_remove_timestamp != 0) + { + last_remove_timestamp = std::to_string(timestamp - value.last_remove_timestamp); + } + + response.emplace_back(key.iface_name, + common::ip_address_t(key.is_v6 ? 6 : 4, key.address.bytes), + common::mac_address_t(value.ether_address.addr_bytes), + last_update_timestamp, + last_remove_timestamp); + } + + return response; +} + +void NeighborCache::UpdateFromDump(const std::vector& dump, uint32_t timestamp) +{ + std::set all_keys; + for (const netlink::Entry& entry : dump) + { + if (entry.mac.has_value()) + { + Insert(entry.ifname, entry.dst, entry.v6, *entry.mac, timestamp, false); + all_keys.insert({entry.ifname, entry.v6, entry.dst}); + } + } + + std::set keys_del; + { + std::lock_guard guard(mutex_); + for (const auto& iter : data_) + { + if (all_keys.find(iter.first) == all_keys.end()) { - stats.hashtable_insert_error++; + keys_del.insert(iter.first); } - }); + } + } + + for (const key_cache& key : keys_del) + { + bool is_static = false; +#ifdef CONFIG_YADECAP_UNITTEST + is_static = true; +#endif + Remove(key.iface_name, key.address, key.is_v6, timestamp, is_static); + } +} + +bool NeighborCache::NeedResolve(std::string iface_name, const ipv6_address_t& dst, bool is_v6, uint32_t timestamp) +{ + key_cache key; + key.iface_name = iface_name; + key.is_v6 = is_v6; + key.address = dst; + + std::lock_guard guard(mutex_); + auto iter = data_.find(key); + if (iter == data_.end()) + { + return true; + } + + dataplane::neighbor::value_cache& value = iter->second; + return value.last_resolve_timestamp != timestamp; +} + +std::pair, std::vector> NeighborCache::GetKeysRemoveAndResolve(uint32_t timestamp) +{ + std::vector keys_to_remove; + std::vector keys_to_resolve; + + std::lock_guard guard(mutex_); + for (const auto& [key, value] : data_) + { + NEIGHBOR_DEBUG("NeighborCache::GetKeysRemoveAndResolve check %s, timestamp=%d, last_remove_timestamp=%d, is_static=%d\n", + netlink::Entry{key.iface_name, key.address, std::nullopt, key.is_v6}.toString().c_str(), + timestamp, + value.last_remove_timestamp, + value.is_static); + if ((value.last_remove_timestamp == 0) || value.is_static) + { + continue; + } + else if (value.last_remove_timestamp + remove_timeout_ < timestamp) + { + keys_to_remove.push_back(key); + } + else if (value.last_resolve_timestamp + checks_interval_ <= timestamp && value.number_resolve_after_remove < resolve_removed_) + { + keys_to_resolve.push_back(key); + } + } + + for (const key_cache& key : keys_to_remove) + { + data_.erase(key); + } + + return {keys_to_remove, keys_to_resolve}; +} + +void NeighborCache::SetSentResolve(const key_cache& key, uint32_t timestamp) +{ + std::lock_guard guard(mutex_); + auto iter = data_.find(key); + if (iter == data_.end()) + { + return; } + + dataplane::neighbor::value_cache& value = iter->second; + value.last_resolve_timestamp = timestamp; + value.number_resolve_after_remove++; +} + +std::map NeighborCache::GetData() const +{ + return data_; +} + +std::lock_guard NeighborCache::LockGuard() const +{ + return std::lock_guard(mutex_); } -} // namespace dataplane::neighbor \ No newline at end of file +} // namespace dataplane::neighbor diff --git a/dataplane/neighbor.h b/dataplane/neighbor.h index e324d9ee..16d5af9a 100644 --- a/dataplane/neighbor.h +++ b/dataplane/neighbor.h @@ -48,16 +48,71 @@ struct key } }; +struct key_cache +{ + std::string iface_name; + bool is_v6; + ipv6_address_t address; + + bool operator<(const key_cache& second) const + { + if (iface_name != second.iface_name) + { + return iface_name < second.iface_name; + } + else if (is_v6 != second.is_v6) + { + return is_v6 < second.is_v6; + } + else + { + return address < second.address; + } + } +}; + static_assert(CONFIG_YADECAP_INTERFACES_SIZE <= 0xFFFF, "invalid size"); struct value { rte_ether_addr ether_address; - uint16_t flags; - uint32_t last_update_timestamp; - uint32_t last_remove_timestamp; - uint32_t last_resolve_timestamp; - uint32_t number_resolve_after_remove; +}; + +struct value_cache +{ + rte_ether_addr ether_address; + bool is_static = false; + uint32_t last_update_timestamp = 0; + uint32_t last_remove_timestamp = 0; + uint32_t last_resolve_timestamp = 0; + uint32_t number_resolve_after_remove = 0; +}; + +class NeighborCache +{ +public: + void Init(uint64_t checks_interval, uint64_t remove_timeout, uint64_t resolve_removed); + void Insert(const std::string& iface_name, const ipv6_address_t& dst, bool is_v6, const rte_ether_addr& mac, uint32_t timestamp, bool is_static); + bool UpdateTimestamp(std::string iface_name, const ipv6_address_t& dst, bool is_v6, uint32_t timestamp); + bool Remove(std::string iface_name, const ipv6_address_t& dst, bool is_v6, uint32_t timestamp, bool is_static); + void UpdateFromDump(const std::vector& dump, uint32_t timestamp); + bool NeedResolve(std::string iface_name, const ipv6_address_t& dst, bool is_v6, uint32_t timestamp); + + common::idp::neighbor_show_cache::response NeighborShow(uint32_t timestamp) const; + + std::pair, std::vector> GetKeysRemoveAndResolve(uint32_t timestamp); + void SetSentResolve(const key_cache& key, uint32_t timestamp); + + std::map GetData() const; + std::lock_guard LockGuard() const; + +private: + mutable std::mutex mutex_; + std::map data_; + + uint64_t checks_interval_ = YANET_CONFIG_NEIGHBOR_CHECK_INTERVAL; + uint64_t remove_timeout_ = YANET_CONFIG_NEIGHBOR_REMOVE_TIMEOUT; + uint64_t resolve_removed_ = YANET_CONFIG_RESOLVE_REMOVED; }; // @@ -69,7 +124,10 @@ using hashtable = hashtable_mod_dynamic; class generation_interface { public: - std::unordered_map interface_name_to_id; + std::unordered_map> ///< interface_id + interface_name_to_id; std::unordered_map> ///< interface_name @@ -111,18 +169,20 @@ class module void update_worker_base(const std::vector>& base_nexts); common::idp::neighbor_show::response neighbor_show() const; + common::idp::neighbor_show_cache::response neighbor_show_cache() const; eResult neighbor_insert(const common::idp::neighbor_insert::request& request); eResult neighbor_remove(const common::idp::neighbor_remove::request& request); eResult neighbor_clear(); eResult neighbor_flush(); eResult neighbor_update_interfaces(const common::idp::neighbor_update_interfaces::request& request); + eResult neighbor_interfaces_switch(); common::idp::neighbor_stats::response neighbor_stats() const; void report(nlohmann::json& json); - void Upsert(tInterfaceId iface, const ipv6_address_t& dst, bool is_v6, const rte_ether_addr& mac); - void UpdateTimestamp(tInterfaceId iface, const ipv6_address_t& dst, bool is_v6); - void Remove(tInterfaceId iface, const ipv6_address_t& dst, bool is_v6); + void Upsert(std::string iface_name, const ipv6_address_t& dst, bool is_v6, const rte_ether_addr& mac); + void UpdateTimestamp(std::string iface_name, const ipv6_address_t& dst, bool is_v6); + void Remove(std::string iface_name, const ipv6_address_t& dst, bool is_v6); void NeighborThreadAction(uint32_t current_time); protected: @@ -131,7 +191,11 @@ class module void StopNetlinkMonitor(); eResult DumpOSNeighbors(); - bool resolve(const dataplane::neighbor::key& key); + bool resolve(const std::string& interface_name, const common::ip_address_t& ip_address); + std::optional GetInterfaceId(const std::string& iface_name); + std::optional GetInterfaceName(tInterfaceId iface_id); + + void UpdateFromCache(bool remove_old); protected: generation_manager generation_interface; @@ -147,6 +211,8 @@ class module void TransformHashtables(UpdaterFunc&& updater); utils::Job resolve_; + NeighborCache neighbor_cache_; + std::atomic time_del_unused_ = 0; }; template diff --git a/dataplane/netlink.cpp b/dataplane/netlink.cpp index a53e2d3f..c6890f88 100644 --- a/dataplane/netlink.cpp +++ b/dataplane/netlink.cpp @@ -9,9 +9,26 @@ namespace netlink { -std::variant ParseNeighbor( - rtnl_neigh* neigh, - std::function(const char*)> get_id) +std::string Entry::toString() const +{ + std::stringstream ss; + ss << "ifname=" << ifname; + if (v6) + { + ss << ", addr=" << common::ipv6_address_t(dst.bytes).toString(); + } + else + { + ss << ", addr=" << common::ipv4_address_t(rte_cpu_to_be_32(dst.mapped_ipv4_address.address)).toString(); + } + if (mac.has_value()) + { + ss << ", mac=" << common::mac_address_t(mac->addr_bytes).toString(); + } + return ss.str(); +} + +std::variant ParseNeighbor(rtnl_neigh* neigh) { int sysifid = rtnl_neigh_get_ifindex(neigh); Entry entry; @@ -21,15 +38,7 @@ std::variant ParseNeighbor( YANET_LOG_INFO("Skipping message for unknown OS interface '%i'\n", sysifid); return NL_OK; } - if (auto id = get_id(ifname); id.has_value()) - { - entry.id = id.value(); - } - else - { - YANET_LOG_INFO("Skipping message for unconfigured interface '%s'\n", ifname); - return NL_OK; - } + entry.ifname = ifname; nl_addr* oaddr = rtnl_neigh_get_dst(neigh); if (!oaddr) @@ -68,8 +77,7 @@ std::variant ParseNeighbor( return entry; } -std::vector Provider::GetHostDump(unsigned rcvbuf_size, - const std::unordered_map& ids) +std::vector Provider::GetHostDump(unsigned rcvbuf_size) { auto deleter = [](nl_sock* sk) { nl_socket_free(sk); }; std::unique_ptr usk{nl_socket_alloc(), deleter}; @@ -101,13 +109,7 @@ std::vector Provider::GetHostDump(unsigned rcvbuf_size, return NL_OK; } - auto var = ParseNeighbor(neigh, [&](const char* name) -> std::optional { - if (auto it = ids.find(name); it != ids.end()) - { - return it->second; - } - return std::nullopt; - }); + auto var = ParseNeighbor(neigh); if (!std::holds_alternative(var)) { @@ -154,10 +156,9 @@ std::vector Provider::GetHostDump(unsigned rcvbuf_size, } void Provider::StartMonitor(unsigned rcvbuf_size, - std::function(const char*)> get_id, - std::function upsert, - std::function remove, - std::function timestamp) + std::function upsert, + std::function remove, + std::function timestamp) { auto deleter = [](nl_sock* sk) { nl_socket_free(sk); }; std::unique_ptr usk{nl_socket_alloc(), deleter}; @@ -181,7 +182,7 @@ void Provider::StartMonitor(unsigned rcvbuf_size, } } - monitor_callback_ = [get_id, upsert, remove, timestamp](nl_msg* msg) -> int { + monitor_callback_ = [upsert, remove, timestamp](nl_msg* msg) -> int { nlmsghdr* msghdr = nlmsg_hdr(msg); if (msghdr->nlmsg_type != RTM_NEWNEIGH && msghdr->nlmsg_type != RTM_DELNEIGH) { @@ -201,7 +202,7 @@ void Provider::StartMonitor(unsigned rcvbuf_size, return NL_OK; } - auto parsed = ParseNeighbor(neigh, get_id); + auto parsed = ParseNeighbor(neigh); if (!std::holds_alternative(parsed)) { diff --git a/dataplane/netlink.hpp b/dataplane/netlink.hpp index b95aa513..ad4b37c8 100644 --- a/dataplane/netlink.hpp +++ b/dataplane/netlink.hpp @@ -15,22 +15,22 @@ namespace netlink struct Entry { - tInterfaceId id; + std::string ifname; ipv6_address_t dst; std::optional mac; bool v6; + + std::string toString() const; }; class Interface { public: - virtual std::vector GetHostDump(unsigned rcvbuf_size, - const std::unordered_map& ids) = 0; + virtual std::vector GetHostDump(unsigned rcvbuf_size) = 0; virtual void StartMonitor(unsigned rcvbuf_size, - std::function(const char*)> get_id, - std::function upsert, - std::function remove, - std::function timestamp) = 0; + std::function upsert, + std::function remove, + std::function timestamp) = 0; virtual void StopMonitor() = 0; virtual ~Interface() = default; virtual bool IsFailedWorkMonitor() = 0; @@ -42,21 +42,19 @@ class Provider : public Interface nl_sock* sk_; std::function monitor_callback_; - std::function upsert_; - std::function remove_; - std::function timestamp_; + std::function upsert_; + std::function remove_; + std::function timestamp_; utils::Job monitor_; std::atomic failed_work_monitor_{false}; public: - std::vector GetHostDump(unsigned rcvbuf_size, - const std::unordered_map& ids) final; + std::vector GetHostDump(unsigned rcvbuf_size) final; void StartMonitor(unsigned rcvbuf_size, - std::function(const char*)> get_id, - std::function upsert, - std::function remove, - std::function timestamp) final; + std::function upsert, + std::function remove, + std::function timestamp) final; void StopMonitor() final; ~Provider() final; bool IsFailedWorkMonitor() final; diff --git a/dataplane/unittest/meson.build b/dataplane/unittest/meson.build index 3beb3acf..3c597c75 100644 --- a/dataplane/unittest/meson.build +++ b/dataplane/unittest/meson.build @@ -12,7 +12,7 @@ sources = files('unittest.cpp', 'sdp.cpp') arch = 'corei7' -cpp_args_append = ['-march=' + arch] +cpp_args_append = ['-march=' + arch, '-DCONFIG_YADECAP_UNITTEST'] unittest = executable('yanet-dataplane-unittest', sources, diff --git a/dataplane/unittest/neighbor.cpp b/dataplane/unittest/neighbor.cpp index b7ecddc4..76c8cf56 100644 --- a/dataplane/unittest/neighbor.cpp +++ b/dataplane/unittest/neighbor.cpp @@ -10,16 +10,14 @@ namespace class MockProvider final : public netlink::Interface { public: - std::vector GetHostDump(unsigned rcvbuf_size, - const std::unordered_map& ids) + std::vector GetHostDump(unsigned rcvbuf_size) { return dump_; } void StartMonitor(unsigned rcvbuf_size, - std::function(const char*)> get_id, - std::function upsert, - std::function remove, - std::function timestamp) + std::function upsert, + std::function remove, + std::function timestamp) { upsert_ = std::move(upsert); timestamp_ = std::move(timestamp); @@ -33,9 +31,9 @@ class MockProvider final : public netlink::Interface { return false; } - std::function upsert_; - std::function timestamp_; - std::function remove_; + std::function upsert_; + std::function timestamp_; + std::function remove_; std::vector dump_; }; @@ -80,15 +78,6 @@ std::ostream& operator<<(std::ostream& os, entry_t e) << std::get<1>(e) << ' ' << std::get(e).toString() << ' ' << std::get(e).toString(); - if (auto& ts = std::get<4>(e)) - { - os << ' ' << ts.value(); - } - else - { - os << " {}"; - } - os << ' ' << std::get<5>(e); return os; } @@ -97,9 +86,7 @@ bool equal(entry_t a, entry_t b) return std::get<0>(a) == std::get<0>(b) && std::get<1>(a) == std::get<1>(b) && std::get<2>(a) == std::get<2>(b) && - std::get<3>(a) == std::get<3>(b) && - std::get<4>(a) == std::get<4>(b) && - std::get<5>(a) == std::get<5>(b); + std::get<3>(a) == std::get<3>(b); } bool equal(response_t a, response_t b) @@ -155,8 +142,9 @@ TEST(NeighborTest, Basic) dut.neighbor_update_interfaces({{1, "route0", "kni1"}}); common::idp::neighbor_show::response expected = { - {"route0", "kni1", Common4FromString("192.168.1.1"), {"DE:AD:BE:EF:01:02"}, {1}, {}}}; - dut.Upsert(1, Ip6FromString("192.168.1.1"), false, EthFromString("DE:AD:BE:EF:01:02")); + {"route0", "kni1", Common4FromString("192.168.1.1"), {"DE:AD:BE:EF:01:02"}}}; + dut.neighbor_interfaces_switch(); + dut.Upsert("kni1", Ip6FromString("192.168.1.1"), false, EthFromString("DE:AD:BE:EF:01:02")); dut.neighbor_flush(); now = 2; @@ -167,42 +155,42 @@ TEST(NeighborTest, Basic) EXPECT_EQ(dut.neighbor_show(), expected); now = 3; - dut.Upsert(1, Ip6FromString("100.200.1.2"), false, EthFromString("DE:AD:BE:EF:08:08")); + dut.Upsert("kni1", Ip6FromString("100.200.1.2"), false, EthFromString("DE:AD:BE:EF:08:08")); dut.neighbor_flush(); expected = { - {"route0", "kni1", Common4FromString("192.168.1.1"), {"DE:AD:BE:EF:01:02"}, {2}, {}}, - {"route0", "kni1", Common4FromString("100.200.1.2"), {"DE:AD:BE:EF:08:08"}, {0}, {}}}; + {"route0", "kni1", Common4FromString("192.168.1.1"), {"DE:AD:BE:EF:01:02"}}, + {"route0", "kni1", Common4FromString("100.200.1.2"), {"DE:AD:BE:EF:08:08"}}}; EXPECT_TRUE(equal(dut.neighbor_show(), expected)); dut.neighbor_flush(); EXPECT_TRUE(equal(dut.neighbor_show(), expected)); now = 4; - dut.UpdateTimestamp(1, Ip6FromString("100.200.1.2"), false); + dut.UpdateTimestamp("kni1", Ip6FromString("100.200.1.2"), false); dut.neighbor_flush(); expected = { - {"route0", "kni1", Common4FromString("192.168.1.1"), {"DE:AD:BE:EF:01:02"}, {3}, {}}, - {"route0", "kni1", Common4FromString("100.200.1.2"), {"DE:AD:BE:EF:08:08"}, {0}, {}}}; + {"route0", "kni1", Common4FromString("192.168.1.1"), {"DE:AD:BE:EF:01:02"}}, + {"route0", "kni1", Common4FromString("100.200.1.2"), {"DE:AD:BE:EF:08:08"}}}; EXPECT_TRUE(equal(dut.neighbor_show(), expected)); dut.neighbor_flush(); EXPECT_TRUE(equal(dut.neighbor_show(), expected)); now = 5; - dut.Remove(1, Ip6FromString("192.168.1.1"), false); + dut.Remove("kni1", Ip6FromString("192.168.1.1"), false); dut.neighbor_flush(); now = 7; expected = { - {"route0", "kni1", Common4FromString("192.168.1.1"), {"DE:AD:BE:EF:01:02"}, 6, "2"}, - {"route0", "kni1", Common4FromString("100.200.1.2"), {"DE:AD:BE:EF:08:08"}, {3}, {}}}; + {"route0", "kni1", Common4FromString("192.168.1.1"), {"DE:AD:BE:EF:01:02"}}, + {"route0", "kni1", Common4FromString("100.200.1.2"), {"DE:AD:BE:EF:08:08"}}}; EXPECT_TRUE(equal(dut.neighbor_show(), expected)); dut.neighbor_flush(); EXPECT_TRUE(equal(dut.neighbor_show(), expected)); - now = 9; + now = 1000; dut.NeighborThreadAction(now); dut.neighbor_flush(); expected = { - {"route0", "kni1", Common4FromString("100.200.1.2"), {"DE:AD:BE:EF:08:08"}, {5}, {}}}; + {"route0", "kni1", Common4FromString("100.200.1.2"), {"DE:AD:BE:EF:08:08"}}}; EXPECT_TRUE(equal(dut.neighbor_show(), expected)); dut.neighbor_flush(); @@ -241,14 +229,14 @@ TEST(NeighborTest, Provider) EXPECT_TRUE(equal(dut.neighbor_show(), {})); mock->dump_ = { - {1, Ip6FromString("192.168.1.1"), EthFromString("DE:AD:BE:EF:01:02"), false}, - {1, Ip6FromString("100.200.1.2"), EthFromString("DE:AD:BE:EF:08:08"), false}}; + {"kni1", Ip6FromString("192.168.1.1"), EthFromString("DE:AD:BE:EF:01:02"), false}, + {"kni1", Ip6FromString("100.200.1.2"), EthFromString("DE:AD:BE:EF:08:08"), false}}; dut.neighbor_clear(); common::idp::neighbor_show::response expected = { - {"route0", "kni1", Common4FromString("192.168.1.1"), {"DE:AD:BE:EF:01:02"}, {0}, {}}, - {"route0", "kni1", Common4FromString("100.200.1.2"), {"DE:AD:BE:EF:08:08"}, {0}, {}}}; + {"route0", "kni1", Common4FromString("192.168.1.1"), {"DE:AD:BE:EF:01:02"}}, + {"route0", "kni1", Common4FromString("100.200.1.2"), {"DE:AD:BE:EF:08:08"}}}; EXPECT_TRUE(equal(dut.neighbor_show(), expected)); } From f8849c910bdd4544ffc8cb8cf3b3be180e19d1c4 Mon Sep 17 00:00:00 2001 From: Alexandr Stepanov Date: Wed, 4 Feb 2026 12:24:16 +0300 Subject: [PATCH 2/3] Fix error in neighbor resolve for ipv4 --- dataplane/neighbor.cpp | 39 ++++++++++++++++++--------------------- dataplane/neighbor.h | 2 +- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/dataplane/neighbor.cpp b/dataplane/neighbor.cpp index 6d491503..d44c8387 100644 --- a/dataplane/neighbor.cpp +++ b/dataplane/neighbor.cpp @@ -424,7 +424,7 @@ eResult module::neighbor_interfaces_switch() if (changed) { - NEIGHBOR_INFO("neighbor_interfaces_switch changed"); + NEIGHBOR_INFO("neighbor_interfaces_switch changed\n"); #ifdef CONFIG_YADECAP_AUTOTEST UpdateFromCache(true); #else // CONFIG_YADECAP_AUTOTEST @@ -463,8 +463,7 @@ void module::StartResolveJob() std::optional interface_name = GetInterfaceName(key.interface_id); if (interface_name.has_value() && neighbor_cache_.NeedResolve(*interface_name, key.address, key.flags & flag_is_ipv6, timestamp)) { - common::ip_address_t ip_address(key.flags & flag_is_ipv6 ? 6 : 4, key.address.bytes); - resolve(*interface_name, ip_address); + resolve(*interface_name, key.address, key.flags & flag_is_ipv6); } } @@ -537,24 +536,26 @@ void module::Remove(std::string iface_name, const ipv6_address_t& dst, bool is_v } } -bool module::resolve(const std::string& interface_name, const common::ip_address_t& ip_address) +bool module::resolve(const std::string& interface_name, const ipv6_address_t& ip_address, bool is_v6) { stats.resolve++; - NEIGHBOR_DEBUG("neighbor resolve: %s, %s\n", interface_name.c_str(), ip_address.toString().c_str()); + NEIGHBOR_DEBUG("neighbor resolve: %s, %s\n", + interface_name.c_str(), + common::ip_address_t(is_v6 ? 6 : 4, ip_address.bytes).toString().c_str()); YANET_LOG_DEBUG("resolve: %s, %s\n", interface_name.data(), - ip_address.toString().data()); + common::ip_address_t(is_v6 ? 6 : 4, ip_address.bytes).toString().data()); bool result = true; #ifdef CONFIG_YADECAP_AUTOTEST NEIGHBOR_INFO("Mocking resolve: %s, %s\n", interface_name.data(), - ip_address.toString().data()); + common::ip_address_t(is_v6 ? 6 : 4, ip_address.bytes).toString().data()); value value; value.ether_address.addr_bytes[0] = 44; value.ether_address.addr_bytes[1] = 44; - if (ip_address.is_ipv6()) + if (is_v6) { value.ether_address.addr_bytes[0] = 66; value.ether_address.addr_bytes[1] = 66; @@ -562,18 +563,15 @@ bool module::resolve(const std::string& interface_name, const common::ip_address dataplane::neighbor::key key; memset(&key, 0, sizeof(key)); - if (ip_address.is_ipv4()) + key.address = ip_address; + if (is_v6) { - key.address.mapped_ipv4_address = ipv4_address_t::convert(ip_address.get_ipv4()); - } - else - { - key.address = ipv6_address_t::convert(ip_address.get_ipv6()); + key.flags |= flag_is_ipv6; } *((uint32_t*)&value.ether_address.addr_bytes[2]) = rte_hash_crc(key.address.bytes, 16, 0); - neighbor_cache_.Insert(interface_name, key.address, ip_address.is_ipv6(), value.ether_address, current_time_provider_(), false); + neighbor_cache_.Insert(interface_name, key.address, is_v6, value.ether_address, current_time_provider_(), false); std::optional interface_id = GetInterfaceId(interface_name); if (interface_id.has_value()) @@ -603,7 +601,7 @@ bool module::resolve(const std::string& interface_name, const common::ip_address int family = AF_INET; int protocol = IPPROTO_ICMP; - if (ip_address.is_ipv6()) + if (is_v6) { family = AF_INET6; protocol = IPPROTO_ICMPV6; @@ -640,11 +638,11 @@ bool module::resolve(const std::string& interface_name, const common::ip_address socklen_t address_length = sizeof(address_v4); - if (ip_address.is_ipv6()) + if (is_v6) { address_v6.sin6_family = AF_INET6; address_v6.sin6_port = 0; - memcpy(address_v6.sin6_addr.__in6_u.__u6_addr8, ip_address.get_ipv6().data(), 16); + memcpy(address_v6.sin6_addr.__in6_u.__u6_addr8, ip_address.bytes, 16); address_length = sizeof(address_v6); } @@ -652,7 +650,7 @@ bool module::resolve(const std::string& interface_name, const common::ip_address { address_v4.sin_family = AF_INET; address_v4.sin_port = 0; - address_v4.sin_addr.s_addr = ip_address.get_ipv6().get_mapped_ipv4_address(); + address_v4.sin_addr.s_addr = ip_address.mapped_ipv4_address.address; } icmphdr header; @@ -722,8 +720,7 @@ void module::NeighborThreadAction(uint32_t current_time) // resolve for (const key_cache& cur_key : keys_to_resolve) { - common::ip_address_t ip_address(cur_key.is_v6 ? 6 : 4, cur_key.address.bytes); - if (resolve(cur_key.iface_name, ip_address)) + if (resolve(cur_key.iface_name, cur_key.address, cur_key.is_v6)) { neighbor_cache_.SetSentResolve(cur_key, current_time_provider_()); } diff --git a/dataplane/neighbor.h b/dataplane/neighbor.h index 16d5af9a..b356b9ed 100644 --- a/dataplane/neighbor.h +++ b/dataplane/neighbor.h @@ -191,7 +191,7 @@ class module void StopNetlinkMonitor(); eResult DumpOSNeighbors(); - bool resolve(const std::string& interface_name, const common::ip_address_t& ip_address); + bool resolve(const std::string& interface_name, const ipv6_address_t& ip_address, bool is_v6); std::optional GetInterfaceId(const std::string& iface_name); std::optional GetInterfaceName(tInterfaceId iface_id); From 9beb80a840769a3532ad9b57d17cfc6fa95337fb Mon Sep 17 00:00:00 2001 From: Alexandr Stepanov Date: Tue, 24 Feb 2026 16:48:41 +0300 Subject: [PATCH 3/3] Change the switching time to the new network interface structure when processing neighbors --- dataplane/controlplane.cpp | 1 + dataplane/neighbor.cpp | 39 +++++++++++++++++++++++++++++--------- dataplane/neighbor.h | 4 ++-- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/dataplane/controlplane.cpp b/dataplane/controlplane.cpp index a1aa5234..af51113a 100644 --- a/dataplane/controlplane.cpp +++ b/dataplane/controlplane.cpp @@ -60,6 +60,7 @@ common::idp::updateGlobalBase::response cControlPlane::updateGlobalBase(const co { return result; } + dataPlane->neighbor.UpdateFromCache(false, true); DEBUG_LATCH_WAIT(common::idp::debug_latch_update::id::global_base_switch); diff --git a/dataplane/neighbor.cpp b/dataplane/neighbor.cpp index d44c8387..92c92f34 100644 --- a/dataplane/neighbor.cpp +++ b/dataplane/neighbor.cpp @@ -238,7 +238,7 @@ eResult module::neighbor_clear() { return result; } - UpdateFromCache(true); + UpdateFromCache(true, false); return eResult::success; } @@ -426,10 +426,10 @@ eResult module::neighbor_interfaces_switch() { NEIGHBOR_INFO("neighbor_interfaces_switch changed\n"); #ifdef CONFIG_YADECAP_AUTOTEST - UpdateFromCache(true); + UpdateFromCache(true, false); #else // CONFIG_YADECAP_AUTOTEST time_del_unused_ = current_time_provider_() + 30; - UpdateFromCache(false); + UpdateFromCache(false, false); #endif // CONFIG_YADECAP_AUTOTEST } @@ -610,7 +610,9 @@ bool module::resolve(const std::string& interface_name, const ipv6_address_t& ip int icmp_socket = socket(family, SOCK_RAW, protocol); if (icmp_socket == -1) { - YANET_LOG_WARNING("neighbor_resolve: socket(): %s\n", + YANET_LOG_WARNING("neighbor_resolve %s on %s: socket(): %s\n", + common::ip_address_t(is_v6 ? 6 : 4, ip_address.bytes).toString().data(), + interface_name.data(), strerror(errno)); return false; } @@ -622,7 +624,8 @@ bool module::resolve(const std::string& interface_name, const ipv6_address_t& ip strlen(interface_name.data()) + 1); if (rc == -1) { - YANET_LOG_WARNING("neighbor_resolve: setsockopt(%s): %s\n", + YANET_LOG_WARNING("neighbor_resolve %s: setsockopt(%s): %s\n", + common::ip_address_t(is_v6 ? 6 : 4, ip_address.bytes).toString().data(), interface_name.data(), strerror(errno)); close(icmp_socket); @@ -662,7 +665,9 @@ bool module::resolve(const std::string& interface_name, const ipv6_address_t& ip &address, address_length) == -1) { - YANET_LOG_WARNING("neighbor_resolve: sendto(): %s\n", + YANET_LOG_WARNING("neighbor_resolve %s on %s: sendto(): %s\n", + common::ip_address_t(is_v6 ? 6 : 4, ip_address.bytes).toString().data(), + interface_name.data(), strerror(errno)); result = false; } @@ -730,7 +735,7 @@ void module::NeighborThreadAction(uint32_t current_time) if (time_del_unused_ != 0 && current_time >= time_del_unused_) { time_del_unused_ = 0; - UpdateFromCache(true); + UpdateFromCache(true, false); } } @@ -747,6 +752,22 @@ std::optional module::GetInterfaceId(const std::string& iface_name return std::get<1>(it->second); } +std::optional module::GetInterfaceIdNext(const std::string& iface_name) +{ + generation_interface.next_lock(); + const auto& interface_name_to_id = generation_interface.next().interface_name_to_id; + auto it = interface_name_to_id.find(iface_name); + if (it == interface_name_to_id.end()) + { + generation_interface.next_unlock(); + return std::nullopt; + } + + tInterfaceId id = std::get<1>(it->second); + generation_interface.next_unlock(); + return id; +} + std::optional module::GetInterfaceName(tInterfaceId iface_id) { auto lock = generation_interface.current_lock_guard(); @@ -763,7 +784,7 @@ std::optional module::GetInterfaceName(tInterfaceId iface_id) return it_interface_name; } -void module::UpdateFromCache(bool remove_old) +void module::UpdateFromCache(bool remove_old, bool use_next_generation) { std::lock_guard lock_cache = neighbor_cache_.LockGuard(); std::map data = neighbor_cache_.GetData(); @@ -771,7 +792,7 @@ void module::UpdateFromCache(bool remove_old) // insert or update all values for (const auto& [cur_key, cur_value] : data) { - std::optional iface_id = GetInterfaceId(cur_key.iface_name); + std::optional iface_id = (use_next_generation ? GetInterfaceIdNext(cur_key.iface_name) : GetInterfaceId(cur_key.iface_name)); if (iface_id.has_value()) { NEIGHBOR_DEBUG("UpdateFromCache %s iface_id=%d\n", netlink::Entry{cur_key.iface_name, cur_key.address, cur_value.ether_address, cur_key.is_v6}.toString().c_str(), *iface_id); diff --git a/dataplane/neighbor.h b/dataplane/neighbor.h index b356b9ed..cb5efd32 100644 --- a/dataplane/neighbor.h +++ b/dataplane/neighbor.h @@ -184,6 +184,7 @@ class module void UpdateTimestamp(std::string iface_name, const ipv6_address_t& dst, bool is_v6); void Remove(std::string iface_name, const ipv6_address_t& dst, bool is_v6); void NeighborThreadAction(uint32_t current_time); + void UpdateFromCache(bool remove_old, bool use_next_generation); protected: void StartResolveJob(); @@ -193,10 +194,9 @@ class module bool resolve(const std::string& interface_name, const ipv6_address_t& ip_address, bool is_v6); std::optional GetInterfaceId(const std::string& iface_name); + std::optional GetInterfaceIdNext(const std::string& iface_name); std::optional GetInterfaceName(tInterfaceId iface_id); - void UpdateFromCache(bool remove_old); - protected: generation_manager generation_interface; generation_manager generation_hashtable;