From c53e151d6ccc866d936651066503e89275378b3f Mon Sep 17 00:00:00 2001 From: Xichen96 Date: Sat, 25 Jul 2026 22:49:08 +0000 Subject: [PATCH 1/7] [dhcpmon]: Make relay disparity detection resilient Track DHCPv4 disparity per message type with absolute watermarks, adjacent-window TX credit, main-loop clear reset, and one report per persistent outage; disable the invalid DHCPv6 same-type signal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83 Signed-off-by: Xichen96 --- src/dhcp_device.cpp | 117 +++++++++++++++++++++++++++++++++++++------ src/dhcp_device.h | 23 +++++++++ src/dhcp_mon.cpp | 8 +++ src/health_check.cpp | 76 +++++++++++++++++++--------- src/health_check.h | 4 ++ 5 files changed, 189 insertions(+), 39 deletions(-) diff --git a/src/dhcp_device.cpp b/src/dhcp_device.cpp index 8b1ed4f6c..985c31a1d 100644 --- a/src/dhcp_device.cpp +++ b/src/dhcp_device.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -69,6 +70,39 @@ static const char *counter_desc[DHCP_COUNTERS_COUNT] = { [DHCP_COUNTERS_SNAPSHOT_V6] = "Snapshot_V6", }; +typedef struct +{ + uint64_t last_rx; + uint64_t last_tx; + uint32_t pending_windows; + uint8_t tx_credit; + bool initialized; +} relay_flow_state_t; + +static std::unordered_map>> relay_flow_states; +static std::mutex relay_flow_state_mutex; + +static void initialize_relay_flow_states(const std::string &ifname, int rx_sock, int tx_sock, + const int *monitored_msgs, size_t monitored_msg_cnt) +{ + 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_cnt; 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) +{ + std::lock_guard lock(relay_flow_state_mutex); + relay_flow_states.clear(); + initialize_relay_flow_states(ifname, rx_sock, tx_sock, + (const int *)monitored_msgs, monitored_msg_sz); +} + /** * @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 @@ -78,28 +112,75 @@ static const char *counter_desc[DHCP_COUNTERS_COUNT] = { * @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 DHCP relay health status */ // 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) { + std::lock_guard lock(relay_flow_state_mutex); 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) { + 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; +} + +std::unordered_map dhcp_device_get_untransmitted_windows(const std::string &ifname) +{ + return get_untransmitted_windows(ifname, rx_sock, tx_sock, + (const int *)monitored_msgs, monitored_msg_sz); } /** @@ -112,8 +193,12 @@ static bool check_counter_not_transmitted(const std::string &ifname, int rx_sock */ static dhcp_mon_status_t dhcp_device_check_positive_health(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; + for (const auto &[msg_type, windows] : dhcp_device_get_untransmitted_windows(ifname)) { + if (windows > 0) { + return DHCP_MON_STATUS_UNHEALTHY; + } + } + return DHCP_MON_STATUS_HEALTHY; } /** @@ -124,10 +209,10 @@ static dhcp_mon_status_t dhcp_device_check_positive_health(const std::string &if * @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 dhcp_mon_status_t dhcp_device_check_positive_health_v6(const std::string &) { - 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; + // Client and relay DHCPv6 message types differ across the relay boundary. + return DHCP_MON_STATUS_INDETERMINATE; } /** @@ -382,7 +467,9 @@ 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)) { + if (check_type != DHCP_DEVICE_CHECK_POSITIVE && check_type != DHCP_DEVICE_CHECK_POSITIVE_V6 && + 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; } diff --git a/src/dhcp_device.h b/src/dhcp_device.h index 5d4b00721..62d61319a 100644 --- a/src/dhcp_device.h +++ b/src/dhcp_device.h @@ -18,6 +18,7 @@ #include #include #include +#include /** DHCP message types */ typedef enum @@ -242,6 +243,28 @@ 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); +/** + * @code dhcp_device_get_untransmitted_windows(ifname); + * + * @brief update and return unmatched DHCPv4 relay RX age in health windows per message type. + * + * @param ifname interface name + * + * @return message type to unmatched-window count + */ +std::unordered_map dhcp_device_get_untransmitted_windows(const std::string &ifname); + +/** + * @code dhcp_device_reset_health_state(ifname); + * + * @brief reset relay health watermarks to the current counters for an interface. + * + * @param ifname interface name + * + * @return none + */ +void dhcp_device_reset_health_state(const std::string &ifname); + /** * @code dhcp_device_print_status(ifname, type); * diff --git a/src/dhcp_mon.cpp b/src/dhcp_mon.cpp index ef4f6623d..27119d510 100644 --- a/src/dhcp_mon.cpp +++ b/src/dhcp_mon.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -41,6 +42,7 @@ static constexpr int MINIMAL_CLEAR_COUNTER_TIMEOUT_SEC = 5; static constexpr int CLEAR_COUNTER_DELAY_AFTER_DB_UPDATE_SEC = 1; /** Mutex lock to modify write_counter_to_db for different threads */ static std::mutex db_sync_mutex; +static std::atomic health_reset_pending{false}; /** tag for db_update event */ static const char db_update_tag[] = "DB_UPDATE"; /** Latest timestamp of writing cache counter to COUNTERS_DB */ @@ -373,6 +375,7 @@ static void update_cache_counter_callback(evutil_socket_t fd, short event, void // for discrepency in interface between cache counter and DB counter, we dont handle it in this function // we leave it to db updater to handle it if (sock_mgr_pause_write_cache_to_db_all_cleared()) { + health_reset_pending = true; syslog(LOG_INFO, "All sockets cleared pause_write_cache_to_db, start write back to DB counter from cache counter"); main_event_mgr->activate_all_events(db_update_tag, EV_TIMEOUT); } @@ -393,6 +396,10 @@ static void timeout_callback(evutil_socket_t fd, short event, void *arg) { syslog_debug(LOG_INFO, "Received timeout signal for DHCP relay health check"); + if (health_reset_pending.exchange(false)) { + reset_dhcp_relay_health_state(agg_dev_all); + } + dhcp_devman_print_all_status_debug(DHCP_COUNTERS_CURRENT); dhcp_devman_print_all_status_debug(DHCP_COUNTERS_SNAPSHOT); dhcp_devman_print_all_status_debug(DHCP_COUNTERS_CURRENT_V6); @@ -520,6 +527,7 @@ 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(); + reset_dhcp_relay_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..04c6fc79a 100644 --- a/src/health_check.cpp +++ b/src/health_check.cpp @@ -4,6 +4,8 @@ */ #include +#include +#include #include #include #include @@ -27,10 +29,8 @@ 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 std::unordered_set reported_disparity_v4; +static std::mutex health_state_mutex; static dhcp_mon_status_t check_mgmt_health() { @@ -59,9 +59,27 @@ static void log_mgmt_error(int duration) duration, mgmt_ifname.c_str()); } -static dhcp_mon_status_t check_agg_health_v6() +static void check_relay_disparity() { - return dhcp_device_get_status(agg_dev_all, DHCP_DEVICE_CHECK_POSITIVE_V6); + auto windows_by_type = dhcp_device_get_untransmitted_windows(agg_dev_all); + uint32_t report_windows = 0; + + for (const auto &[msg_type, windows] : windows_by_type) { + if (windows == 0) { + reported_disparity_v4.erase(msg_type); + continue; + } + if (windows > static_cast(dhcp_unhealthy_max_count) && + reported_disparity_v4.insert(msg_type).second) { + report_windows = std::max(report_windows, windows); + } + } + + if (report_windows > 0) { + int duration = static_cast(report_windows) * window_interval_sec; + alert_dhcp_relay_disparity(duration); + log_agg_error(duration); + } } static dhcp_mon_status_t check_mgmt_health_v6() @@ -148,46 +166,40 @@ static dhcp_mon_status_t check_per_interface_tx_health_v6() /** 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, + .reported = false, }, - [2] = { - .check_health = check_agg_health_v6, - .alert = alert_dhcp_relay_disparity, - .log = log_agg_error, - .count = 0, - }, - [3] = { + [1] = { .check_health = check_mgmt_health_v6, .log = log_mgmt_error, .count = 0, + .reported = false, }, - [4] = { + [2] = { .check_health = check_per_interface_rx_health, .log = log_agg_per_interface_rx_error, .count = 0, + .reported = false, }, - [5] = { + [3] = { .check_health = check_per_interface_tx_health, .log = log_agg_per_interface_tx_error, .count = 0, + .reported = false, }, - [6] = { + [4] = { .check_health = check_per_interface_rx_health_v6, .log = log_agg_per_interface_rx_error, .count = 0, + .reported = false, }, - [7] = { + [5] = { .check_health = check_per_interface_tx_health_v6, .log = log_agg_per_interface_tx_error, .count = 0, + .reported = false, }, }; @@ -195,13 +207,16 @@ static size_t state_data_sz = sizeof(state_data) / sizeof(*state_data); void check_dhcp_relay_health() { + std::lock_guard lock(health_state_mutex); syslog_debug(LOG_INFO, "Checking DHCP relay health"); + check_relay_disparity(); + for (uint8_t i = 0; i < state_data_sz; i++) { dhcp_mon_status_t dhcp_mon_status = state_data[i].check_health(); switch (dhcp_mon_status) { case DHCP_MON_STATUS_UNHEALTHY: - if (++state_data[i].count > dhcp_unhealthy_max_count) { + if (++state_data[i].count > dhcp_unhealthy_max_count && !state_data[i].reported) { int duration = state_data[i].count * window_interval_sec; if (state_data[i].alert) { @@ -210,10 +225,12 @@ void check_dhcp_relay_health() if (state_data[i].log) { state_data[i].log(duration); } + state_data[i].reported = true; } break; case DHCP_MON_STATUS_HEALTHY: state_data[i].count = 0; + state_data[i].reported = false; break; case DHCP_MON_STATUS_INDETERMINATE: if (state_data[i].count) { @@ -227,4 +244,15 @@ void check_dhcp_relay_health() } syslog_debug(LOG_INFO, "Completed DHCP relay health check"); +} + +void reset_dhcp_relay_health_state(const std::string &ifname) +{ + std::lock_guard lock(health_state_mutex); + reported_disparity_v4.clear(); + for (auto &state : state_data) { + state.count = 0; + state.reported = false; + } + dhcp_device_reset_health_state(ifname); } \ No newline at end of file diff --git a/src/health_check.h b/src/health_check.h index 552dd528c..0879c8d67 100644 --- a/src/health_check.h +++ b/src/health_check.h @@ -17,6 +17,7 @@ typedef struct 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 */ + bool reported; /** whether the current unhealthy episode was reported */ } dhcp_mon_state_t; extern event_handle_t g_events_handle; @@ -36,4 +37,7 @@ extern int dhcp_unhealthy_max_count; */ void check_dhcp_relay_health(); +/** Reset all relay health persistence, report state, and flow watermarks */ +void reset_dhcp_relay_health_state(const std::string &ifname); + #endif // HEALTH_CHECK_H \ No newline at end of file From 317c68a84e8025478514afeaa6f45370ec367194 Mon Sep 17 00:00:00 2001 From: Xichen96 Date: Sat, 25 Jul 2026 23:05:22 +0000 Subject: [PATCH 2/7] [dhcpmon]: Tighten disparity state scope Reset only the requested interface, include direct container dependencies, and label the alert as DHCPv4-only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83 Signed-off-by: Xichen96 --- src/dhcp_device.cpp | 4 +++- src/health_check.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dhcp_device.cpp b/src/dhcp_device.cpp index 985c31a1d..bfe4d920b 100644 --- a/src/dhcp_device.cpp +++ b/src/dhcp_device.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include "dhcp_device.h" @@ -98,7 +100,7 @@ static void initialize_relay_flow_states(const std::string &ifname, int rx_sock, void dhcp_device_reset_health_state(const std::string &ifname) { std::lock_guard lock(relay_flow_state_mutex); - relay_flow_states.clear(); + relay_flow_states[rx_sock].erase(ifname); initialize_relay_flow_states(ifname, rx_sock, tx_sock, (const int *)monitored_msgs, monitored_msg_sz); } diff --git a/src/health_check.cpp b/src/health_check.cpp index 04c6fc79a..bf374a01d 100644 --- a/src/health_check.cpp +++ b/src/health_check.cpp @@ -48,7 +48,7 @@ static void alert_dhcp_relay_disparity(int duration) 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", + syslog(LOG_ALERT, "dhcpmon detected DHCPv4 packets received but none transmitted. Duration: %d (sec) for intf: %s", duration, agg_dev_all.c_str()); } From d22262c8f7f3db962b933dab64f42563c3368901 Mon Sep 17 00:00:00 2001 From: Xichen96 Date: Sun, 26 Jul 2026 11:03:19 +1000 Subject: [PATCH 3/7] [dhcpmon]: Clarify relay health reset scope Include std::string directly and document the global report-state reset separately from the per-interface flow watermark reset. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83 Signed-off-by: Xichen96 --- src/health_check.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/health_check.h b/src/health_check.h index 0879c8d67..7d4550ccb 100644 --- a/src/health_check.h +++ b/src/health_check.h @@ -8,6 +8,8 @@ #include "dhcp_device.h" +#include + #include /** DHCP device/interface state */ @@ -37,7 +39,7 @@ extern int dhcp_unhealthy_max_count; */ void check_dhcp_relay_health(); -/** Reset all relay health persistence, report state, and flow watermarks */ +/** Reset relay report state globally and flow watermarks for the given interface */ void reset_dhcp_relay_health_state(const std::string &ifname); #endif // HEALTH_CHECK_H \ No newline at end of file From 2638f46aa007e070d09907f3449b62db90648177 Mon Sep 17 00:00:00 2001 From: Xichen96 Date: Sun, 26 Jul 2026 11:09:52 +1000 Subject: [PATCH 4/7] [dhcpmon]: Preserve adjacent-window transmit credit Keep one-window TX credit whenever the current health window observes a transmit, even when that activity also clears a pending or current RX. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83 Signed-off-by: Xichen96 --- src/dhcp_device.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dhcp_device.cpp b/src/dhcp_device.cpp index bfe4d920b..8397bd981 100644 --- a/src/dhcp_device.cpp +++ b/src/dhcp_device.cpp @@ -154,7 +154,7 @@ static std::unordered_map get_untransmitted_windows(const std::st 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; + state.tx_credit = current_tx_activity ? 1 : 0; } else { state.pending_windows++; state.tx_credit = 0; @@ -165,7 +165,7 @@ static std::unordered_map get_untransmitted_windows(const std::st state.tx_credit = current_tx_activity ? 1 : 0; } else if (current_tx_activity) { state.pending_windows = 0; - state.tx_credit = 0; + state.tx_credit = 1; } else { state.pending_windows = 1; state.tx_credit = 0; From 9a1897b14be224409e06af2d07ee1692282368ac Mon Sep 17 00:00:00 2001 From: Xichen96 Date: Sun, 26 Jul 2026 12:19:25 +1000 Subject: [PATCH 5/7] [dhcpmon]: Keep idle positive health indeterminate Advance pending relay-flow state on every health window, but report positive health only when monitored DHCPv4 counters show current activity. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83 Signed-off-by: Xichen96 --- src/dhcp_device.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/dhcp_device.cpp b/src/dhcp_device.cpp index 8397bd981..fcc8d3be7 100644 --- a/src/dhcp_device.cpp +++ b/src/dhcp_device.cpp @@ -185,6 +185,9 @@ std::unordered_map dhcp_device_get_untransmitted_windows(const st (const int *)monitored_msgs, monitored_msg_sz); } +static bool check_counter_increased(const std::string &ifname, int sock, + const int *monitored_msgs, size_t monitored_msg_cnt); + /** * @code dhcp_device_check_positive_health(ifname); * @brief Check that DHCP relayed messages are being transmitted out of this interface/dev @@ -195,12 +198,16 @@ std::unordered_map dhcp_device_get_untransmitted_windows(const st */ static dhcp_mon_status_t dhcp_device_check_positive_health(const std::string &ifname) { + 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); for (const auto &[msg_type, windows] : dhcp_device_get_untransmitted_windows(ifname)) { if (windows > 0) { return DHCP_MON_STATUS_UNHEALTHY; } } - return DHCP_MON_STATUS_HEALTHY; + return has_activity ? DHCP_MON_STATUS_HEALTHY : DHCP_MON_STATUS_INDETERMINATE; } /** From bf0ed1bdcf0bc7d932f0055b9e532a6b135d05ba Mon Sep 17 00:00:00 2001 From: Xichen96 Date: Sun, 26 Jul 2026 13:03:12 +1000 Subject: [PATCH 6/7] [dhcpmon]: Clarify relay disparity log Describe the per-message missing corresponding transmit without implying that all DHCPv4 transmit activity stopped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83 Signed-off-by: Xichen96 --- src/health_check.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/health_check.cpp b/src/health_check.cpp index bf374a01d..71ce5e982 100644 --- a/src/health_check.cpp +++ b/src/health_check.cpp @@ -48,7 +48,7 @@ static void alert_dhcp_relay_disparity(int duration) static void log_agg_error(int duration) { - syslog(LOG_ALERT, "dhcpmon detected DHCPv4 packets received but none transmitted. Duration: %d (sec) for intf: %s", + syslog(LOG_ALERT, "dhcpmon detected DHCPv4 receive activity without a corresponding transmit. Duration: %d (sec) for intf: %s", duration, agg_dev_all.c_str()); } From de64627d6320b6b606f1fe706f1ab533ff4f5bac Mon Sep 17 00:00:00 2001 From: Xichen96 Date: Sun, 26 Jul 2026 13:53:11 +1000 Subject: [PATCH 7/7] [dhcpmon]: Document health counter locking Record the counter-state lock requirement for relay watermark helpers and update the health-state description to match the active checks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83 Signed-off-by: Xichen96 --- src/dhcp_device.h | 2 ++ src/health_check.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dhcp_device.h b/src/dhcp_device.h index 62d61319a..3927beae3 100644 --- a/src/dhcp_device.h +++ b/src/dhcp_device.h @@ -247,6 +247,7 @@ dhcp_mon_status_t dhcp_device_get_status(const std::string &ifname, dhcp_device_ * @code dhcp_device_get_untransmitted_windows(ifname); * * @brief update and return unmatched DHCPv4 relay RX age in health windows per message type. + * Caller must hold the counter-state write lock or otherwise quiesce packet handlers. * * @param ifname interface name * @@ -258,6 +259,7 @@ std::unordered_map dhcp_device_get_untransmitted_windows(const st * @code dhcp_device_reset_health_state(ifname); * * @brief reset relay health watermarks to the current counters for an interface. + * Caller must hold the counter-state write lock or otherwise quiesce packet handlers. * * @param ifname interface name * diff --git a/src/health_check.cpp b/src/health_check.cpp index 71ce5e982..594acf961 100644 --- a/src/health_check.cpp +++ b/src/health_check.cpp @@ -163,7 +163,7 @@ static dhcp_mon_status_t check_per_interface_tx_health_v6() return DHCP_MON_STATUS_HEALTHY; } -/** DHCP monitor state data for aggregate device for mgmt device */ +/** DHCP monitor state for management traffic and interface hierarchy consistency */ static dhcp_mon_state_t state_data[] = { [0] = { .check_health = check_mgmt_health,