diff --git a/src/dhcp_device.cpp b/src/dhcp_device.cpp index 8b1ed4f6c..a5056ab0a 100644 --- a/src/dhcp_device.cpp +++ b/src/dhcp_device.cpp @@ -20,11 +20,7 @@ extern bool debug_on; -extern std::string agg_dev_all; -extern std::string agg_dev_prefix; - extern std::unordered_map> rev_vlan_map; -extern std::unordered_map> rev_portchan_map; const std::string db_counter_name[DHCP_MESSAGE_TYPE_COUNT] = { "Unknown", "Discover", "Offer", "Request", "Decline", "Ack", "Nak", "Release", "Inform", "Bootp", "Malformed", "Ignored" @@ -55,6 +51,26 @@ const dhcpv6_message_type_t monitored_v6_msgs[] = { uint8_t monitored_v6_msg_sz = sizeof(monitored_v6_msgs) / sizeof(*monitored_v6_msgs); +static const dhcpv6_message_type_t monitored_v6_forward_rx_msgs[] = { + DHCPV6_MESSAGE_TYPE_SOLICIT, + DHCPV6_MESSAGE_TYPE_REQUEST, + DHCPV6_MESSAGE_TYPE_RELAY_FORW +}; + +static const dhcpv6_message_type_t monitored_v6_forward_tx_msgs[] = { + DHCPV6_MESSAGE_TYPE_RELAY_FORW +}; + +static const dhcpv6_message_type_t monitored_v6_reply_rx_msgs[] = { + DHCPV6_MESSAGE_TYPE_RELAY_REPL +}; + +static const dhcpv6_message_type_t monitored_v6_reply_tx_msgs[] = { + DHCPV6_MESSAGE_TYPE_ADVERTISE, + DHCPV6_MESSAGE_TYPE_REPLY, + DHCPV6_MESSAGE_TYPE_RELAY_REPL +}; + const char *intf_type_name[DHCP_DEVICE_INTF_TYPE_COUNT] = { [DHCP_DEVICE_INTF_TYPE_UPLINK] = "uplink (north)", [DHCP_DEVICE_INTF_TYPE_DOWNLINK] = "downlink (south)", @@ -69,65 +85,131 @@ static const char *counter_desc[DHCP_COUNTERS_COUNT] = { [DHCP_COUNTERS_SNAPSHOT_V6] = "Snapshot_V6", }; +typedef struct +{ + uint64_t last_rx = 0; + uint64_t last_tx = 0; + uint32_t pending_windows = 0; + uint8_t tx_credit = 0; + bool initialized = false; +} relay_flow_state_t; + +static std::unordered_map>> relay_flow_states; + +static void initialize_relay_flow_states(const std::string &ifname) +{ + const counter_t &rx_counters = sock_mgr_get_sock_info(rx_sock).all_counters.at(ifname); + const counter_t &tx_counters = sock_mgr_get_sock_info(tx_sock).all_counters.at(ifname); + for (size_t i = 0; i < monitored_msg_sz; i++) { + int msg_type = monitored_msgs[i]; + relay_flow_states[rx_sock][ifname][msg_type] = { + rx_counters.at(msg_type), tx_counters.at(msg_type), 0, 0, true + }; + } +} + +void dhcp_device_reset_health_state(const std::string &ifname) +{ + relay_flow_states[rx_sock].erase(ifname); + initialize_relay_flow_states(ifname); +} + /** - * @code check_counter_not_transmitted(ifname, rx_sock, tx_sock, monitored_msgs, monitored_msg_cnt); - * @brief Check if there are received DHCP messages that are not transmitted out - * of this interface/device using its counters. + * @brief Update and return unmatched RX age per DHCP message type. * @param ifname interface name * @param rx_sock rx socket * @param tx_sock tx socket * @param monitored_msgs array of monitored message types * @param monitored_msg_cnt number of monitored message types - * @return true if there are received messages not transmitted out, false otherwise + * @return message type to unmatched-window count */ -// these helpers use const int * to accept both dhcp_message_type_t and dhcpv6_message_type_t arrays -// without duplicating the function for each enum type; safe on GCC/Linux where unscoped enums use int -static bool check_counter_not_transmitted(const std::string &ifname, int rx_sock, int tx_sock, const int *monitored_msgs, size_t monitored_msg_cnt) +static std::unordered_map get_untransmitted_windows( + const std::string &ifname, int rx_sock, int tx_sock, + const int *monitored_msgs, size_t monitored_msg_cnt, + bool *reset_detected = NULL) { const sock_info_t &rx_sock_info = sock_mgr_get_sock_info(rx_sock); const counter_t &rx_counters = rx_sock_info.all_counters.at(ifname); - const counter_t &rx_counters_snapshot = rx_sock_info.all_counters_snapshot.at(ifname); - const sock_info_t &tx_sock_info = sock_mgr_get_sock_info(tx_sock); const counter_t &tx_counters = tx_sock_info.all_counters.at(ifname); - const counter_t &tx_counters_snapshot = tx_sock_info.all_counters_snapshot.at(ifname); - // when there is packet in, no packet out + std::unordered_map result; for (size_t i = 0; i < monitored_msg_cnt; i++) { - if (rx_counters.at(monitored_msgs[i]) > rx_counters_snapshot.at(monitored_msgs[i]) && - tx_counters.at(monitored_msgs[i]) <= tx_counters_snapshot.at(monitored_msgs[i])) { - return true; + int msg_type = monitored_msgs[i]; + uint64_t current_rx = rx_counters.at(msg_type); + uint64_t current_tx = tx_counters.at(msg_type); + relay_flow_state_t &state = relay_flow_states[rx_sock][ifname][msg_type]; + + if (!state.initialized || current_rx < state.last_rx || current_tx < state.last_tx) { + if (reset_detected != NULL && state.initialized) { + *reset_detected = true; + } + state = {current_rx, current_tx, 0, 0, true}; + result[msg_type] = 0; + continue; } + + uint64_t rx_delta = current_rx - state.last_rx; + uint64_t tx_delta = current_tx - state.last_tx; + bool had_pending = state.pending_windows > 0; + bool previous_tx_credit = state.tx_credit > 0; + bool current_tx_activity = tx_delta > 0; + state.last_rx = current_rx; + state.last_tx = current_tx; + + if (had_pending) { + if (previous_tx_credit || current_tx_activity) { + state.pending_windows = 0; + state.tx_credit = previous_tx_credit && current_tx_activity ? 1 : 0; + } else { + state.pending_windows++; + state.tx_credit = 0; + } + } else if (rx_delta > 0) { + if (previous_tx_credit) { + state.pending_windows = 0; + state.tx_credit = current_tx_activity ? 1 : 0; + } else if (current_tx_activity) { + state.pending_windows = 0; + state.tx_credit = 0; + } else { + state.pending_windows = 1; + state.tx_credit = 0; + } + } else { + state.pending_windows = 0; + state.tx_credit = current_tx_activity ? 1 : 0; + } + result[msg_type] = state.pending_windows; } - return false; + return result; } -/** - * @code dhcp_device_check_positive_health(ifname); - * @brief Check that DHCP relayed messages are being transmitted out of this interface/dev - * using its counters. The interface is positively healthy if there are DHCP message - * travelling through it. - * @param ifname interface name - * @return DHCP_MON_STATUS_HEALTHY, DHCP_MON_STATUS_UNHEALTHY, or DHCP_MON_STATUS_INDETERMINATE - */ -static dhcp_mon_status_t dhcp_device_check_positive_health(const std::string &ifname) +std::unordered_map dhcp_device_get_untransmitted_windows(const std::string &ifname) { - return check_counter_not_transmitted(ifname, rx_sock, tx_sock, (const int *)monitored_msgs, monitored_msg_sz) ? - DHCP_MON_STATUS_UNHEALTHY : DHCP_MON_STATUS_HEALTHY; + return get_untransmitted_windows(ifname, rx_sock, tx_sock, + (const int *)monitored_msgs, monitored_msg_sz); } -/** - * @code dhcp_device_check_positive_health_v6(ifname); - * @brief Check that DHCPv6 relayed messages are being transmitted out of this interface/dev - * using its counters. The interface is positively healthy if there are DHCPv6 message - * travelling through it. - * @param ifname interface name - * @return DHCP_MON_STATUS_HEALTHY, DHCP_MON_STATUS_UNHEALTHY, or DHCP_MON_STATUS_INDETERMINATE - */ -static dhcp_mon_status_t dhcp_device_check_positive_health_v6(const std::string &ifname) +static bool check_counter_not_transmitted(const std::string &ifname, int rx_sock, int tx_sock, + const int *monitored_msgs, size_t monitored_msg_cnt) { - return check_counter_not_transmitted(ifname, rx_sock_v6, tx_sock_v6, (const int *)monitored_v6_msgs, monitored_v6_msg_sz) ? - DHCP_MON_STATUS_UNHEALTHY : DHCP_MON_STATUS_HEALTHY; + const sock_info_t &rx_sock_info = sock_mgr_get_sock_info(rx_sock); + const counter_t &rx_counters = rx_sock_info.all_counters.at(ifname); + const counter_t &rx_snapshot = rx_sock_info.all_counters_snapshot.at(ifname); + const sock_info_t &tx_sock_info = sock_mgr_get_sock_info(tx_sock); + const counter_t &tx_counters = tx_sock_info.all_counters.at(ifname); + const counter_t &tx_snapshot = tx_sock_info.all_counters_snapshot.at(ifname); + + for (size_t i = 0; i < monitored_msg_cnt; i++) { + int msg_type = monitored_msgs[i]; + if (rx_counters.at(msg_type) > rx_snapshot.at(msg_type) && + tx_counters.at(msg_type) <= tx_snapshot.at(msg_type)) { + return true; + } + } + return false; } /** @@ -157,6 +239,62 @@ static bool check_counter_increased(const std::string &ifname, int sock, const i return false; } +/** + * @code dhcp_device_check_positive_health(ifname); + * @brief Check that RX Discover, Offer, Request, and Ack activity has matching same-type TX activity. + * @param ifname interface name + * @return DHCP_MON_STATUS_HEALTHY, DHCP_MON_STATUS_UNHEALTHY, or DHCP_MON_STATUS_INDETERMINATE + */ +static dhcp_mon_status_t dhcp_device_check_positive_health(const std::string &ifname) +{ + bool reset_detected = false; + bool has_activity = check_counter_increased(ifname, rx_sock, + (const int *)monitored_msgs, monitored_msg_sz) || + check_counter_increased(ifname, tx_sock, + (const int *)monitored_msgs, monitored_msg_sz); + auto windows_by_type = get_untransmitted_windows( + ifname, rx_sock, tx_sock, (const int *)monitored_msgs, monitored_msg_sz, + &reset_detected); + if (reset_detected) { + // Counter replacement ends any pre-reset unhealthy episode. + return DHCP_MON_STATUS_HEALTHY; + } + for (const auto &[msg_type, windows] : windows_by_type) { + if (windows > 0) { + return DHCP_MON_STATUS_UNHEALTHY; + } + } + return has_activity ? DHCP_MON_STATUS_HEALTHY : DHCP_MON_STATUS_INDETERMINATE; +} + +/** + * @code dhcp_device_check_positive_health_v6(ifname); + * @brief Check that RX Solicit, Request, or Relay-Forward activity has TX Relay-Forward activity, + * and RX Relay-Reply activity has TX Advertise, Reply, or Relay-Reply activity. + * @param ifname interface name + * @return DHCP_MON_STATUS_HEALTHY, DHCP_MON_STATUS_UNHEALTHY, or DHCP_MON_STATUS_INDETERMINATE + */ +static dhcp_mon_status_t dhcp_device_check_positive_health_v6(const std::string &ifname) +{ + const bool forward_rx = check_counter_increased( + ifname, rx_sock_v6, (const int *)monitored_v6_forward_rx_msgs, + sizeof(monitored_v6_forward_rx_msgs) / sizeof(*monitored_v6_forward_rx_msgs)); + const bool forward_tx = check_counter_increased( + ifname, tx_sock_v6, (const int *)monitored_v6_forward_tx_msgs, + sizeof(monitored_v6_forward_tx_msgs) / sizeof(*monitored_v6_forward_tx_msgs)); + const bool reply_rx = check_counter_increased( + ifname, rx_sock_v6, (const int *)monitored_v6_reply_rx_msgs, + sizeof(monitored_v6_reply_rx_msgs) / sizeof(*monitored_v6_reply_rx_msgs)); + const bool reply_tx = check_counter_increased( + ifname, tx_sock_v6, (const int *)monitored_v6_reply_tx_msgs, + sizeof(monitored_v6_reply_tx_msgs) / sizeof(*monitored_v6_reply_tx_msgs)); + + if ((forward_rx && !forward_tx) || (reply_rx && !reply_tx)) { + return DHCP_MON_STATUS_UNHEALTHY; + } + return forward_rx || reply_rx ? DHCP_MON_STATUS_HEALTHY : DHCP_MON_STATUS_INDETERMINATE; +} + /** * @code dhcp_device_check_negative_health(ifname); * @@ -204,7 +342,7 @@ static dhcp_mon_status_t dhcp_device_check_negative_health_v6(const std::string * @return true if deltas are equal with given ratio, false otherwise */ static bool check_counters_delta_expected(const std::string &ifname, const std::string &other_ifname, int sock, - uint8_t ratio, const int *monitored_msgs, size_t monitored_msg_cnt) + size_t ratio, const int *monitored_msgs, size_t monitored_msg_cnt) { const sock_info_t &sock_info = sock_mgr_get_sock_info(sock); const counter_t &counters = sock_info.all_counters.at(ifname); @@ -223,63 +361,49 @@ static bool check_counters_delta_expected(const std::string &ifname, const std:: return true; } -static dhcp_mon_status_t dhcp_device_check_agg_equal_rx(const std::string &ifname) -{ - std::string agg_ifname = agg_dev_prefix + ifname; - return check_counters_delta_expected(ifname, agg_ifname, rx_sock, 1, (const int *)monitored_msgs, monitored_msg_sz) ? - DHCP_MON_STATUS_HEALTHY : DHCP_MON_STATUS_UNHEALTHY; -} - -static dhcp_mon_status_t dhcp_device_check_agg_equal_tx(const std::string &ifname) -{ - std::string agg_ifname = agg_dev_prefix + ifname; - return check_counters_delta_expected(ifname, agg_ifname, tx_sock, 1, (const int *)monitored_msgs, monitored_msg_sz) ? - DHCP_MON_STATUS_HEALTHY : DHCP_MON_STATUS_UNHEALTHY; -} - -static dhcp_mon_status_t dhcp_device_check_agg_equal_rx_v6(const std::string &ifname) -{ - std::string agg_ifname = agg_dev_prefix + ifname; - return check_counters_delta_expected(ifname, agg_ifname, rx_sock_v6, 1, (const int *)monitored_v6_msgs, monitored_v6_msg_sz) ? - DHCP_MON_STATUS_HEALTHY : DHCP_MON_STATUS_UNHEALTHY; -} - -static dhcp_mon_status_t dhcp_device_check_agg_equal_tx_v6(const std::string &ifname) -{ - std::string agg_ifname = agg_dev_prefix + ifname; - return check_counters_delta_expected(ifname, agg_ifname, tx_sock_v6, 1, (const int *)monitored_v6_msgs, monitored_v6_msg_sz) ? - DHCP_MON_STATUS_HEALTHY : DHCP_MON_STATUS_UNHEALTHY; -} - -static dhcp_mon_status_t dhcp_device_check_agg_multiple_rx(const std::string &ifname) -{ - std::string agg_ifname = agg_dev_prefix + ifname; - return check_counters_delta_expected(ifname, agg_ifname, rx_sock, readonly_access(rev_vlan_map, ifname).size() + readonly_access(rev_portchan_map, ifname).size(), - (const int *)monitored_msgs, monitored_msg_sz) ? - DHCP_MON_STATUS_HEALTHY : DHCP_MON_STATUS_UNHEALTHY; -} - -static dhcp_mon_status_t dhcp_device_check_agg_multiple_tx(const std::string &ifname) -{ - std::string agg_ifname = agg_dev_prefix + ifname; - return check_counters_delta_expected(ifname, agg_ifname, tx_sock, readonly_access(rev_vlan_map, ifname).size() + readonly_access(rev_portchan_map, ifname).size(), - (const int *)monitored_msgs, monitored_msg_sz) ? - DHCP_MON_STATUS_HEALTHY : DHCP_MON_STATUS_UNHEALTHY; -} - -static dhcp_mon_status_t dhcp_device_check_agg_multiple_rx_v6(const std::string &ifname) +/** + * @code get_aggregate_ratio(ifname, sock); + * + * @brief Get the expected aggregate ratio for an interface and packet direction + * + * @param ifname Parent interface name + * @param sock Socket identifying the protocol family and direction + * + * @return One for RX and PortChannel TX; direct VLAN member count for VLAN TX + */ +static size_t get_aggregate_ratio(const std::string &ifname, int sock) { - std::string agg_ifname = agg_dev_prefix + ifname; - return check_counters_delta_expected(ifname, agg_ifname, rx_sock_v6, readonly_access(rev_vlan_map, ifname).size() + readonly_access(rev_portchan_map, ifname).size(), - (const int *)monitored_v6_msgs, monitored_v6_msg_sz) ? - DHCP_MON_STATUS_HEALTHY : DHCP_MON_STATUS_UNHEALTHY; + // RX crosses one edge. VLAN TX is observed on every direct member; PortChannel TX is observed on one member. + if (sock_mgr_get_sock_info(sock).is_rx) { + return 1; + } + const auto vlan = rev_vlan_map.find(ifname); + return vlan == rev_vlan_map.end() ? 1 : vlan->second.size(); } -static dhcp_mon_status_t dhcp_device_check_agg_multiple_tx_v6(const std::string &ifname) +/** + * @code check_aggregate_health(ifname, sock, monitored_msgs, monitored_msg_cnt); + * + * @brief Compare a parent interface counter with the aggregate of its direct member-interface counters + * + * @param ifname Parent interface name + * @param sock Socket identifying the protocol family and direction + * @param monitored_msgs Message types to compare + * @param monitored_msg_cnt Number of message types to compare + * + * @return HEALTHY when idle or matching, UNHEALTHY when the expected ratio does not match + */ +static dhcp_mon_status_t check_aggregate_health(const std::string &ifname, int sock, const int *monitored_msgs, + size_t monitored_msg_cnt) { - std::string agg_ifname = agg_dev_prefix + ifname; - return check_counters_delta_expected(ifname, agg_ifname, tx_sock_v6, readonly_access(rev_vlan_map, ifname).size() + readonly_access(rev_portchan_map, ifname).size(), - (const int *)monitored_v6_msgs, monitored_v6_msg_sz) ? + const std::string agg_ifname = get_agg_counter_ifname(ifname); + if (!check_counter_increased(ifname, sock, monitored_msgs, monitored_msg_cnt) && + !check_counter_increased(agg_ifname, sock, monitored_msgs, monitored_msg_cnt)) { + return DHCP_MON_STATUS_HEALTHY; + } + return check_counters_delta_expected( + ifname, agg_ifname, sock, get_aggregate_ratio(ifname, sock), + monitored_msgs, monitored_msg_cnt) ? DHCP_MON_STATUS_HEALTHY : DHCP_MON_STATUS_UNHEALTHY; } @@ -382,7 +506,14 @@ void dhcp_device_print_status_debug(const std::string &ifname, dhcp_counters_typ dhcp_mon_status_t dhcp_device_get_status(const std::string &ifname, dhcp_device_check_t check_type) { - if (sock_mgr_counters_unchanged(ifname, (const int *)monitored_msgs, monitored_msg_sz, (const int *)monitored_v6_msgs, monitored_v6_msg_sz)) { + bool aggregate_check = + check_type == DHCP_DEVICE_CHECK_AGG_RX || + check_type == DHCP_DEVICE_CHECK_AGG_TX || + check_type == DHCP_DEVICE_CHECK_AGG_RX_V6 || + check_type == DHCP_DEVICE_CHECK_AGG_TX_V6; + if (check_type != DHCP_DEVICE_CHECK_POSITIVE && !aggregate_check && + sock_mgr_counters_unchanged(ifname, (const int *)monitored_msgs, monitored_msg_sz, + (const int *)monitored_v6_msgs, monitored_v6_msg_sz)) { return DHCP_MON_STATUS_INDETERMINATE; } @@ -395,27 +526,17 @@ dhcp_mon_status_t dhcp_device_get_status(const std::string &ifname, dhcp_device_ return dhcp_device_check_positive_health_v6(ifname); case DHCP_DEVICE_CHECK_NEGATIVE_V6: return dhcp_device_check_negative_health_v6(ifname); - case DHCP_DEVICE_CHECK_AGG_EQUAL_RX: - return dhcp_device_check_agg_equal_rx(ifname); - case DHCP_DEVICE_CHECK_AGG_EQUAL_TX: - return dhcp_device_check_agg_equal_tx(ifname); - case DHCP_DEVICE_CHECK_AGG_EQUAL_RX_V6: - return dhcp_device_check_agg_equal_rx_v6(ifname); - case DHCP_DEVICE_CHECK_AGG_EQUAL_TX_V6: - return dhcp_device_check_agg_equal_tx_v6(ifname); - case DHCP_DEVICE_CHECK_AGG_MULTIPLE_RX: - return dhcp_device_check_agg_multiple_rx(ifname); - case DHCP_DEVICE_CHECK_AGG_MULTIPLE_TX: - return dhcp_device_check_agg_multiple_tx(ifname); - case DHCP_DEVICE_CHECK_AGG_MULTIPLE_RX_V6: - return dhcp_device_check_agg_multiple_rx_v6(ifname); - case DHCP_DEVICE_CHECK_AGG_MULTIPLE_TX_V6: - return dhcp_device_check_agg_multiple_tx_v6(ifname); + case DHCP_DEVICE_CHECK_AGG_RX: + return check_aggregate_health(ifname, rx_sock, (const int *)monitored_msgs, monitored_msg_sz); + case DHCP_DEVICE_CHECK_AGG_TX: + return check_aggregate_health(ifname, tx_sock, (const int *)monitored_msgs, monitored_msg_sz); + case DHCP_DEVICE_CHECK_AGG_RX_V6: + return check_aggregate_health(ifname, rx_sock_v6, (const int *)monitored_v6_msgs, monitored_v6_msg_sz); + case DHCP_DEVICE_CHECK_AGG_TX_V6: + return check_aggregate_health(ifname, tx_sock_v6, (const int *)monitored_v6_msgs, monitored_v6_msg_sz); default: - break; + return DHCP_MON_STATUS_UNHEALTHY; } - - return DHCP_MON_STATUS_UNHEALTHY; } int initialize_intf_mac_and_ip_addr(dhcp_device_context_t *context) diff --git a/src/dhcp_device.h b/src/dhcp_device.h index 5d4b00721..45dc2f7d0 100644 --- a/src/dhcp_device.h +++ b/src/dhcp_device.h @@ -18,6 +18,7 @@ #include #include #include +#include /** DHCP message types */ typedef enum @@ -135,15 +136,11 @@ typedef enum DHCP_DEVICE_CHECK_NEGATIVE, /** Presence of relayed DHCP packets activity is flagged as unhealthy state */ DHCP_DEVICE_CHECK_POSITIVE, /** Validate that received DORA packets are relayed */ DHCP_DEVICE_CHECK_NEGATIVE_V6, /** Presence of relayed DHCPv6 packets activity is flagged as unhealthy state */ - DHCP_DEVICE_CHECK_POSITIVE_V6, /** Validate that received SARR packets are relayed */ - DHCP_DEVICE_CHECK_AGG_EQUAL_RX, /** Validate that aggregate device rx counters equal sum of member interfaces rx counters */ - DHCP_DEVICE_CHECK_AGG_EQUAL_TX, /** Validate that aggregate device tx counters equal sum of member interfaces tx counters */ - DHCP_DEVICE_CHECK_AGG_EQUAL_RX_V6, /** Validate that aggregate device rx counters equal sum of member interfaces rx counters for IPv6 */ - DHCP_DEVICE_CHECK_AGG_EQUAL_TX_V6, /** Validate that aggregate device tx counters equal sum of member interfaces tx counters for IPv6 */ - DHCP_DEVICE_CHECK_AGG_MULTIPLE_RX, /** Validate that aggregate device rx counters are multiple of member interfaces rx counters */ - DHCP_DEVICE_CHECK_AGG_MULTIPLE_TX, /** Validate that aggregate device tx counters are multiple of member interfaces tx counters */ - DHCP_DEVICE_CHECK_AGG_MULTIPLE_RX_V6, /** Validate that aggregate device rx counters are multiple of member interfaces rx counters for IPv6 */ - DHCP_DEVICE_CHECK_AGG_MULTIPLE_TX_V6 /** Validate that aggregate device tx counters are multiple of member interfaces tx counters for IPv6 */ + DHCP_DEVICE_CHECK_POSITIVE_V6, /** Validate SARR and DHCPv6 relay-wrapper transformations */ + DHCP_DEVICE_CHECK_AGG_RX, /** Compare IPv4 RX on a parent interface with its member aggregate */ + DHCP_DEVICE_CHECK_AGG_TX, /** Compare IPv4 TX on a parent interface with its member aggregate */ + DHCP_DEVICE_CHECK_AGG_RX_V6, /** Compare IPv6 RX on a parent interface with its member aggregate */ + DHCP_DEVICE_CHECK_AGG_TX_V6 /** Compare IPv6 TX on a parent interface with its member aggregate */ } dhcp_device_check_t; /** Monitored DHCP message type */ @@ -242,6 +239,14 @@ void dhcp_device_free(dhcp_device_context_t *context); */ dhcp_mon_status_t dhcp_device_get_status(const std::string &ifname, dhcp_device_check_t check_type); +/** + * @brief Update and return unmatched DHCPv4 relay RX age in health windows per message type. + */ +std::unordered_map dhcp_device_get_untransmitted_windows(const std::string &ifname); + +/** Reset DHCPv4 relay-flow watermarks */ +void dhcp_device_reset_health_state(const std::string &ifname); + /** * @code dhcp_device_print_status(ifname, type); * diff --git a/src/dhcp_devman.cpp b/src/dhcp_devman.cpp index 9fa951e3c..7d8fc09f4 100644 --- a/src/dhcp_devman.cpp +++ b/src/dhcp_devman.cpp @@ -180,22 +180,9 @@ int dhcp_devman_setup_dual_tor_mode(const char *name) bool dhcp_devman_is_tracked_interface(const std::string &ifname) { - auto itr = intfs.find(ifname); - if (itr != intfs.end()) { - return true; - } - auto vlan_itr = vlan_map.find(ifname); - if (vlan_itr != vlan_map.end()) { - return true; - } - auto portchan_itr = portchan_map.find(ifname); - if (portchan_itr != portchan_map.end()) { - return true; - } - if (ifname == mgmt_ifname) { - return true; - } - return false; + return intfs.find(ifname) != intfs.end() || + !dhcp_devman_get_parent_ifname(ifname).empty() || + ifname == mgmt_ifname; } /** @@ -248,7 +235,10 @@ static void update_portchannel_mapping() auto second = key.find_last_of('|'); auto portchannel = key.substr(first + 1, second - first - 1); auto ifname = key.substr(second + 1); - if (intfs.find(portchannel) == intfs.end()) { + bool portchannel_is_context = intfs.find(portchannel) != intfs.end(); + bool portchannel_is_vlan_member = vlan_map.find(portchannel) != vlan_map.end(); + // Dual-ToR downlink counters require MUX attribution that is unavailable on a nested PortChannel. + if (!portchannel_is_context && (!portchannel_is_vlan_member || dual_tor_mode)) { all_skipped_ifname += "<" + ifname + ", " + portchannel + ">, "; continue; } @@ -296,7 +286,7 @@ int dhcp_devman_init() agg_dev_all = "Agg-" + downstream_ifname; agg_dev_prefix = agg_dev_all + "-"; - // vlan and its members, portchannel and its members are initialized regardless of whether they are in cmdline + // PortChannel members depend on VLAN mappings to recognize a PortChannel under a monitored VLAN. update_vlan_mapping(); update_portchannel_mapping(); @@ -315,21 +305,59 @@ void dhcp_devman_free() intfs.clear(); } -const dhcp_device_context_t *dhcp_devman_get_device_context(const std::string &ifname) +static constexpr unsigned int MAX_CONTEXT_DEPTH = 3; + +std::string dhcp_devman_get_parent_ifname(const std::string &ifname) { - const auto iter = intfs.find(ifname); - if (iter != intfs.end()) { - return iter->second; + if (intfs.find(ifname) != intfs.end()) { + return ""; } const auto vlan = vlan_map.find(ifname); if (vlan != vlan_map.end() && ifname != vlan->second) { - return dhcp_devman_get_device_context(vlan->second); + return vlan->second; } const auto port_channel = portchan_map.find(ifname); if (port_channel != portchan_map.end() && ifname != port_channel->second) { - return dhcp_devman_get_device_context(port_channel->second); + return port_channel->second; } - return NULL; + return ""; +} + +/** + * @code get_device_context(ifname, depth); + * + * @brief Follow parent mappings until reaching a tracked input interface + * + * @param ifname Interface name to resolve + * @param depth Current parent traversal depth + * + * @return Tracked interface context, or NULL when no context is found + */ +static const dhcp_device_context_t *get_device_context( + const std::string &ifname, unsigned int depth) +{ + if (depth > MAX_CONTEXT_DEPTH) { + syslog_debug(LOG_WARNING, "Exceeded interface membership depth at %s", ifname.c_str()); + return NULL; + } + const auto iter = intfs.find(ifname); + if (iter != intfs.end()) { + return iter->second; + } + const std::string parent_ifname = dhcp_devman_get_parent_ifname(ifname); + return parent_ifname.empty() ? NULL : get_device_context(parent_ifname, depth + 1); +} + +const dhcp_device_context_t *dhcp_devman_get_device_context(const std::string &ifname) +{ + return get_device_context(ifname, 0); +} + +std::string dhcp_devman_get_agg_counter_ifname(const std::string &ifname) +{ + const std::string parent_ifname = dhcp_devman_get_parent_ifname(ifname); + return parent_ifname.empty() ? agg_dev_all : + get_agg_counter_ifname(parent_ifname); } void dhcp_devman_print_all_status(dhcp_counters_type_t type) diff --git a/src/dhcp_devman.h b/src/dhcp_devman.h index 73c1cd6f2..f0cdde3be 100644 --- a/src/dhcp_devman.h +++ b/src/dhcp_devman.h @@ -127,17 +127,39 @@ int dhcp_devman_init(); */ void dhcp_devman_free(); +/** + * @code dhcp_devman_get_parent_ifname(ifname); + * + * @brief find the immediate parent interface of a tracked interface. + * + * @param ifname interface name + * + * @return Immediate parent interface name, or an empty string when the interface is a tracked root or is unmapped + */ +std::string dhcp_devman_get_parent_ifname(const std::string &ifname); + /** * @code dhcp_devman_get_device_context(ifname); * - * @brief find device context, if its physical interface, will query vlan_map and portchannel_map first + * @brief find the tracked input interface that owns an interface. * * @param ifname interface name * - * @return pointer to device (interface) context if found, NULL otherwise + * @return The interface's tracked context; a tracked input interface returns its own context */ const dhcp_device_context_t* dhcp_devman_get_device_context(const std::string &ifname); +/** + * @code dhcp_devman_get_agg_counter_ifname(ifname); + * + * @brief find the aggregate counter updated by an interface observation. + * + * @param ifname interface name + * + * @return immediate-parent aggregate, or the root aggregate when no parent exists + */ +std::string dhcp_devman_get_agg_counter_ifname(const std::string &ifname); + /** * @code dhcp_devman_print_all_status(type); * diff --git a/src/dhcp_mon.cpp b/src/dhcp_mon.cpp index ef4f6623d..94990d959 100644 --- a/src/dhcp_mon.cpp +++ b/src/dhcp_mon.cpp @@ -81,7 +81,7 @@ static void recalculate_agg_counter(all_counters_t &all_counters) if (mgmt_ifname == context->intf) { continue; } - counter_t &agg_counter = all_counters.at(get_agg_counter_ifname(ifname, context->intf)); + counter_t &agg_counter = all_counters.at(dhcp_devman_get_agg_counter_ifname(ifname)); for (const auto &[msg_type, count] : counter) { agg_counter[msg_type] += count; } @@ -466,7 +466,9 @@ static void initialize_all_intf_counters() initialize_all_counters(ifname); } initialize_all_counters(vlan); - sock_mgr_init_cache_counters(agg_dev_prefix + vlan, DHCP_MESSAGE_TYPE_COUNT, DHCPV6_MESSAGE_TYPE_COUNT); + sock_mgr_init_cache_counters( + get_agg_counter_ifname(vlan), + DHCP_MESSAGE_TYPE_COUNT, DHCPV6_MESSAGE_TYPE_COUNT); } for (const auto &[portchan, intfs] : rev_portchan_map) { @@ -474,7 +476,9 @@ static void initialize_all_intf_counters() initialize_all_counters(ifname); } initialize_all_counters(portchan); - sock_mgr_init_cache_counters(agg_dev_prefix + portchan, DHCP_MESSAGE_TYPE_COUNT, DHCPV6_MESSAGE_TYPE_COUNT); + sock_mgr_init_cache_counters( + get_agg_counter_ifname(portchan), + DHCP_MESSAGE_TYPE_COUNT, DHCPV6_MESSAGE_TYPE_COUNT); } // Now all vlan and portchannel related interfaces have entries in counters, now do the rest (uplink) @@ -520,6 +524,8 @@ int dhcp_mon_init(size_t snaplen, int window_sec, int max_count, int db_update_i // deinitialization of counters is not our responsibility // cache counter will be cleanup by sock_mgr_free and the initialized db we intend to keep initialize_all_intf_counters(); + initialize_dhcp_relay_health(); + dhcp_device_reset_health_state(agg_dev_all); syslog(LOG_INFO, "Initialized all counters for tracked interfaces"); window_interval_sec = window_sec; diff --git a/src/health_check.cpp b/src/health_check.cpp index e9949d18c..8ea39d3ff 100644 --- a/src/health_check.cpp +++ b/src/health_check.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "health_check.h" @@ -22,202 +23,98 @@ int dhcp_unhealthy_max_count = 10; extern std::string mgmt_ifname; extern std::string agg_dev_all; -extern std::string agg_dev_prefix; extern std::unordered_map> rev_vlan_map; extern std::unordered_map> rev_portchan_map; -static dhcp_mon_status_t check_agg_health() -{ - return dhcp_device_get_status(agg_dev_all, DHCP_DEVICE_CHECK_POSITIVE); -} - -static dhcp_mon_status_t check_mgmt_health() -{ - if (mgmt_ifname.size() > 0) { - return dhcp_device_get_status(mgmt_ifname, DHCP_DEVICE_CHECK_NEGATIVE); - } - return DHCP_MON_STATUS_HEALTHY; -} +static const char relay_disparity_error[] = + "dhcpmon detected DHCPv4/v6 packets received but none transmitted for intf: %s. Duration: %d (sec)"; +static const char mgmt_error[] = + "dhcpmon detected DHCP packets traveling through mgmt interface (please check BGP routes.)" + " Intf: %s. Duration: %d (sec)"; +static const char agg_rx_disparity_error[] = + "dhcpmon detected an IPv4 RX disparity between interface %s and the aggregate of its member interface counters." + " Duration: %d (sec)"; +static const char agg_tx_disparity_error[] = + "dhcpmon detected an IPv4 TX disparity between interface %s and the aggregate of its member interface counters." + " Duration: %d (sec)"; +static const char agg_rx_v6_disparity_error[] = + "dhcpmon detected an IPv6 RX disparity between interface %s and the aggregate of its member interface counters." + " Duration: %d (sec)"; +static const char agg_tx_v6_disparity_error[] = + "dhcpmon detected an IPv6 TX disparity between interface %s and the aggregate of its member interface counters." + " Duration: %d (sec)"; +/** + * @code alert_dhcp_relay_disparity(duration); + * + * @brief Publish the existing DHCP relay disparity event + * + * @param duration Unhealthy duration in seconds + * + * @return None + */ static void alert_dhcp_relay_disparity(int duration) { event_params_t params = {{ "vlan", agg_dev_all}, { "duration", std::to_string(duration)}}; event_publish(g_events_handle, "dhcp-relay-disparity", ¶ms); } -static void log_agg_error(int duration) -{ - syslog(LOG_ALERT, "dhcpmon detected DHCPv4/v6 packets received but none transmitted. Duration: %d (sec) for intf: %s", - duration, agg_dev_all.c_str()); -} +static std::vector state_data; -static void log_mgmt_error(int duration) +void initialize_dhcp_relay_health() { - syslog(LOG_ALERT, "dhcpmon detected DHCP packets traveling through mgmt interface (please check BGP routes.)" - " Duration: %d (sec) for intf: %s", - duration, mgmt_ifname.c_str()); -} + state_data.clear(); -static dhcp_mon_status_t check_agg_health_v6() -{ - return dhcp_device_get_status(agg_dev_all, DHCP_DEVICE_CHECK_POSITIVE_V6); -} - -static dhcp_mon_status_t check_mgmt_health_v6() -{ + state_data.push_back({agg_dev_all, DHCP_DEVICE_CHECK_POSITIVE, alert_dhcp_relay_disparity, + relay_disparity_error, 0}); if (mgmt_ifname.size() > 0) { - return dhcp_device_get_status(mgmt_ifname, DHCP_DEVICE_CHECK_NEGATIVE_V6); - } - return DHCP_MON_STATUS_HEALTHY; -} - -static dhcp_mon_status_t check_per_interface_rx_health() -{ - for (const auto &[vlan, _] : rev_vlan_map) { - if (dhcp_device_get_status(vlan, DHCP_DEVICE_CHECK_AGG_EQUAL_RX) == DHCP_MON_STATUS_UNHEALTHY) { - return DHCP_MON_STATUS_UNHEALTHY; - } - } - for (const auto &[portchan, _] : rev_portchan_map) { - if (dhcp_device_get_status(portchan, DHCP_DEVICE_CHECK_AGG_EQUAL_RX) == DHCP_MON_STATUS_UNHEALTHY) { - return DHCP_MON_STATUS_UNHEALTHY; - } - } - return DHCP_MON_STATUS_HEALTHY; -} - -static void log_agg_per_interface_rx_error(int duration) -{ - syslog(LOG_ALERT, "sum of rx per interface counter does not equal corresponding vlan/portchan counter." - " Duration: %d (sec)", duration); -} - -static dhcp_mon_status_t check_per_interface_tx_health() -{ - for (const auto &[vlan, _] : rev_vlan_map) { - if (dhcp_device_get_status(vlan, DHCP_DEVICE_CHECK_AGG_MULTIPLE_TX) == DHCP_MON_STATUS_UNHEALTHY) { - return DHCP_MON_STATUS_UNHEALTHY; - } + state_data.push_back({mgmt_ifname, DHCP_DEVICE_CHECK_NEGATIVE, NULL, mgmt_error, 0}); } - for (const auto &[portchan, _] : rev_portchan_map) { - if (dhcp_device_get_status(portchan, DHCP_DEVICE_CHECK_AGG_EQUAL_TX) == DHCP_MON_STATUS_UNHEALTHY) { - return DHCP_MON_STATUS_UNHEALTHY; - } + state_data.push_back({agg_dev_all, DHCP_DEVICE_CHECK_POSITIVE_V6, alert_dhcp_relay_disparity, + relay_disparity_error, 0}); + if (mgmt_ifname.size() > 0) { + state_data.push_back({mgmt_ifname, DHCP_DEVICE_CHECK_NEGATIVE_V6, NULL, mgmt_error, 0}); } - return DHCP_MON_STATUS_HEALTHY; -} - -static void log_agg_per_interface_tx_error(int duration) -{ - syslog(LOG_ALERT, "each tx per interface counter does not equal corresponding vlan counter," - " or sum of tx per interface counter does not equal corresponding portchan counter." - " Duration: %d (sec)", duration); -} -static dhcp_mon_status_t check_per_interface_rx_health_v6() -{ for (const auto &[vlan, _] : rev_vlan_map) { - if (dhcp_device_get_status(vlan, DHCP_DEVICE_CHECK_AGG_EQUAL_RX_V6) == DHCP_MON_STATUS_UNHEALTHY) { - return DHCP_MON_STATUS_UNHEALTHY; - } + state_data.push_back({vlan, DHCP_DEVICE_CHECK_AGG_RX, NULL, agg_rx_disparity_error, 0}); + state_data.push_back({vlan, DHCP_DEVICE_CHECK_AGG_TX, NULL, agg_tx_disparity_error, 0}); + state_data.push_back({vlan, DHCP_DEVICE_CHECK_AGG_RX_V6, NULL, agg_rx_v6_disparity_error, 0}); + state_data.push_back({vlan, DHCP_DEVICE_CHECK_AGG_TX_V6, NULL, agg_tx_v6_disparity_error, 0}); } - for (const auto &[portchan, _] : rev_portchan_map) { - if (dhcp_device_get_status(portchan, DHCP_DEVICE_CHECK_AGG_EQUAL_RX_V6) == DHCP_MON_STATUS_UNHEALTHY) { - return DHCP_MON_STATUS_UNHEALTHY; - } - } - return DHCP_MON_STATUS_HEALTHY; -} -static dhcp_mon_status_t check_per_interface_tx_health_v6() -{ - for (const auto &[vlan, _] : rev_vlan_map) { - if (dhcp_device_get_status(vlan, DHCP_DEVICE_CHECK_AGG_MULTIPLE_TX_V6) == DHCP_MON_STATUS_UNHEALTHY) { - return DHCP_MON_STATUS_UNHEALTHY; - } - } for (const auto &[portchan, _] : rev_portchan_map) { - if (dhcp_device_get_status(portchan, DHCP_DEVICE_CHECK_AGG_EQUAL_TX_V6) == DHCP_MON_STATUS_UNHEALTHY) { - return DHCP_MON_STATUS_UNHEALTHY; - } + state_data.push_back({portchan, DHCP_DEVICE_CHECK_AGG_RX, NULL, agg_rx_disparity_error, 0}); + state_data.push_back({portchan, DHCP_DEVICE_CHECK_AGG_TX, NULL, agg_tx_disparity_error, 0}); + state_data.push_back({portchan, DHCP_DEVICE_CHECK_AGG_RX_V6, NULL, agg_rx_v6_disparity_error, 0}); + state_data.push_back({portchan, DHCP_DEVICE_CHECK_AGG_TX_V6, NULL, agg_tx_v6_disparity_error, 0}); } - return DHCP_MON_STATUS_HEALTHY; } -/** DHCP monitor state data for aggregate device for mgmt device */ -static dhcp_mon_state_t state_data[] = { - [0] = { - .check_health = check_agg_health, - .alert = alert_dhcp_relay_disparity, - .log = log_agg_error, - .count = 0, - }, - [1] = { - .check_health = check_mgmt_health, - .log = log_mgmt_error, - .count = 0, - }, - [2] = { - .check_health = check_agg_health_v6, - .alert = alert_dhcp_relay_disparity, - .log = log_agg_error, - .count = 0, - }, - [3] = { - .check_health = check_mgmt_health_v6, - .log = log_mgmt_error, - .count = 0, - }, - [4] = { - .check_health = check_per_interface_rx_health, - .log = log_agg_per_interface_rx_error, - .count = 0, - }, - [5] = { - .check_health = check_per_interface_tx_health, - .log = log_agg_per_interface_tx_error, - .count = 0, - }, - [6] = { - .check_health = check_per_interface_rx_health_v6, - .log = log_agg_per_interface_rx_error, - .count = 0, - }, - [7] = { - .check_health = check_per_interface_tx_health_v6, - .log = log_agg_per_interface_tx_error, - .count = 0, - }, -}; - -static size_t state_data_sz = sizeof(state_data) / sizeof(*state_data); - void check_dhcp_relay_health() { syslog_debug(LOG_INFO, "Checking DHCP relay health"); - for (uint8_t i = 0; i < state_data_sz; i++) { - dhcp_mon_status_t dhcp_mon_status = state_data[i].check_health(); + for (auto &state : state_data) { + dhcp_mon_status_t dhcp_mon_status = dhcp_device_get_status(state.ifname, state.check_type); switch (dhcp_mon_status) { case DHCP_MON_STATUS_UNHEALTHY: - if (++state_data[i].count > dhcp_unhealthy_max_count) { - int duration = state_data[i].count * window_interval_sec; + if (++state.count > dhcp_unhealthy_max_count) { + int duration = state.count * window_interval_sec; - if (state_data[i].alert) { - state_data[i].alert(duration); - } - if (state_data[i].log) { - state_data[i].log(duration); + if (state.alert) { + state.alert(duration); } + syslog(LOG_ALERT, state.error_format, state.ifname.c_str(), duration); } break; case DHCP_MON_STATUS_HEALTHY: - state_data[i].count = 0; + state.count = 0; break; case DHCP_MON_STATUS_INDETERMINATE: - if (state_data[i].count) { - state_data[i].count++; + if (state.count) { + state.count++; } break; default: diff --git a/src/health_check.h b/src/health_check.h index 552dd528c..16243438a 100644 --- a/src/health_check.h +++ b/src/health_check.h @@ -9,14 +9,16 @@ #include "dhcp_device.h" #include +#include /** DHCP device/interface state */ typedef struct { - dhcp_mon_status_t (*check_health)(); /** check function */ - void (*alert)(int duration); /** alert function when check failed */ - void (*log)(int duration); /** log function when check passed */ - int count; /** count in the number of unhealthy checks */ + std::string ifname; /** interface to check */ + dhcp_device_check_t check_type; /** check to apply */ + void (*alert)(int duration); /** alert function */ + const char *error_format; /** threshold error format */ + int count; /** consecutive unhealthy checks */ } dhcp_mon_state_t; extern event_handle_t g_events_handle; @@ -26,7 +28,18 @@ extern int window_interval_sec; extern int dhcp_unhealthy_max_count; /** - * @code check_dhcp_relay_health(state_data); + * @code initialize_dhcp_relay_health(); + * + * @brief Populate health states from discovered VLAN and PortChannel members + * + * @param none + * + * @return none + */ +void initialize_dhcp_relay_health(); + +/** + * @code check_dhcp_relay_health(); * * @brief check DHCP relay overall health * diff --git a/src/packet_handler.cpp b/src/packet_handler.cpp index 7ec5d07a3..c6ea0dda7 100644 --- a/src/packet_handler.cpp +++ b/src/packet_handler.cpp @@ -47,14 +47,12 @@ static void increase_cache_counter(const std::string &ifname, const dhcp_device_ { _increase_cache_counter(ifname, sock, type); - // we seperate mgmt interface from others and do not increase agg counter + // we separate mgmt interface from others and do not increase agg counter if (mgmt_ifname != "" && mgmt_ifname.compare(context->intf) == 0) { return; } - // when ifname belongs to another context ifname, increase the aggregate counter for that context, - // else when ifname is the context, we increase agg counter for all. - _increase_cache_counter(get_agg_counter_ifname(ifname, context->intf), sock, type); + _increase_cache_counter(dhcp_devman_get_agg_counter_ifname(ifname), sock, type); // optionally duplicate to context ifname, it will only be true when this is standby physical interface under a vlan on a dual tor if (dup_to_context) { diff --git a/src/util.h b/src/util.h index f4fbd24c3..293688822 100644 --- a/src/util.h +++ b/src/util.h @@ -217,15 +217,14 @@ inline bool is_agg_counter(const std::string &ifname) } /** - * @code get_agg_counter_ifname(ifname, context); - * @brief Get aggregate counter name for given ifname and device context - * @param ifname Interface name - * @param context Pointer to device context + * @code get_agg_counter_ifname(parent_ifname); + * @brief Get aggregate counter name for a parent's immediate children + * @param parent_ifname Parent interface name * @return Aggregate counter name */ -inline std::string get_agg_counter_ifname(const std::string &ifname, const std::string &context_ifname) +inline std::string get_agg_counter_ifname(const std::string &parent_ifname) { - return ifname != context_ifname ? agg_dev_prefix + context_ifname : agg_dev_all; + return agg_dev_prefix + parent_ifname; } /**