diff --git a/src/config_interface.cpp b/src/config_interface.cpp index e6b07fa..2834eae 100644 --- a/src/config_interface.cpp +++ b/src/config_interface.cpp @@ -21,7 +21,7 @@ void initialize_swss(std::unordered_map &vlans) std::shared_ptr configDbPtr = std::make_shared ("CONFIG_DB", 0); swss::SubscriberStateTable ipHelpersTable(configDbPtr.get(), "DHCP_RELAY"); swssSelect.addSelectable(&ipHelpersTable); - get_dhcp(vlans, &ipHelpersTable, false); + get_dhcp(vlans, &ipHelpersTable, false, configDbPtr); } catch (const std::bad_alloc &e) { syslog(LOG_ERR, "Failed allocate memory. Exception details: %s", e.what()); @@ -53,13 +53,15 @@ void deinitialize_swss() /** - * @code void get_dhcp(std::unordered_map &vlans, swss::SubscriberStateTable *ipHelpersTable, bool dynamic) + * @code void get_dhcp(std::unordered_map &vlans, swss::SubscriberStateTable *ipHelpersTable, bool dynamic, + std::shared_ptr config_db) * * @brief initialize and get vlan table information from DHCP_RELAY * * @return none */ -void get_dhcp(std::unordered_map &vlans, swss::SubscriberStateTable *ipHelpersTable, bool dynamic) { +void get_dhcp(std::unordered_map &vlans, swss::SubscriberStateTable *ipHelpersTable, bool dynamic, + std::shared_ptr config_db) { swss::Selectable *selectable; int ret = swssSelect.select(&selectable, DEFAULT_TIMEOUT_MSEC); if (ret == swss::Select::ERROR) { @@ -69,7 +71,7 @@ void get_dhcp(std::unordered_map &vlans, swss::Subscr } if (selectable == static_cast (ipHelpersTable)) { if (!dynamic) { - handleRelayNotification(*ipHelpersTable, vlans); + handleRelayNotification(*ipHelpersTable, vlans, config_db); } else { syslog(LOG_WARNING, "relay config changed, " "need restart container to take effect"); @@ -78,7 +80,8 @@ void get_dhcp(std::unordered_map &vlans, swss::Subscr } /** - * @code void handleRelayNotification(swss::SubscriberStateTable &ipHelpersTable, std::unordered_map &vlans) + * @code void handleRelayNotification(swss::SubscriberStateTable &ipHelpersTable, std::unordered_map &vlans, + * std::shared_ptr config_db) * * @brief handles DHCPv6 relay configuration change notification * @@ -87,16 +90,18 @@ void get_dhcp(std::unordered_map &vlans, swss::Subscr * * @return none */ -void handleRelayNotification(swss::SubscriberStateTable &ipHelpersTable, std::unordered_map &vlans) +void handleRelayNotification(swss::SubscriberStateTable &ipHelpersTable, std::unordered_map &vlans, + std::shared_ptr config_db) { std::deque entries; ipHelpersTable.pops(entries); - processRelayNotification(entries, vlans); + processRelayNotification(entries, vlans, config_db); } /** - * @code void processRelayNotification(std::deque &entries, std::unordered_map vlans) + * @code void processRelayNotification(std::deque &entries, std::unordered_map vlans, + std::shared_ptr config_db) * * @brief process DHCPv6 relay servers and options configuration change notification * @@ -105,7 +110,8 @@ void handleRelayNotification(swss::SubscriberStateTable &ipHelpersTable, std::un * * @return none */ -void processRelayNotification(std::deque &entries, std::unordered_map &vlans) +void processRelayNotification(std::deque &entries, std::unordered_map &vlans, + std::shared_ptr config_db) { std::vector servers; bool option_79_default = true; @@ -119,6 +125,27 @@ void processRelayNotification(std::deque &entries, std::string vlan = kfvKey(entry); std::string operation = kfvOp(entry); std::vector fieldValues = kfvFieldsValues(entry); + bool has_ipv6_address = false; + + const std::string match_pattern = "VLAN_INTERFACE|" + vlan + "|*"; + auto keys = config_db->keys(match_pattern); + for (const auto &itr : keys) { + auto found = itr.find_last_of('|'); + if (found == std::string::npos) { + syslog(LOG_WARNING, "%s doesn't exist in VLAN_INTERFACE table, skip it", vlan.c_str()); + continue; + } + std::string ip_address = itr.substr(found + 1); + if (ip_address.find(":") != std::string::npos) { + has_ipv6_address = true; + break; + } + } + + if (!has_ipv6_address) { + syslog(LOG_WARNING, "%s doesn't have IPv6 address configured, skip it", vlan.c_str()); + continue; + } relay_config intf; intf.is_option_79 = option_79_default; @@ -126,6 +153,7 @@ void processRelayNotification(std::deque &entries, intf.interface = vlan; intf.mux_key = ""; intf.state_db = nullptr; + intf.is_lla_ready = false; for (auto &fieldValue: fieldValues) { std::string f = fvField(fieldValue); std::string v = fvValue(fieldValue); @@ -154,3 +182,28 @@ void processRelayNotification(std::deque &entries, vlans[vlan] = intf; } } + +/** + * @code bool check_is_lla_ready(std::string vlan) + * + * @brief Check whether link local address appear in vlan interface + * + * @param vlan string of vlan name + * + * @return bool value indicates whether lla ready + */ +bool check_is_lla_ready(std::string vlan) { + const std::string cmd = "ip -6 addr show " + vlan + " scope link 2> /dev/null"; + std::array buffer; + std::string result; + std::unique_ptr pipe(popen(cmd.c_str(), "r"), pclose); + if (pipe) { + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + result += buffer.data(); + } + if (!result.empty()) { + return true; + } + } + return false; +} diff --git a/src/config_interface.h b/src/config_interface.h index 019838a..31d25e6 100644 --- a/src/config_interface.h +++ b/src/config_interface.h @@ -33,16 +33,19 @@ void initialize_swss(std::unordered_map &vlans); void deinitialize_swss(); /** - * @code void get_dhcp(std::unordered_map &vlans, swss::SubscriberStateTable *ipHelpersTable, bool dynamic) + * @code void get_dhcp(std::unordered_map &vlans, swss::SubscriberStateTable *ipHelpersTable, bool dynamic, + * std::shared_ptr config_db) * * @brief initialize and get vlan information from DHCP_RELAY * * @return none */ -void get_dhcp(std::unordered_map &vlans, swss::SubscriberStateTable *ipHelpersTable, bool dynamic); +void get_dhcp(std::unordered_map &vlans, swss::SubscriberStateTable *ipHelpersTable, bool dynamic, + std::shared_ptr config_db); /** - * @code void handleRelayNotification(swss::SubscriberStateTable &ipHelpersTable, std::unordered_map &vlans) + * @code void handleRelayNotification(swss::SubscriberStateTable &ipHelpersTable, std::unordered_map &vlans, + * std::shared_ptr config_db) * * @brief handles DHCPv6 relay configuration change notification * @@ -51,10 +54,12 @@ void get_dhcp(std::unordered_map &vlans, swss::Subscr * * @return none */ -void handleRelayNotification(swss::SubscriberStateTable &ipHelpersTable, std::unordered_map &vlans); +void handleRelayNotification(swss::SubscriberStateTable &ipHelpersTable, std::unordered_map &vlans, + std::shared_ptr config_db); /** - * @code void processRelayNotification(std::deque &entries, std::unordered_map &vlans) + * @code void processRelayNotification(std::deque &entries, std::unordered_map &vlans, + * std::shared_ptr config_db) * * @brief process DHCPv6 relay servers and options configuration change notification * @@ -63,4 +68,16 @@ void handleRelayNotification(swss::SubscriberStateTable &ipHelpersTable, std::un * * @return none */ -void processRelayNotification(std::deque &entries, std::unordered_map &vlans); +void processRelayNotification(std::deque &entries, std::unordered_map &vlans, + std::shared_ptr config_db); + +/** + * @code bool check_is_lla_ready(std::string vlan) + * + * @brief Check whether link local address appear in vlan interface + * + * @param vlan string of vlan name + * + * @return bool value indicates whether lla ready + */ +bool check_is_lla_ready(std::string vlan); diff --git a/src/relay.cpp b/src/relay.cpp index 60de068..d2b29b7 100644 --- a/src/relay.cpp +++ b/src/relay.cpp @@ -271,6 +271,7 @@ bool DHCPv6Msg::UnmarshalBinary(const uint8_t *packet, uint16_t len) { * @return none */ void initialize_counter(std::shared_ptr state_db, std::string &ifname) { + clear_counter(state_db); std::string table_name = counter_table + ifname; for (auto &intr : counterMap) { state_db->hset(table_name, intr.second, toString(0)); @@ -894,6 +895,8 @@ void client_callback(evutil_socket_t fd, short event, void *arg) { } std::string intf(interfaceName); + // For Vlans that lla is not ready, they wouldn't be added into vlan_map, hence it would be blocked here, no need to + // add is_lla_ready flag check in this callback func auto vlan = vlan_map.find(intf); if (vlan == vlan_map.end()) { if (intf.find(CLIENT_IF_PREFIX) != std::string::npos) { @@ -1087,6 +1090,10 @@ void server_callback_dualtor(evutil_socket_t fd, short event, void *arg) { syslog(LOG_WARNING, "Invalid DHCPv6 header content on loopback socket, packet will be dropped\n"); continue; } + if (!config->is_lla_ready) { + syslog(LOG_WARNING, "Link local address for %s is not ready, packet will be dropped\n", config->interface.c_str()); + continue; + } auto loopback_str = std::string(loopback); increase_counter(config->state_db, loopback_str, msg_type); relay_relay_reply(server_recv_buffer, buffer_sz, config); @@ -1278,7 +1285,110 @@ void loop_relay(std::unordered_map &vlans) { } } - for(auto &vlan : vlans) { + // Add timer to periodly check lla un-ready vlan + struct event *timer_event; + struct timeval tv; + auto timer_args = new std::tuple< + std::unordered_map &, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + std::vector, + int, + int, + struct event * + >(vlans, config_db, state_db, mStateDbMuxTablePtr, sockets, lo_sock, lo_sock, nullptr); + timer_event = event_new(base, -1, EV_PERSIST, lla_check_callback, timer_args); + std::get<7>(*timer_args) = timer_event; + evutil_timerclear(&tv); + // Check timer is set to 60s + tv.tv_sec = 60; + event_add(timer_event, &tv); + + // We set check timer to be executed every 60s, it would case that its first excution be delayed 60s, + // hence manually invoke it here to immediate execute it + lla_check_callback(-1, 0, timer_args); + + if(signal_init() == 0 && signal_start() == 0) { + shutdown_relay(); + for(std::size_t i = 0; i < sockets.size(); i++) { + close(sockets.at(i)); + } + } +} + +/** + * @code shutdown_relay(); + * + * @brief free signals and terminate threads + */ +void shutdown_relay() { + event_del(ev_sigint); + event_del(ev_sigterm); + event_free(ev_sigint); + event_free(ev_sigterm); + event_base_free(base); + deinitialize_swss(); +} + +/** + * @code clear_counter(std::shared_ptr state_db); + * + * @brief Clear all counter + * + * @param state_db state_db connector pointer + * + */ +void clear_counter(std::shared_ptr state_db) { + std::string match_pattern = counter_table + std::string("*"); + auto keys = state_db->keys(match_pattern); + for (auto &itr : keys) { + state_db->del(itr); + } +} + +/** + * @code void lla_check_callback(evutil_socket_t fd, short event, void *arg); + * + * @brief callback for libevent timer to check whether lla is ready for vlan + * + * @param fd libevent socket + * @param event libevent triggered event + * @param arg callback argument provided by user + * + * @return none + */ +void lla_check_callback(evutil_socket_t fd, short event, void *arg) { + auto args = reinterpret_cast *, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + std::vector, + int, + int, + struct event * + > *>(arg); + auto vlans = std::get<0>(*args); + auto config_db = std::get<1>(*args); + auto state_db = std::get<2>(*args); + auto mStateDbMuxTablePtr = std::get<3>(*args); + auto sockets = std::get<4>(*args); + auto lo_sock = std::get<5>(*args); + auto filter = std::get<6>(*args); + auto timer_event = std::get<7>(*args); + + bool all_llas_are_ready = true; + for(auto &vlan : *vlans) { + if (vlan.second.is_lla_ready) { + continue; + } + if (!check_is_lla_ready(vlan.first)) { + syslog(LOG_WARNING, "Link local address for %s is not ready\n", vlan.first.c_str()); + all_llas_are_ready = false; + continue; + } + vlan.second.is_lla_ready = true; int gua_sock = 0; int lla_sock = 0; vlan.second.config_db = config_db; @@ -1299,12 +1409,12 @@ void loop_relay(std::unordered_map &vlans) { sockets.push_back(lla_sock); prepare_relay_config(vlan.second, gua_sock, filter); if (!dual_tor_sock) { - auto event = event_new(base, gua_sock, EV_READ|EV_PERSIST, + auto server_callback_event = event_new(base, gua_sock, EV_READ|EV_PERSIST, server_callback, &(vlan.second)); - if (event == NULL) { + if (server_callback_event == NULL) { syslog(LOG_ERR, "libevent: Failed to create server listen libevent\n"); } - event_add(event, NULL); + event_add(server_callback_event, NULL); syslog(LOG_INFO, "libevent: add server listen socket for %s\n", vlan.first.c_str()); } } else { @@ -1312,25 +1422,8 @@ void loop_relay(std::unordered_map &vlans) { exit(EXIT_FAILURE); } } - - if(signal_init() == 0 && signal_start() == 0) { - shutdown_relay(); - for(std::size_t i = 0; i < sockets.size(); i++) { - close(sockets.at(i)); - } + if (all_llas_are_ready) { + syslog(LOG_INFO, "All Vlans' lla are ready, terminate check timer"); + event_del(timer_event); } } - -/** - * @code shutdown_relay(); - * - * @brief free signals and terminate threads - */ -void shutdown_relay() { - event_del(ev_sigint); - event_del(ev_sigterm); - event_free(ev_sigint); - event_free(ev_sigterm); - event_base_free(base); - deinitialize_swss(); -} diff --git a/src/relay.h b/src/relay.h index 5fb009d..2b129fc 100644 --- a/src/relay.h +++ b/src/relay.h @@ -76,6 +76,7 @@ struct relay_config { bool is_interface_id; std::shared_ptr mux_table; std::shared_ptr config_db; + bool is_lla_ready; }; /* DHCPv6 messages and options */ @@ -474,3 +475,25 @@ void client_packet_handler(uint8_t *buffer, ssize_t length, struct relay_config */ void server_callback(evutil_socket_t fd, short event, void *arg); +/** + * @code clear_counter(std::shared_ptr state_db); + * + * @brief Clear all counter + * + * @param state_db state_db connector pointer + * + */ +void clear_counter(std::shared_ptr state_db); + +/** + * @code void lla_check_callback(evutil_socket_t fd, short event, void *arg); + * + * @brief callback for libevent timer to check whether lla is ready for vlan + * + * @param fd libevent socket + * @param event libevent triggered event + * @param arg callback argument provided by user + * + * @return none + */ +void lla_check_callback(evutil_socket_t fd, short event, void *arg); diff --git a/test/mock_config_interface.cpp b/test/mock_config_interface.cpp index 9298f3c..104ef96 100644 --- a/test/mock_config_interface.cpp +++ b/test/mock_config_interface.cpp @@ -7,6 +7,7 @@ TEST(configInterface, initialize_swss) { config_db->hset("DHCP_RELAY|Vlan1000", "dhcpv6_servers@", "fc02:2000::1,fc02:2000::2,fc02:2000::3,fc02:2000::4"); config_db->hset("DHCP_RELAY|Vlan1000", "dhcpv6_option|rfc6939_support", "false"); config_db->hset("DHCP_RELAY|Vlan1000", "dhcpv6_option|interface_id", "true"); + config_db->hset("VLAN_INTERFACE|Vlan1000|fc02:1000::1", "", ""); std::unordered_map vlans; ASSERT_NO_THROW(initialize_swss(vlans)); EXPECT_EQ(vlans.size(), 1); @@ -23,13 +24,13 @@ TEST(configInterface, get_dhcp) { config_db->hset("DHCP_RELAY|Vlan1000", "dhcpv6_option|interface_id", "true"); swss::SubscriberStateTable ipHelpersTable(config_db.get(), "DHCP_RELAY"); std::unordered_map vlans; - - ASSERT_NO_THROW(get_dhcp(vlans, &ipHelpersTable, false)); + + ASSERT_NO_THROW(get_dhcp(vlans, &ipHelpersTable, false, config_db)); EXPECT_EQ(vlans.size(), 0); swssSelect.addSelectable(&ipHelpersTable); - ASSERT_NO_THROW(get_dhcp(vlans, &ipHelpersTable, false)); + ASSERT_NO_THROW(get_dhcp(vlans, &ipHelpersTable, false, config_db)); EXPECT_EQ(vlans.size(), 1); } @@ -37,7 +38,7 @@ TEST(configInterface, handleRelayNotification) { std::shared_ptr cfg_db = std::make_shared ("CONFIG_DB", 0); swss::SubscriberStateTable ipHelpersTable(cfg_db.get(), "DHCP_RELAY"); std::unordered_map vlans; - handleRelayNotification(ipHelpersTable, vlans); + handleRelayNotification(ipHelpersTable, vlans, cfg_db); } TEST(configInterface, processRelayNotification) { @@ -51,7 +52,7 @@ TEST(configInterface, processRelayNotification) { ipHelpersTable.pops(entries); std::unordered_map vlans; - processRelayNotification(entries, vlans); + processRelayNotification(entries, vlans, config_db); EXPECT_EQ(vlans.size(), 1); EXPECT_FALSE(vlans["Vlan1000"].is_option_79); @@ -64,4 +65,8 @@ MOCK_GLOBAL_FUNC0(stopSwssNotificationPoll, void(void)); TEST(configInterface, stopSwssNotificationPoll) { EXPECT_GLOBAL_CALL(stopSwssNotificationPoll, stopSwssNotificationPoll()).Times(1); ASSERT_NO_THROW(stopSwssNotificationPoll()); -} \ No newline at end of file +} + +TEST(configInterface, check_is_lla_ready) { + EXPECT_FALSE(check_is_lla_ready("Vlan1000")); +} diff --git a/test/mock_relay.cpp b/test/mock_relay.cpp index c4be1ee..6fe7a56 100644 --- a/test/mock_relay.cpp +++ b/test/mock_relay.cpp @@ -345,6 +345,38 @@ TEST(counter, increase_counter) EXPECT_EQ(*ptr, "1"); } +TEST(counter, clear_counter) +{ + std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); + std::string ifname = "Vlan1000"; + initialize_counter(state_db, ifname); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Unknown")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Solicit")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Advertise")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Request")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Confirm")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Renew")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Rebind")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Reply")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Release")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Decline")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Relay-Forward")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Relay-Reply")); + clear_counter(state_db); + EXPECT_FALSE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Unknown")); + EXPECT_FALSE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Solicit")); + EXPECT_FALSE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Advertise")); + EXPECT_FALSE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Request")); + EXPECT_FALSE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Confirm")); + EXPECT_FALSE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Renew")); + EXPECT_FALSE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Rebind")); + EXPECT_FALSE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Reply")); + EXPECT_FALSE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Release")); + EXPECT_FALSE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Decline")); + EXPECT_FALSE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Relay-Forward")); + EXPECT_FALSE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Relay-Reply")); +} + TEST(relay, relay_client) { uint8_t msg[] = {