diff --git a/src/dhcp_device.cpp b/src/dhcp_device.cpp index 59a0bc0ef..e9846c20f 100644 --- a/src/dhcp_device.cpp +++ b/src/dhcp_device.cpp @@ -448,6 +448,10 @@ int initialize_intf_mac_and_ip_addr(dhcp_device_context_t *context) generate_addr_string(context->mac, ETHER_ADDR_LEN).c_str()); close(fd); + memset(&context->ip, 0, sizeof(context->ip)); + memset(&context->ipv6_gua, 0, sizeof(context->ipv6_gua)); + memset(&context->ipv6_lla, 0, sizeof(context->ipv6_lla)); + // Get ip address struct ifaddrs *ifaddr; int num_ip_addr = 0, num_ipv6_gua = 0, num_ipv6_lla = 0; @@ -499,9 +503,14 @@ int initialize_intf_mac_and_ip_addr(dhcp_device_context_t *context) } freeifaddrs(ifaddr); - if (num_ip_addr != 1 || (num_ipv6_gua != 1 && num_ipv6_lla != 1)) { - syslog(LOG_ALERT, "Unable to find exactly 1 ip addr, 1 ipv6 GUA and 1 ipv6 LLA on physical interface " - "and 1 ipv6 loopback addr on loopback interface: %s", context->intf); + if (num_ip_addr > 1) { + syslog(LOG_ALERT, "Interface %s has %d primary IPv4 addresses, expected at most 1", + context->intf, num_ip_addr); + break; + } + if (num_ipv6_gua > 1 || num_ipv6_lla > 1) { + syslog(LOG_ALERT, "Interface %s has IPv6 GUA=%d and LLA=%d, expected at most 1 each", + context->intf, num_ipv6_gua, num_ipv6_lla); break; } diff --git a/src/dhcp_device.h b/src/dhcp_device.h index 5d4b00721..486a3c13a 100644 --- a/src/dhcp_device.h +++ b/src/dhcp_device.h @@ -176,15 +176,15 @@ typedef struct uint8_t mac[ETHER_ADDR_LEN]; /** hardware address of this device (interface) */ dhcp_device_intf_t intf_type; /** interface type: uplink, downlink, or mgmt */ char intf[IF_NAMESIZE]; /** device (interface) name */ - struct in_addr ip; /** network address of this device (interface) */ - struct in6_addr ipv6_gua; /** network address of this device (interface) */ - struct in6_addr ipv6_lla; /** link local address of this device (interface) */ + struct in_addr ip; /** network address of this device (interface), zero if unavailable */ + struct in6_addr ipv6_gua; /** network address of this device (interface), zero if unavailable */ + struct in6_addr ipv6_lla; /** link local address of this device (interface), zero if unavailable */ } dhcp_device_context_t; /** * @code initialize_intf_mac_and_ip_addr(context); * - * @brief initializes device (interface) mac/ip addresses + * @brief initializes device (interface) mac/ip addresses that are available * * @param context pointer to device (interface) context * diff --git a/src/dhcp_devman.cpp b/src/dhcp_devman.cpp index 4e849d7a7..1b786d9b1 100644 --- a/src/dhcp_devman.cpp +++ b/src/dhcp_devman.cpp @@ -19,6 +19,8 @@ #include bool dual_tor_mode = false; +bool dhcpv4_enabled = false; +bool dhcpv6_enabled = false; in_addr vlan_ip = {0}; in6_addr vlan_ipv6_gua = {0}; @@ -274,6 +276,45 @@ int dhcp_devman_init() return -1; } + // IPv4: VLAN IPv4 && (!DualToR || Loopback0 IPv4) + // IPv6: VLAN GUA && VLAN LLA && (!DualToR || Loopback0 GUA) + bool vlan_has_ipv4 = vlan_ip.s_addr != INADDR_ANY; + bool vlan_has_ipv6_gua = !IN6_IS_ADDR_UNSPECIFIED(&vlan_ipv6_gua); + bool vlan_has_ipv6_lla = !IN6_IS_ADDR_UNSPECIFIED(&vlan_ipv6_lla); + bool loopback_has_ipv4 = loopback_ip.s_addr != INADDR_ANY; + bool loopback_has_ipv6_gua = !IN6_IS_ADDR_UNSPECIFIED(&loopback_ipv6_gua); + + dhcpv4_enabled = vlan_has_ipv4 && + (!dual_tor_mode || loopback_has_ipv4); + dhcpv6_enabled = vlan_has_ipv6_gua && + vlan_has_ipv6_lla && + (!dual_tor_mode || loopback_has_ipv6_gua); + + if (!vlan_has_ipv4) { + syslog(LOG_WARNING, "DHCPv4 monitoring disabled for %s: downstream IPv4 address is unavailable", + downstream_ifname.c_str()); + } + if (!vlan_has_ipv6_gua || !vlan_has_ipv6_lla) { + syslog(LOG_WARNING, "DHCPv6 monitoring disabled for %s: downstream IPv6 GUA and LLA are required " + "(GUA=%s, LLA=%s)", downstream_ifname.c_str(), + vlan_has_ipv6_gua ? "available" : "missing", + vlan_has_ipv6_lla ? "available" : "missing"); + } + if (dual_tor_mode && !loopback_has_ipv4) { + syslog(LOG_WARNING, "DHCPv4 monitoring disabled for %s: Dual-ToR loopback IPv4 address is unavailable", + downstream_ifname.c_str()); + } + if (dual_tor_mode && !loopback_has_ipv6_gua) { + syslog(LOG_WARNING, "DHCPv6 monitoring disabled for %s: Dual-ToR loopback IPv6 GUA is unavailable", + downstream_ifname.c_str()); + } + if (!dhcpv4_enabled && !dhcpv6_enabled) { + syslog(LOG_ALERT, "No address family satisfies monitoring requirements for %s", downstream_ifname.c_str()); + return -1; + } + syslog(LOG_INFO, "Enabled DHCP monitoring families for %s: IPv4=%s, IPv6=%s", + downstream_ifname.c_str(), dhcpv4_enabled ? "true" : "false", dhcpv6_enabled ? "true" : "false"); + giaddr_ip = dual_tor_mode ? loopback_ip : vlan_ip; giaddr_ipv6_gua = dual_tor_mode ? loopback_ipv6_gua : vlan_ipv6_gua; giaddr_ipv6_lla = dual_tor_mode ? loopback_ipv6_lla : vlan_ipv6_lla; @@ -307,6 +348,8 @@ int dhcp_devman_init() void dhcp_devman_free() { + dhcpv4_enabled = false; + dhcpv6_enabled = false; vlan_map.clear(); portchan_map.clear(); for (const auto &[ifname, context] : intfs) { diff --git a/src/dhcp_devman.h b/src/dhcp_devman.h index c8a3dad9f..4411725e5 100644 --- a/src/dhcp_devman.h +++ b/src/dhcp_devman.h @@ -24,6 +24,10 @@ /** if we are in dual tor mode */ extern bool dual_tor_mode; +/** address families monitored for the downstream VLAN */ +extern bool dhcpv4_enabled; +extern bool dhcpv6_enabled; + /** ip information for downstream vlan interface */ extern in_addr vlan_ip; extern in6_addr vlan_ipv6_gua; @@ -109,8 +113,7 @@ bool dhcp_devman_is_tracked_interface(const std::string &ifname); /** * @code dhcp_devman_init(snaplen); * - * @brief initializes device (interface) manager that keeps track of interfaces and assert that there is one south - * interface and as many north interfaces + * @brief initializes the device manager, validates interface counts, and enables the downstream VLAN address families * * @return 0 on success, negative otherwise */ diff --git a/src/dhcp_mon.cpp b/src/dhcp_mon.cpp index 6f53b506d..a2942f58a 100644 --- a/src/dhcp_mon.cpp +++ b/src/dhcp_mon.cpp @@ -110,7 +110,7 @@ static void initialize_db_counters(const std::string &ifname) init_value = generate_json_string(NULL, DHCP_MESSAGE_TYPE_COUNT, db_counter_name); mCountersDbPtr->hset(table_name, "RX", init_value); mCountersDbPtr->hset(table_name, "TX", init_value); - + table_name = construct_counter_db_table_key(ifname, true); init_value = generate_json_string(NULL, DHCPV6_MESSAGE_TYPE_COUNT, db_counter_name_v6); mCountersDbPtr->hset(table_name, "RX", init_value); @@ -152,7 +152,7 @@ static bool db_counters_initialized(const std::string &ifname) /** * @code initialize_all_counters(ifname, init_db); - * @brief Initialize both db counters (we do not for agg device) and cache counters for given interface name + * @brief Initialize both-family db counters and enabled-family cache counters for the given interface * @param ifname interface name * @param init_db whether to initialize db counters * @return none @@ -177,8 +177,7 @@ static bool all_counters_initialized(const std::string &ifname) /** * @code cleanup_stale_db_counters(); - * @brief Clean up stale counter entries in counters_db for interfaces that are no longer present - * (not in intfs map) + * @brief Clean up stale counter entries for interfaces that are no longer present * @param none * @return none */ @@ -202,6 +201,35 @@ static void cleanup_stale_db_counters() } } +/** + * @code update_disabled_cache_counter(); + * + * @brief complete cache update synchronization for disabled address families + * + * A disabled family has no socket cache to update. This function assumes the + * completion role of update_cache_counter_callback by marking both directions + * done in STATE_DB for each disabled family without modifying cache counters. + * + * @return none + */ +static void update_disabled_cache_counter() +{ + std::lock_guard lock(db_sync_mutex); + + if (!dhcpv4_enabled) { + std::string state_key = STATE_DB_COUNTER_UPDATE_PREFIX + downstream_ifname; + mStateDbPtr->hset(state_key, "rx_cache_update", "done"); + mStateDbPtr->hset(state_key, "tx_cache_update", "done"); + syslog(LOG_INFO, "Set DHCPv4 cache update done in STATE_DB; monitoring is disabled"); + } + if (!dhcpv6_enabled) { + std::string state_key = STATE_DB_COUNTER_UPDATE_V6_PREFIX + downstream_ifname; + mStateDbPtr->hset(state_key, "rx_cache_update", "done"); + mStateDbPtr->hset(state_key, "tx_cache_update", "done"); + syslog(LOG_INFO, "Set DHCPv6 cache update done in STATE_DB; monitoring is disabled"); + } +} + /** * @code signal_callback(fd, event, arg); * @@ -216,9 +244,13 @@ static void cleanup_stale_db_counters() static void signal_callback(evutil_socket_t fd, short event, void *arg) { syslog(LOG_INFO, "Received signal: %s", strsignal(fd)); - - dhcp_devman_print_all_status(DHCP_COUNTERS_CURRENT); - dhcp_devman_print_all_status(DHCP_COUNTERS_CURRENT_V6); + + if (dhcpv4_enabled) { + dhcp_devman_print_all_status(DHCP_COUNTERS_CURRENT); + } + if (dhcpv6_enabled) { + dhcp_devman_print_all_status(DHCP_COUNTERS_CURRENT_V6); + } if ((fd == SIGTERM) || (fd == SIGINT)) { syslog(LOG_INFO, "Received signal to stop dhcpmon"); @@ -237,6 +269,7 @@ static void signal_callback(evutil_socket_t fd, short event, void *arg) if (fd == SIGUSR2) { syslog(LOG_INFO, "Received signal to sync DB counter to cache counter"); sock_mgr_trigger_cache_counter_updater(); + update_disabled_cache_counter(); } } @@ -397,10 +430,14 @@ static void timeout_callback(evutil_socket_t fd, short event, void *arg) { syslog_debug(LOG_INFO, "Received timeout signal for DHCP relay health check"); - 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); - dhcp_devman_print_all_status_debug(DHCP_COUNTERS_SNAPSHOT_V6); + if (dhcpv4_enabled) { + dhcp_devman_print_all_status_debug(DHCP_COUNTERS_CURRENT); + dhcp_devman_print_all_status_debug(DHCP_COUNTERS_SNAPSHOT); + } + if (dhcpv6_enabled) { + dhcp_devman_print_all_status_debug(DHCP_COUNTERS_CURRENT_V6); + dhcp_devman_print_all_status_debug(DHCP_COUNTERS_SNAPSHOT_V6); + } check_dhcp_relay_health(); diff --git a/src/health_check.cpp b/src/health_check.cpp index e9949d18c..1eaea522c 100644 --- a/src/health_check.cpp +++ b/src/health_check.cpp @@ -24,6 +24,9 @@ extern std::string mgmt_ifname; extern std::string agg_dev_all; extern std::string agg_dev_prefix; +extern bool dhcpv4_enabled; +extern bool dhcpv6_enabled; + extern std::unordered_map> rev_vlan_map; extern std::unordered_map> rev_portchan_map; @@ -152,42 +155,50 @@ static dhcp_mon_state_t state_data[] = { .alert = alert_dhcp_relay_disparity, .log = log_agg_error, .count = 0, + .is_v6 = false, }, [1] = { .check_health = check_mgmt_health, .log = log_mgmt_error, .count = 0, + .is_v6 = false, }, [2] = { .check_health = check_agg_health_v6, .alert = alert_dhcp_relay_disparity, .log = log_agg_error, .count = 0, + .is_v6 = true, }, [3] = { .check_health = check_mgmt_health_v6, .log = log_mgmt_error, .count = 0, + .is_v6 = true, }, [4] = { .check_health = check_per_interface_rx_health, .log = log_agg_per_interface_rx_error, .count = 0, + .is_v6 = false, }, [5] = { .check_health = check_per_interface_tx_health, .log = log_agg_per_interface_tx_error, .count = 0, + .is_v6 = false, }, [6] = { .check_health = check_per_interface_rx_health_v6, .log = log_agg_per_interface_rx_error, .count = 0, + .is_v6 = true, }, [7] = { .check_health = check_per_interface_tx_health_v6, .log = log_agg_per_interface_tx_error, .count = 0, + .is_v6 = true, }, }; @@ -198,6 +209,10 @@ void check_dhcp_relay_health() syslog_debug(LOG_INFO, "Checking DHCP relay health"); for (uint8_t i = 0; i < state_data_sz; i++) { + if ((state_data[i].is_v6 && !dhcpv6_enabled) || + (!state_data[i].is_v6 && !dhcpv4_enabled)) { + continue; + } dhcp_mon_status_t dhcp_mon_status = state_data[i].check_health(); switch (dhcp_mon_status) { case DHCP_MON_STATUS_UNHEALTHY: diff --git a/src/health_check.h b/src/health_check.h index 552dd528c..1752c5728 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 is_v6; /** whether this state monitors DHCPv6 */ } dhcp_mon_state_t; extern event_handle_t g_events_handle; diff --git a/src/sock_mgr.cpp b/src/sock_mgr.cpp index f47c7c85e..0df04ee07 100644 --- a/src/sock_mgr.cpp +++ b/src/sock_mgr.cpp @@ -17,11 +17,12 @@ #include "sock_mgr.h" +#include "dhcp_devman.h" /** for enabled address families */ #include "packet_handler.h" /** for attaching packet handler */ #include "util.h" /** for db counter key generation */ #include -int rx_sock, tx_sock, rx_sock_v6, tx_sock_v6; +int rx_sock = -1, tx_sock = -1, rx_sock_v6 = -1, tx_sock_v6 = -1; /** String identifier of sock fd for printing */ static const char rx_sock_name[] = "rx"; @@ -79,53 +80,129 @@ static int open_socket() } /** - * @code init_socket(); + * @code init_v4_socket(); * - * @brief initializes rx/tx sockets, bind it to interface and bpf program + * @brief initialize the DHCPv4 inbound and outbound sockets * * @return 0 on success, negative on failure */ -static int init_socket() +static int init_v4_socket() { rx_sock = open_socket(); if (rx_sock < 0) { - syslog(LOG_ALERT, "Failed to open and bind socket"); + syslog(LOG_ALERT, "Failed to open and bind DHCPv4 inbound socket"); goto no_close; } tx_sock = open_socket(); if (tx_sock < 0) { - syslog(LOG_ALERT, "Failed to open and bind socket"); + syslog(LOG_ALERT, "Failed to open and bind DHCPv4 outbound socket"); goto close_rx_sock; } + + return 0; + +close_rx_sock: + close(rx_sock); + rx_sock = -1; +no_close: + return -1; +} + +/** + * @code free_v4_socket(); + * + * @brief close the initialized DHCPv4 sockets + * + * @return none + */ +static void free_v4_socket() +{ + if (tx_sock >= 0) { + close(tx_sock); + tx_sock = -1; + } + if (rx_sock >= 0) { + close(rx_sock); + rx_sock = -1; + } +} + +/** + * @code init_v6_socket(); + * + * @brief initialize the DHCPv6 inbound and outbound sockets + * + * @return 0 on success, negative on failure + */ +static int init_v6_socket() +{ rx_sock_v6 = open_socket(); if (rx_sock_v6 < 0) { - syslog(LOG_ALERT, "Failed to open and bind socket"); - goto close_tx_sock; + syslog(LOG_ALERT, "Failed to open and bind DHCPv6 inbound socket"); + goto no_close; } tx_sock_v6 = open_socket(); if (tx_sock_v6 < 0) { - syslog(LOG_ALERT, "Failed to open and bind socket"); + syslog(LOG_ALERT, "Failed to open and bind DHCPv6 outbound socket"); goto close_rx_sock_v6; } - syslog(LOG_INFO, "Initialized all sockets successfully"); - syslog(LOG_INFO, " rx_sock=%d, tx_sock=%d, rx_sock_v6=%d, tx_sock_v6=%d", - rx_sock, tx_sock, rx_sock_v6, tx_sock_v6); - return 0; -close_tx_sock_v6: - close(tx_sock_v6); close_rx_sock_v6: close(rx_sock_v6); -close_tx_sock: - close(tx_sock); -close_rx_sock: - close(rx_sock); + rx_sock_v6 = -1; no_close: return -1; } +/** + * @code free_v6_socket(); + * + * @brief close the initialized DHCPv6 sockets + * + * @return none + */ +static void free_v6_socket() +{ + if (tx_sock_v6 >= 0) { + close(tx_sock_v6); + tx_sock_v6 = -1; + } + if (rx_sock_v6 >= 0) { + close(rx_sock_v6); + rx_sock_v6 = -1; + } +} + +/** + * @code init_socket(); + * + * @brief initializes rx/tx sockets, bind it to interface and bpf program + * + * @return 0 on success, negative on failure + */ +static int init_socket() +{ + if (dhcpv4_enabled && init_v4_socket() < 0) { + goto no_free; + } + if (dhcpv6_enabled && init_v6_socket() < 0) { + goto free_v4; + } + + syslog(LOG_INFO, "Initialized enabled address-family sockets successfully"); + syslog(LOG_INFO, " rx_sock=%d, tx_sock=%d, rx_sock_v6=%d, tx_sock_v6=%d", + rx_sock, tx_sock, rx_sock_v6, tx_sock_v6); + + return 0; + +free_v4: + free_v4_socket(); +no_free: + return -1; +} + /** * @code free_socket() * @@ -133,13 +210,11 @@ static int init_socket() */ static void free_socket() { - close(tx_sock_v6); - close(rx_sock_v6); - close(tx_sock); - close(rx_sock); - syslog(LOG_INFO, "Closed all opened sockets"); - syslog(LOG_INFO, " rx_sock=%d, tx_sock=%d, rx_sock_v6=%d, tx_sock_v6=%d", + syslog(LOG_INFO, "Closing sockets: rx_sock=%d, tx_sock=%d, rx_sock_v6=%d, tx_sock_v6=%d", rx_sock, tx_sock, rx_sock_v6, tx_sock_v6); + free_v6_socket(); + free_v4_socket(); + syslog(LOG_INFO, "Closed all opened sockets"); } /** @@ -199,7 +274,7 @@ static void sock_mgr_free_all_bpf_prog() /** * @code compile_all_bpf_prog(); * - * @brief compile all 4 tcpdump filters into classic bpf progs + * @brief compile all enabled tcpdump filters into classic bpf progs */ static int sock_mgr_compile_all_bpf_prog() { @@ -291,53 +366,57 @@ int sock_mgr_init(uint32_t snaplen) goto no_free; } - sock_map[rx_sock] = sock_info_t { - .sock = rx_sock, - .name = rx_sock_name, - .is_rx = true, - .is_v6 = false, - .snaplen = snaplen, - .filter = dhcp_inbound_filter, - .all_counters = all_counters_t(), - .all_counters_snapshot = all_counters_t(), - .packet_handler = (void *)packet_handler, - }; - - sock_map[tx_sock] = sock_info_t { - .sock = tx_sock, - .name = tx_sock_name, - .is_rx = false, - .is_v6 = false, - .snaplen = snaplen, - .filter = dhcp_outbound_filter, - .all_counters = all_counters_t(), - .all_counters_snapshot = all_counters_t(), - .packet_handler = (void *)packet_handler, - }; - - sock_map[rx_sock_v6] = sock_info_t { - .sock = rx_sock_v6, - .name = rx_sock_name_v6, - .is_rx = true, - .is_v6 = true, - .snaplen = snaplen, - .filter = dhcpv6_inbound_filter, - .all_counters = all_counters_t(), - .all_counters_snapshot = all_counters_t(), - .packet_handler = (void *)packet_handler_v6, - }; - - sock_map[tx_sock_v6] = sock_info_t { - .sock = tx_sock_v6, - .name = tx_sock_name_v6, - .is_rx = false, - .is_v6 = true, - .snaplen = snaplen, - .filter = dhcpv6_outbound_filter, - .all_counters = all_counters_t(), - .all_counters_snapshot = all_counters_t(), - .packet_handler = (void *)packet_handler_v6, - }; + if (dhcpv4_enabled) { + sock_map[rx_sock] = sock_info_t { + .sock = rx_sock, + .name = rx_sock_name, + .is_rx = true, + .is_v6 = false, + .snaplen = snaplen, + .filter = dhcp_inbound_filter, + .all_counters = all_counters_t(), + .all_counters_snapshot = all_counters_t(), + .packet_handler = (void *)packet_handler, + }; + + sock_map[tx_sock] = sock_info_t { + .sock = tx_sock, + .name = tx_sock_name, + .is_rx = false, + .is_v6 = false, + .snaplen = snaplen, + .filter = dhcp_outbound_filter, + .all_counters = all_counters_t(), + .all_counters_snapshot = all_counters_t(), + .packet_handler = (void *)packet_handler, + }; + } + + if (dhcpv6_enabled) { + sock_map[rx_sock_v6] = sock_info_t { + .sock = rx_sock_v6, + .name = rx_sock_name_v6, + .is_rx = true, + .is_v6 = true, + .snaplen = snaplen, + .filter = dhcpv6_inbound_filter, + .all_counters = all_counters_t(), + .all_counters_snapshot = all_counters_t(), + .packet_handler = (void *)packet_handler_v6, + }; + + sock_map[tx_sock_v6] = sock_info_t { + .sock = tx_sock_v6, + .name = tx_sock_name_v6, + .is_rx = false, + .is_v6 = true, + .snaplen = snaplen, + .filter = dhcpv6_outbound_filter, + .all_counters = all_counters_t(), + .all_counters_snapshot = all_counters_t(), + .packet_handler = (void *)packet_handler_v6, + }; + } if (sock_mgr_init_all_buffer() < 0) { syslog(LOG_ALERT, "Failed to initialize all socket buffers"); diff --git a/src/sock_mgr.h b/src/sock_mgr.h index 82ead205e..9024372a0 100644 --- a/src/sock_mgr.h +++ b/src/sock_mgr.h @@ -39,7 +39,9 @@ typedef struct { std::thread event_thread; } sock_info_t; -/** sock file descriptors, serve as the identifier of all related information described in sock_info_t */ +/** sock file descriptors, serve as the identifier of all related information described in sock_info_t; + * descriptors for a disabled address family remain -1 + */ extern int rx_sock, tx_sock, rx_sock_v6, tx_sock_v6; /** Initialize socket manager with given snaplen */