From 37d39815d3022a38375a9b00e655740cc7f85797 Mon Sep 17 00:00:00 2001 From: Shivashankar C R Date: Wed, 30 Apr 2025 05:12:01 -0700 Subject: [PATCH 01/17] Introducing dhcp v4 relay changes, created dhcp6relay[existing code] and dhcp4relay[new code] folders Co-authored-by: Akshath TR Co-authored-by: Ashutosh Agrawal --- .gitignore | 15 +- .gitmodules | 4 +- dhcp4relay/Makefile | 88 ++ dhcp4relay/debian/changelog | 5 + {debian => dhcp4relay/debian}/compat | 0 dhcp4relay/debian/control | 14 + {debian => dhcp4relay/debian}/rules | 0 dhcp4relay/src/dhcp4_sender.cpp | 30 + dhcp4relay/src/dhcp4_sender.h | 21 + dhcp4relay/src/dhcp4relay.cpp | 1145 +++++++++++++++++ dhcp4relay/src/dhcp4relay.h | 278 ++++ dhcp4relay/src/dhcp4relay_mgr.cpp | 320 +++++ dhcp4relay/src/dhcp4relay_mgr.h | 30 + dhcp4relay/src/dhcp4relay_stats.cpp | 240 ++++ dhcp4relay/src/dhcp4relay_stats.h | 42 + dhcp4relay/src/main.cpp | 20 + dhcp4relay/src/subdir.mk | 6 + dhcp4relay/test/database_config.json | 27 + dhcp4relay/test/main.cpp | 9 + dhcp4relay/test/mock_consumerstatetable.cpp | 40 + dhcp4relay/test/mock_dbconnector.cpp | 64 + dhcp4relay/test/mock_hiredis.cpp | 40 + dhcp4relay/test/mock_redisreply.cpp | 16 + dhcp4relay/test/mock_relay.cpp | 438 +++++++ dhcp4relay/test/mock_relay.h | 17 + dhcp4relay/test/mock_relay_stats.cpp | 230 ++++ dhcp4relay/test/mock_table.cpp | 183 +++ dhcp4relay/test/mock_table.h | 8 + dhcp4relay/test/subdir.mk | 13 + Makefile => dhcp6relay/Makefile | 0 dhcp6relay/debian/changelog | 5 + dhcp6relay/debian/compat | 1 + {debian => dhcp6relay/debian}/control | 0 dhcp6relay/debian/rules | 6 + dhcp6relay/gmock_global | 1 + {src => dhcp6relay/src}/config_interface.cpp | 0 {src => dhcp6relay/src}/config_interface.h | 0 {src => dhcp6relay/src}/main.cpp | 0 {src => dhcp6relay/src}/relay.cpp | 0 {src => dhcp6relay/src}/relay.h | 0 {src => dhcp6relay/src}/sender.cpp | 0 {src => dhcp6relay/src}/sender.h | 0 {src => dhcp6relay/src}/subdir.mk | 0 .../test}/database_config.json | 0 {test => dhcp6relay/test}/main.cpp | 0 .../test}/mock_config_interface.cpp | 0 .../test}/mock_config_interface.h | 0 {test => dhcp6relay/test}/mock_relay.cpp | 0 {test => dhcp6relay/test}/mock_relay.h | 0 {test => dhcp6relay/test}/mock_send.cpp | 0 {test => dhcp6relay/test}/mock_send.h | 0 {test => dhcp6relay/test}/subdir.mk | 0 gmock_global | 1 - 53 files changed, 3349 insertions(+), 8 deletions(-) create mode 100644 dhcp4relay/Makefile create mode 100644 dhcp4relay/debian/changelog rename {debian => dhcp4relay/debian}/compat (100%) create mode 100644 dhcp4relay/debian/control rename {debian => dhcp4relay/debian}/rules (100%) create mode 100644 dhcp4relay/src/dhcp4_sender.cpp create mode 100644 dhcp4relay/src/dhcp4_sender.h create mode 100644 dhcp4relay/src/dhcp4relay.cpp create mode 100644 dhcp4relay/src/dhcp4relay.h create mode 100644 dhcp4relay/src/dhcp4relay_mgr.cpp create mode 100644 dhcp4relay/src/dhcp4relay_mgr.h create mode 100644 dhcp4relay/src/dhcp4relay_stats.cpp create mode 100644 dhcp4relay/src/dhcp4relay_stats.h create mode 100644 dhcp4relay/src/main.cpp create mode 100644 dhcp4relay/src/subdir.mk create mode 100644 dhcp4relay/test/database_config.json create mode 100644 dhcp4relay/test/main.cpp create mode 100644 dhcp4relay/test/mock_consumerstatetable.cpp create mode 100644 dhcp4relay/test/mock_dbconnector.cpp create mode 100644 dhcp4relay/test/mock_hiredis.cpp create mode 100644 dhcp4relay/test/mock_redisreply.cpp create mode 100644 dhcp4relay/test/mock_relay.cpp create mode 100644 dhcp4relay/test/mock_relay.h create mode 100644 dhcp4relay/test/mock_relay_stats.cpp create mode 100644 dhcp4relay/test/mock_table.cpp create mode 100644 dhcp4relay/test/mock_table.h create mode 100644 dhcp4relay/test/subdir.mk rename Makefile => dhcp6relay/Makefile (100%) create mode 100644 dhcp6relay/debian/changelog create mode 100644 dhcp6relay/debian/compat rename {debian => dhcp6relay/debian}/control (100%) create mode 100755 dhcp6relay/debian/rules create mode 160000 dhcp6relay/gmock_global rename {src => dhcp6relay/src}/config_interface.cpp (100%) rename {src => dhcp6relay/src}/config_interface.h (100%) rename {src => dhcp6relay/src}/main.cpp (100%) rename {src => dhcp6relay/src}/relay.cpp (100%) rename {src => dhcp6relay/src}/relay.h (100%) rename {src => dhcp6relay/src}/sender.cpp (100%) rename {src => dhcp6relay/src}/sender.h (100%) rename {src => dhcp6relay/src}/subdir.mk (100%) rename {test => dhcp6relay/test}/database_config.json (100%) rename {test => dhcp6relay/test}/main.cpp (100%) rename {test => dhcp6relay/test}/mock_config_interface.cpp (100%) rename {test => dhcp6relay/test}/mock_config_interface.h (100%) rename {test => dhcp6relay/test}/mock_relay.cpp (100%) rename {test => dhcp6relay/test}/mock_relay.h (100%) rename {test => dhcp6relay/test}/mock_send.cpp (100%) rename {test => dhcp6relay/test}/mock_send.h (100%) rename {test => dhcp6relay/test}/subdir.mk (100%) delete mode 160000 gmock_global diff --git a/.gitignore b/.gitignore index 9d09ae6..467902d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ -debian/* -!debian/changelog -!debian/compat -!debian/control -!debian/rules +dhcp6relay/debian/* +!dhcp6relay/debian/changelog +!dhcp6relay/debian/compat +!dhcp6relay/debian/control +!dhcp6relay/debian/rules +dhcp4relay/debian/* +!dhcp4relay/debian/changelog +!dhcp4relay/debian/compat +!dhcp4relay/debian/control +!dhcp4relay/debian/rules diff --git a/.gitmodules b/.gitmodules index 73f0083..b65f2a4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "gmock_global"] - path = gmock_global +[submodule "dhcp6relay/gmock_global"] + path = dhcp6relay/gmock_global url = https://github.com/apriorit/gmock-global.git diff --git a/dhcp4relay/Makefile b/dhcp4relay/Makefile new file mode 100644 index 0000000..e290bce --- /dev/null +++ b/dhcp4relay/Makefile @@ -0,0 +1,88 @@ +.ONESHELL: +SHELL = /bin/bash + +RM := rm -rf +WORKING_DIR := $(abspath .) +BUILD_DIR := build +BUILD_TEST_DIR := build-test +DHCP4RELAY_TARGET := $(BUILD_DIR)/dhcp4relay +DHCP4RELAY_TEST_TARGET := $(BUILD_TEST_DIR)/dhcp4relay-test +CP := cp +MKDIR := mkdir +MV := mv +FIND := find +GCOVR := gcovr + +LD_PCAPPLUSPLUS_LIB := -lPcap++ -lPacket++ -lCommon++ + +#pcap plus plus zip file pcappp_v24.09.zip +PCAPPPVAR := 24.09 +PCAPPP_URL = https://github.com/seladb/PcapPlusPlus/releases/download/v${PCAPPPVAR}/pcapplusplus-${PCAPPPVAR}-ubuntu-20.04-gcc-9.4.0-x86_64.tar.gz +PCAPPLUSPLUS_DIR := $(WORKING_DIR)/pcapplusplus-$(PCAPPPVAR)-ubuntu-20.04-gcc-9.4.0-x86_64 +PCAPPP_DONE = $(WORKING_DIR)/pcappp.stamp +INCLUDE_DIR = $(PCAPPLUSPLUS_DIR)/include +LIB_DIR = $(PCAPPLUSPLUS_DIR)/lib + +override LDLIBS += -levent -lhiredis -lswsscommon -pthread -lboost_thread -lboost_system $(LD_PCAPPLUSPLUS_LIB) -lpcap +override CPPFLAGS += -Wall -std=c++17 -fPIE -I/usr/include/swss -I$(INCLUDE_DIR) +override CPPFLAGS += -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@)" +override LDFLAGS += -L$(LIB_DIR) -Wl,-rpath=$(abspath $(LIB_DIR)) +CPPFLAGS_TEST := --coverage -fprofile-arcs -ftest-coverage -fprofile-generate -fsanitize=address -DUNIT_TEST +LDLIBS_TEST := --coverage -lgtest -lgmock -pthread -lstdc++fs -fsanitize=address +PWD := $(shell pwd) + +.PHONY: $(PCAPPP_DONE) +$(PCAPPP_DONE): + wget $(PCAPPP_URL) -O pcapplusplus.tar.gz + tar -xzf pcapplusplus.tar.gz + touch $@ + +all: $(DHCP4RELAY_TARGET) $(DHCP4RELAY_TEST_TARGET) + +-include src/subdir.mk +-include test/subdir.mk + +# Use different build directories based on whether it's a regular build or a +# test build. This is because in the test build, code coverage is enabled, +# which means the object files that get built will be different +OBJS = $(SRCS:%.cpp=$(BUILD_DIR)/%.o) +TEST_OBJS = $(TEST_SRCS:%.cpp=$(BUILD_TEST_DIR)/%.o) + +ifneq ($(MAKECMDGOALS),clean) +-include $(OBJS:%.o=%.d) +-include $(TEST_OBJS:%.o=%.d) +endif + +$(BUILD_DIR)/%.o: %.cpp + @mkdir -p $(@D) + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< + +$(DHCP4RELAY_TARGET): $(PCAPPP_DONE) $(OBJS) + $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@ + +$(BUILD_TEST_DIR)/%.o: %.cpp + @mkdir -p $(@D) + $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(CPPFLAGS_TEST) -c -o $@ $< + +$(DHCP4RELAY_TEST_TARGET): $(TEST_OBJS) + $(CXX) $(LDFLAGS) $^ $(LDLIBS) $(LDLIBS_TEST) -o $@ + +test: $(DHCP4RELAY_TEST_TARGET) + sudo ASAN_OPTIONS=detect_leaks=0 ./$(DHCP4RELAY_TEST_TARGET) --gtest_output=xml:$(DHCP4RELAY_TEST_TARGET)-test-result.xml || true + $(GCOVR) -r ./ --html --html-details -o $(DHCP4RELAY_TEST_TARGET)-code-coverage.html + $(GCOVR) -r ./ --xml-pretty -o $(DHCP4RELAY_TEST_TARGET)-code-coverage.xml + +install: $(DHCP4RELAY_TARGET) + install -D $(DHCP4RELAY_TARGET) $(DESTDIR)/usr/sbin/$(notdir $(DHCP4RELAY_TARGET)) + +uninstall: + $(RM) $(DESTDIR)/usr/sbin/$(notdir $(DHCP4RELAY_TARGET)) + +clean: + -$(RM) $(BUILD_DIR) $(BUILD_TEST_DIR) *.html *.xml pcap* + $(FIND) . -name *.gcda -exec rm -f {} \; + $(FIND) . -name *.gcno -exec rm -f {} \; + $(FIND) . -name *.gcov -exec rm -f {} \; + -@echo ' ' + +.PHONY: all clean test install uninstall diff --git a/dhcp4relay/debian/changelog b/dhcp4relay/debian/changelog new file mode 100644 index 0000000..77d02c6 --- /dev/null +++ b/dhcp4relay/debian/changelog @@ -0,0 +1,5 @@ +sonic-dhcp4relay (1.0.0-0) UNRELEASED; urgency=medium + + * Initial release. + + -- Ashutosh Agrawal Thu, 16 Jan 2025 09:11:40 -0700 diff --git a/debian/compat b/dhcp4relay/debian/compat similarity index 100% rename from debian/compat rename to dhcp4relay/debian/compat diff --git a/dhcp4relay/debian/control b/dhcp4relay/debian/control new file mode 100644 index 0000000..8623275 --- /dev/null +++ b/dhcp4relay/debian/control @@ -0,0 +1,14 @@ +Source: sonic-dhcp4relay +Section: devel +Priority: optional +Maintainer: Ashutosh Agrawal +Build-Depends: debhelper (>= 12.0.0), libevent-dev, libboost-thread-dev, libboost-system-dev, libswsscommon-dev +Standards-Version: 3.9.3 +Homepage: https://github.com/Azure/sonic-buildimage +XS-Go-Import-Path: github.com/Azure/sonic-buildimage + +Package: sonic-dhcp4relay +Architecture: any +Built-Using: ${misc:Built-Using} +Depends: ${shlibs:Depends} +Description: SONiC DHCPv4 Relay diff --git a/debian/rules b/dhcp4relay/debian/rules similarity index 100% rename from debian/rules rename to dhcp4relay/debian/rules diff --git a/dhcp4relay/src/dhcp4_sender.cpp b/dhcp4relay/src/dhcp4_sender.cpp new file mode 100644 index 0000000..228ef3e --- /dev/null +++ b/dhcp4relay/src/dhcp4_sender.cpp @@ -0,0 +1,30 @@ +#include "dhcp4_sender.h" + +#include +#include +#include + +#include + +/** + * @code bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t n); + * + * @brief send udp packet and return true if successful + * + * @param *buffer message buffer + * @param sockaddr_in target target socket + * @param n length of message + * @param relay_config *config pointer to relay_config + * @param uint8_t msg_type message type of dhcpv6 option of relayed message + * + * @return boolean True if packet successfully sent + */ +bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t n) { + if (sendto(sock, buffer, n, 0, (const struct sockaddr *)&target, sizeof(target)) == -1) { + char server_addr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(target.sin_addr), server_addr, INET_ADDRSTRLEN); + syslog(LOG_ERR, "sendto: Failed to send to target address: %s, error: %s\n", server_addr, strerror(errno)); + return false; + } + return true; +} diff --git a/dhcp4relay/src/dhcp4_sender.h b/dhcp4relay/src/dhcp4_sender.h new file mode 100644 index 0000000..fbe51c6 --- /dev/null +++ b/dhcp4relay/src/dhcp4_sender.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +#include + +/** + * @code bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t n); + * + * @brief send udp packet and return true if successful + * + * @param *buffer message buffer + * @param sockaddr_in target target socket + * @param n length of message + * @param relay_config *config pointer to relay_config + * @param uint8_t msg_type message type of dhcpv6 option of relayed message + * + * @return boolean True if packet successfully sent + */ +bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t n); diff --git a/dhcp4relay/src/dhcp4relay.cpp b/dhcp4relay/src/dhcp4relay.cpp new file mode 100644 index 0000000..953b43a --- /dev/null +++ b/dhcp4relay/src/dhcp4relay.cpp @@ -0,0 +1,1145 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "configdb.h" +#include "dhcp4_sender.h" +#include "dhcp4relay_mgr.h" +#include "dhcp4relay_stats.h" +#include "sonicv2connector.h" + +struct event_base *base; +struct event *ev_sigint; +struct event *ev_sigterm; +static std::string vlan_member = "VLAN_MEMBER|"; +static std::string counter_table = "DHCPv4_COUNTER_TABLE|"; + +static uint8_t client_recv_buffer[BUFFER_SIZE]; +int config_pipe[2]; + +/* DHCPv4 filter */ +/* sudo tcpdump -dd "inbound and ip dst 255.255.255.255 && udp dst port 67" */ + +static struct sock_filter ether_relay_filter[] = { + /* Make sure this is an IP packet... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8), + + /* Make sure it's a UDP packet... */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6), + + /* Make sure this isn't a fragment... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20), + BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0), + + /* Get the IP header length... */ + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14), + + /* Make sure it's to the right port... */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 67, 0, 1), /* patch */ + + /* If we passed all the tests, ask for the whole packet. */ + BPF_STMT(BPF_RET + BPF_K, (u_int)-1), + + /* Otherwise, drop it. */ + BPF_STMT(BPF_RET + BPF_K, 0), +}; + +const struct sock_fprog ether_relay_fprog = { + lengthof(ether_relay_filter), + ether_relay_filter}; + +/* interface to vlan mapping */ +std::unordered_map vlan_map; + +/* VRF sock map is created to avoid multiple sockets for same VRF + We can expect multiple servers on same VRF, we no need to open VRF sockets + for each VRF instead we can make use of existing VRF socket opened. + to map that i will maintain a map with VRF againist socket of vrf. + when ever there is a configuration to open a new socket for the VRF + check in this MAP if exists update relay_config or else update created + socket against VRF. +*/ +/* VRF sock map */ +std::unordered_map vrf_sock_map; + +/* This map will have client vlan to client VRF mapping */ +std::unordered_map vlan_vrf_map; + +/* This map will have interface name to interface alias map */ +std::unordered_map phy_interface_alias_map; + +/* DHCP Relay Counter Table Instance */ +DHCPCounter_table dhcp_cntr_table; + +/*DHCP Relay config manager Instance */ +DHCPMgr dhcp_mgr; + +#ifdef UNIT_TEST +using namespace swss; +#endif + +std::shared_ptr config_db = std::make_shared("CONFIG_DB", 0); + +std::shared_ptr state_db = std::make_shared("STATE_DB", 0); +/** + * @code std::string to_string(uint64_t count); + * + * @brief convert uint16_t to string + * + * @param count count of messages in counter + * + * @return count in string + */ +std::string to_string(uint64_t count) { + std::stringstream ss; + ss << count; + std::string count_value = ss.str(); + return count_value; +} + +/** + * @code sock_open(const struct sock_fprog *fprog); + * + * @brief prepare socket to receive all DHCP packet + * + * @param fprog bpf filter "udp and port 67" + * + * @return socket descriptor + */ +int sock_open(const struct sock_fprog *fprog) { + int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (s == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] socket: Failed to create socket with error %s\n", strerror(errno)); + return -1; + } + + evutil_make_listen_socket_reuseable(s); + evutil_make_socket_nonblocking(s); + + struct sockaddr_ll sll = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_ALL), + .sll_ifindex = 0 // any interface + }; + + if (bind(s, (struct sockaddr *)&sll, sizeof sll) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] bind: Failed to bind to specified interface, error: %s\n", strerror(errno)); + (void)close(s); + return -1; + } + if (fprog && setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, fprog, sizeof *fprog) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] setsockopt: Failed to attach filter, error: %s\n", strerror(errno)); + (void)close(s); + return -1; + } + + int optval = 0; + socklen_t optlen = sizeof(optval); + if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, &optval, &optlen) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] getsockopt: Failed to get recv buffer size, error: %s\n", strerror(errno)); + (void)close(s); + return -1; + } + + int optval_new = RAWSOCKET_RECV_SIZE; + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &optval_new, sizeof(optval_new)) == -1) { + syslog(LOG_WARNING, "[DHCPV4_RELAY] setsockopt: Failed to set recv buffer size to %d, use default value\n", optval_new); + } else { + syslog(LOG_INFO, "[DHCPV4_RELAY] setsockopt: change raw socket recv buffer size from %d to %d\n", optval, optval_new); + } + + return s; +} + +void prepare_relay_server_config(relay_config &interface_config) { + for (auto server : interface_config.servers) { + sockaddr_in tmp; + if (inet_pton(AF_INET, server.c_str(), &tmp.sin_addr) != 1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] inet_pton: Failed to convert IPv4 address\n"); + return; + } + tmp.sin_family = AF_INET; + tmp.sin_port = htons(RELAY_PORT); + interface_config.servers_sock.push_back(tmp); + } +} +/** + * @code prepare_relay_interface_config(relay_config &interface_config); + * + * @brief prepare for specified relay interface config: server and link address + * + * @param interface_config pointer to relay config to be prepared + * + * @return none + */ +void prepare_relay_interface_config(relay_config &interface_config) { + struct ifaddrs *ifa, *ifa_tmp; + sockaddr_in intf_addr; + sockaddr_in net_mask; + sockaddr_in src_intf_sel; + bool intf_name_set = false; + bool source_intf_sel_opt = false; + + if (getifaddrs(&ifa) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] getifaddrs: Unable to get network interfaces, error: %s\n", strerror(errno)); + return; + } + + if (interface_config.source_interface.length() > 0) { + syslog(LOG_INFO, "[DHCPV4_INFO] source interface addr is set to %s\n", + interface_config.source_interface.c_str()); + } else { + /* This flag is used to make sure source interface address is copied to config cache + if source interface selection is not set with interface we set this flag true + and we wont copy any source interface selection related information + */ + source_intf_sel_opt = true; + } + + ifa_tmp = ifa; + while (ifa_tmp) { + if (ifa_tmp->ifa_addr && ifa_tmp->ifa_addr->sa_family == AF_INET) { + struct sockaddr_in *in = (struct sockaddr_in *)ifa_tmp->ifa_addr; + struct sockaddr_in *mask = (struct sockaddr_in *)ifa_tmp->ifa_netmask; + if (strcmp(ifa_tmp->ifa_name, interface_config.vlan.c_str()) == 0) { + intf_addr = *in; + net_mask = *mask; + intf_name_set = true; + } else if ((!source_intf_sel_opt) && + (strcmp(ifa_tmp->ifa_name, interface_config.source_interface.c_str()) == 0)) { + src_intf_sel = *in; + source_intf_sel_opt = true; + } + } + if (intf_name_set && source_intf_sel_opt) { + break; + } + ifa_tmp = ifa_tmp->ifa_next; + } + freeifaddrs(ifa); + + interface_config.link_address = intf_addr; + interface_config.link_address_netmask = net_mask; + interface_config.src_intf_sel_addr = src_intf_sel; +} + +int prepare_vrf_sockets(relay_config &config) { + /* Open a socket per server VRF, check socket is already available for that VRF */ + int vrf_sock = -1; + auto itr = vrf_sock_map.find(config.vrf.c_str()); + if (itr == vrf_sock_map.end()) { + /* Vrf sock is not available in map need to create */ + vrf_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (vrf_sock == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] socket: Failed to create client_addr socket vrf %s err = %s\n", + config.vrf.c_str(), strerror(errno)); + return -1; + } + + evutil_make_listen_socket_reuseable(vrf_sock); + evutil_make_socket_nonblocking(vrf_sock); + + /* Default instance binding to the socket will fail, in default instance case will not bind the socket */ + if (config.vrf != "default") { + if (setsockopt(vrf_sock, SOL_SOCKET, SO_BINDTODEVICE, + config.vrf.c_str(), strlen(config.vrf.c_str())) < 0) { + syslog(LOG_ERR, "[DHCPV4_RELAY] setsockopt: Failed to bind socket to %s VRF err = %s\n", + config.vrf.c_str(), strerror(errno)); + close(vrf_sock); + return -1; + } + } + + /* Update the map */ + vrf_sock_map[config.vrf] = {vrf_sock, 1}; + } + + if (vrf_sock > 0) { + config.vrf_sock = vrf_sock; + } else { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to obtain vrf socket(%s) error:%s \n", config.vrf.c_str(), strerror(errno)); + return -1; + } + return 0; +} + +/** + * @code prepare_vlan_sockets(int &client_sock, relay_config &config); + * + * @brief prepare vlan L3 socket for sending + * + * @param client_sock socket binded to ip address of vlan interface on which server is configured. + * This socket will be used to send DHCP packet to server and client. + * + * @return int + */ +int prepare_vlan_sockets(relay_config &config) { +#ifdef UNIT_TEST + config.client_sock = 1; + config.lo_sock = 1; +#else + struct ifaddrs *ifa, *ifa_tmp; + sockaddr_in client_addr = {0}; + int lo_sock = -1, client_sock = 0; + if ((client_sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] socket: Failed to create client_addr socket on interface %s, error: %s\n", + config.vlan.c_str(), strerror(errno)); + return -1; + } + + evutil_make_listen_socket_reuseable(client_sock); + evutil_make_socket_nonblocking(client_sock); + + /* Bind client socket to vlan interface */ + if (setsockopt(client_sock, SOL_SOCKET, SO_BINDTODEVICE, config.vlan.c_str(), + strlen(config.vlan.c_str())) < 0) { + syslog(LOG_ERR, "[DHCPV4_RELAY] failed to bind client_sock to vlan %s, error: %s\n", + config.vlan.c_str(), strerror(errno)); + close(client_sock); + return -1; + } + + int retry = 0; + bool bind_client_addr = false; + do { + if (getifaddrs(&ifa) == -1) { + syslog(LOG_WARNING, "[DHCPV4_RELAY] getifaddrs: Unable to get network interfaces with %s\n", strerror(errno)); + } else { + ifa_tmp = ifa; + while (ifa_tmp) { + if (ifa_tmp->ifa_addr && (ifa_tmp->ifa_addr->sa_family == AF_INET)) { + if (strcmp(ifa_tmp->ifa_name, config.vlan.c_str()) == 0) { + struct sockaddr_in *in = (struct sockaddr_in *)ifa_tmp->ifa_addr; + bind_client_addr = true; + client_addr = *in; + client_addr.sin_family = AF_INET; + client_addr.sin_port = htons(RELAY_PORT); + } + } + ifa_tmp = ifa_tmp->ifa_next; + } + freeifaddrs(ifa); + } + + if (bind_client_addr) { + break; + } + + syslog(LOG_WARNING, "[DHCPV4_RELAY] Retry #%d to bind to sockets on interface %s\n", ++retry, config.vlan.c_str()); + sleep(5); + } while (retry < 6); + + if ((!bind_client_addr) || (bind(client_sock, (sockaddr *)&client_addr, sizeof(client_addr)) == -1)) { + syslog(LOG_ERR, "[DHCPV4_RELAY] bind: Failed to bind socket to global ipv4 address on interface %s after %d retries with %s\n", + config.vlan.c_str(), retry, strerror(errno)); + /* TODO: Need to close vrf socket if ref count is zero in all failure case */ + close(client_sock); + return -1; + } + + int broadcast_enable = 1; + if (setsockopt(client_sock, SOL_SOCKET, SO_BROADCAST, &broadcast_enable, sizeof(broadcast_enable)) < 0) { + syslog(LOG_ERR, "[DHCPV4_RELAY] setsockopt: Failed to set socket to receive broadcast address, error: %s\n", + strerror(errno)); + close(client_sock); + return -1; + } + + int optval = 1; + if (setsockopt(client_sock, IPPROTO_IP, IP_PKTINFO, &optval, sizeof(optval)) < 0) { + syslog(LOG_ERR, + "[DHCPV4_RELAY] setsockopt: Failed to set socket option to " + "get IP PKT information, error: %s\n", + strerror(errno)); + close(client_sock); + return -1; + } + + config.client_sock = client_sock; + config.lo_sock = lo_sock; +#endif + return 0; +} + +uint8_t encode_tlv(uint8_t *buf, uint8_t t, uint8_t l, uint8_t *v) { + *buf = t; + *(buf + DHCP_SUB_OPT_TLV_LENGTH_OFFSET) = l; + memcpy((buf + DHCP_SUB_OPT_TLV_HEADER_LEN), v, l); + + return (l + DHCP_SUB_OPT_TLV_HEADER_LEN); +} + +void encode_relay_option(pcpp::DhcpLayer *dhcp_pkt, relay_config *config) { + uint8_t buf[256] = {0}; + uint8_t buf_offset = 0; + + auto vrf = vlan_vrf_map[config->vlan.c_str()]; + + /* Get interface alias */ + std::string intf_alias; + if (phy_interface_alias_map.find(config->phy_interface) != phy_interface_alias_map.end()) { + intf_alias = phy_interface_alias_map[config->phy_interface]; + } else { + std::shared_ptr port_tbl = + std::make_shared(config->config_db.get(), CFG_PORT_TABLE_NAME); + port_tbl->hget(config->phy_interface.c_str(), "alias", intf_alias); + phy_interface_alias_map[config->phy_interface] = intf_alias; + } + + /* Encode circuit ID sub-option */ + /* | 1 | 4 | hostname:interface_alias:vlan | */ + std::string circuit_id = config->hostname + ":" + intf_alias + ":" + config->vlan; + auto offset = encode_tlv(buf, OPTION82_SUBOPT_CIRCUIT_ID, circuit_id.length(), + (uint8_t *)circuit_id.c_str()); + buf_offset += offset; + + /* Encode remote ID sub-option */ + /* | 2 | 6 | my_mac| */ + offset = encode_tlv((buf + buf_offset), OPTION82_SUBOPT_REMOTE_ID, + MAC_ADDR_LEN, (uint8_t *)(config->host_mac_addr)); + buf_offset += offset; + + /* TODO: this sub-option should be set if source interface selection is enabled */ + /* | 5 | 4 | ipv4 | */ + if (config->link_selection_opt == "enable") { + uint32_t link_sel_ip = ((config->link_address.sin_addr.s_addr) & + (config->link_address_netmask.sin_addr.s_addr)); + offset = encode_tlv((buf + buf_offset), OPTION82_SUBOPT_LINK_SELECTION, sizeof(uint32_t), + (uint8_t *)&link_sel_ip); + buf_offset += offset; + } + + /* | 11 | 4 | ipv4 | */ + if (config->server_id_override_opt == "enable") { + offset = encode_tlv((buf + buf_offset), OPTION82_SUBOPT_SERVER_OVERRIDE, sizeof(uint32_t), + (uint8_t *)(&(config->link_address.sin_addr.s_addr))); + buf_offset += offset; + } + + /* Encode VSS sub-option 151 if client is not default VRF */ + /* | 151 | vrf_len | 0 | vrf_name | */ + uint8_t vss_buf[32] = {0}; + if (config->vrf_selection_opt == "enable") { + uint8_t zero_encode = 0; + memcpy(vss_buf, &zero_encode, sizeof(uint8_t)); + memcpy((vss_buf + 1), (uint8_t *)vrf.c_str(), (uint8_t)vrf.length()); + + offset = encode_tlv((buf + buf_offset), OPTION82_SUBOPT_VIRTUAL_SUBNET, + (uint8_t)(vrf.length() + 1), vss_buf); + buf_offset += offset; + } + + /* We shouldn't append relay information if packet size is exceeding MTU size */ + if ((dhcp_pkt->getHeaderLen() + buf_offset) > MAX_DHCP_PKT_SIZE) { + syslog(LOG_ERR, + "[DHCPV4_RELAY] %ld packet size is exceeding allowed size %d" + " from interface %s", + (dhcp_pkt->getHeaderLen() + buf_offset), + MAX_DHCP_PKT_SIZE, config->vlan.c_str()); + return; + } + + dhcp_pkt->addOption(pcpp::DhcpOptionBuilder(pcpp::DHCPOPT_DHCP_AGENT_OPTIONS, + buf, buf_offset)); + return; +} + +/** + * @code void from_client(pcpp::DhcpLayer* dhcp_pkt, relay_config *config) + * + * @brief construct relay-forward message + * + * @param dhcp_pkt DHCP layered packet information. + * @param config pointer to the relay interface config + * + * @return none + */ +void from_client(pcpp::DhcpLayer *dhcp_pkt, relay_config &config) { + /* Update giaddr */ + if (!(dhcp_pkt->getDhcpHeader()->gatewayIpAddress)) { + if (config.source_interface.length() > 0) { + /* find the IP of the interface and update to giaddr */ + dhcp_pkt->getDhcpHeader()->gatewayIpAddress = + config.src_intf_sel_addr.sin_addr.s_addr; + } else { + dhcp_pkt->getDhcpHeader()->gatewayIpAddress = + config.link_address.sin_addr.s_addr; + } + encode_relay_option(dhcp_pkt, &config); + } else { + /* If the relay packet is from another relay, we should act based on + configuration of agent_relay_mode. + forward_and_append - Forward the packet with appending relay agent. + forward_and_replace - Delete existing option 82 and add my relay option. + forward_untouched - Forward without adding my relay information. + discard - Discard the incoming packet. + */ + if (config.agent_relay_mode == "forward_and_replace") { + dhcp_pkt->removeOption(pcpp::DHCPOPT_DHCP_AGENT_OPTIONS); + encode_relay_option(dhcp_pkt, &config); + } else if (config.agent_relay_mode == "discard") { + syslog(LOG_INFO, "[DHCPV4_RELAY] agent relay mode is discard, dropping the packet %s", + config.vlan.c_str()); + return; + } else if (config.agent_relay_mode == "forward_untouched") { + syslog(LOG_INFO, + "[DHCPV4_RELAY] agent relay mode is forward untouch," + " not appending self relay info %s", + config.vlan.c_str()); + } else { + /* By default it will be forward_and_append */ + encode_relay_option(dhcp_pkt, &config); + } + } + + /* Increase the hop count */ + /*TODO: check hops count is less than max hops count allowed */ + dhcp_pkt->getDhcpHeader()->hops = dhcp_pkt->getDhcpHeader()->hops + 1; + + int sock = config.vrf_sock; + uint32_t index = 0; + for (auto server : config.servers_sock) { + if (send_udp(sock, (uint8_t *)dhcp_pkt->getDhcpHeader(), server, dhcp_pkt->getHeaderLen())) { + syslog(LOG_INFO, "[DHCPV4_RELAY] DHCP packet is sent to configured server: %s, interface: %s", + config.servers[index].c_str(), config.vlan.c_str()); + dhcp_cntr_table.increment_counter(config.vlan, "TX", (int)dhcp_pkt->getMessageType()); + } else { + syslog(LOG_NOTICE, "[DHCPV4_RELAY] DHCP packet sending FAILED for configured server: %s, interface: %s", + config.servers[index].c_str(), config.vlan.c_str()); + // increment drop counter + dhcp_cntr_table.increment_counter(config.vlan, "TX", DHCPv4_MESSAGE_TYPE_DROP); + } + index++; + } +} + +uint8_t *decode_tlv(const uint8_t *buf, uint8_t t, uint8_t &l, uint32_t options_total_size) { + uint8_t *temp = (uint8_t *)buf; + uint8_t len = 0; + + while (temp && (len < options_total_size)) { + len = *(temp + DHCP_SUB_OPT_TLV_LENGTH_OFFSET); + if (t == *temp) { + syslog(LOG_INFO, "[DHCPV4_INFO] Decoding realy agent sub-option %d of len %d\n", t, len); + l = len; + return (temp + DHCP_SUB_OPT_TLV_HEADER_LEN); + } + temp += (len + DHCP_SUB_OPT_TLV_HEADER_LEN); + } + return NULL; +} + +/** + * @code void to_client(pcpp::DhcpLayer* dhcp_pkt, std::unordered_map *vlans, std::string src_ip); + * + * @brief API will send DHCP relay message to client. + * + * @param dhcp_pkt DHCP layer class, which will have information of DHCP packet. + * @param vlans Client information including socket to send DHCP packet to client. + * + * @return none + */ +void to_client(pcpp::DhcpLayer *dhcp_pkt, std::unordered_map *vlans, + std::string src_ip) { + struct ifaddrs *ifa, *ifa_tmp; + struct sockaddr_in target_addr = {0}; + uint32_t giaddr = dhcp_pkt->getDhcpHeader()->gatewayIpAddress; + uint32_t broadcast_addr = DHCP_BROADCAST_IPADDR; + std::unordered_map::iterator config_itr = vlans->end(); + + if (getifaddrs(&ifa) == -1) { + syslog(LOG_WARNING, "[DHCPV4_RELAY] getifaddrs: Unable to get network interfaces, error: %s\n", strerror(errno)); + exit(1); + } + + /* Return if giaddr is empty */ + if (giaddr == 0) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Message received with empty giaddr from server %s\n", + src_ip.c_str()); + return; + } + + auto agent_option = dhcp_pkt->getOptionData(pcpp::DHCPOPT_DHCP_AGENT_OPTIONS); + auto options_ptr = agent_option.getValue(); + auto agent_option_size = agent_option.getDataSize(); + if (options_ptr == NULL) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Relay options for reply packets are missing from server %s", + src_ip.c_str()); + return; + } + + uint8_t circuit_id_len = 0; + auto circuit_id_ptr = decode_tlv((const uint8_t *)options_ptr, OPTION82_SUBOPT_CIRCUIT_ID, + circuit_id_len, agent_option_size); + if (circuit_id_ptr == NULL) { + syslog(LOG_ERR, + "[DHCPV4_RELAY] Circuit id sub-option is missing in relay" + " agent option from server %s", + src_ip.c_str()); + return; + } + + std::string circuit_id((const char *)circuit_id_ptr, circuit_id_len); + + std::string vlan_interface; + auto vlan_intf_pos = circuit_id.rfind(':'); + if (vlan_intf_pos != std::string::npos) { + vlan_interface = circuit_id.substr(vlan_intf_pos + 1); + } + + if (vlan_interface.length() > 0) { + config_itr = vlans->find(vlan_interface); + if (config_itr == vlans->end()) { + syslog(LOG_INFO, + "[DHCPV4_RELAY] Vlan config not found for the circuit" + "id encoded interface %s\n", + vlan_interface.c_str()); + } + } + + /* If we couldnt able to find vlan config using circuit ID + Walk through all the interfaces and match for giaddrs. */ + if (config_itr == vlans->end()) { + std::string intf_name; + ifa_tmp = ifa; + while (ifa_tmp) { + if (ifa_tmp->ifa_addr && ifa_tmp->ifa_addr->sa_family == AF_INET) { + struct sockaddr_in *in = (struct sockaddr_in *)ifa_tmp->ifa_addr; + if (in->sin_addr.s_addr == giaddr) { + intf_name = ifa_tmp->ifa_name; + break; + } + } + ifa_tmp = ifa_tmp->ifa_next; + } + freeifaddrs(ifa); + + if (intf_name.length() == 0) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to find interface attached to address %u\n", giaddr); + return; + } + + // TODO: Add check if interface is prefix with vlan or else try to get vlan attached to ethernet + // find vlan attach using vlan map. Relay config is mapped to vlan. + + /* Expecting interface is SVI interface of vlan */ + auto config_itr = vlans->find(intf_name); + if (config_itr == vlans->end()) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Config not found for vlan %s\n", intf_name.c_str()); + return; + } + } else { + freeifaddrs(ifa); + } + auto config = config_itr->second; + + dhcp_cntr_table.increment_counter(config.vlan, "RX", (int)dhcp_pkt->getMessageType()); + /* TODO: Also check it is matching remote ID*/ + + memcpy(&target_addr.sin_addr, &broadcast_addr, sizeof(struct in_addr)); + target_addr.sin_family = AF_INET; + target_addr.sin_port = htons(CLIENT_PORT); + + /* TODO: Send unicast message to client if BOOTP flag from client is set to unicast */ + + dhcp_pkt->removeOption(pcpp::DHCPOPT_DHCP_AGENT_OPTIONS); + if (send_udp(config.client_sock, (uint8_t *)dhcp_pkt->getDhcpHeader(), target_addr, dhcp_pkt->getHeaderLen())) { + syslog(LOG_INFO, "[DHCPV4_RELAY] dhcp relay message is broadcast to client %s from server %s", + config.vlan.c_str(), src_ip.c_str()); + dhcp_cntr_table.increment_counter(config.vlan, "TX", (int)dhcp_pkt->getMessageType()); + } +} + +bool string_to_mac_addr(const std::string &mac_str, std::array &mac_addr) { + if (mac_str.size() != 17) { + return false; + } + + std::istringstream iss(mac_str); + int byte; + char separator; + + for (size_t i = 0; i < mac_addr.size(); ++i) { + if (!(iss >> std::hex >> byte)) { + return false; + } + mac_addr[i] = static_cast(byte); + + if (i < mac_addr.size() - 1) { + if (!(iss >> separator) || (separator != ':' && separator != '-')) { + return false; + } + } + } + + return true; +} + +/** + * @code update_vlan_mapping(std::string vlan, bool is_add); + * + * @brief build vlan member interface to vlan mapping table + * + * @param vlan vlan name string + * @param add add or delete entry + * + * @return none + */ +/** + * @brief Updates the VLAN mapping for a given VLAN. + * + * This function retrieves all VLAN members for the specified VLAN from the configuration database + * and updates the global VLAN map with the interface and VLAN information. + * + * @param vlan The VLAN identifier as a string. + * @param cfgdb A shared pointer to the configuration database connector. + */ +void update_vlan_mapping(std::string vlan, bool is_add) { +#ifdef UNIT_TEST + std::vector keys; + swss::Table vlan_member_table(config_db.get(), "VLAN_MEMBER"); + vlan_member_table.getKeys(keys); +#else + auto match_pattern = std::string("VLAN_MEMBER|") + vlan + std::string("|*"); + auto keys = config_db->keys(match_pattern); +#endif + for (auto &itr : keys) { + auto found = itr.find_last_of('|'); + auto interface = itr.substr(found + 1); + if (is_add) { + vlan_map[interface] = vlan; + dhcp_cntr_table.initialize_interface(vlan); + syslog(LOG_INFO, "[DHCPV4_RELAY] Add <%s, %s> into interface vlan map\n", interface.c_str(), vlan.c_str()); + } else { + vlan_map.erase(interface); + dhcp_cntr_table.remove_interface(vlan); + syslog(LOG_INFO, "[DHCPV4_RELAY] Remove <%s, %s> from interface vlan map\n", interface.c_str(), vlan.c_str()); + } + } + + /* get VRF attached to the vlan from VLAN_INTERFACE table */ + if (is_add) { + std::string value; + std::shared_ptr vlan_intf_tbl = std::make_shared(config_db.get(), CFG_VLAN_INTF_TABLE_NAME); + vlan_intf_tbl->hget(vlan, "vrf_name", value); + if (value.size() <= 0) { + /* use default instance as vrf */ + vlan_vrf_map[vlan] = "default"; + } else { + vlan_vrf_map[vlan] = value; + } + } else { + vlan_vrf_map.erase(vlan); + } +} + +/** + * @code pkt_in_callback(evutil_socket_t fd, short event, void *arg); + * + * @brief callback for libevent that is called everytime data is received at the filter socket + * this is expected to receive both server and client sent DHCP packets + * + * @param fd filter socket + * @param event libevent triggered event + * @param arg callback argument provided by user + * + * @return none + */ +void pkt_in_callback(evutil_socket_t fd, short event, void *arg) { + auto vlans = reinterpret_cast *>(arg); + struct sockaddr_ll sll; + socklen_t slen = sizeof(sll); + timeval time; + int pkts_num = 0; + char interface_name[IF_NAMESIZE]; + + while (pkts_num++ < BATCH_SIZE) { + auto buffer_sz = recvfrom(fd, client_recv_buffer, BUFFER_SIZE, 0, (struct sockaddr *)&sll, &slen); + if (buffer_sz <= 0) { + if (errno != EAGAIN) { + syslog(LOG_ERR, "[DHCPV4_RELAY] recv: Failed to receive data at filter socket: %s\n", strerror(errno)); + } + return; + } + + if (if_indextoname(sll.sll_ifindex, interface_name) == NULL) { + syslog(LOG_WARNING, "[DHCPV4_RELAY] Invalid input interface index %d\n", sll.sll_ifindex); + continue; + } + std::string intf(interface_name); + + gettimeofday(&time, nullptr); + + // Construct raw socket. + pcpp::RawPacket raw_packet(static_cast(client_recv_buffer), buffer_sz, time, false); + + pcpp::Packet raw_pkt(&raw_packet); + + /* Extract packets in each layers */ + pcpp::EthLayer *eth_layer = raw_pkt.getLayerOfType(); + if (eth_layer == nullptr) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Invalid Ethernet packet from interface %s\n", intf.c_str()); + return; + } + + pcpp::IPv4Layer *ip_layer = raw_pkt.getLayerOfType(); + if (ip_layer == nullptr) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Invalid IP packet from interface %s\n", intf.c_str()); + return; + } + auto src_ip = ip_layer->getSrcIPv4Address().toString(); + + pcpp::UdpLayer *udp_layer = raw_pkt.getLayerOfType(); + if (udp_layer == nullptr) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Invalid UDP packet from interface %s\n", intf.c_str()); + return; + } + + pcpp::DhcpLayer *dhcp_pkt = raw_pkt.getLayerOfType(); + if (dhcp_pkt == nullptr) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Invalid DHCP packet from interface %s\n", intf.c_str()); + return; + } + + if (dhcp_pkt->getDhcpHeader()->opCode == BOOTPREQUEST) { + auto vlan = vlan_map.find(intf); + if (vlan == vlan_map.end()) { + if (intf.find(CLIENT_IF_PREFIX) != std::string::npos) { + syslog(LOG_WARNING, "[DHCPV4_RELAY] Invalid input interface %s\n", interface_name); + } + continue; + } + auto config_itr = vlans->find(vlan->second); + if (config_itr == vlans->end()) { + syslog(LOG_WARNING, "[DHCPV4_RELAY] Config not found for vlan %s\n", vlan->second.c_str()); + continue; + } + auto config = config_itr->second; + config_itr->second.phy_interface = intf; + + dhcp_cntr_table.increment_counter(config.vlan, "RX", (int)dhcp_pkt->getMessageType()); + from_client(dhcp_pkt, config_itr->second); + } else if (dhcp_pkt->getDhcpHeader()->opCode == BOOTPREPLY) { + /* TODO: This work around need to be fixed. + * Currently we are receiving same dhcp reply packet on physical interface. + * on SVI interface(VLAN) and on the bridge. + * Trap is added for DHCP packet on all interface. + */ + if ((intf.find("Ethernet") == std::string::npos) && + (intf.find("Bridge") == std::string::npos)) { + to_client(dhcp_pkt, vlans, src_ip); + } + } else { + syslog(LOG_ERR, "[DHCPV4_RELAY] Received a invalid DHCPv4 packet\n"); + return; + } + } +} + +/** + * @code signal_init(); + * + * @brief initialize DHCPv6 Relay libevent signals + */ +int signal_init() { + int rv = -1; + do { + ev_sigint = evsignal_new(base, SIGINT, signal_callback, base); + if (ev_sigint == NULL) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Could not create SIGINT libevent signal\n"); + break; + } + + ev_sigterm = evsignal_new(base, SIGTERM, signal_callback, base); + if (ev_sigterm == NULL) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Could not create SIGTERM libevent signal\n"); + break; + } + rv = 0; + } while (0); + return rv; +} + +/** + * @code signal_start(); + * + * @brief start DHCPv6 Relay libevent base and add signals + */ +int signal_start() { + int rv = -1; + do { + if (evsignal_add(ev_sigint, NULL) != 0) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Could not add SIGINT libevent signal\n"); + break; + } + + if (evsignal_add(ev_sigterm, NULL) != 0) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Could not add SIGTERM libevent signal\n"); + break; + } + + if (event_base_dispatch(base) != 0) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Could not start libevent dispatching loop\n"); + } + + rv = 0; + } while (0); + + return rv; +} + +/** + * @code signal_callback(fd, event, arg); + * + * @brief signal handler for dhcp4relay. Initiate shutdown when signal is caught + * + * @param fd libevent socket + * @param event event triggered + * @param arg pointer to libevent base + * + * @return none + */ +void signal_callback(evutil_socket_t fd, short event, void *arg) { + syslog(LOG_ALERT, "[DHCPV4_RELAY] Received signal: '%s'\n", strsignal(fd)); + if ((fd == SIGTERM) || (fd == SIGINT)) { + dhcp4relay_stop(); + } +} + +/** + * @code dhcp4relay_stop(); + * + * @brief stop DHCPv6 Relay libevent loop upon signal + */ +void dhcp4relay_stop() { + event_base_loopexit(base, NULL); +} + +void config_event_callback(evutil_socket_t fd, short event, void *arg) { + std::unordered_map *vlans = static_cast *>(arg); + event_config received_event; + ssize_t bytes_read = read(fd, &received_event, sizeof(received_event)); + + if (bytes_read == sizeof(received_event)) { + if (received_event.type == DHCPv4_RELAY_CONFIG_UPDATE) { + relay_config *relay_msg = static_cast(received_event.msg); + if (relay_msg) { + syslog(LOG_INFO, "[DHCPV4_RELAY] Processing config update: VLAN %s", relay_msg->vlan.c_str()); + + if (relay_msg->is_add) { + if (vlans->find(relay_msg->vlan) == vlans->end()) { + /*If entry not exist then creating the entry with empty structure.*/ + (*vlans)[relay_msg->vlan] = relay_config{}; + (*vlans)[relay_msg->vlan].vlan = relay_msg->vlan; + (*vlans)[relay_msg->vlan].config_db = config_db; + (*vlans)[relay_msg->vlan].state_db = state_db; + update_vlan_mapping(relay_msg->vlan, true); + if (prepare_vlan_sockets((*vlans)[relay_msg->vlan]) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to create Vlan listen socket"); + exit(EXIT_FAILURE); + } + /* Intially filling the vlan interface IP address. */ + if (relay_msg->source_interface.empty()) { + prepare_relay_interface_config((*vlans)[relay_msg->vlan]); + } + } + + if ((*vlans)[relay_msg->vlan].servers != relay_msg->servers) { + (*vlans)[relay_msg->vlan].servers = relay_msg->servers; + prepare_relay_server_config((*vlans)[relay_msg->vlan]); + } + + /* Compare the existing vrf value and the new DB updated vrf value for vrf modification case. */ + if ((*vlans)[relay_msg->vlan].vrf != relay_msg->vrf) { + /* Decrement the ref count for old vrf value in the vrf_sock_map and after decrement + * if the value is zero then close the client socket and delete the entry in the map. */ + if (vrf_sock_map.find((*vlans)[relay_msg->vlan].vrf) != vrf_sock_map.end()) { + vrf_sock_map[(*vlans)[relay_msg->vlan].vrf].ref_count--; + if (vrf_sock_map[(*vlans)[relay_msg->vlan].vrf].ref_count == 0) { + close((*vlans)[relay_msg->vlan].vrf_sock); + vrf_sock_map.erase((*vlans)[relay_msg->vlan].vrf); + } + } + + /*Updating the new vrf value to vlans structure. */ + (*vlans)[relay_msg->vlan].vrf = relay_msg->vrf; + /* For the new server vrf update case, if the entry exists in the vrf_sock_map then + * increment the ref count only else create a socket and update the ref count. */ + if (vrf_sock_map.find(relay_msg->vrf) != vrf_sock_map.end()) { + (*vlans)[relay_msg->vlan].vrf_sock = vrf_sock_map[relay_msg->vrf.c_str()].sock; + vrf_sock_map[relay_msg->vrf].ref_count++; + } else { + if (prepare_vrf_sockets((*vlans)[relay_msg->vlan]) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to create vrf listen socket"); + exit(EXIT_FAILURE); + } + } + } + if ((*vlans)[relay_msg->vlan].source_interface != relay_msg->source_interface) { + (*vlans)[relay_msg->vlan].source_interface = relay_msg->source_interface; + prepare_relay_interface_config((*vlans)[relay_msg->vlan]); + } + + (*vlans)[relay_msg->vlan].link_selection_opt = relay_msg->link_selection_opt; + (*vlans)[relay_msg->vlan].server_id_override_opt = relay_msg->server_id_override_opt; + (*vlans)[relay_msg->vlan].vrf_selection_opt = relay_msg->vrf_selection_opt; + (*vlans)[relay_msg->vlan].agent_relay_mode = relay_msg->agent_relay_mode; + } else { + if (vlans->find(relay_msg->vlan) != vlans->end()) { + /* In case of vlan deletion, close all the sockets.*/ + if ((*vlans)[relay_msg->vlan].client_sock > 0) { + close((*vlans)[relay_msg->vlan].client_sock); + } + if ((*vlans)[relay_msg->vlan].lo_sock > 0) { + close((*vlans)[relay_msg->vlan].lo_sock); + } + if ((*vlans)[relay_msg->vlan].vrf_sock > 0) { + vrf_sock_map[(*vlans)[relay_msg->vlan].vrf].ref_count--; + if (vrf_sock_map[(*vlans)[relay_msg->vlan].vrf].ref_count == 0) { + close((*vlans)[relay_msg->vlan].vrf_sock); + vrf_sock_map.erase((*vlans)[relay_msg->vlan].vrf); + } + } + vlans->erase(relay_msg->vlan); + syslog(LOG_INFO, "[DHCPV4_RELAY] Deleted VLAN %s from configuration", relay_msg->vlan.c_str()); + update_vlan_mapping(relay_msg->vlan, false); + } else { + syslog(LOG_WARNING, "[DHCPV4_RELAY] Attempted to delete non-existent VLAN %s", relay_msg->vlan.c_str()); + } + } + delete relay_msg; + } + } else if (received_event.type == DHCPv4_RELAY_INTERFACE_UPDATE) { + relay_config *relay_msg = static_cast(received_event.msg); + if (relay_msg) { + syslog(LOG_INFO, "[DHCPV4_RELAY] Updating source interface for VLAN %s", relay_msg->vlan.c_str()); + if (relay_msg->is_add) { + (*vlans)[relay_msg->vlan].src_intf_sel_addr = relay_msg->src_intf_sel_addr; + } else { + memset(&(*vlans)[relay_msg->vlan].src_intf_sel_addr, 0, sizeof(sockaddr_in)); + } + delete relay_msg; + } + } else if (received_event.type == DHCPv4_RELAY_METADATA_UPDATE) { + relay_config *device_data = static_cast(received_event.msg); + if (device_data) { + syslog(LOG_INFO, "[DHCPV4_RELAY] Processing Device Metadata Event Update."); + + for (auto &vlan : *vlans) { + vlan.second.hostname = device_data->hostname; + std::copy(std::begin(device_data->host_mac_addr), std::end(device_data->host_mac_addr), + std::begin(vlan.second.host_mac_addr)); + } + delete device_data; + } + } + } else { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to read config update: expected %lu bytes, got %zd bytes", sizeof(received_event), bytes_read); + } +} + +/** + * @code loop_relay(std::unordered_map &vlans); + * + * @brief main loop: configure sockets, create libevent base, start server listener thread + * + * @param vlans list of vlans retrieved from config_db + */ + +/** + * @brief Main loop for the DHCP relay functionality. + * + * This function sets up the necessary event base and socket listeners for + * handling DHCP relay operations. It initializes connections to the state + * and configuration databases, sets up event listeners for client and server + * sockets, and prepares VLAN-specific configurations. + * + * @param vlans A reference to an unordered map containing VLAN configurations. + * + */ +void loop_relay(std::unordered_map &vlans) { + base = event_base_new(); + if (base == NULL) { + syslog(LOG_ERR, "[DHCPV4_RELAY] libevent: Failed to create event base\n"); + exit(EXIT_FAILURE); + } + + // Create the pipe for inter-thread communication + if (pipe(config_pipe) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to create config update pipe"); + exit(EXIT_FAILURE); + } + + // Set the read-end of the pipe to non-blocking mode + fcntl(config_pipe[0], F_SETFL, O_NONBLOCK); + + // Add the pipe to libevent for async config updates + struct event *config_event = event_new(base, config_pipe[0], EV_READ | EV_PERSIST, + config_event_callback, reinterpret_cast(&vlans)); + if (!config_event) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to create event for config pipe"); + exit(EXIT_FAILURE); + } + + event_add(config_event, NULL); + syslog(LOG_INFO, "[DHCPV4_RELAY] Added event listener for config updates"); + + /* Open a socket with dhcp port, protocol filter */ + auto filter = sock_open(ðer_relay_fprog); + if (filter != -1) { + /* Register to the callbck func when there is new packet to the socket from client */ + auto event = event_new(base, filter, EV_READ | EV_PERSIST, pkt_in_callback, + reinterpret_cast(&vlans)); + if (event == NULL) { + syslog(LOG_ERR, "[DHCPV4_RELAY] libevent: Failed to create client listen event\n"); + exit(EXIT_FAILURE); + } + event_add(event, NULL); + syslog(LOG_INFO, "[DHCPV4_RELAY] libevent: Add client listen socket event\n"); + } else { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to create client listen socket"); + exit(EXIT_FAILURE); + } + + // Start thread for periodic counters updates to DB + dhcp_cntr_table.start_db_updates(); + + // Start thread for listening of config DB updates + dhcp_mgr.initialize_config_listner(); + + if (signal_init() == 0 && signal_start() == 0) { + shutdown_relay(); + if (filter != -1) { + close(filter); + } + } +} + +/** + * @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); +} diff --git a/dhcp4relay/src/dhcp4relay.h b/dhcp4relay/src/dhcp4relay.h new file mode 100644 index 0000000..d5f8b81 --- /dev/null +++ b/dhcp4relay/src/dhcp4relay.h @@ -0,0 +1,278 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "dbconnector.h" +#include "dhcp4_sender.h" +#include "table.h" + +#define PACKED __attribute__((packed)) + +#define RELAY_PORT 67 +#define CLIENT_PORT 68 +#define HOP_LIMIT 4 +#define DHCPv4_OPTION_LIMIT 255 +#define RAWSOCKET_RECV_SIZE 1048576 +#define CLIENT_IF_PREFIX "Ethernet" +#define BUFFER_SIZE 9200 // TODO: change to dynamic size based on MTU +#define MAX_DHCP_PKT_SIZE 1472 // 1500 - (IP + UDP)headers +#define MAC_ADDR_LEN 6 + +#define BOOTPREQUEST 1 +#define BOOTPREPLY 2 +#define BOOTP_HTYPE_ETHERNET 1 +#define BOOTP_HLEN_ETHERNET 6 +#define BOOTP_FLAGS_BROADCAST 0x8000 +#define DHCP_BROADCAST_IPADDR 0xFFFFFFFF + +#define DHCP_SUB_OPT_TLV_LENGTH_OFFSET 1 +#define DHCP_SUB_OPT_TLV_HEADER_LEN 2 + +#define lengthof(A) (sizeof(A) / sizeof(A)[0]) + +extern char vrf_single[IF_NAMESIZE]; +extern bool vrf_sock_set; +extern int config_pipe[2]; + +#define OPTION_RELAY_MSG 82 +#define OPTION82_SUBOPT_CIRCUIT_ID 1 +#define OPTION82_SUBOPT_REMOTE_ID 2 +#define OPTION82_SUBOPT_LINK_SELECTION 5 +#define OPTION82_SUBOPT_SERVER_OVERRIDE 11 +#define OPTION82_SUBOPT_VIRTUAL_SUBNET 151 + +#define DHCP_ETHERNET_HDR_LEN 14 +#define DHCP_IP_HDR_LEN 20 +#define DHCP_UDP_HDR_LEN 8 +#define DHCP_UDP_OVERHEAD_LEN (DHCP_ETHERNET_HDR_LEN + DHCP_IP_HDR_LEN + DHCP_UDP_HDR_LEN) +#define DHCP_SNAME_LEN 64 +#define DHCP_FILE_LEN 128 +#define DHCP_FIXED_NON_UDP_LEN 236 +#define DHCP_FIXED_LEN (DHCP_FIXED_NON_UDP_LEN + DHCP_UDP_OVERHEAD_LEN) +#define DHCP_MTU_MAX 9216 +#define DHCP_OPTION_LEN (DHCP_MTU_MAX - DHCP_FIXED_LEN) + +#define BATCH_SIZE 64 + +extern char loopback[IF_NAMESIZE]; + +struct VrfSocketInfo { + int sock; + uint16_t ref_count; +}; + +/* DHCPv4 message types */ +typedef enum { + DHCPv4_MESSAGE_TYPE_UNKNOWN, + DHCPv4_MESSAGE_TYPE_DISCOVER, + DHCPv4_MESSAGE_TYPE_OFFER, + DHCPv4_MESSAGE_TYPE_REQUEST, + DHCPv4_MESSAGE_TYPE_DECLINE, + DHCPv4_MESSAGE_TYPE_ACK, + DHCPv4_MESSAGE_TYPE_NAK, + DHCPv4_MESSAGE_TYPE_RELEASE, + DHCPv4_MESSAGE_TYPE_INFORM, + DHCPv4_MESSAGE_TYPE_MALFORMED, + DHCPv4_MESSAGE_TYPE_DROP, + + DHCPv4_MESSAGE_TYPE_COUNT +} dhcp_message_type_t; + +struct relay_config { + /* Client facing socket, use to send packet to client */ + int client_sock; + /* Server facing socket, use to send packet to server */ + int vrf_sock; + int lo_sock; + int filter; + sockaddr_in link_address; + sockaddr_in link_address_netmask; + sockaddr_in src_intf_sel_addr; + uint32_t link_ifindex; + uint8_t host_mac_addr[MAC_ADDR_LEN]; + std::shared_ptr state_db; + std::string vlan; + std::string phy_interface; + std::string vrf; // This is server VRF. + std::string source_interface; + std::string link_selection_opt; + std::string server_id_override_opt; + std::string vrf_selection_opt; + std::string agent_relay_mode; + std::string hostname; + std::vector servers; + std::vector servers_sock; + bool is_interface_id; + bool is_add; + std::shared_ptr config_db; +}; + +typedef enum { + DHCPv4_RELAY_CONFIG_UNKNOWN, + DHCPv4_RELAY_CONFIG_UPDATE, + DHCPv4_RELAY_INTERFACE_UPDATE, + DHCPv4_RELAY_METADATA_UPDATE +} event_type; + +struct event_config { + event_type type; + void *msg; +}; + +/** + * @code sock_open(const struct sock_fprog *fprog); + * + * @brief prepare L2 socket to attach to "udp and port 67" filter + * + * @param fprog bpf filter "udp and port 67" + * + * @return socket descriptor + */ +int sock_open(const struct sock_fprog *fprog); + +/** + * @code prepare_vlan_sockets(relay_config &config); + * + * @brief prepare vlan L3 socket for sending + * + * @return int + */ +int prepare_vlan_sockets(relay_config &config); + +/** + * @code prepare_vrf_sockets(relay_config &config); + * + * @brief prepare vrf L3 socket for sending + * + * @return int + */ +int prepare_vrf_sockets(relay_config &config); + +/** + * @code prepare_relay_interface_config(relay_config &interface_config); + * + * @brief prepare for specified relay interface config + * + * @param interface_config pointer to relay config to be prepared + * + * @return none + */ +void prepare_relay_interface_config(relay_config &interface_config); + +/** + * @code prepare_relay_server_config(relay_config &interface_config); + * + * @brief prepare for specified relay server and link address + * + * @param interface_config pointer to relay config to be prepared + * + * @return none + */ +void prepare_relay_server_config(relay_config &interface_config); + +/** + * @code loop_relay(std::unordered_map &vlans); + * + * @brief main loop: configure sockets, create libevent base, start server listener thread + * + * @param vlans list of vlans retrieved from config_db + * @param state_db state_db connector + */ +void loop_relay(std::unordered_map &vlans); + +/** + * @code signal_init(); + * + * @brief initialize DHCPv4 Relay libevent signals + */ +int signal_init(); + +/** + * @code signal_start(); + * + * @brief start DHCPv4 Relay libevent base and add signals + */ +int signal_start(); + +/** + * @code dhcp4relay_stop(); + * + * @brief stop DHCPv4 Relay libevent loop upon signal + */ +void dhcp4relay_stop(); + +/** + * @code signal_callback(fd, event, arg); + * + * @brief signal handler for dhcp4relay. Initiate shutdown when signal is caught + * + * @param fd libevent socket + * @param event event triggered + * @param arg pointer to libevent base + * + * @return none + */ +void signal_callback(evutil_socket_t fd, short event, void *arg); + +/** + * @code shutdown(); + * + * @brief free signals and terminate threads + */ +void shutdown_relay(); + +/* Helper functions */ + +/** + * @code std::string to_string(uint64_t count); + * + * @brief convert uint64_t to string + * + * @param count count of messages in counter + * + * @return count in string + */ +std::string to_string(uint64_t count); + +/** + * @code update_vlan_mapping(std::string vlan, bool is_add); + * + * @brief build vlan member interface to vlan mapping table + * + * @param vlan vlan name string + * @param is_add add or delete entry + * + * @return none + */ +void update_vlan_mapping(std::string vlan, bool is_add); + +/** + * @code pkt_in_callback(evutil_socket_t fd, short event, void *arg); + * + * @brief callback for libevent that is called everytime data is received at the filter socket + * this is expected to receive both server and client sent DHCP packets + * + * @param fd filter socket + * @param event libevent triggered event + * @param arg callback argument provided by user + * + * @return none + */ +void pkt_in_callback(evutil_socket_t fd, short event, void *arg); +bool string_to_mac_addr(const std::string &mac_str, std::array &mac_addr); +void config_event_callback(evutil_socket_t fd, short event, void *arg); +uint8_t *decode_tlv(const uint8_t *buf, uint8_t t, uint8_t &l, uint32_t options_total_size); +uint8_t encode_tlv(uint8_t *buf, uint8_t t, uint8_t l, uint8_t *v); diff --git a/dhcp4relay/src/dhcp4relay_mgr.cpp b/dhcp4relay/src/dhcp4relay_mgr.cpp new file mode 100644 index 0000000..e647201 --- /dev/null +++ b/dhcp4relay/src/dhcp4relay_mgr.cpp @@ -0,0 +1,320 @@ + +#include "dhcp4relay_mgr.h" + +#include +#include +constexpr auto DEFAULT_TIMEOUT_MSEC = 1000; + +static std::unordered_map vlans_copy; + +#ifdef UNIT_TEST +using namespace swss; +#endif + +/** + * @brief Initializes the configuration listener for the DHCP manager. + * + * This function starts a new detached thread that listens for SWSS (Switch State Service) + * notifications by invoking the handle_swss_notification method. It also sets the stop_thread + * flag to false to indicate that the listener thread should be running. + * + * @note The spawned thread is detached, so it will run independently of the main thread. + */ +void DHCPMgr::initialize_config_listner() { + stop_thread = false; + std::thread m_swss_thread(&DHCPMgr::handle_swss_notification, this); + m_swss_thread.detach(); +} + +/** + * @brief Handles SWSS (Sonic Warehouse State Service) notifications for DHCPv4 relay manager. + * + * This method listens for configuration changes in various tables within the CONFIG_DB, + * such as DHCPV4_RELAY, INTERFACE, LOOPBACK_INTERFACE, PORTCHANNEL_INTERFACE, and DEVICE_METADATA. + * It uses a select loop to wait for notifications from these tables and processes them accordingly. + * + * The function continues to run until the `stop_thread` flag is set. For each notification, + * it determines the source table and invokes the appropriate handler to process the entries. + * Errors and unknown return values from the select operation are logged. + * + * Tables monitored: + * - DHCPV4_RELAY: Triggers relay notification processing. + * - INTERFACE, LOOPBACK_INTERFACE, PORTCHANNEL_INTERFACE: Triggers interface notification processing. + * - DEVICE_METADATA: Triggers device metadata notification processing. + * + * @note This function is intended to be run in a dedicated thread. + */ +void DHCPMgr::handle_swss_notification() { + std::shared_ptr config_db_ptr = std::make_shared("CONFIG_DB", 0); + swss::SubscriberStateTable config_db_relaymgr_table(config_db_ptr.get(), "DHCPV4_RELAY"); + swss::SubscriberStateTable config_db_interface_table(config_db_ptr.get(), "INTERFACE"); + swss::SubscriberStateTable config_db_loopback_table(config_db_ptr.get(), "LOOPBACK_INTERFACE"); + swss::SubscriberStateTable config_db_portchannel_table(config_db_ptr.get(), "PORTCHANNEL_INTERFACE"); + swss::SubscriberStateTable config_db_device_metadata_table(config_db_ptr.get(), "DEVICE_METADATA"); + + std::deque entries; + swss::Select swss_select; + swss_select.addSelectable(&config_db_relaymgr_table); + swss_select.addSelectable(&config_db_interface_table); + swss_select.addSelectable(&config_db_loopback_table); + swss_select.addSelectable(&config_db_portchannel_table); + swss_select.addSelectable(&config_db_device_metadata_table); + + while (!stop_thread) { + swss::Selectable *selectable; + int ret = swss_select.select(&selectable, DEFAULT_TIMEOUT_MSEC); + + if (ret == swss::Select::ERROR) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Error had been returned in select"); + continue; + } else if (ret == swss::Select::TIMEOUT) { + continue; + } else if (ret != swss::Select::OBJECT) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Unknown return value from Select: %d", ret); + continue; + } + + if (selectable == static_cast(&config_db_relaymgr_table)) { + config_db_relaymgr_table.pops(entries); + process_relay_notification(entries); + } else if (selectable == static_cast(&config_db_interface_table)) { + config_db_interface_table.pops(entries); + process_interface_notification(entries); + } else if (selectable == static_cast(&config_db_loopback_table)) { + config_db_loopback_table.pops(entries); + process_interface_notification(entries); + } else if (selectable == static_cast(&config_db_portchannel_table)) { + config_db_portchannel_table.pops(entries); + process_interface_notification(entries); + } else if (selectable == static_cast(&config_db_device_metadata_table)) { + config_db_device_metadata_table.pops(entries); + process_device_metadata_notification(entries); + } + } +} + +/** + * @brief Processes device metadata notifications and sends metadata update events if necessary. + * + * This function iterates over a deque of device metadata entries, checks for relevant updates, + * and sends a metadata update event through a configuration pipe if the entry corresponds to "localhost". + * It extracts fields such as hostname and MAC address from the metadata, constructs a relay_config object, + * and ensures a default hostname ("sonic") is set if not present. If memory allocation fails or writing + * to the pipe fails, appropriate error messages are logged. + * + * @param entries A deque of KeyOpFieldsValuesTuple containing device metadata notifications. + */ +void DHCPMgr::process_device_metadata_notification(std::deque &entries) { + // If there is no DHCPv4 Relay config, then no need to send event for metadata update. + if (vlans_copy.empty()) { + return; + } + + for (auto &entry : entries) { + std::string key = kfvKey(entry); + std::vector field_values = kfvFieldsValues(entry); + + if (key != "localhost") { + continue; + } + + relay_config *device_data = nullptr; + try { + device_data = new relay_config(); + } catch (const std::bad_alloc &e) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Memory allocation failed: %s", e.what()); + return; + } + + for (auto &field : field_values) { + std::string f = fvField(field); + std::string v = fvValue(field); + + if (f == "hostname") { + device_data->hostname = v; + } else if (f == "mac") { + std::array host_mac_addr; + string_to_mac_addr(v, host_mac_addr); + std::copy(host_mac_addr.begin(), host_mac_addr.end(), device_data->host_mac_addr); + } + } + + // Sending sonic as default hostname if it is not present in metadata + if (device_data->hostname.length() == 0) { + device_data->hostname = "sonic"; + } + + event_config metadata_event; + metadata_event.type = DHCPv4_RELAY_METADATA_UPDATE; + metadata_event.msg = static_cast(device_data); + // Write event to config pipe + if (write(config_pipe[1], &metadata_event, sizeof(metadata_event)) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to write metadata update event to pipe: %s", strerror(errno)); + delete device_data; + } + } +} + +/** + * @brief Processes interface notifications and updates DHCP relay configuration accordingly. + * + * This method iterates over a deque of interface notification entries, each containing + * a key, operation, and associated values. For each entry, it parses the interface name + * and IP address, checks if the interface is configured as a DHCP relay source interface, + * and prepares a relay configuration update event. Depending on the operation ("SET" or "DEL"), + * it sets up the relay configuration to add or remove the interface. The configuration update + * event is then written to a pipe for further processing. + * + * Memory allocation failures and invalid IP addresses are logged as errors. + * + * @param entries A deque of KeyOpFieldsValuesTuple objects representing interface notifications. + */ +void DHCPMgr::process_interface_notification(std::deque &entries) { + for (auto &entry : entries) { + std::string key = kfvKey(entry); + std::string operation = kfvOp(entry); + + size_t found = key.find("|"); + + std::string intf_name; + std::string ip_with_mask; + std::string ip; + if (found != std::string::npos) { + intf_name = key.substr(0, found); + ip_with_mask = key.substr(found + 1); + ip = ip_with_mask.substr(0, ip_with_mask.find('/')); + } else { + continue; + } + + // Check the source interface is configured in dhcp relay config. + for (auto &vlan : vlans_copy) { + if (vlan.second.source_interface == intf_name) { + relay_config *relay_msg = nullptr; + try { + relay_msg = new relay_config(); + } catch (const std::bad_alloc &e) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Memory allocation failed: %s", e.what()); + return; + } + + relay_msg->vlan = vlan.second.vlan; + if (operation == "SET") { + relay_msg->is_add = true; + if (inet_pton(AF_INET, ip.c_str(), &relay_msg->src_intf_sel_addr.sin_addr) != 1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Invalid IP address"); + delete relay_msg; + return; + } + + relay_msg->src_intf_sel_addr.sin_family = AF_INET; + } else if (operation == "DEL") { + relay_msg->is_add = false; + } + + event_config event; + event.type = DHCPv4_RELAY_INTERFACE_UPDATE; + event.msg = static_cast(relay_msg); + // Write the pointer address to the pipe + if (write(config_pipe[1], &event, sizeof(event)) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to write to config update pipe: %s", strerror(errno)); + delete relay_msg; + } + } + } + } +} + +/** + * @brief Processes a batch of relay configuration notifications for DHCPv4 relay. + * + * This method iterates over a deque of relay configuration entries, parses each entry, + * and updates the internal VLAN relay configuration cache accordingly. For "SET" operations, + * it creates or updates the relay configuration for the specified VLAN, parsing relevant fields + * such as DHCPv4 servers, VRF, source interface, and various relay options. For "DEL" operations, + * it removes the relay configuration for the specified VLAN from the cache. + * + * After processing each entry, it constructs an event containing the updated relay configuration + * and writes it to a configuration update pipe for further handling. The method also logs + * relevant information and errors using syslog. + * + * @param entries A deque of KeyOpFieldsValuesTuple objects representing relay configuration notifications. + */ +void DHCPMgr::process_relay_notification(std::deque &entries) { + for (auto &entry : entries) { + std::string vlan = kfvKey(entry); + std::string operation = kfvOp(entry); + std::vector field_values = kfvFieldsValues(entry); + relay_config *relay_msg = nullptr; + try { + relay_msg = new relay_config(); + } catch (const std::bad_alloc &e) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Memory allocation failed: %s", e.what()); + return; + } + + relay_msg->vlan = vlan; + + if (operation == "SET") { + relay_msg->is_add = true; + for (auto &field_value : field_values) { + std::string f = fvField(field_value); + std::string v = fvValue(field_value); + if (f == "dhcpv4_servers") { + std::stringstream ss(v); + while (ss.good()) { + std::string substr; + getline(ss, substr, ','); + relay_msg->servers.push_back(substr); + } + } else if (f == "server_vrf") { + relay_msg->vrf = v; + } else if (f == "source_interface") { + relay_msg->source_interface = v; + } else if (f == "link_selection") { + relay_msg->link_selection_opt = v; + } else if (f == "server_id_override") { + relay_msg->server_id_override_opt = v; + } else if (f == "vrf_selection") { + relay_msg->vrf_selection_opt = v; + } else if (f == "agent_relay_mode") { + relay_msg->agent_relay_mode = v; + } + syslog(LOG_DEBUG, "[DHCPV4_RELAY] key: %s, Operation: %s, f: %s, v: %s", vlan.c_str(), operation.c_str(), f.c_str(), v.c_str()); + } + + // Updating the vrf value with default if vrf is not configured. + if (relay_msg->vrf.length() == 0) { + relay_msg->vrf = "default"; + } + + // Update the vlan cache entry + vlans_copy[relay_msg->vlan] = *relay_msg; + } else if (operation == "DEL") { + syslog(LOG_INFO, "[DHCPV4_RELAY] Received DELETE operation for VLAN %s", vlan.c_str()); + relay_msg->is_add = false; + // Remove the vlan cache entry + vlans_copy.erase(relay_msg->vlan); + } + + if (relay_msg->servers.empty() && operation != "DEL") { + syslog(LOG_WARNING, "[DHCPV4_RELAY] No servers found for VLAN %s, skipping configuration.", vlan.c_str()); + continue; + } + syslog(LOG_INFO, "[DHCPV4_RELAY] %s %s relay config\n", operation.c_str(), vlan.c_str()); + + event_config event; + event.type = DHCPv4_RELAY_CONFIG_UPDATE; + event.msg = static_cast(relay_msg); + + // Write the pointer address to the pipe + if (write(config_pipe[1], &event, sizeof(event)) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to write to config update pipe: %s", strerror(errno)); + delete relay_msg; + } + } +} + +DHCPMgr::~DHCPMgr() { + stop_thread = true; +} diff --git a/dhcp4relay/src/dhcp4relay_mgr.h b/dhcp4relay/src/dhcp4relay_mgr.h new file mode 100644 index 0000000..e60934d --- /dev/null +++ b/dhcp4relay/src/dhcp4relay_mgr.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "dbconnector.h" +#include "dhcp4relay.h" +#include "select.h" +#include "subscriberstatetable.h" +#include "table.h" + +class DHCPMgr { + private: + std::atomic stop_thread; + + public: + DHCPMgr() : stop_thread(false) {} + ~DHCPMgr(); + + void initialize_config_listner(); + void handle_swss_notification(); + void process_relay_notification(std::deque &entries); + void process_interface_notification(std::deque &entries); + void process_device_metadata_notification(std::deque &entries); +}; diff --git a/dhcp4relay/src/dhcp4relay_stats.cpp b/dhcp4relay/src/dhcp4relay_stats.cpp new file mode 100644 index 0000000..a776fdd --- /dev/null +++ b/dhcp4relay/src/dhcp4relay_stats.cpp @@ -0,0 +1,240 @@ +#include "dhcp4relay_stats.h" + +#include "dbconnector.h" +#include "dhcp4relay.h" +#include "table.h" + +/* DHCPv4 counter name map */ +std::map counter_map = { + {DHCPv4_MESSAGE_TYPE_UNKNOWN, "Unknown"}, + {DHCPv4_MESSAGE_TYPE_DISCOVER, "Discover"}, + {DHCPv4_MESSAGE_TYPE_OFFER, "Offer"}, + {DHCPv4_MESSAGE_TYPE_REQUEST, "Request"}, + {DHCPv4_MESSAGE_TYPE_DECLINE, "Decline"}, + {DHCPv4_MESSAGE_TYPE_ACK, "Acknowledge"}, + {DHCPv4_MESSAGE_TYPE_NAK, "NegativeAcknowledge"}, + {DHCPv4_MESSAGE_TYPE_RELEASE, "Release"}, + {DHCPv4_MESSAGE_TYPE_INFORM, "Inform"}, + {DHCPv4_MESSAGE_TYPE_MALFORMED, "Malformed"}, + {DHCPv4_MESSAGE_TYPE_DROP, "Dropped"}}; + +/** + * @code calculate_delta(uint64_t new_value, uint64_t old_value); + * + * @brief Helper function to calculate safe delta with overflow handling. + * +.* @param new_value new uint64_t value +.* @param old_value old uint64_t value + * + * @return delta value + */ +uint64_t calculate_delta(uint64_t new_value, uint64_t old_value) { + if (new_value >= old_value) { + return new_value - old_value; + } else { + // Handle overflow case + return (std::numeric_limits::max() - old_value) + new_value + 1; + } +} + +/** + * @code get_counters_data() + * + * @brief GET function to fetch data of private member interfaces_cntr_table + * + * @return std::unordered_map + */ +std::unordered_map DHCPCounter_table::get_counters_data() { + return interfaces_cntr_table; +} + +/** + * @code DHCPCounter_table::db_update_loop(); + * + * @brief Loop to update dhcp stats to the DB periodically. + * This loop is triggered by a new thread which is responsible to update stats to DB. + * + * Mutex lock is taken to copy the data locally, before parsing and setting to DB. + * + * @return none + */ +void DHCPCounter_table::db_update_loop() { + std::shared_ptr state_db = std::make_shared("STATE_DB", 0); + std::shared_ptr cntr_table = std::make_shared( + state_db.get(), "DHCPV4_COUNTER_TABLE"); + + while (!stop_thread) { + std::this_thread::sleep_for(std::chrono::seconds(DHCP_RELAY_DB_UPDATE_TIMER_VAL)); + + // Copy the data by taking lock for processing and populating to DB + std::unordered_map interfaces_copy; + { + std::lock_guard lock(interfaces_mutex); + interfaces_copy = interfaces_cntr_table; + } + + /* These steps are followed before updating to Redis: + 1. Fetch present values from redis - existing _fields + 2. Update counters with 'cache values' + 'existing_feilds' + 3. Populate to DB + */ + for (const auto& [interface, counters] : interfaces_copy) { + // RX counters + std::vector existing_rx_fields; + std::string rx_key = interface + "|RX"; + cntr_table->get(rx_key, existing_rx_fields); + + std::vector rx_fields; + std::unordered_map rx_counter_map; + + // Populate existing counters + for (const auto& field : existing_rx_fields) { + rx_counter_map[fvField(field)] = std::stoi(fvValue(field)); + } + + // Update with new values + for (const auto& [type, value] : counters.RX) { + rx_counter_map[type] += value; + rx_fields.emplace_back(type, std::to_string(rx_counter_map[type])); + } + + cntr_table->set(rx_key, rx_fields); + + // TX counters (similar logic) + std::vector existing_tx_fields; + std::string tx_key = interface + "|TX"; + cntr_table->get(tx_key, existing_tx_fields); + + std::vector tx_fields; + std::unordered_map tx_counter_map; + + // Populate existing counters + for (const auto& field : existing_tx_fields) { + tx_counter_map[fvField(field)] = std::stoi(fvValue(field)); + } + + // Update with new values + for (const auto& [type, value] : counters.TX) { + tx_counter_map[type] += value; + tx_fields.emplace_back(type, std::to_string(tx_counter_map[type])); + } + + cntr_table->set(tx_key, tx_fields); + } + + // Update local changes after syncing to Redis + // We will take the delta values of running data in interfaces_cntr_table + // and previosuly copied interfaces_copy values. + { + // Taking lock inside a block so that is released automatically + std::lock_guard lock(interfaces_mutex); + for (auto& [interface, counters] : interfaces_cntr_table) { + for (auto& [type, value] : counters.RX) { + value = calculate_delta(value, interfaces_copy[interface].RX[type]); + } + for (auto& [type, value] : counters.TX) { + value = calculate_delta(value, interfaces_copy[interface].TX[type]); + } + } + } + syslog(LOG_INFO, "DHCPV4_RELAY: DHCPCounter_table::db_update_loop() : Data Updated to DB \n"); + } +} + +/** + * @code DHCPCounter_table::start_db_updates(); + * + * @brief Method to start thread to update stats to DB periodically. + * + * @return none + */ +void DHCPCounter_table::start_db_updates() { + db_update_thread = std::thread(&DHCPCounter_table::db_update_loop, this); +} + +/** + * @code DHCPCounter_table::stop_db_updates(); + * + * @brief Method to stop thread which updates stats to DB periodically. + * + * @return none + */ +void DHCPCounter_table::stop_db_updates() { + stop_thread = true; + if (db_update_thread.joinable()) { + db_update_thread.join(); + } +} + +/** + * @code DHCPCounter_table::initialize_interface(std::string& interface); + * + * @brief Method to initialize counters in DB for a particular interface + * +.* @param interface Name of the interface for which RX and TX counters need to be initialized + * + * @return none + */ +void DHCPCounter_table::initialize_interface(const std::string& interface) { + std::lock_guard lock(interfaces_mutex); + DHCPCounters counter; + for (const auto& type : counter_map) { + counter.RX[type.second] = 0; + counter.TX[type.second] = 0; + } + interfaces_cntr_table[interface] = counter; +} + +/** + * @code DHCPCounter_table::increment_counter(const std::string& interface, + * const std::string& direction, + * int msg_type); + * + * @brief Method to increment counters in DB for a particular interface, + * direction(RX|TX) and dhcp_message_type_t + * +.* @param interface Name of the interface for which counters need to be incremented + * + * @return none + */ +void DHCPCounter_table::increment_counter(const std::string& interface, + const std::string& direction, + int msg_type) { + std::string type = counter_map.find(msg_type)->second; + // Initialize counters if not present + if (interfaces_cntr_table.find(interface) == interfaces_cntr_table.end()) + DHCPCounter_table::initialize_interface(interface); + + std::lock_guard lock(interfaces_mutex); + + if (direction == "RX") { + interfaces_cntr_table[interface].RX[type]++; + } else if (direction == "TX") { + interfaces_cntr_table[interface].TX[type]++; + } +} + +/** + * @code DHCPCounter_table::remove_interface(const std::string& interface); + * + * @brief Method to remove counters from global datastructure. + * +.* @param interface Name of the interface for which counters need to be removed. + * + * @return none + */ +void DHCPCounter_table::remove_interface(const std::string& interface) { + std::lock_guard lock(interfaces_mutex); + interfaces_cntr_table.erase(interface); +} + +/** + * @code DHCPCounter_table:~DHCPCounter_table() + * + * @brief Destructor. + * + * @return none + */ +DHCPCounter_table::~DHCPCounter_table() { + stop_db_updates(); +} diff --git a/dhcp4relay/src/dhcp4relay_stats.h b/dhcp4relay/src/dhcp4relay_stats.h new file mode 100644 index 0000000..805baa4 --- /dev/null +++ b/dhcp4relay/src/dhcp4relay_stats.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DHCP_RELAY_DB_UPDATE_TIMER_VAL 30 + +extern std::map counter_map; + +struct DHCPCounters { + std::unordered_map RX; + std::unordered_map TX; +}; + +class DHCPCounter_table { +private: + std::unordered_map interfaces_cntr_table; + std::mutex interfaces_mutex; + std::atomic stop_thread{false}; + std::thread db_update_thread; + + void db_update_loop(); + +public: + void start_db_updates(); + void stop_db_updates(); + void initialize_interface(const std::string& interface); + void increment_counter(const std::string& interface, const std::string& direction, + int msg_type); + void remove_interface(const std::string& interface); + std::unordered_map get_counters_data(); + + ~DHCPCounter_table(); +}; + +uint64_t calculate_delta(uint64_t new_value, uint64_t old_value); diff --git a/dhcp4relay/src/main.cpp b/dhcp4relay/src/main.cpp new file mode 100644 index 0000000..8a4f67f --- /dev/null +++ b/dhcp4relay/src/main.cpp @@ -0,0 +1,20 @@ +#include +#include + +#include + +#include "dhcp4relay.h" + +bool dual_tor_sock = false; +char loopback[IF_NAMESIZE] = "Loopback0"; + +int main(int argc, char *argv[]) { + try { + std::unordered_map vlans; + loop_relay(vlans); + } catch (std::exception &e) { + syslog(LOG_ERR, "An exception occurred.\n"); + return 1; + } + return 0; +} diff --git a/dhcp4relay/src/subdir.mk b/dhcp4relay/src/subdir.mk new file mode 100644 index 0000000..f6d21fa --- /dev/null +++ b/dhcp4relay/src/subdir.mk @@ -0,0 +1,6 @@ +SRCS += \ +src/dhcp4_sender.cpp \ +src/dhcp4relay.cpp \ +src/dhcp4relay_stats.cpp \ +src/dhcp4relay_mgr.cpp \ +src/main.cpp diff --git a/dhcp4relay/test/database_config.json b/dhcp4relay/test/database_config.json new file mode 100644 index 0000000..b40e808 --- /dev/null +++ b/dhcp4relay/test/database_config.json @@ -0,0 +1,27 @@ +{ + "INSTANCES": { + "redis":{ + "hostname" : "127.0.0.1", + "port" : 6379, + "unix_socket_path" : "/var/run/redis/redis.sock" + }, + "redis_chassis":{ + "hostname" : "redis_chassis.server", + "port" : 6380, + "unix_socket_path" : "/var/run/redis/redis_chassis.sock" + } + }, + "DATABASES" : { + "CONFIG_DB" : { + "id" : 4, + "separator": "|", + "instance" : "redis" + }, + "STATE_DB" : { + "id" : 6, + "separator": "|", + "instance" : "redis" + } + }, + "VERSION" : "1.0" +} diff --git a/dhcp4relay/test/main.cpp b/dhcp4relay/test/main.cpp new file mode 100644 index 0000000..cc17eba --- /dev/null +++ b/dhcp4relay/test/main.cpp @@ -0,0 +1,9 @@ +#include "gtest/gtest.h" +#include + +int main(int argc, char* argv[]) +{ + + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/dhcp4relay/test/mock_consumerstatetable.cpp b/dhcp4relay/test/mock_consumerstatetable.cpp new file mode 100644 index 0000000..2764bb1 --- /dev/null +++ b/dhcp4relay/test/mock_consumerstatetable.cpp @@ -0,0 +1,40 @@ +#include "consumerstatetable.h" + +namespace swss +{ + ConsumerStateTable::ConsumerStateTable(DBConnector *db, const std::string &tableName, int popBatchSize, int pri) : + ConsumerTableBase(db, tableName, popBatchSize, pri), + TableName_KeySet(tableName) + { + } + + void ConsumerStateTable::pops(std::deque &vkco, const std::string& /*prefix*/) + { + int count = 0; + swss::Table table(getDbConnector(), getTableName()); + std::vector keys; + table.getKeys(keys); + for (const auto &key: keys) + { + // pop with batch size + if (count < POP_BATCH_SIZE) + { + count++; + } + else + { + break; + } + + KeyOpFieldsValuesTuple kco; + kfvKey(kco) = key; + kfvOp(kco) = SET_COMMAND; + if (!table.get(key, kfvFieldsValues(kco))) + { + continue; + } + table.del(key); + vkco.push_back(kco); + } + } +} diff --git a/dhcp4relay/test/mock_dbconnector.cpp b/dhcp4relay/test/mock_dbconnector.cpp new file mode 100644 index 0000000..8c3aa0d --- /dev/null +++ b/dhcp4relay/test/mock_dbconnector.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "dbconnector.h" + +namespace swss +{ + DBConnector::DBConnector(int dbId, const std::string &hostname, int port, unsigned int timeout) : + m_dbId(dbId) + { + auto conn = (redisContext *)calloc(1, sizeof(redisContext)); + conn->connection_type = REDIS_CONN_TCP; + conn->tcp.host = strdup(hostname.c_str()); + conn->tcp.port = port; + conn->fd = socket(AF_UNIX, SOCK_DGRAM, 0); + setContext(conn); + } + + DBConnector::DBConnector(int dbId, const std::string &unixPath, unsigned int timeout) : + m_dbId(dbId) + { + auto conn = (redisContext *)calloc(1, sizeof(redisContext)); + conn->connection_type = REDIS_CONN_UNIX; + conn->unix_sock.path = strdup(unixPath.c_str()); + conn->fd = socket(AF_UNIX, SOCK_DGRAM, 0); + setContext(conn); + } + + DBConnector::DBConnector(const std::string& dbName, unsigned int timeout, bool isTcpConn) + : m_dbName(dbName) + { + if (swss::SonicDBConfig::isInit() == false) + swss::SonicDBConfig::initialize("./test/database_config.json"); + m_dbId = swss::SonicDBConfig::getDbId(dbName); + if (isTcpConn) + { + auto conn = (redisContext *)calloc(1, sizeof(redisContext)); + conn->connection_type = REDIS_CONN_TCP; + conn->tcp.host = strdup(swss::SonicDBConfig::getDbHostname(dbName).c_str()); + conn->tcp.port = swss::SonicDBConfig::getDbPort(dbName); + conn->fd = socket(AF_UNIX, SOCK_DGRAM, 0); + setContext(conn); + } + else + { + auto conn = (redisContext *)calloc(1, sizeof(redisContext)); + conn->connection_type = REDIS_CONN_UNIX; + conn->unix_sock.path = strdup(swss::SonicDBConfig::getDbSock(dbName).c_str()); + conn->fd = socket(AF_UNIX, SOCK_DGRAM, 0); + setContext(conn); + } + } + + int DBConnector::getDbId() const + { + return m_dbId; + } +} diff --git a/dhcp4relay/test/mock_hiredis.cpp b/dhcp4relay/test/mock_hiredis.cpp new file mode 100644 index 0000000..8c1c905 --- /dev/null +++ b/dhcp4relay/test/mock_hiredis.cpp @@ -0,0 +1,40 @@ +#include +#include +#include + +// Add a global redisReply for user to mock +redisReply *mockReply = nullptr; + +int redisGetReply(redisContext *c, void **reply) +{ + if (mockReply == nullptr) + { + *reply = calloc(sizeof(redisReply), 1); + ((redisReply *)*reply)->type = 3; + } + else + { + *reply = mockReply; + } + return 0; +} + +int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) +{ + return 0; +} + +int redisvAppendCommand(redisContext *c, const char *format, va_list ap) +{ + return 0; +} + +int redisAppendCommand(redisContext *c, const char *format, ...) +{ + return 0; +} + +int redisGetReplyFromReader(redisContext *c, void **reply) +{ + return 0; +} diff --git a/dhcp4relay/test/mock_redisreply.cpp b/dhcp4relay/test/mock_redisreply.cpp new file mode 100644 index 0000000..391b853 --- /dev/null +++ b/dhcp4relay/test/mock_redisreply.cpp @@ -0,0 +1,16 @@ +#include "redisreply.h" + +namespace swss +{ + void RedisReply::checkStatus(const char *status) + { + } + + void RedisReply::checkReply() + { + } + + void RedisReply::checkReplyType(int expectedType) + { + } +} \ No newline at end of file diff --git a/dhcp4relay/test/mock_relay.cpp b/dhcp4relay/test/mock_relay.cpp new file mode 100644 index 0000000..77c35c2 --- /dev/null +++ b/dhcp4relay/test/mock_relay.cpp @@ -0,0 +1,438 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "mock_relay.h" +#include + +using namespace ::testing; +using namespace swss; + +MOCK_GLOBAL_FUNC1(getifaddrs, int(struct ifaddrs **)); +MOCK_GLOBAL_FUNC1(freeifaddrs, void(struct ifaddrs *)); +MOCK_GLOBAL_FUNC3(write, ssize_t(int, const void*, size_t)); + +ssize_t RealWrite(int fd, const void *buf, size_t count) { + return syscall(SYS_write, fd, buf, count); +} + +struct ifaddrs *CreateMockIfaddrs(const std::string &vlan_ip, const std::string &vlan_mask, const std::string &vlan_name, + const std::string &src_ip, const std::string &src_name) { + struct ifaddrs *mock_ifaddrs = new ifaddrs; + memset(mock_ifaddrs, 0, sizeof(ifaddrs)); + + struct sockaddr_in *addr = new sockaddr_in; + struct sockaddr_in *mask = new sockaddr_in; + addr->sin_family = AF_INET; + addr->sin_addr.s_addr = inet_addr(vlan_ip.c_str()); + mask->sin_family = AF_INET; + mask->sin_addr.s_addr = inet_addr(vlan_mask.c_str()); + + mock_ifaddrs->ifa_addr = reinterpret_cast(addr); + mock_ifaddrs->ifa_netmask = reinterpret_cast(mask); + mock_ifaddrs->ifa_name = strdup(vlan_name.c_str()); + + struct ifaddrs *mock_ifaddrs_src = new ifaddrs; + memset(mock_ifaddrs_src, 0, sizeof(ifaddrs)); + + struct sockaddr_in *src_addr = new sockaddr_in; + src_addr->sin_family = AF_INET; + src_addr->sin_addr.s_addr = inet_addr(src_ip.c_str()); + + mock_ifaddrs_src->ifa_addr = reinterpret_cast(src_addr); + mock_ifaddrs_src->ifa_name = strdup(src_name.c_str()); + + mock_ifaddrs->ifa_next = mock_ifaddrs_src; + return mock_ifaddrs; +} + +void FreeMockIfaddrs(struct ifaddrs *mock_ifaddrs) { + if (mock_ifaddrs) { + delete reinterpret_cast(mock_ifaddrs->ifa_addr); + delete reinterpret_cast(mock_ifaddrs->ifa_netmask); + if (mock_ifaddrs->ifa_next) { + delete reinterpret_cast(mock_ifaddrs->ifa_next->ifa_addr); + free(mock_ifaddrs->ifa_next->ifa_name); + delete mock_ifaddrs->ifa_next; + } + free(mock_ifaddrs->ifa_name); + delete mock_ifaddrs; + } +} + +TEST(helper, to_string) +{ + EXPECT_EQ("0", to_string(0)); +} + +TEST(helper, string_to_mac_addr) { + std::array mac_addr; + ASSERT_TRUE(string_to_mac_addr("00:11:22:33:44:55", mac_addr)); + std::array expected = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + EXPECT_EQ(mac_addr, expected); +} + +TEST(EncodeDecodeTLV, EncodeAndDecode) { + uint8_t buffer[10] = {}; + uint8_t value[3] = {0x11, 0x22, 0x33}; + uint8_t length = 0; + + uint8_t encoded_length = encode_tlv(buffer, 1, 3, value); + EXPECT_EQ(encoded_length, 5); + EXPECT_EQ(buffer[0], 1); + EXPECT_EQ(buffer[1], 3); + EXPECT_EQ(buffer[2], 0x11); + EXPECT_EQ(buffer[3], 0x22); + EXPECT_EQ(buffer[4], 0x33); + + uint8_t *decoded_value = decode_tlv(buffer, 1, length, 5); + ASSERT_NE(decoded_value, nullptr); + EXPECT_EQ(length, 3); + EXPECT_EQ(decoded_value[0], 0x11); + EXPECT_EQ(decoded_value[1], 0x22); + EXPECT_EQ(decoded_value[2], 0x33); +} + +TEST(sock, sock_open) +{ + struct sock_filter ether_relay_filter[] = { + { 0x6, 0, 0, 0x00040000 }, + }; + const struct sock_fprog ether_relay_fprog = { + lengthof(ether_relay_filter), + ether_relay_filter + }; + EXPECT_GE(sock_open(ðer_relay_fprog), 0); +} + +TEST(sock, sock_open_invalid_filter) +{ + const struct sock_fprog ether_relay_fprog = {0,{}}; + EXPECT_EQ(sock_open(ðer_relay_fprog), -1); +} + +TEST(prepareConfig, prepare_relay_server_config) { + struct relay_config config{}; + config.servers.push_back("192.168.1.1"); + config.servers.push_back("10.0.0.1"); + + prepare_relay_server_config(config); + + ASSERT_EQ(config.servers_sock.size(), 2); + + EXPECT_EQ(config.servers_sock[0].sin_family, AF_INET); + EXPECT_EQ(config.servers_sock[0].sin_port, htons(67)); + EXPECT_EQ(config.servers_sock[0].sin_addr.s_addr, inet_addr("192.168.1.1")); + + EXPECT_EQ(config.servers_sock[1].sin_family, AF_INET); + EXPECT_EQ(config.servers_sock[1].sin_port, htons(67)); + EXPECT_EQ(config.servers_sock[1].sin_addr.s_addr, inet_addr("10.0.0.1")); +} + +TEST(prepareConfig, prepare_relay_interface_config) { + struct ifaddrs *mock_ifaddrs = CreateMockIfaddrs("192.168.1.1", "255.255.255.0", "Vlan100", "192.168.1.2", "Ethernet4"); + struct relay_config interface_config{}; + interface_config.vlan = "Vlan100"; + interface_config.source_interface = "Ethernet4"; + + EXPECT_GLOBAL_CALL(getifaddrs, getifaddrs(_)).WillOnce(DoAll(testing::SetArgPointee<0>(mock_ifaddrs), Return(0))); + EXPECT_GLOBAL_CALL(freeifaddrs, freeifaddrs(_)).Times(1); + + prepare_relay_interface_config(interface_config); + + EXPECT_EQ(interface_config.link_address.sin_addr.s_addr, inet_addr("192.168.1.1")); + EXPECT_EQ(interface_config.link_address_netmask.sin_addr.s_addr, inet_addr("255.255.255.0")); + EXPECT_EQ(interface_config.src_intf_sel_addr.sin_addr.s_addr, inet_addr("192.168.1.2")); // Now should pass! + + FreeMockIfaddrs(mock_ifaddrs); +} + +TEST(prepareConfig, prepare_vlan_sockets) +{ + struct relay_config config{}; + config.link_address.sin_addr.s_addr = htonl(0x01010101); + struct iphdr ip_hdr; + std::string s_addr = "1.1.1.1"; + inet_pton(AF_INET, s_addr.c_str(), &ip_hdr.saddr); + + config.servers.push_back("3.3.3.3"); + config.servers.push_back("4.4.4.4"); + + config.vlan = "Vlan200"; + EXPECT_EQ(prepare_vlan_sockets(config), 0); + EXPECT_GE(config.client_sock, 0); + EXPECT_GE(config.lo_sock, 0); +} + +TEST(prepareConfig, prepare_vrf_sockets) { + struct relay_config config{}; + config.vrf = "default"; + + EXPECT_EQ(prepare_vrf_sockets(config), 0); + EXPECT_GE(config.vrf_sock, 0); + EXPECT_GE(vrf_sock_map["default"].sock, 0); + EXPECT_EQ(vrf_sock_map["default"].ref_count, 1); + vrf_sock_map.clear(); +} + +TEST(prepareConfig, update_vlan_mapping) +{ + std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); + swss::Table vlan_member_table(config_db.get(), "VLAN_MEMBER"); + swss::Table vlan_interface_table(config_db.get(), "VLAN_INTERFACE"); + + std::string key = "Vlan200|Ethernet8"; + std::vector> values = { + {"tagging_mode", "untagged"}, + }; + + std::string vlan_key = "Vlan200"; + std::vector> vlan_values = { + {"vrf_name", "VrfRed"}, + }; + + vlan_member_table.set(key, values); + vlan_interface_table.set(vlan_key, vlan_values); + + // add case + update_vlan_mapping(vlan_key, true); + + EXPECT_EQ(vlan_map["Ethernet8"], vlan_key); + EXPECT_EQ(vlan_vrf_map[vlan_key], "VrfRed"); + + //delete case + update_vlan_mapping(vlan_key, false); + + EXPECT_EQ(vlan_map.find("Ethernet8"), vlan_map.end()); + EXPECT_EQ(vlan_vrf_map.find(vlan_key), vlan_vrf_map.end()); +} + +TEST(relayConfig, handle_vlan_events) { + event_config event; + EXPECT_GLOBAL_CALL(write, write(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(RealWrite)); + std::unordered_map vlans; + std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); + std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); + + swss::Table vlan_member_table(config_db.get(), "VLAN_MEMBER"); + swss::Table vlan_interface_table(config_db.get(), "VLAN_INTERFACE"); + + std::string key = "Vlan200|Ethernet8"; + std::vector> values = { + {"tagging_mode", "untagged"}, + }; + + std::string vlan_key = "Vlan200"; + std::vector> vlan_values = { + {"vrf_name", "VrfRed"}, + }; + + vlan_member_table.set(key, values); + vlan_interface_table.set(vlan_key, vlan_values); + + struct ifaddrs *mock_ifaddrs = CreateMockIfaddrs("1.1.1.1", "255.255.255.0", "Vlan200", "2.2.2.2", "Ethernet8"); + + EXPECT_GLOBAL_CALL(getifaddrs, getifaddrs(_)).WillOnce(DoAll(testing::SetArgPointee<0>(mock_ifaddrs), Return(0))); + EXPECT_GLOBAL_CALL(freeifaddrs, freeifaddrs(_)).Times(1); + relay_config *config = new relay_config(); + config->vlan = "Vlan200"; + config->is_add = true; + config->config_db = config_db; + config->state_db = state_db; + config->source_interface = "Ethernet8"; + config->servers = {"192.168.1.1","10.0.0.1"}; + config->vrf = "default"; + event.type = DHCPv4_RELAY_CONFIG_UPDATE; + event.msg = static_cast(config); + + int pipe_fds[2]; + ASSERT_NE(pipe(pipe_fds), -1); + + ASSERT_NE(write(pipe_fds[1], &event, sizeof(event)), -1); + + config_event_callback(pipe_fds[0], 0, &vlans); + + ASSERT_TRUE(vlans.find("Vlan200") != vlans.end()); + + ASSERT_EQ(vlans["Vlan200"].servers_sock.size(), 2); + + EXPECT_EQ(vlans["Vlan200"].servers_sock[0].sin_family, AF_INET); + EXPECT_EQ(vlans["Vlan200"].servers_sock[0].sin_port, htons(67)); + EXPECT_EQ(vlans["Vlan200"].servers_sock[0].sin_addr.s_addr, inet_addr("192.168.1.1")); + + EXPECT_EQ(vlans["Vlan200"].servers_sock[1].sin_family, AF_INET); + EXPECT_EQ(vlans["Vlan200"].servers_sock[1].sin_port, htons(67)); + EXPECT_EQ(vlans["Vlan200"].servers_sock[1].sin_addr.s_addr, inet_addr("10.0.0.1")); + + EXPECT_EQ(vlans["Vlan200"].link_address.sin_addr.s_addr, inet_addr("1.1.1.1")); + EXPECT_EQ(vlans["Vlan200"].link_address_netmask.sin_addr.s_addr, inet_addr("255.255.255.0")); + EXPECT_EQ(vlans["Vlan200"].src_intf_sel_addr.sin_addr.s_addr, inet_addr("2.2.2.2")); + + EXPECT_GE(vlans["Vlan200"].vrf_sock, 0); + EXPECT_GE(vrf_sock_map["default"].sock, 0); + EXPECT_EQ(vrf_sock_map["default"].ref_count, 1); + + EXPECT_GE(vlans["Vlan200"].client_sock, 0); + EXPECT_GE(vlans["Vlan200"].lo_sock, 0); + + /*Vlan deletion.*/ + relay_config *config_del = new relay_config(); + config_del->vlan = "Vlan200"; + config_del->is_add = false; + + vlans["Vlan200"].client_sock = -1; + vlans["Vlan200"].lo_sock = -1; + vlans["Vlan200"].vrf_sock = -1; + event.msg = static_cast(config_del); + + ASSERT_NE(write(pipe_fds[1], &event, sizeof(event)), -1); + + config_event_callback(pipe_fds[0], 0, &vlans); + ASSERT_TRUE(vlans.find("Vlan200") == vlans.end()); + close(pipe_fds[0]); + close(pipe_fds[1]); + FreeMockIfaddrs(mock_ifaddrs); +} + +TEST(relayConfig, handle_interface_events) { + int pipe_fds[2]; + EXPECT_GLOBAL_CALL(write, write(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(RealWrite)); + ASSERT_NE(pipe(pipe_fds), -1); + relay_config *config = new relay_config(); + std::unordered_map vlans; + config->vlan = "Vlan100"; + config->is_add = true; + config->src_intf_sel_addr.sin_family = AF_INET; + config->src_intf_sel_addr.sin_addr.s_addr = inet_addr("192.168.1.1"); + + event_config event; + event.type = DHCPv4_RELAY_INTERFACE_UPDATE; + event.msg = static_cast(config); + + ASSERT_NE(write(pipe_fds[1], &event, sizeof(event)), -1); + + config_event_callback(pipe_fds[0], 0, &vlans); + + ASSERT_TRUE(vlans.find("Vlan100") != vlans.end()); + EXPECT_EQ(vlans["Vlan100"].src_intf_sel_addr.sin_family, AF_INET); + EXPECT_EQ(vlans["Vlan100"].src_intf_sel_addr.sin_addr.s_addr, inet_addr("192.168.1.1")); + close(pipe_fds[0]); + close(pipe_fds[1]); +} + +TEST(relayConfig, handle_metadata_events) { + int pipe_fds[2]; + EXPECT_GLOBAL_CALL(write, write(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(RealWrite)); + ASSERT_NE(pipe(pipe_fds), -1); + std::unordered_map vlans; + relay_config vlan1, vlan2; + + vlan1.vlan = "Vlan100"; + vlan2.vlan = "Vlan200"; + + vlans["Vlan100"] = vlan1; + vlans["Vlan200"] = vlan2; + + relay_config *metadata = new relay_config(); + + metadata->hostname = "newHost"; + uint8_t test_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + std::copy(std::begin(test_mac), std::end(test_mac), std::begin(metadata->host_mac_addr)); + + event_config event; + event.type = DHCPv4_RELAY_METADATA_UPDATE; + event.msg = static_cast(metadata); + + ASSERT_NE(write(pipe_fds[1], &event, sizeof(event)), -1); + + config_event_callback(pipe_fds[0], 0, &vlans); + + for (const auto& vlan : vlans) { + ASSERT_EQ(vlan.second.hostname, "newHost"); + + ASSERT_TRUE(std::equal(std::begin(vlan.second.host_mac_addr), std::end(vlan.second.host_mac_addr), + std::begin(test_mac))); + } + close(pipe_fds[0]); + close(pipe_fds[1]); +} + +TEST(relay, signal_init) { + signal_init(); + EXPECT_NE((uintptr_t)ev_sigint, NULL); + EXPECT_NE((uintptr_t)ev_sigterm, NULL); +} + +MOCK_GLOBAL_FUNC1(event_base_dispatch, int(struct event_base *)); +MOCK_GLOBAL_FUNC2(event_add, int(struct event *, const struct timeval *)); + +TEST(relay, signal_start) { + EXPECT_GLOBAL_CALL(event_add, event_add(_, NULL)).Times(5) + .WillOnce(Return(-1)) + .WillOnce(Return(0)).WillOnce(Return(-1)) + .WillOnce(Return(0)).WillOnce(Return(0)); + EXPECT_EQ(signal_start(), -1); + EXPECT_EQ(signal_start(), -1); + EXPECT_GLOBAL_CALL(event_base_dispatch, event_base_dispatch(_)).Times(1).WillOnce(Return(-1)); + EXPECT_EQ(signal_start(), 0); +} + +TEST(DHCPMgrTest, initialize_config_listner) { + DHCPMgr dhcpMgr; + EXPECT_GLOBAL_CALL(write, write(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(-1)); + dhcpMgr.initialize_config_listner(); + std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); + + swss::Table dhcp_table(config_db.get(), "DHCPV4_RELAY"); + swss::Table intf_table(config_db.get(), "INTERFACE"); + swss::Table loopback_intf_table(config_db.get(), "LOOPBACK_INTERFACE"); + swss::Table portchannel_intf_table(config_db.get(), "PORTCHANNEL_INTERFACE"); + swss::Table metadata_table(config_db.get(), "DEVICE_METADATA"); + + std::string dhcp_key = "Vlan200"; + std::vector> dhcp_values = { + {"dhcpv4_servers", "1.1.1.1,1.1.1.2"}, + {"server_vrf", "VrfRed"}, + {"source_interface", "Ethernet4"}, + {"agent_relay_mode", "discard"}, + {"link_selection", "enable"}, + {"server_id_override", "enable"}, + {"vrf_selection", "enable"} + }; + + dhcp_table.set(dhcp_key, dhcp_values); + + std::string intf_key = "Ethernet4|192.168.1.1/24"; + std::vector> intf_values = { + {"NULL", "NULL"}, + }; + + intf_table.set(intf_key, intf_values); + loopback_intf_table.set(intf_key, intf_values); + portchannel_intf_table.set(intf_key, intf_values); + + std::string metadata_key = "localhost"; + std::vector> metadata_values = { + {"hostname", "newHost"}, + {"mac", "00:11:22:33:44:55"} + }; + + metadata_table.set("host", metadata_values); + metadata_table.set(metadata_key, metadata_values); + + std::this_thread::sleep_for(std::chrono::seconds(2)); +} diff --git a/dhcp4relay/test/mock_relay.h b/dhcp4relay/test/mock_relay.h new file mode 100644 index 0000000..4aa7465 --- /dev/null +++ b/dhcp4relay/test/mock_relay.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../src/dhcp4relay.h" +#include "../src/dhcp4relay_mgr.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "../../dhcp6relay/gmock_global/include/gmock-global/gmock-global.h" +#include +#include + +extern struct event_base *base; +extern struct event *ev_sigint; +extern struct event *ev_sigterm; +extern std::unordered_map vlan_map; +extern std::unordered_map vlan_vrf_map; +extern swss::Select swssSelect; +extern std::unordered_map vrf_sock_map; diff --git a/dhcp4relay/test/mock_relay_stats.cpp b/dhcp4relay/test/mock_relay_stats.cpp new file mode 100644 index 0000000..4274198 --- /dev/null +++ b/dhcp4relay/test/mock_relay_stats.cpp @@ -0,0 +1,230 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "mock_relay.h" +#include "../src/dhcp4relay_stats.h" + +using namespace swss; + +// Test fixture +class DHCPCounter_table_test : public ::testing::Test { +protected: + std::unique_ptr counter_table; + + void SetUp() override { + counter_table = std::make_unique(); + } + + void TearDown() override { + counter_table.reset(); + } +}; + +// Test the delta calculation function +TEST(Calculate_delta_test, Calculates_delta_correctly) { + // Normal case + EXPECT_EQ(calculate_delta(10, 5), 5); + + // Zero delta + EXPECT_EQ(calculate_delta(5, 5), 0); + + // Handle overflow case + uint64_t max_val = std::numeric_limits::max(); + uint64_t old_val = max_val - 5; + uint64_t new_val = 10; + + EXPECT_EQ(calculate_delta(new_val, old_val), 16); +} + +// Test interface initialization +TEST_F(DHCPCounter_table_test, Initialize_interface) { + const std::string interface = "Ethernet0"; + + // Initialize the interface + counter_table->initialize_interface(interface); + + // Increment counters + counter_table->increment_counter(interface, "RX", DHCPv4_MESSAGE_TYPE_DISCOVER); + counter_table->increment_counter(interface, "TX", DHCPv4_MESSAGE_TYPE_OFFER); + + // Verify Incremented counters from table + std::unordered_map interfaces_cntr_table = counter_table->get_counters_data(); + EXPECT_EQ(interfaces_cntr_table[interface].RX[counter_map.find(DHCPv4_MESSAGE_TYPE_DISCOVER)->second], 1); + EXPECT_EQ(interfaces_cntr_table[interface].TX[counter_map.find(DHCPv4_MESSAGE_TYPE_OFFER)->second], 1); + + // If initialization failed, the above would likely crash + SUCCEED(); +} + +// Test counter incrementation +TEST_F(DHCPCounter_table_test, Increment_counter) { + const std::string interface = "Ethernet0"; + + // Initialize and increment counters + counter_table->initialize_interface(interface); + + // Increment RX counter multiple times + for (int i = 0; i < 5; i++) { + counter_table->increment_counter(interface, "RX", DHCPv4_MESSAGE_TYPE_DISCOVER); + } + + // Increment TX counter once + counter_table->increment_counter(interface, "TX", DHCPv4_MESSAGE_TYPE_ACK); + + // Verify Incremented counters from table + std::unordered_map interfaces_cntr_table = counter_table->get_counters_data(); + EXPECT_EQ(interfaces_cntr_table[interface].RX[counter_map.find(DHCPv4_MESSAGE_TYPE_DISCOVER)->second], 5); + EXPECT_EQ(interfaces_cntr_table[interface].TX[counter_map.find(DHCPv4_MESSAGE_TYPE_ACK)->second], 1); + + // Negative case - other counters should NOT have incremented + EXPECT_EQ(interfaces_cntr_table[interface].RX[counter_map.find(DHCPv4_MESSAGE_TYPE_REQUEST)->second], 0); + EXPECT_EQ(interfaces_cntr_table[interface].RX[counter_map.find(DHCPv4_MESSAGE_TYPE_OFFER)->second], 0); + EXPECT_EQ(interfaces_cntr_table[interface].RX[counter_map.find(DHCPv4_MESSAGE_TYPE_ACK)->second], 0); + EXPECT_EQ(interfaces_cntr_table[interface].TX[counter_map.find(DHCPv4_MESSAGE_TYPE_DECLINE)->second], 0); + EXPECT_EQ(interfaces_cntr_table[interface].TX[counter_map.find(DHCPv4_MESSAGE_TYPE_INFORM)->second], 0); + + SUCCEED(); +} + +// Test with uninitialized interface +TEST_F(DHCPCounter_table_test, Auto_initialize_interface) { + const std::string interface = "Ethernet1"; + + // Try to increment without explicitly initializing + counter_table->increment_counter(interface, "RX", DHCPv4_MESSAGE_TYPE_DISCOVER); + + // If auto-initialization works, this should succeed + counter_table->increment_counter(interface, "TX", DHCPv4_MESSAGE_TYPE_ACK); + + // Verify Incremented counters from table + std::unordered_map interfaces_cntr_table = counter_table->get_counters_data(); + EXPECT_EQ(interfaces_cntr_table[interface].RX[counter_map.find(DHCPv4_MESSAGE_TYPE_DISCOVER)->second], 1); + EXPECT_EQ(interfaces_cntr_table[interface].TX[counter_map.find(DHCPv4_MESSAGE_TYPE_ACK)->second], 1); + + SUCCEED(); +} + +// Test interface removal +TEST_F(DHCPCounter_table_test, Remove_interface) { + const std::string interface = "Ethernet0"; + + // Initialize and increment counters + counter_table->initialize_interface(interface); + counter_table->increment_counter(interface, "RX", DHCPv4_MESSAGE_TYPE_DISCOVER); + + // Verify Incremented counters from table + std::unordered_map interfaces_cntr_table = counter_table->get_counters_data(); + EXPECT_EQ(interfaces_cntr_table[interface].RX[counter_map.find(DHCPv4_MESSAGE_TYPE_DISCOVER)->second], 1); + + // Remove the interface + counter_table->remove_interface(interface); + + // Now add it again - if removal worked, this should reinitialize from scratch + // and should not crash + counter_table->initialize_interface(interface); + + // Increment the same counter and verify + counter_table->increment_counter(interface, "RX", DHCPv4_MESSAGE_TYPE_DISCOVER); + // verify counter + interfaces_cntr_table = counter_table->get_counters_data(); + EXPECT_EQ(interfaces_cntr_table[interface].RX[counter_map.find(DHCPv4_MESSAGE_TYPE_DISCOVER)->second], 1); + + SUCCEED(); +} + +// Test starting and stopping the DB update thread +TEST_F(DHCPCounter_table_test, Start_stop_db_updates) { + // Start the DB update thread + counter_table->start_db_updates(); + + // Sleep briefly to allow thread to execute + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Stop the thread + counter_table->stop_db_updates(); + + // If the start/stop mechanisms work correctly, this will complete without hanging + SUCCEED(); +} + +// Integration test for database update loop +TEST_F(DHCPCounter_table_test, DBUpdate_loop_integration) { + const std::string interface = "Ethernet0"; + + // Initialize and increment counters + counter_table->initialize_interface(interface); + + // Add some counter increments + counter_table->increment_counter(interface, "RX", DHCPv4_MESSAGE_TYPE_DISCOVER); + counter_table->increment_counter(interface, "RX", DHCPv4_MESSAGE_TYPE_REQUEST); + counter_table->increment_counter(interface, "TX", DHCPv4_MESSAGE_TYPE_OFFER); + counter_table->increment_counter(interface, "TX", DHCPv4_MESSAGE_TYPE_ACK); + + // Start DB updates + counter_table->start_db_updates(); + + // Let the update thread run briefly (less than the normal interval for test speed) + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + // Stop the updates + counter_table->stop_db_updates(); + + std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); + swss::Table cntr_table(state_db.get(), "DHCPV4_COUNTER_TABLE"); + std::vector existing_rx_fields; + std::vector existing_tx_fields; + cntr_table.get(interface+"|RX", existing_rx_fields); + cntr_table.get(interface+"|TX", existing_tx_fields); + + //Verify Incremented Rx fields + for (const auto& field : existing_rx_fields) { + if ( (fvField(field)) == "Discover" || (fvField(field)) == "Request") { + EXPECT_EQ(std::stoi(fvValue(field)), 1); + } + } + //Verify Incremented Tx fields + for (const auto& field : existing_tx_fields) { + if ( (fvField(field)) == "Offer" || (fvField(field)) == "Acknowledge") { + EXPECT_EQ(std::stoi(fvValue(field)), 1); + } + } + + SUCCEED(); +} + +// Test for handling multiple interfaces +TEST_F(DHCPCounter_table_test, Multiple_interfaces) { + const std::vector interfaces = {"Ethernet0", "Ethernet1", "Ethernet2"}; + + // Initialize multiple interfaces + for (const auto& intf : interfaces) { + counter_table->initialize_interface(intf); + } + + // Increment counters for different interfaces + counter_table->increment_counter(interfaces[0], "RX", DHCPv4_MESSAGE_TYPE_DISCOVER); + counter_table->increment_counter(interfaces[1], "RX", DHCPv4_MESSAGE_TYPE_REQUEST); + counter_table->increment_counter(interfaces[2], "TX", DHCPv4_MESSAGE_TYPE_ACK); + + // Verify Incremented counters from table + std::unordered_map interfaces_cntr_table = counter_table->get_counters_data(); + EXPECT_EQ(interfaces_cntr_table[interfaces[0]].RX[counter_map.find(DHCPv4_MESSAGE_TYPE_DISCOVER)->second], 1); + EXPECT_EQ(interfaces_cntr_table[interfaces[1]].RX[counter_map.find(DHCPv4_MESSAGE_TYPE_REQUEST)->second], 1); + EXPECT_EQ(interfaces_cntr_table[interfaces[2]].TX[counter_map.find(DHCPv4_MESSAGE_TYPE_ACK)->second], 1); + + // Remove one interface + counter_table->remove_interface(interfaces[1]); + + // Try incrementing the removed interface (should auto-initialize) + counter_table->increment_counter(interfaces[1], "TX", DHCPv4_MESSAGE_TYPE_OFFER); + // Verify incremented counter + interfaces_cntr_table = counter_table->get_counters_data(); + EXPECT_EQ(interfaces_cntr_table[interfaces[1]].TX[counter_map.find(DHCPv4_MESSAGE_TYPE_OFFER)->second], 1); + + SUCCEED(); +} diff --git a/dhcp4relay/test/mock_table.cpp b/dhcp4relay/test/mock_table.cpp new file mode 100644 index 0000000..7dc77d4 --- /dev/null +++ b/dhcp4relay/test/mock_table.cpp @@ -0,0 +1,183 @@ +#include "table.h" +#include "producerstatetable.h" +#include "producertable.h" +#include +#include + +using TableDataT = std::map>; +using TablesT = std::map; + +namespace testing_db +{ + + TableDataT gTableData; + TablesT gTables; + std::map gDB; + + void reset() + { + gDB.clear(); + } +} + +namespace swss +{ + + using namespace testing_db; + + void merge_values(std::vector &existing_values, const std::vector &values) + { + std::vector new_values(values); + std::set field_set; + for (auto &value : values) + { + field_set.insert(fvField(value)); + } + for (auto &value : existing_values) + { + auto &field = fvField(value); + if (field_set.find(field) != field_set.end()) + { + continue; + } + new_values.push_back(value); + } + existing_values.swap(new_values); + } + + bool _hget(int dbId, const std::string &tableName, const std::string &key, const std::string &field, std::string &value) + { + auto table = gDB[dbId][tableName]; + if (table.find(key) == table.end()) + { + return false; + } + + for (const auto &it : table[key]) + { + if (it.first == field) + { + value = it.second; + return true; + } + } + + return false; + } + + bool Table::get(const std::string &key, std::vector &ovalues) + { + auto table = gDB[m_pipe->getDbId()][getTableName()]; + if (table.find(key) == table.end()) + { + return false; + } + + ovalues = table[key]; + return true; + } + + bool Table::hget(const std::string &key, const std::string &field, std::string &value) + { + return _hget(m_pipe->getDbId(), getTableName(), key, field, value); + } + + void Table::set(const std::string &key, + const std::vector &values, + const std::string &op, + const std::string &prefix) + { + auto &table = gDB[m_pipe->getDbId()][getTableName()]; + auto iter = table.find(key); + if (iter == table.end()) + { + table[key] = values; + } + else + { + merge_values(iter->second, values); + } + } + + void Table::getKeys(std::vector &keys) + { + keys.clear(); + auto table = gDB[m_pipe->getDbId()][getTableName()]; + for (const auto &it : table) + { + keys.push_back(it.first); + } + } + + void Table::del(const std::string &key, const std::string& /* op */, const std::string& /*prefix*/) + { + auto table = gDB[m_pipe->getDbId()].find(getTableName()); + if (table != gDB[m_pipe->getDbId()].end()){ + table->second.erase(key); + } + } + + void ProducerStateTable::set(const std::string &key, + const std::vector &values, + const std::string &op, + const std::string &prefix) + { + auto &table = gDB[m_pipe->getDbId()][getTableName()]; + auto iter = table.find(key); + if (iter == table.end()) + { + table[key] = values; + } + else + { + merge_values(iter->second, values); + } + } + + void ProducerStateTable::del(const std::string &key, + const std::string &op, + const std::string &prefix) + { + auto &table = gDB[m_pipe->getDbId()][getTableName()]; + table.erase(key); + } + + std::shared_ptr DBConnector::hget(const std::string &key, const std::string &field) + { + std::string value; + if (_hget(getDbId(), key, "", field, value)) + { + std::shared_ptr ptr(new std::string(value)); + return ptr; + } + else + { + return std::shared_ptr(NULL); + } + } + + void ProducerTable::set(const std::string &key, + const std::vector &values, + const std::string &op, + const std::string &prefix) + { + auto &table = gDB[m_pipe->getDbId()][getTableName()]; + auto iter = table.find(key); + if (iter == table.end()) + { + table[key] = values; + } + else + { + merge_values(iter->second, values); + } + } + + void ProducerTable::del(const std::string &key, + const std::string &op, + const std::string &prefix) + { + auto &table = gDB[m_pipe->getDbId()][getTableName()]; + table.erase(key); + } +} diff --git a/dhcp4relay/test/mock_table.h b/dhcp4relay/test/mock_table.h new file mode 100644 index 0000000..88aed22 --- /dev/null +++ b/dhcp4relay/test/mock_table.h @@ -0,0 +1,8 @@ +#pragma once + +#include "table.h" + +namespace testing_db +{ + void reset(); +} diff --git a/dhcp4relay/test/subdir.mk b/dhcp4relay/test/subdir.mk new file mode 100644 index 0000000..51ba58a --- /dev/null +++ b/dhcp4relay/test/subdir.mk @@ -0,0 +1,13 @@ +TEST_SRCS += \ +test/main.cpp \ +test/mock_relay.cpp \ +src/dhcp4relay_mgr.cpp \ +src/dhcp4relay_stats.cpp \ +src/dhcp4relay.cpp \ +src/dhcp4_sender.cpp \ +test/mock_dbconnector.cpp \ +test/mock_table.cpp \ +test/mock_consumerstatetable.cpp \ +test/mock_hiredis.cpp \ +test/mock_redisreply.cpp \ +test/mock_relay_stats.cpp diff --git a/Makefile b/dhcp6relay/Makefile similarity index 100% rename from Makefile rename to dhcp6relay/Makefile diff --git a/dhcp6relay/debian/changelog b/dhcp6relay/debian/changelog new file mode 100644 index 0000000..f8e448d --- /dev/null +++ b/dhcp6relay/debian/changelog @@ -0,0 +1,5 @@ +sonic-dhcp6relay (1.0.0-0) UNRELEASED; urgency=medium + + * Initial release. + + -- Kelly Yeh Fri, 20 Aug 2021 09:11:40 -0700 diff --git a/dhcp6relay/debian/compat b/dhcp6relay/debian/compat new file mode 100644 index 0000000..48082f7 --- /dev/null +++ b/dhcp6relay/debian/compat @@ -0,0 +1 @@ +12 diff --git a/debian/control b/dhcp6relay/debian/control similarity index 100% rename from debian/control rename to dhcp6relay/debian/control diff --git a/dhcp6relay/debian/rules b/dhcp6relay/debian/rules new file mode 100755 index 0000000..ac2cd63 --- /dev/null +++ b/dhcp6relay/debian/rules @@ -0,0 +1,6 @@ +#!/usr/bin/make -f + +export DEB_BUILD_MAINT_OPTIONS=hardening=+all + +%: + dh $@ --parallel diff --git a/dhcp6relay/gmock_global b/dhcp6relay/gmock_global new file mode 160000 index 0000000..80af085 --- /dev/null +++ b/dhcp6relay/gmock_global @@ -0,0 +1 @@ +Subproject commit 80af0854e0700b5b562c7f4b0877b4876530a553 diff --git a/src/config_interface.cpp b/dhcp6relay/src/config_interface.cpp similarity index 100% rename from src/config_interface.cpp rename to dhcp6relay/src/config_interface.cpp diff --git a/src/config_interface.h b/dhcp6relay/src/config_interface.h similarity index 100% rename from src/config_interface.h rename to dhcp6relay/src/config_interface.h diff --git a/src/main.cpp b/dhcp6relay/src/main.cpp similarity index 100% rename from src/main.cpp rename to dhcp6relay/src/main.cpp diff --git a/src/relay.cpp b/dhcp6relay/src/relay.cpp similarity index 100% rename from src/relay.cpp rename to dhcp6relay/src/relay.cpp diff --git a/src/relay.h b/dhcp6relay/src/relay.h similarity index 100% rename from src/relay.h rename to dhcp6relay/src/relay.h diff --git a/src/sender.cpp b/dhcp6relay/src/sender.cpp similarity index 100% rename from src/sender.cpp rename to dhcp6relay/src/sender.cpp diff --git a/src/sender.h b/dhcp6relay/src/sender.h similarity index 100% rename from src/sender.h rename to dhcp6relay/src/sender.h diff --git a/src/subdir.mk b/dhcp6relay/src/subdir.mk similarity index 100% rename from src/subdir.mk rename to dhcp6relay/src/subdir.mk diff --git a/test/database_config.json b/dhcp6relay/test/database_config.json similarity index 100% rename from test/database_config.json rename to dhcp6relay/test/database_config.json diff --git a/test/main.cpp b/dhcp6relay/test/main.cpp similarity index 100% rename from test/main.cpp rename to dhcp6relay/test/main.cpp diff --git a/test/mock_config_interface.cpp b/dhcp6relay/test/mock_config_interface.cpp similarity index 100% rename from test/mock_config_interface.cpp rename to dhcp6relay/test/mock_config_interface.cpp diff --git a/test/mock_config_interface.h b/dhcp6relay/test/mock_config_interface.h similarity index 100% rename from test/mock_config_interface.h rename to dhcp6relay/test/mock_config_interface.h diff --git a/test/mock_relay.cpp b/dhcp6relay/test/mock_relay.cpp similarity index 100% rename from test/mock_relay.cpp rename to dhcp6relay/test/mock_relay.cpp diff --git a/test/mock_relay.h b/dhcp6relay/test/mock_relay.h similarity index 100% rename from test/mock_relay.h rename to dhcp6relay/test/mock_relay.h diff --git a/test/mock_send.cpp b/dhcp6relay/test/mock_send.cpp similarity index 100% rename from test/mock_send.cpp rename to dhcp6relay/test/mock_send.cpp diff --git a/test/mock_send.h b/dhcp6relay/test/mock_send.h similarity index 100% rename from test/mock_send.h rename to dhcp6relay/test/mock_send.h diff --git a/test/subdir.mk b/dhcp6relay/test/subdir.mk similarity index 100% rename from test/subdir.mk rename to dhcp6relay/test/subdir.mk diff --git a/gmock_global b/gmock_global deleted file mode 160000 index 6552faa..0000000 --- a/gmock_global +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6552faa2a94250ed0192dbd62e608d37fafcc216 From dffb7e7140bcac3d176d29e97f030a4c10f6c617 Mon Sep 17 00:00:00 2001 From: Akshath TR Date: Wed, 21 May 2025 21:27:56 -0700 Subject: [PATCH 02/17] Addressing MSFT comments on HLD --- dhcp4relay/Makefile | 2 +- dhcp4relay/src/dhcp4_sender.cpp | 17 +- dhcp4relay/src/dhcp4_sender.h | 7 +- dhcp4relay/src/dhcp4relay.cpp | 113 +++++-------- dhcp4relay/src/dhcp4relay.h | 6 +- dhcp4relay/src/dhcp4relay_mgr.cpp | 39 +---- dhcp4relay/src/dhcp4relay_stats.cpp | 4 +- dhcp4relay/test/mock_relay.cpp | 246 ++++++++++++++++++++++------ 8 files changed, 274 insertions(+), 160 deletions(-) diff --git a/dhcp4relay/Makefile b/dhcp4relay/Makefile index e290bce..2cc03de 100644 --- a/dhcp4relay/Makefile +++ b/dhcp4relay/Makefile @@ -62,7 +62,7 @@ $(DHCP4RELAY_TARGET): $(PCAPPP_DONE) $(OBJS) $(BUILD_TEST_DIR)/%.o: %.cpp @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(CPPFLAGS_TEST) -c -o $@ $< + $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(CPPFLAGS_TEST) $(LDLIBS) -c -o $@ $< $(DHCP4RELAY_TEST_TARGET): $(TEST_OBJS) $(CXX) $(LDFLAGS) $^ $(LDLIBS) $(LDLIBS_TEST) -o $@ diff --git a/dhcp4relay/src/dhcp4_sender.cpp b/dhcp4relay/src/dhcp4_sender.cpp index 228ef3e..e566f14 100644 --- a/dhcp4relay/src/dhcp4_sender.cpp +++ b/dhcp4relay/src/dhcp4_sender.cpp @@ -7,20 +7,28 @@ #include /** - * @code bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t n); + * @code bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t len); * * @brief send udp packet and return true if successful * * @param *buffer message buffer * @param sockaddr_in target target socket - * @param n length of message + * @param len length of message * @param relay_config *config pointer to relay_config * @param uint8_t msg_type message type of dhcpv6 option of relayed message * * @return boolean True if packet successfully sent */ -bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t n) { - if (sendto(sock, buffer, n, 0, (const struct sockaddr *)&target, sizeof(target)) == -1) { +#ifndef UNIT_TEST +bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t len) { + /* Pad additional bytes if length is lesser than 300 + * to make DHCP packet length to minimum of 300 bytes */ + if (len < BOOTP_MIN_LEN) { + auto pad_len = BOOTP_MIN_LEN - len; + memset(buffer+len, 0, pad_len); + len = BOOTP_MIN_LEN; + } + if (sendto(sock, buffer, len, 0, (const struct sockaddr *)&target, sizeof(target)) == -1) { char server_addr[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &(target.sin_addr), server_addr, INET_ADDRSTRLEN); syslog(LOG_ERR, "sendto: Failed to send to target address: %s, error: %s\n", server_addr, strerror(errno)); @@ -28,3 +36,4 @@ bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t n) } return true; } +#endif diff --git a/dhcp4relay/src/dhcp4_sender.h b/dhcp4relay/src/dhcp4_sender.h index fbe51c6..0738f47 100644 --- a/dhcp4relay/src/dhcp4_sender.h +++ b/dhcp4relay/src/dhcp4_sender.h @@ -5,17 +5,18 @@ #include +#define BOOTP_MIN_LEN 300 /** - * @code bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t n); + * @code bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t len); * * @brief send udp packet and return true if successful * * @param *buffer message buffer * @param sockaddr_in target target socket - * @param n length of message + * @param len length of message * @param relay_config *config pointer to relay_config * @param uint8_t msg_type message type of dhcpv6 option of relayed message * * @return boolean True if packet successfully sent */ -bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t n); +bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t len); diff --git a/dhcp4relay/src/dhcp4relay.cpp b/dhcp4relay/src/dhcp4relay.cpp index 953b43a..afd61a1 100644 --- a/dhcp4relay/src/dhcp4relay.cpp +++ b/dhcp4relay/src/dhcp4relay.cpp @@ -26,6 +26,9 @@ struct event *ev_sigterm; static std::string vlan_member = "VLAN_MEMBER|"; static std::string counter_table = "DHCPv4_COUNTER_TABLE|"; +extern std::string host_mac_addr; +extern std::string hostname; + static uint8_t client_recv_buffer[BUFFER_SIZE]; int config_pipe[2]; @@ -265,6 +268,13 @@ int prepare_vrf_sockets(relay_config &config) { } } + /* Bind socket with UDP port to DHCP src number */ + struct sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(RELAY_PORT); + bind(vrf_sock, (struct sockaddr*)&addr, sizeof(addr)); + /* Update the map */ vrf_sock_map[config.vrf] = {vrf_sock, 1}; } @@ -403,7 +413,7 @@ void encode_relay_option(pcpp::DhcpLayer *dhcp_pkt, relay_config *config) { /* Encode circuit ID sub-option */ /* | 1 | 4 | hostname:interface_alias:vlan | */ - std::string circuit_id = config->hostname + ":" + intf_alias + ":" + config->vlan; + std::string circuit_id = hostname + ":" + intf_alias + ":" + config->vlan; auto offset = encode_tlv(buf, OPTION82_SUBOPT_CIRCUIT_ID, circuit_id.length(), (uint8_t *)circuit_id.c_str()); buf_offset += offset; @@ -411,7 +421,7 @@ void encode_relay_option(pcpp::DhcpLayer *dhcp_pkt, relay_config *config) { /* Encode remote ID sub-option */ /* | 2 | 6 | my_mac| */ offset = encode_tlv((buf + buf_offset), OPTION82_SUBOPT_REMOTE_ID, - MAC_ADDR_LEN, (uint8_t *)(config->host_mac_addr)); + MAC_ADDR_STR_LEN, (uint8_t *)(host_mac_addr.c_str())); buf_offset += offset; /* TODO: this sub-option should be set if source interface selection is enabled */ @@ -480,7 +490,11 @@ void from_client(pcpp::DhcpLayer *dhcp_pkt, relay_config &config) { dhcp_pkt->getDhcpHeader()->gatewayIpAddress = config.link_address.sin_addr.s_addr; } - encode_relay_option(dhcp_pkt, &config); + if ((dhcp_pkt->getDhcpHeader()->magicNumber) && + (dhcp_pkt->getDhcpHeader()->magicNumber) == DHCP_MAGIC_NUMBER) { + syslog(LOG_WARNING, "[DHCPV4_RELAY] encode DHCP relay option"); + encode_relay_option(dhcp_pkt, &config); + } } else { /* If the relay packet is from another relay, we should act based on configuration of agent_relay_mode. @@ -578,38 +592,36 @@ void to_client(pcpp::DhcpLayer *dhcp_pkt, std::unordered_mapgetOptionData(pcpp::DHCPOPT_DHCP_AGENT_OPTIONS); auto options_ptr = agent_option.getValue(); auto agent_option_size = agent_option.getDataSize(); - if (options_ptr == NULL) { - syslog(LOG_ERR, "[DHCPV4_RELAY] Relay options for reply packets are missing from server %s", - src_ip.c_str()); - return; - } - uint8_t circuit_id_len = 0; - auto circuit_id_ptr = decode_tlv((const uint8_t *)options_ptr, OPTION82_SUBOPT_CIRCUIT_ID, - circuit_id_len, agent_option_size); - if (circuit_id_ptr == NULL) { - syslog(LOG_ERR, - "[DHCPV4_RELAY] Circuit id sub-option is missing in relay" - " agent option from server %s", - src_ip.c_str()); - return; - } + /* If option 82 is available fetch Vlan information from circuit ID */ + if (options_ptr != NULL) { + uint8_t circuit_id_len = 0; + auto circuit_id_ptr = decode_tlv((const uint8_t *)options_ptr, OPTION82_SUBOPT_CIRCUIT_ID, + circuit_id_len, agent_option_size); + if (circuit_id_ptr == NULL) { + syslog(LOG_ERR, + "[DHCPV4_RELAY] Circuit id sub-option is missing in relay" + " agent option from server %s", + src_ip.c_str()); + return; + } - std::string circuit_id((const char *)circuit_id_ptr, circuit_id_len); + std::string circuit_id((const char *)circuit_id_ptr, circuit_id_len); - std::string vlan_interface; - auto vlan_intf_pos = circuit_id.rfind(':'); - if (vlan_intf_pos != std::string::npos) { - vlan_interface = circuit_id.substr(vlan_intf_pos + 1); - } + std::string vlan_interface; + auto vlan_intf_pos = circuit_id.rfind(':'); + if (vlan_intf_pos != std::string::npos) { + vlan_interface = circuit_id.substr(vlan_intf_pos + 1); + } - if (vlan_interface.length() > 0) { - config_itr = vlans->find(vlan_interface); - if (config_itr == vlans->end()) { - syslog(LOG_INFO, - "[DHCPV4_RELAY] Vlan config not found for the circuit" - "id encoded interface %s\n", - vlan_interface.c_str()); + if (vlan_interface.length() > 0) { + config_itr = vlans->find(vlan_interface); + if (config_itr == vlans->end()) { + syslog(LOG_INFO, + "[DHCPV4_RELAY] Vlan config not found for the circuit" + "id encoded interface %s\n", + vlan_interface.c_str()); + } } } @@ -639,7 +651,7 @@ void to_client(pcpp::DhcpLayer *dhcp_pkt, std::unordered_mapfind(intf_name); + config_itr = vlans->find(intf_name); if (config_itr == vlans->end()) { syslog(LOG_ERR, "[DHCPV4_RELAY] Config not found for vlan %s\n", intf_name.c_str()); return; @@ -666,31 +678,6 @@ void to_client(pcpp::DhcpLayer *dhcp_pkt, std::unordered_map &mac_addr) { - if (mac_str.size() != 17) { - return false; - } - - std::istringstream iss(mac_str); - int byte; - char separator; - - for (size_t i = 0; i < mac_addr.size(); ++i) { - if (!(iss >> std::hex >> byte)) { - return false; - } - mac_addr[i] = static_cast(byte); - - if (i < mac_addr.size() - 1) { - if (!(iss >> separator) || (separator != ':' && separator != '-')) { - return false; - } - } - } - - return true; -} - /** * @code update_vlan_mapping(std::string vlan, bool is_add); * @@ -1036,18 +1023,6 @@ void config_event_callback(evutil_socket_t fd, short event, void *arg) { } delete relay_msg; } - } else if (received_event.type == DHCPv4_RELAY_METADATA_UPDATE) { - relay_config *device_data = static_cast(received_event.msg); - if (device_data) { - syslog(LOG_INFO, "[DHCPV4_RELAY] Processing Device Metadata Event Update."); - - for (auto &vlan : *vlans) { - vlan.second.hostname = device_data->hostname; - std::copy(std::begin(device_data->host_mac_addr), std::end(device_data->host_mac_addr), - std::begin(vlan.second.host_mac_addr)); - } - delete device_data; - } } } else { syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to read config update: expected %lu bytes, got %zd bytes", sizeof(received_event), bytes_read); diff --git a/dhcp4relay/src/dhcp4relay.h b/dhcp4relay/src/dhcp4relay.h index d5f8b81..1ca3dbd 100644 --- a/dhcp4relay/src/dhcp4relay.h +++ b/dhcp4relay/src/dhcp4relay.h @@ -29,7 +29,8 @@ #define CLIENT_IF_PREFIX "Ethernet" #define BUFFER_SIZE 9200 // TODO: change to dynamic size based on MTU #define MAX_DHCP_PKT_SIZE 1472 // 1500 - (IP + UDP)headers -#define MAC_ADDR_LEN 6 +#define MAC_ADDR_STR_LEN 17 +#define DHCP_MAGIC_NUMBER 0x63538263 #define BOOTPREQUEST 1 #define BOOTPREPLY 2 @@ -102,7 +103,6 @@ struct relay_config { sockaddr_in link_address_netmask; sockaddr_in src_intf_sel_addr; uint32_t link_ifindex; - uint8_t host_mac_addr[MAC_ADDR_LEN]; std::shared_ptr state_db; std::string vlan; std::string phy_interface; @@ -112,7 +112,6 @@ struct relay_config { std::string server_id_override_opt; std::string vrf_selection_opt; std::string agent_relay_mode; - std::string hostname; std::vector servers; std::vector servers_sock; bool is_interface_id; @@ -272,7 +271,6 @@ void update_vlan_mapping(std::string vlan, bool is_add); * @return none */ void pkt_in_callback(evutil_socket_t fd, short event, void *arg); -bool string_to_mac_addr(const std::string &mac_str, std::array &mac_addr); void config_event_callback(evutil_socket_t fd, short event, void *arg); uint8_t *decode_tlv(const uint8_t *buf, uint8_t t, uint8_t &l, uint32_t options_total_size); uint8_t encode_tlv(uint8_t *buf, uint8_t t, uint8_t l, uint8_t *v); diff --git a/dhcp4relay/src/dhcp4relay_mgr.cpp b/dhcp4relay/src/dhcp4relay_mgr.cpp index e647201..bc012ea 100644 --- a/dhcp4relay/src/dhcp4relay_mgr.cpp +++ b/dhcp4relay/src/dhcp4relay_mgr.cpp @@ -1,4 +1,3 @@ - #include "dhcp4relay_mgr.h" #include @@ -11,6 +10,9 @@ static std::unordered_map vlans_copy; using namespace swss; #endif +std::string host_mac_addr; +std::string hostname = "sonic"; + /** * @brief Initializes the configuration listener for the DHCP manager. * @@ -105,11 +107,6 @@ void DHCPMgr::handle_swss_notification() { * @param entries A deque of KeyOpFieldsValuesTuple containing device metadata notifications. */ void DHCPMgr::process_device_metadata_notification(std::deque &entries) { - // If there is no DHCPv4 Relay config, then no need to send event for metadata update. - if (vlans_copy.empty()) { - return; - } - for (auto &entry : entries) { std::string key = kfvKey(entry); std::vector field_values = kfvFieldsValues(entry); @@ -118,39 +115,19 @@ void DHCPMgr::process_device_metadata_notification(std::dequehostname = v; + hostname = v; } else if (f == "mac") { - std::array host_mac_addr; - string_to_mac_addr(v, host_mac_addr); - std::copy(host_mac_addr.begin(), host_mac_addr.end(), device_data->host_mac_addr); + host_mac_addr = v; } } - - // Sending sonic as default hostname if it is not present in metadata - if (device_data->hostname.length() == 0) { - device_data->hostname = "sonic"; - } - - event_config metadata_event; - metadata_event.type = DHCPv4_RELAY_METADATA_UPDATE; - metadata_event.msg = static_cast(device_data); - // Write event to config pipe - if (write(config_pipe[1], &metadata_event, sizeof(metadata_event)) == -1) { - syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to write metadata update event to pipe: %s", strerror(errno)); - delete device_data; + /* Re-set hostname to default value if hostname is deleted */ + if (hostname.length() == 0) { + hostname = "sonic"; } } } diff --git a/dhcp4relay/src/dhcp4relay_stats.cpp b/dhcp4relay/src/dhcp4relay_stats.cpp index a776fdd..778bcaa 100644 --- a/dhcp4relay/src/dhcp4relay_stats.cpp +++ b/dhcp4relay/src/dhcp4relay_stats.cpp @@ -59,9 +59,9 @@ std::unordered_map DHCPCounter_table::get_counters_da * @return none */ void DHCPCounter_table::db_update_loop() { - std::shared_ptr state_db = std::make_shared("STATE_DB", 0); + std::shared_ptr cntrs_db = std::make_shared("COUNTERS_DB", 0); std::shared_ptr cntr_table = std::make_shared( - state_db.get(), "DHCPV4_COUNTER_TABLE"); + cntrs_db.get(), "COUNTERS_DHCPV4"); while (!stop_thread) { std::this_thread::sleep_for(std::chrono::seconds(DHCP_RELAY_DB_UPDATE_TIMER_VAL)); diff --git a/dhcp4relay/test/mock_relay.cpp b/dhcp4relay/test/mock_relay.cpp index 77c35c2..04f775f 100644 --- a/dhcp4relay/test/mock_relay.cpp +++ b/dhcp4relay/test/mock_relay.cpp @@ -11,12 +11,28 @@ #include "mock_relay.h" #include +#include +#include +#include +#include +#include +#include + using namespace ::testing; using namespace swss; +extern std::string host_mac_addr; +extern std::string hostname; + MOCK_GLOBAL_FUNC1(getifaddrs, int(struct ifaddrs **)); MOCK_GLOBAL_FUNC1(freeifaddrs, void(struct ifaddrs *)); MOCK_GLOBAL_FUNC3(write, ssize_t(int, const void*, size_t)); +MOCK_GLOBAL_FUNC4(send_udp, bool(int, uint8_t *, struct sockaddr_in, uint32_t)); + +void encode_relay_option(pcpp::DhcpLayer *dhcp_pkt, relay_config *config); +void to_client(pcpp::DhcpLayer* dhcp_pkt, std::unordered_map *vlans, + std::string src_ip); +void from_client(pcpp::DhcpLayer *dhcp_pkt, relay_config &config); ssize_t RealWrite(int fd, const void *buf, size_t count) { return syscall(SYS_write, fd, buf, count); @@ -71,13 +87,6 @@ TEST(helper, to_string) EXPECT_EQ("0", to_string(0)); } -TEST(helper, string_to_mac_addr) { - std::array mac_addr; - ASSERT_TRUE(string_to_mac_addr("00:11:22:33:44:55", mac_addr)); - std::array expected = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; - EXPECT_EQ(mac_addr, expected); -} - TEST(EncodeDecodeTLV, EncodeAndDecode) { uint8_t buffer[10] = {}; uint8_t value[3] = {0x11, 0x22, 0x33}; @@ -330,45 +339,6 @@ TEST(relayConfig, handle_interface_events) { close(pipe_fds[1]); } -TEST(relayConfig, handle_metadata_events) { - int pipe_fds[2]; - EXPECT_GLOBAL_CALL(write, write(_, _, _)) - .Times(AtLeast(1)) - .WillRepeatedly(Invoke(RealWrite)); - ASSERT_NE(pipe(pipe_fds), -1); - std::unordered_map vlans; - relay_config vlan1, vlan2; - - vlan1.vlan = "Vlan100"; - vlan2.vlan = "Vlan200"; - - vlans["Vlan100"] = vlan1; - vlans["Vlan200"] = vlan2; - - relay_config *metadata = new relay_config(); - - metadata->hostname = "newHost"; - uint8_t test_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; - std::copy(std::begin(test_mac), std::end(test_mac), std::begin(metadata->host_mac_addr)); - - event_config event; - event.type = DHCPv4_RELAY_METADATA_UPDATE; - event.msg = static_cast(metadata); - - ASSERT_NE(write(pipe_fds[1], &event, sizeof(event)), -1); - - config_event_callback(pipe_fds[0], 0, &vlans); - - for (const auto& vlan : vlans) { - ASSERT_EQ(vlan.second.hostname, "newHost"); - - ASSERT_TRUE(std::equal(std::begin(vlan.second.host_mac_addr), std::end(vlan.second.host_mac_addr), - std::begin(test_mac))); - } - close(pipe_fds[0]); - close(pipe_fds[1]); -} - TEST(relay, signal_init) { signal_init(); EXPECT_NE((uintptr_t)ev_sigint, NULL); @@ -436,3 +406,187 @@ TEST(DHCPMgrTest, initialize_config_listner) { std::this_thread::sleep_for(std::chrono::seconds(2)); } + +void mac_to_string(const uint8_t mac[6], char *out_str, char sep = ':') { + sprintf(out_str, "%02X%c%02X%c%02X%c%02X%c%02X%c%02X", + mac[0], sep, mac[1], sep, mac[2], sep, + mac[3], sep, mac[4], sep, mac[5]); +} + +TEST(DHCPRelayTest, encode_relay_option) { + std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); + pcpp::EthLayer ethLayer(pcpp::MacAddress("00:13:72:25:fa:cd"), pcpp::MacAddress("00:e0:b1:49:39:02")); + + pcpp::IPv4Address srcIp("172.22.178.234"); + pcpp::IPv4Address dstIp("10.10.8.240"); + pcpp::IPv4Layer ipLayer(srcIp, dstIp); + ipLayer.getIPv4Header()->ipId = htobe16(20370); + ipLayer.getIPv4Header()->timeToLive = 128; + + pcpp::UdpLayer udpLayer((uint16_t)67, (uint16_t)67); + + pcpp::MacAddress clientMac(std::string("00:0e:86:11:c0:75")); + pcpp::DhcpLayer dhcpLayer(pcpp::DHCP_DISCOVER, clientMac); + dhcpLayer.getDhcpHeader()->hops = 1; + + Table portTbl(config_db.get(), CFG_PORT_TABLE_NAME); + + std::vector fvVector; + fvVector.emplace_back("alias", "eth12"); + portTbl.set("Ethernet12", fvVector); + + relay_config config = {}; + config.config_db = config_db; + config.phy_interface = "Ethernet12"; + config.vlan = "Vlan10"; + config.link_selection_opt = "enable"; + config.server_id_override_opt = "enable"; + config.link_address.sin_addr.s_addr = inet_addr("192.168.10.10"); + config.link_address_netmask.sin_addr.s_addr = inet_addr("255.255.255.0"); + config.vrf_selection_opt = "enable"; + vlan_vrf_map["Vlan10"] = "Vrf01"; + + hostname = "cisco"; + host_mac_addr = "12:32:54:24:95:36"; + + encode_relay_option(&dhcpLayer, &config); + + auto agent_option = dhcpLayer.getOptionData(pcpp::DHCPOPT_DHCP_AGENT_OPTIONS); + auto options_ptr = agent_option.getValue(); + auto agent_option_size = agent_option.getDataSize(); + EXPECT_NE((uintptr_t)options_ptr, NULL); + + uint8_t circuit_id_len = 0; + auto circuit_id_ptr = decode_tlv((const uint8_t *)options_ptr, OPTION82_SUBOPT_CIRCUIT_ID, + circuit_id_len, agent_option_size); + std::string circuit_id((const char*)circuit_id_ptr, circuit_id_len); + + EXPECT_EQ(circuit_id, "cisco:eth12:Vlan10"); + uint8_t remote_id_len = 0; + auto remote_id_ptr = decode_tlv((const uint8_t *)options_ptr, OPTION82_SUBOPT_REMOTE_ID, + remote_id_len, agent_option_size); + + EXPECT_EQ(memcmp(host_mac_addr.c_str(), remote_id_ptr, 17), 0); + + uint8_t link_sel_len = 0; + auto link_sel_ip_ptr = decode_tlv((const uint8_t *)options_ptr, OPTION82_SUBOPT_LINK_SELECTION, + link_sel_len, agent_option_size); + auto link_sel_ip = *((uint32_t *)link_sel_ip_ptr); + EXPECT_EQ((config.link_address.sin_addr.s_addr & config.link_address_netmask.sin_addr.s_addr), link_sel_ip); + + auto srv_ovr_ride = decode_tlv((const uint8_t *)options_ptr, OPTION82_SUBOPT_SERVER_OVERRIDE, + link_sel_len, agent_option_size); + auto srv_ip = *((uint32_t *)srv_ovr_ride); + EXPECT_EQ(srv_ip, config.link_address.sin_addr.s_addr); + + uint8_t vrf_len = 0; + auto vrf_ptr = decode_tlv((const uint8_t *)options_ptr, OPTION82_SUBOPT_VIRTUAL_SUBNET, + vrf_len, agent_option_size); + std::string vrf((char *)vrf_ptr, vrf_len); + uint8_t vss_buf[32] = {0}; + uint8_t zero_encode = 0; + memcpy(vss_buf, &zero_encode, sizeof(uint8_t)); + memcpy((vss_buf + 1), (uint8_t*)vlan_vrf_map["Vlan10"].c_str(), (uint8_t)vlan_vrf_map["Vlan10"].length()); + + EXPECT_EQ(memcmp(vss_buf, vrf_ptr, 6), 0); +} + +TEST(DHCPRelayTest, to_client) { + std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); + pcpp::EthLayer ethLayer(pcpp::MacAddress("00:13:72:25:fa:cd"), pcpp::MacAddress("00:e0:b1:49:39:02")); + std::unordered_map vlans; + + pcpp::IPv4Address srcIp("172.22.178.234"); + pcpp::IPv4Address dstIp("10.10.8.240"); + pcpp::IPv4Layer ipLayer(srcIp, dstIp); + ipLayer.getIPv4Header()->ipId = htobe16(20370); + ipLayer.getIPv4Header()->timeToLive = 128; + + pcpp::UdpLayer udpLayer((uint16_t)67, (uint16_t)67); + + pcpp::MacAddress clientMac(std::string("00:0e:86:11:c0:75")); + pcpp::DhcpLayer dhcpLayer(pcpp::DHCP_OFFER, clientMac); + dhcpLayer.getDhcpHeader()->hops = 1; + dhcpLayer.getDhcpHeader()->gatewayIpAddress = inet_addr("192.168.1.1"); + dhcpLayer.getDhcpHeader()->opCode = 1; + + Table portTbl(config_db.get(), CFG_PORT_TABLE_NAME); + + std::vector fvVector; + fvVector.emplace_back("alias", "eth12"); + portTbl.set("Ethernet12", fvVector); + + relay_config config = {}; + config.config_db = config_db; + config.phy_interface = "Ethernet12"; + config.vlan = "Vlan10"; + config.link_selection_opt = "enable"; + config.server_id_override_opt = "enable"; + config.link_address.sin_addr.s_addr = inet_addr("192.168.10.10"); + config.link_address_netmask.sin_addr.s_addr = inet_addr("255.255.255.0"); + config.vrf_selection_opt = "enable"; + vlan_vrf_map["Vlan10"] = "Vrf01"; + + host_mac_addr = "12:32:54:24:95:36"; + vlans["Vlan10"] = config; + encode_relay_option(&dhcpLayer, &config); + + struct ifaddrs *mock_ifaddrs = CreateMockIfaddrs("192.168.1.1", "255.255.255.0", "Vlan100", "192.168.1.2", "Ethernet4"); + EXPECT_GLOBAL_CALL(getifaddrs, getifaddrs(_)).WillOnce(DoAll(testing::SetArgPointee<0>(mock_ifaddrs), Return(0))); + EXPECT_GLOBAL_CALL(freeifaddrs, freeifaddrs(_)).Times(1); + EXPECT_GLOBAL_CALL(send_udp, send_udp(_, _, _, _)).WillOnce([](int sock, uint8_t* hdr, struct sockaddr_in target, uint32_t n) { + pcpp::dhcp_header* dhcp_hdr = (pcpp::dhcp_header*)hdr; + EXPECT_EQ((dhcp_hdr->opCode), 1); + EXPECT_EQ((dhcp_hdr->hops), 1); + EXPECT_EQ((dhcp_hdr->gatewayIpAddress), inet_addr("192.168.1.1")); + return true; + }); + to_client(&dhcpLayer, &vlans, "172.22.178.234"); +} + +TEST(DHCPRelayTest, from_client) { + std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); + + pcpp::MacAddress clientMac(std::string("00:0e:86:11:c0:75")); + pcpp::DhcpLayer dhcpLayer(pcpp::DHCP_DISCOVER, clientMac); + dhcpLayer.getDhcpHeader()->hops = 0; + dhcpLayer.getDhcpHeader()->gatewayIpAddress = inet_addr("192.168.1.1"); + dhcpLayer.getDhcpHeader()->opCode = 0; + + Table portTbl(config_db.get(), CFG_PORT_TABLE_NAME); + + std::vector fvVector; + fvVector.emplace_back("alias", "eth12"); + portTbl.set("Ethernet12", fvVector); + + relay_config config = {}; + config.config_db = config_db; + config.phy_interface = "Ethernet12"; + config.vlan = "Vlan10"; + config.link_selection_opt = "enable"; + config.server_id_override_opt = "enable"; + struct sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("192.168.20.100"); + config.servers_sock = {addr}; + config.servers = {"192.168.20.100"}; + config.link_address.sin_addr.s_addr = inet_addr("192.168.10.10"); + config.link_address_netmask.sin_addr.s_addr = inet_addr("255.255.255.0"); + config.vrf_selection_opt = "enable"; + vlan_vrf_map["Vlan10"] = "Vrf01"; + + host_mac_addr = "12:32:54:24:95:36"; + encode_relay_option(&dhcpLayer, &config); + + //struct ifaddrs *mock_ifaddrs = CreateMockIfaddrs("192.168.1.1", "255.255.255.0", "Vlan100", "192.168.1.2", "Ethernet4"); + //EXPECT_GLOBAL_CALL(getifaddrs, getifaddrs(_)).WillOnce(DoAll(testing::SetArgPointee<0>(mock_ifaddrs), Return(0))); + //EXPECT_GLOBAL_CALL(freeifaddrs, freeifaddrs(_)).Times(1); + EXPECT_GLOBAL_CALL(send_udp, send_udp(_, _, _, _)).WillOnce([](int sock, uint8_t* hdr, struct sockaddr_in target, uint32_t n) { + pcpp::dhcp_header* dhcp_hdr = (pcpp::dhcp_header*)hdr; + EXPECT_EQ((dhcp_hdr->opCode), 0); + EXPECT_EQ((dhcp_hdr->hops), 1); + EXPECT_EQ((dhcp_hdr->gatewayIpAddress), inet_addr("192.168.1.1")); + return true; + }); + from_client(&dhcpLayer, config); +} From a4f3fe1d0a56edea57ebc1a03a0b7b44ce4f976d Mon Sep 17 00:00:00 2001 From: Akshath TR Date: Thu, 3 Jul 2025 07:12:46 -0700 Subject: [PATCH 03/17] Added code changes to support DualTor and dhcp_server --- dhcp4relay/src/dhcp4_sender.cpp | 71 +++- dhcp4relay/src/dhcp4_sender.h | 12 +- dhcp4relay/src/dhcp4relay.cpp | 500 +++++++++++++++++++----- dhcp4relay/src/dhcp4relay.h | 21 +- dhcp4relay/src/dhcp4relay_mgr.cpp | 551 ++++++++++++++++++++++++++- dhcp4relay/src/dhcp4relay_mgr.h | 10 + dhcp4relay/test/database_config.json | 5 + dhcp4relay/test/mock_relay.cpp | 156 +++++++- dhcp4relay/test/mock_relay_stats.cpp | 4 +- 9 files changed, 1184 insertions(+), 146 deletions(-) diff --git a/dhcp4relay/src/dhcp4_sender.cpp b/dhcp4relay/src/dhcp4_sender.cpp index e566f14..3286835 100644 --- a/dhcp4relay/src/dhcp4_sender.cpp +++ b/dhcp4relay/src/dhcp4_sender.cpp @@ -7,32 +7,71 @@ #include /** - * @code bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t len); + * @code bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t len, const char* src_ip, bool use_src_ip); * * @brief send udp packet and return true if successful * * @param *buffer message buffer * @param sockaddr_in target target socket * @param len length of message - * @param relay_config *config pointer to relay_config - * @param uint8_t msg_type message type of dhcpv6 option of relayed message + * @param src_ip source IP address as string (optional) + * @param use_src_ip if true, use src_ip as source address * * @return boolean True if packet successfully sent */ #ifndef UNIT_TEST -bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t len) { - /* Pad additional bytes if length is lesser than 300 - * to make DHCP packet length to minimum of 300 bytes */ - if (len < BOOTP_MIN_LEN) { - auto pad_len = BOOTP_MIN_LEN - len; - memset(buffer+len, 0, pad_len); - len = BOOTP_MIN_LEN; - } - if (sendto(sock, buffer, len, 0, (const struct sockaddr *)&target, sizeof(target)) == -1) { - char server_addr[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &(target.sin_addr), server_addr, INET_ADDRSTRLEN); - syslog(LOG_ERR, "sendto: Failed to send to target address: %s, error: %s\n", server_addr, strerror(errno)); - return false; +bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t len, in_addr src_ip, bool use_src_ip) { + /* Pad additional bytes if length is lesser than 300 + * to make DHCP packet length to minimum of 300 bytes */ + if (len < BOOTP_MIN_LEN) { + auto pad_len = BOOTP_MIN_LEN - len; + memset(buffer+len, 0, pad_len); + len = BOOTP_MIN_LEN; + } + + if (use_src_ip && src_ip.s_addr != 0) { + // Enable IP_PKTINFO on the socket + int on = 1; + setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)); + + struct msghdr msg = {}; + struct iovec iov = {}; + char cmsgbuf[CMSG_SPACE(sizeof(struct in_pktinfo))]; + + iov.iov_base = buffer; + iov.iov_len = len; + + msg.msg_name = ⌖ + msg.msg_namelen = sizeof(target); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + + struct in_pktinfo *pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg); + memset(pktinfo, 0, sizeof(struct in_pktinfo)); + pktinfo->ipi_spec_dst = src_ip; + + msg.msg_controllen = cmsg->cmsg_len; + + if (sendmsg(sock, &msg, 0) == -1) { + char server_addr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(target.sin_addr), server_addr, INET_ADDRSTRLEN); + syslog(LOG_ERR, "sendmsg: Failed to send to target address: %s, error: %s\n", server_addr, strerror(errno)); + return false; + } + } else { + if (sendto(sock, buffer, len, 0, (const struct sockaddr *)&target, sizeof(target)) == -1) { + char server_addr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(target.sin_addr), server_addr, INET_ADDRSTRLEN); + syslog(LOG_ERR, "sendto: Failed to send to target address: %s, error: %s\n", server_addr, strerror(errno)); + return false; + } } return true; } diff --git a/dhcp4relay/src/dhcp4_sender.h b/dhcp4relay/src/dhcp4_sender.h index 0738f47..a072459 100644 --- a/dhcp4relay/src/dhcp4_sender.h +++ b/dhcp4relay/src/dhcp4_sender.h @@ -7,16 +7,16 @@ #define BOOTP_MIN_LEN 300 /** - * @code bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t len); + * @code bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t len, const char* src_ip, bool use_src_ip); * * @brief send udp packet and return true if successful * * @param *buffer message buffer - * @param sockaddr_in target target socket - * @param len length of message - * @param relay_config *config pointer to relay_config - * @param uint8_t msg_type message type of dhcpv6 option of relayed message + * @param sockaddr_in target target socket + * @param len length of message + * @param src_ip source IP address as string (optional) + * @param use_src_ip if true, use src_ip as source address * * @return boolean True if packet successfully sent */ -bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t len); +bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in target, uint32_t len, in_addr src_ip, bool use_src_ip); diff --git a/dhcp4relay/src/dhcp4relay.cpp b/dhcp4relay/src/dhcp4relay.cpp index afd61a1..e14c272 100644 --- a/dhcp4relay/src/dhcp4relay.cpp +++ b/dhcp4relay/src/dhcp4relay.cpp @@ -28,13 +28,15 @@ static std::string counter_table = "DHCPv4_COUNTER_TABLE|"; extern std::string host_mac_addr; extern std::string hostname; +extern uint32_t deployment_id; +extern bool feature_dhcp_server_enabled; +extern std::string global_dhcp_server_ip; +extern bool is_dualTor; static uint8_t client_recv_buffer[BUFFER_SIZE]; int config_pipe[2]; /* DHCPv4 filter */ -/* sudo tcpdump -dd "inbound and ip dst 255.255.255.255 && udp dst port 67" */ - static struct sock_filter ether_relay_filter[] = { /* Make sure this is an IP packet... */ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), @@ -89,9 +91,12 @@ std::unordered_map phy_interface_alias_map; /* DHCP Relay Counter Table Instance */ DHCPCounter_table dhcp_cntr_table; -/*DHCP Relay config manager Instance */ +/* DHCP Relay config manager Instance */ DHCPMgr dhcp_mgr; +/* Interfaces list in config DB */ +std::vector interface_list; + #ifdef UNIT_TEST using namespace swss; #endif @@ -166,6 +171,20 @@ int sock_open(const struct sock_fprog *fprog) { syslog(LOG_INFO, "[DHCPV4_RELAY] setsockopt: change raw socket recv buffer size from %d to %d\n", optval, optval_new); } + optval = 1; + if (setsockopt(s, SOL_PACKET, PACKET_AUXDATA, &optval, sizeof(optval)) == -1) { + syslog(LOG_WARNING, "[DHCPV4_RELAY] setsockopt: Failed to set packet AUXDATA \n"); + (void)close(s); + return -1; + } + + int ignore_outgoing = 1; + if (setsockopt(s, SOL_PACKET, PACKET_IGNORE_OUTGOING, &ignore_outgoing, sizeof(ignore_outgoing)) == -1) { + syslog(LOG_WARNING, "[DHCPV4_RELAY] setsockopt: Failed to ignore outgoing \n"); + } else { + syslog(LOG_INFO, "[DHCPV4_RELAY] setsockopt: outgoing packet is ignored\n"); + } + return s; } @@ -178,9 +197,36 @@ void prepare_relay_server_config(relay_config &interface_config) { } tmp.sin_family = AF_INET; tmp.sin_port = htons(RELAY_PORT); - interface_config.servers_sock.push_back(tmp); + auto it = std::find_if( + interface_config.servers_sock.begin(), + interface_config.servers_sock.end(), + [&tmp](const sockaddr_in &entry) { + return entry.sin_addr.s_addr == tmp.sin_addr.s_addr; + } + ); + /*If server_sock is already present then don't add it.*/ + if (it == interface_config.servers_sock.end()) { + interface_config.servers_sock.push_back(tmp); + } + } + if (interface_config.servers_sock.size() != interface_config.servers.size()) { + for (size_t index = 0; index < interface_config.servers_sock.size();) { + char ip_str[INET_ADDRSTRLEN] = {0}; + inet_ntop(AF_INET, &interface_config.servers_sock[index].sin_addr, ip_str, INET_ADDRSTRLEN); + + auto found = std::find(interface_config.servers.begin(), + interface_config.servers.end(), + std::string(ip_str)); + + if (found == interface_config.servers.end()) { + interface_config.servers_sock.erase(interface_config.servers_sock.begin() + index); + } else { + ++index; + } + } } } + /** * @code prepare_relay_interface_config(relay_config &interface_config); * @@ -203,6 +249,15 @@ void prepare_relay_interface_config(relay_config &interface_config) { return; } + if (is_dualTor) { + /* If DualTor is enabled, we set source interface to "Loopback0" + and link_selection option will be enabled during encoding if is_dualTor is enabled */ + interface_config.source_interface = "Loopback0"; + syslog(LOG_INFO, + "[DHCPV4_INFO][DualTor] %s: link_selection_opt is enabled and source interface is set to %s\n", + interface_config.vlan.c_str(), interface_config.source_interface.c_str()); + } + if (interface_config.source_interface.length() > 0) { syslog(LOG_INFO, "[DHCPV4_INFO] source interface addr is set to %s\n", interface_config.source_interface.c_str()); @@ -220,9 +275,16 @@ void prepare_relay_interface_config(relay_config &interface_config) { struct sockaddr_in *in = (struct sockaddr_in *)ifa_tmp->ifa_addr; struct sockaddr_in *mask = (struct sockaddr_in *)ifa_tmp->ifa_netmask; if (strcmp(ifa_tmp->ifa_name, interface_config.vlan.c_str()) == 0) { - intf_addr = *in; - net_mask = *mask; - intf_name_set = true; + char ip_str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(in->sin_addr), ip_str, INET_ADDRSTRLEN); + std::string value; + std::shared_ptr vlan_intf_tbl = std::make_shared(config_db.get(), CFG_VLAN_INTF_TABLE_NAME); + vlan_intf_tbl->hget((interface_config.vlan + "|" + ip_str), "secondary", value); + if ((value.size() == 0) || (value != "true")) { + intf_addr = *in; + net_mask = *mask; + intf_name_set = true; + } } else if ((!source_intf_sel_opt) && (strcmp(ifa_tmp->ifa_name, interface_config.source_interface.c_str()) == 0)) { src_intf_sel = *in; @@ -257,7 +319,6 @@ int prepare_vrf_sockets(relay_config &config) { evutil_make_listen_socket_reuseable(vrf_sock); evutil_make_socket_nonblocking(vrf_sock); - /* Default instance binding to the socket will fail, in default instance case will not bind the socket */ if (config.vrf != "default") { if (setsockopt(vrf_sock, SOL_SOCKET, SO_BINDTODEVICE, config.vrf.c_str(), strlen(config.vrf.c_str())) < 0) { @@ -301,11 +362,10 @@ int prepare_vrf_sockets(relay_config &config) { int prepare_vlan_sockets(relay_config &config) { #ifdef UNIT_TEST config.client_sock = 1; - config.lo_sock = 1; #else struct ifaddrs *ifa, *ifa_tmp; sockaddr_in client_addr = {0}; - int lo_sock = -1, client_sock = 0; + int client_sock = 0; if ((client_sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { syslog(LOG_ERR, "[DHCPV4_RELAY] socket: Failed to create client_addr socket on interface %s, error: %s\n", config.vlan.c_str(), strerror(errno)); @@ -381,7 +441,6 @@ int prepare_vlan_sockets(relay_config &config) { } config.client_sock = client_sock; - config.lo_sock = lo_sock; #endif return 0; } @@ -413,7 +472,12 @@ void encode_relay_option(pcpp::DhcpLayer *dhcp_pkt, relay_config *config) { /* Encode circuit ID sub-option */ /* | 1 | 4 | hostname:interface_alias:vlan | */ - std::string circuit_id = hostname + ":" + intf_alias + ":" + config->vlan; + std::string circuit_id; + if (feature_dhcp_server_enabled) { + circuit_id = hostname + ":" + intf_alias; + } else { + circuit_id = hostname + ":" + intf_alias + ":" + config->vlan; + } auto offset = encode_tlv(buf, OPTION82_SUBOPT_CIRCUIT_ID, circuit_id.length(), (uint8_t *)circuit_id.c_str()); buf_offset += offset; @@ -426,7 +490,7 @@ void encode_relay_option(pcpp::DhcpLayer *dhcp_pkt, relay_config *config) { /* TODO: this sub-option should be set if source interface selection is enabled */ /* | 5 | 4 | ipv4 | */ - if (config->link_selection_opt == "enable") { + if (is_dualTor || config->link_selection_opt == "enable") { uint32_t link_sel_ip = ((config->link_address.sin_addr.s_addr) & (config->link_address_netmask.sin_addr.s_addr)); offset = encode_tlv((buf + buf_offset), OPTION82_SUBOPT_LINK_SELECTION, sizeof(uint32_t), @@ -498,37 +562,40 @@ void from_client(pcpp::DhcpLayer *dhcp_pkt, relay_config &config) { } else { /* If the relay packet is from another relay, we should act based on configuration of agent_relay_mode. - forward_and_append - Forward the packet with appending relay agent. - forward_and_replace - Delete existing option 82 and add my relay option. - forward_untouched - Forward without adding my relay information. + append - Forward the packet with appending relay agent. + replace - Delete existing option 82 and add my relay option. discard - Discard the incoming packet. */ - if (config.agent_relay_mode == "forward_and_replace") { + if (config.agent_relay_mode == "append") { + encode_relay_option(dhcp_pkt, &config); + } else if (config.agent_relay_mode == "replace") { dhcp_pkt->removeOption(pcpp::DHCPOPT_DHCP_AGENT_OPTIONS); encode_relay_option(dhcp_pkt, &config); - } else if (config.agent_relay_mode == "discard") { + } else { + /* By default it will discard packet from relay agent */ + dhcp_cntr_table.increment_counter(config.vlan, "TX", DHCPv4_MESSAGE_TYPE_DROP); syslog(LOG_INFO, "[DHCPV4_RELAY] agent relay mode is discard, dropping the packet %s", config.vlan.c_str()); return; - } else if (config.agent_relay_mode == "forward_untouched") { - syslog(LOG_INFO, - "[DHCPV4_RELAY] agent relay mode is forward untouch," - " not appending self relay info %s", - config.vlan.c_str()); - } else { - /* By default it will be forward_and_append */ - encode_relay_option(dhcp_pkt, &config); } } /* Increase the hop count */ /*TODO: check hops count is less than max hops count allowed */ dhcp_pkt->getDhcpHeader()->hops = dhcp_pkt->getDhcpHeader()->hops + 1; - int sock = config.vrf_sock; uint32_t index = 0; + + // Backward compatibility for deployment_id 8. If deployment_id is 8, use client interface IP as source IP + bool use_intf_ip_as_src_ip = false; + in_addr src_ip = {0}; + if (deployment_id == 8) { + use_intf_ip_as_src_ip = true; + src_ip.s_addr = config.link_address.sin_addr.s_addr; + } + for (auto server : config.servers_sock) { - if (send_udp(sock, (uint8_t *)dhcp_pkt->getDhcpHeader(), server, dhcp_pkt->getHeaderLen())) { + if (send_udp(sock, (uint8_t *)dhcp_pkt->getDhcpHeader(), server, dhcp_pkt->getHeaderLen(), src_ip, use_intf_ip_as_src_ip)) { syslog(LOG_INFO, "[DHCPV4_RELAY] DHCP packet is sent to configured server: %s, interface: %s", config.servers[index].c_str(), config.vlan.c_str()); dhcp_cntr_table.increment_counter(config.vlan, "TX", (int)dhcp_pkt->getMessageType()); @@ -668,10 +735,11 @@ void to_client(pcpp::DhcpLayer *dhcp_pkt, std::unordered_mapremoveOption(pcpp::DHCPOPT_DHCP_AGENT_OPTIONS); - if (send_udp(config.client_sock, (uint8_t *)dhcp_pkt->getDhcpHeader(), target_addr, dhcp_pkt->getHeaderLen())) { + if (send_udp(config.client_sock, (uint8_t *)dhcp_pkt->getDhcpHeader(), target_addr, dhcp_pkt->getHeaderLen(), ip_zero, false)) { syslog(LOG_INFO, "[DHCPV4_RELAY] dhcp relay message is broadcast to client %s from server %s", config.vlan.c_str(), src_ip.c_str()); dhcp_cntr_table.increment_counter(config.vlan, "TX", (int)dhcp_pkt->getMessageType()); @@ -736,6 +804,26 @@ void update_vlan_mapping(std::string vlan, bool is_add) { } } +uint16_t ipv4_checksum_cal(const uint8_t* ipv4_header, size_t header_len) { + uint8_t header[header_len] = {0}; + + memcpy(header, ipv4_header, header_len); + pcpp::iphdr *header_ptr = (pcpp::iphdr *)header; + + header_ptr->headerChecksum = 0; + uint32_t sum = 0; + for (size_t i = 0; i + 1 < header_len; i += 2) { + sum += (header[i] << 8) | header[i + 1]; + } + if (header_len & 1) { + sum += header[header_len - 1] << 8; + } + while (sum >> 16) { + sum = (sum & 0xFFFF) + (sum >> 16); + } + return ((uint16_t)~sum); +} + /** * @code pkt_in_callback(evutil_socket_t fd, short event, void *arg); * @@ -750,14 +838,29 @@ void update_vlan_mapping(std::string vlan, bool is_add) { */ void pkt_in_callback(evutil_socket_t fd, short event, void *arg) { auto vlans = reinterpret_cast *>(arg); - struct sockaddr_ll sll; - socklen_t slen = sizeof(sll); timeval time; + struct cmsghdr *cmsg = NULL; + struct tpacket_auxdata *aux = NULL; + struct sockaddr_ll *sll; + struct sockaddr_ll addr = {0}; + struct msghdr msg = {0}; + struct iovec iov = {0}; int pkts_num = 0; + int vlan_id = 0; char interface_name[IF_NAMESIZE]; + char control[1024] = {0}; + + iov.iov_base = client_recv_buffer; + iov.iov_len = BUFFER_SIZE; + msg.msg_name = &addr; + msg.msg_namelen = sizeof(addr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); while (pkts_num++ < BATCH_SIZE) { - auto buffer_sz = recvfrom(fd, client_recv_buffer, BUFFER_SIZE, 0, (struct sockaddr *)&sll, &slen); + auto buffer_sz = recvmsg(fd, &msg, 0); if (buffer_sz <= 0) { if (errno != EAGAIN) { syslog(LOG_ERR, "[DHCPV4_RELAY] recv: Failed to receive data at filter socket: %s\n", strerror(errno)); @@ -765,11 +868,45 @@ void pkt_in_callback(evutil_socket_t fd, short event, void *arg) { return; } - if (if_indextoname(sll.sll_ifindex, interface_name) == NULL) { - syslog(LOG_WARNING, "[DHCPV4_RELAY] Invalid input interface index %d\n", sll.sll_ifindex); + /* Find ingress VLAN */ + sll = (struct sockaddr_ll *)msg.msg_name; + cmsg = (struct cmsghdr *)msg.msg_control; + vlan_id = 0; + if (cmsg != NULL) { + if ((cmsg->cmsg_level == (int)SOL_PACKET) && (cmsg->cmsg_type == (int)PACKET_AUXDATA)) { + aux = (struct tpacket_auxdata *)(cmsg->__cmsg_data); + if (aux != NULL) { + vlan_id = (aux->tp_vlan_tci & VLAN_MASK); + } + } + } + + if (if_indextoname(sll->sll_ifindex, interface_name) == NULL) { + syslog(LOG_WARNING, "[DHCPV4_RELAY] Invalid input interface index %d\n", sll->sll_ifindex); continue; } std::string intf(interface_name); + auto itr = std::find(interface_list.begin(), interface_list.end(), intf); + /* To avoid duplicate packets, we are only processing packets from + interface in PORT_TABLE and packets from VXLAN interface */ + if ((itr == interface_list.end()) && (intf.find("VXLAN") != std::string::npos)) { + continue; + } + + std::string vlan_str; + if (vlan_id == 0) { + /* vlan_id can be 0 when we receive packet from the server */ + auto vlan = vlan_map.find(intf); + if (vlan == vlan_map.end()) { + if (intf.find(CLIENT_IF_PREFIX) != std::string::npos) { + syslog(LOG_WARNING, "[DHCPV4_RELAY] Invalid input interface %s\n", interface_name); + } + } else { + vlan_str = vlan->second; + } + } else { + vlan_str = "Vlan" + std::to_string(vlan_id); + } gettimeofday(&time, nullptr); @@ -781,40 +918,71 @@ void pkt_in_callback(evutil_socket_t fd, short event, void *arg) { /* Extract packets in each layers */ pcpp::EthLayer *eth_layer = raw_pkt.getLayerOfType(); if (eth_layer == nullptr) { - syslog(LOG_ERR, "[DHCPV4_RELAY] Invalid Ethernet packet from interface %s\n", intf.c_str()); - return; + syslog(LOG_WARNING, "[DHCPV4_RELAY] Invalid Ethernet packet from interface %s\n", intf.c_str()); + if (vlan_id != 0 && !vlan_str.empty()) { + dhcp_cntr_table.increment_counter(vlan_str, "RX", DHCPv4_MESSAGE_TYPE_MALFORMED); + } + continue; } pcpp::IPv4Layer *ip_layer = raw_pkt.getLayerOfType(); if (ip_layer == nullptr) { - syslog(LOG_ERR, "[DHCPV4_RELAY] Invalid IP packet from interface %s\n", intf.c_str()); - return; + syslog(LOG_WARNING, "[DHCPV4_RELAY] Invalid IP packet from interface %s\n", intf.c_str()); + if (vlan_id != 0 && !vlan_str.empty()) { + dhcp_cntr_table.increment_counter(vlan_str, "RX", DHCPv4_MESSAGE_TYPE_MALFORMED); + } + continue; } + + /* Validate IP checksum is correct */ + pcpp::iphdr* ip_hdr = ip_layer->getIPv4Header(); + auto ipv4_checksum = ipv4_checksum_cal((const uint8_t*)ip_hdr, ip_layer->getHeaderLen()); + if (ip_hdr->headerChecksum != htons(ipv4_checksum)) { + if (vlan_id != 0 && !vlan_str.empty()) { + dhcp_cntr_table.increment_counter(vlan_str, "RX", DHCPv4_MESSAGE_TYPE_MALFORMED); + } + syslog(LOG_WARNING, "[DHCPV4_RELAY] Checksum failed for IP packet from interface %s\n", intf.c_str()); + continue; + } + auto src_ip = ip_layer->getSrcIPv4Address().toString(); pcpp::UdpLayer *udp_layer = raw_pkt.getLayerOfType(); if (udp_layer == nullptr) { - syslog(LOG_ERR, "[DHCPV4_RELAY] Invalid UDP packet from interface %s\n", intf.c_str()); - return; + syslog(LOG_WARNING, "[DHCPV4_RELAY] Invalid UDP packet from interface %s\n", intf.c_str()); + if (vlan_id != 0 && !vlan_str.empty()) { + dhcp_cntr_table.increment_counter(vlan_str, "RX", DHCPv4_MESSAGE_TYPE_MALFORMED); + } + continue; + } + + /* Validate UDP checksum is correct */ + auto udp_checksum = udp_layer->calculateChecksum(false); + if (htobe16(udp_checksum) != udp_layer->getUdpHeader()->headerChecksum) { + syslog(LOG_WARNING, "[DHCPV4_REALY] UDP checksum validation is failing " + " packet is from interface %s\n", intf.c_str()); + if (vlan_id != 0 && !vlan_str.empty()) { + dhcp_cntr_table.increment_counter(vlan_str, "RX", DHCPv4_MESSAGE_TYPE_MALFORMED); + } + continue; } pcpp::DhcpLayer *dhcp_pkt = raw_pkt.getLayerOfType(); if (dhcp_pkt == nullptr) { - syslog(LOG_ERR, "[DHCPV4_RELAY] Invalid DHCP packet from interface %s\n", intf.c_str()); - return; + syslog(LOG_WARNING, "[DHCPV4_RELAY] Invalid DHCP packet from interface %s\n", intf.c_str()); + if (vlan_id != 0 && !vlan_str.empty()) { + dhcp_cntr_table.increment_counter(vlan_str, "RX", DHCPv4_MESSAGE_TYPE_MALFORMED); + } + continue; } if (dhcp_pkt->getDhcpHeader()->opCode == BOOTPREQUEST) { - auto vlan = vlan_map.find(intf); - if (vlan == vlan_map.end()) { - if (intf.find(CLIENT_IF_PREFIX) != std::string::npos) { - syslog(LOG_WARNING, "[DHCPV4_RELAY] Invalid input interface %s\n", interface_name); - } + if (vlan_str.empty()) { continue; } - auto config_itr = vlans->find(vlan->second); + auto config_itr = vlans->find(vlan_str); if (config_itr == vlans->end()) { - syslog(LOG_WARNING, "[DHCPV4_RELAY] Config not found for vlan %s\n", vlan->second.c_str()); + syslog(LOG_WARNING, "[DHCPV4_RELAY] Config not found for vlan %s\n", intf.c_str()); continue; } auto config = config_itr->second; @@ -823,18 +991,12 @@ void pkt_in_callback(evutil_socket_t fd, short event, void *arg) { dhcp_cntr_table.increment_counter(config.vlan, "RX", (int)dhcp_pkt->getMessageType()); from_client(dhcp_pkt, config_itr->second); } else if (dhcp_pkt->getDhcpHeader()->opCode == BOOTPREPLY) { - /* TODO: This work around need to be fixed. - * Currently we are receiving same dhcp reply packet on physical interface. - * on SVI interface(VLAN) and on the bridge. - * Trap is added for DHCP packet on all interface. - */ - if ((intf.find("Ethernet") == std::string::npos) && - (intf.find("Bridge") == std::string::npos)) { - to_client(dhcp_pkt, vlans, src_ip); - } + to_client(dhcp_pkt, vlans, src_ip); } else { - syslog(LOG_ERR, "[DHCPV4_RELAY] Received a invalid DHCPv4 packet\n"); - return; + if (vlan_id != 0 && !vlan_str.empty()) { + dhcp_cntr_table.increment_counter(vlan_str, "RX", DHCPv4_MESSAGE_TYPE_UNKNOWN); + } + continue; } } } @@ -918,13 +1080,69 @@ void dhcp4relay_stop() { event_base_loopexit(base, NULL); } +int handle_server_sock(relay_config &vlan_config, std::string new_vrf) +{ + /* Decrement the ref count for old vrf value in the vrf_sock_map and after decrement + * if the value is zero then close the client socket and delete the entry in the map. */ + if (vrf_sock_map.find(vlan_config.vrf) != vrf_sock_map.end()) { + vrf_sock_map[vlan_config.vrf].ref_count--; + if (vrf_sock_map[vlan_config.vrf].ref_count == 0) { + close(vlan_config.vrf_sock); + vrf_sock_map.erase((vlan_config.vrf)); + } + } + + /*Updating the new vrf value to vlans structure. */ + vlan_config.vrf = new_vrf; + /* For the new server vrf update case, if the entry exists in the vrf_sock_map then + * increment the ref count only else create a socket and update the ref count. */ + if (vrf_sock_map.find(new_vrf) != vrf_sock_map.end()) { + vlan_config.vrf_sock = vrf_sock_map[new_vrf.c_str()].sock; + vrf_sock_map[new_vrf].ref_count++; + } else { + if (prepare_vrf_sockets(vlan_config) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to create vrf listen socket"); + return -1; + } + } + return 0; +} + +/** + * @code void delete_all_relay_configs(std::unordered_map *vlans); + * + * @brief Delete all the existing vlan entries in case of dhcp_server is enabled/disbaled. + * + * @param vlans Client information including socket to send DHCP packet to client. + * + * @return none + */ +void delete_all_relay_configs(std::unordered_map *vlans) { + for (auto vlan = vlans->begin(); vlan != vlans->end(); ) { + if (vlan->second.client_sock > 0) { + close(vlan->second.client_sock); + } + if (vlan->second.vrf_sock > 0) { + vrf_sock_map[vlan->second.vrf].ref_count--; + if (vrf_sock_map[vlan->second.vrf].ref_count == 0) { + close(vlan->second.vrf_sock); + vrf_sock_map.erase(vlan->second.vrf); + } + } + update_vlan_mapping(vlan->first, false); + vlan = vlans->erase(vlan); + } +} + void config_event_callback(evutil_socket_t fd, short event, void *arg) { std::unordered_map *vlans = static_cast *>(arg); event_config received_event; ssize_t bytes_read = read(fd, &received_event, sizeof(received_event)); if (bytes_read == sizeof(received_event)) { - if (received_event.type == DHCPv4_RELAY_CONFIG_UPDATE) { + //Do not update the relay configs if dhcp_server is enabled + if (((received_event.type == DHCPv4_RELAY_CONFIG_UPDATE) && !feature_dhcp_server_enabled) || + (received_event.type == DHCPv4_SERVER_RELAY_CONFIG_UPDATE)) { relay_config *relay_msg = static_cast(received_event.msg); if (relay_msg) { syslog(LOG_INFO, "[DHCPV4_RELAY] Processing config update: VLAN %s", relay_msg->vlan.c_str()); @@ -939,7 +1157,7 @@ void config_event_callback(evutil_socket_t fd, short event, void *arg) { update_vlan_mapping(relay_msg->vlan, true); if (prepare_vlan_sockets((*vlans)[relay_msg->vlan]) == -1) { syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to create Vlan listen socket"); - exit(EXIT_FAILURE); + return; } /* Intially filling the vlan interface IP address. */ if (relay_msg->source_interface.empty()) { @@ -954,28 +1172,8 @@ void config_event_callback(evutil_socket_t fd, short event, void *arg) { /* Compare the existing vrf value and the new DB updated vrf value for vrf modification case. */ if ((*vlans)[relay_msg->vlan].vrf != relay_msg->vrf) { - /* Decrement the ref count for old vrf value in the vrf_sock_map and after decrement - * if the value is zero then close the client socket and delete the entry in the map. */ - if (vrf_sock_map.find((*vlans)[relay_msg->vlan].vrf) != vrf_sock_map.end()) { - vrf_sock_map[(*vlans)[relay_msg->vlan].vrf].ref_count--; - if (vrf_sock_map[(*vlans)[relay_msg->vlan].vrf].ref_count == 0) { - close((*vlans)[relay_msg->vlan].vrf_sock); - vrf_sock_map.erase((*vlans)[relay_msg->vlan].vrf); - } - } - - /*Updating the new vrf value to vlans structure. */ - (*vlans)[relay_msg->vlan].vrf = relay_msg->vrf; - /* For the new server vrf update case, if the entry exists in the vrf_sock_map then - * increment the ref count only else create a socket and update the ref count. */ - if (vrf_sock_map.find(relay_msg->vrf) != vrf_sock_map.end()) { - (*vlans)[relay_msg->vlan].vrf_sock = vrf_sock_map[relay_msg->vrf.c_str()].sock; - vrf_sock_map[relay_msg->vrf].ref_count++; - } else { - if (prepare_vrf_sockets((*vlans)[relay_msg->vlan]) == -1) { - syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to create vrf listen socket"); - exit(EXIT_FAILURE); - } + if (handle_server_sock((*vlans)[relay_msg->vlan], relay_msg->vrf) < 0) { + return; } } if ((*vlans)[relay_msg->vlan].source_interface != relay_msg->source_interface) { @@ -993,9 +1191,6 @@ void config_event_callback(evutil_socket_t fd, short event, void *arg) { if ((*vlans)[relay_msg->vlan].client_sock > 0) { close((*vlans)[relay_msg->vlan].client_sock); } - if ((*vlans)[relay_msg->vlan].lo_sock > 0) { - close((*vlans)[relay_msg->vlan].lo_sock); - } if ((*vlans)[relay_msg->vlan].vrf_sock > 0) { vrf_sock_map[(*vlans)[relay_msg->vlan].vrf].ref_count--; if (vrf_sock_map[(*vlans)[relay_msg->vlan].vrf].ref_count == 0) { @@ -1023,6 +1218,127 @@ void config_event_callback(evutil_socket_t fd, short event, void *arg) { } delete relay_msg; } + } else if (received_event.type == DHCPv4_RELAY_VLAN_MEMBER_UPDATE) { + vlan_member_config *msg = static_cast(received_event.msg); + if (msg) { + syslog(LOG_INFO, "[DHCPV4_RELAY] Updating vlan member for VLAN %s", msg->vlan.c_str()); + if (vlans->find(msg->vlan) == vlans->end()) { + delete msg; + return; + } + + if ((*vlans)[msg->vlan].client_sock > 0) { + close((*vlans)[msg->vlan].client_sock); + } + + if (msg->is_add) { + vlan_map[msg->interface] = msg->vlan; + dhcp_cntr_table.initialize_interface(msg->vlan); + syslog(LOG_INFO, "[DHCPV4_RELAY] Add <%s, %s> into interface vlan map\n", msg->interface.c_str(), msg->vlan.c_str()); + } else { + vlan_map.erase(msg->interface); + dhcp_cntr_table.remove_interface(msg->vlan); + syslog(LOG_INFO, "[DHCPV4_RELAY] Remove <%s, %s> from interface vlan map\n", msg->interface.c_str(), msg->vlan.c_str()); + } + if (prepare_vlan_sockets((*vlans)[msg->vlan]) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to create Vlan listen socket"); + return; + } + delete msg; + } + } else if (received_event.type == DHCPv4_RELAY_VLAN_INTERFACE_UPDATE) { + vlan_interface_config *msg = static_cast(received_event.msg); + if (msg) { + syslog(LOG_INFO, "[DHCPV4_RELAY] Updating vlan interface event for VLAN %s", msg->vlan.c_str()); + if (vlans->find(msg->vlan) == vlans->end()) { + delete msg; + return; + } + + if (!msg->vrf.empty()) { + vlan_vrf_map[msg->vlan] = msg->vrf; + } else { + if ((*vlans)[msg->vlan].client_sock > 0) { + close((*vlans)[msg->vlan].client_sock); + } + if (prepare_vlan_sockets((*vlans)[msg->vlan]) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to create Vlan listen socket"); + return; + } + prepare_relay_interface_config((*vlans)[msg->vlan]); + } + + std::string value; + std::shared_ptr dhcp_relay_tbl = std::make_shared(config_db.get(), + "DHCPV4_RELAY"); + dhcp_relay_tbl->hget((msg->vlan), "server_vrf", value); + if ((msg->vrf.empty()) || (value.length() != 0)) { + return; + } + + if ((*vlans)[msg->vlan].vrf != msg->vrf) { + if (handle_server_sock((*vlans)[msg->vlan], msg->vrf) < 0) { + return; + } + } + delete msg; + } + } else if ((received_event.type == DHCPv4_SERVER_FEATURE_UPDATE) || + (received_event.type == DHCPv4_SERVER_IP_DELETE)) { + syslog(LOG_INFO, "[DHCPV4_RELAY] dhcp_server feature table update or server ip delete event received"); + delete_all_relay_configs(vlans); + } else if (received_event.type == DHCPv4_SERVER_IP_UPDATE) { + syslog(LOG_INFO, "[DHCPV4_RELAY] dhcp_server IP update in state DB event received"); + for (auto it = vlans->begin(); it != vlans->end(); ++it) { + relay_config &config = it->second; + config.servers.clear(); + config.servers_sock.clear(); + config.servers.push_back(global_dhcp_server_ip); + prepare_relay_server_config(config); + } + } else if (received_event.type == DHCPv4_RELAY_DUAL_TOR_UPDATE) { + relay_config *relay_msg = static_cast(received_event.msg); + if (relay_msg) { + if (relay_msg->is_add) { + syslog(LOG_INFO, + "[DHCPV4_RELAY][DualTor] Adding link-selection and source-interface as Loopback0 for existing vlans"); + } else { + syslog(LOG_INFO, + "[DHCPV4_RELAY][DualTor] Deleting/Restoring link-selection and source-interface configs for existing vlans"); + } + + for (auto& vlan : *vlans) { + if (relay_msg->is_add) { + prepare_relay_interface_config(vlan.second); + } else { + std::shared_ptr v4_relay_intf_tbl = std::make_shared(config_db.get(), "DHCPV4_RELAY"); + std::string value; + + // Check for the presence of specific keys + v4_relay_intf_tbl->hget(vlan.second.vlan, "link_selection", value); + // Check if "link_selection" is present + if (value.length() > 0) { + // Fetch the value of "link_selection" from the database + vlan.second.link_selection_opt = value; + } else { + // link_selection key not found in DB, clear the value + vlan.second.link_selection_opt.clear(); + } + + v4_relay_intf_tbl->hget(vlan.second.vlan, "source_interface", value); + // Check if "source_interface" is present + if (value.length() > 0) { + // Fetch the value of "source_interface" from the database + vlan.second.source_interface = value; + } else { + // source_interface key not found in DB, clear the value + vlan.second.source_interface.clear(); + } + prepare_relay_interface_config(vlan.second); + } + } + delete relay_msg; + } } } else { syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to read config update: expected %lu bytes, got %zd bytes", sizeof(received_event), bytes_read); @@ -1055,6 +1371,16 @@ void loop_relay(std::unordered_map &vlans) { exit(EXIT_FAILURE); } + /* Keep a list of physical interafce available in config DB*/ + auto match_pattern = std::string("PORT|*"); + auto keys = config_db->keys(match_pattern); + + for (auto &itr : keys) { + auto found = itr.find_last_of('|'); + auto interface = itr.substr(found + 1); + interface_list.push_back(interface); + } + // Create the pipe for inter-thread communication if (pipe(config_pipe) == -1) { syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to create config update pipe"); diff --git a/dhcp4relay/src/dhcp4relay.h b/dhcp4relay/src/dhcp4relay.h index 1ca3dbd..90c8200 100644 --- a/dhcp4relay/src/dhcp4relay.h +++ b/dhcp4relay/src/dhcp4relay.h @@ -67,6 +67,7 @@ extern int config_pipe[2]; #define DHCP_OPTION_LEN (DHCP_MTU_MAX - DHCP_FIXED_LEN) #define BATCH_SIZE 64 +#define VLAN_MASK 0x0FFF extern char loopback[IF_NAMESIZE]; @@ -97,7 +98,6 @@ struct relay_config { int client_sock; /* Server facing socket, use to send packet to server */ int vrf_sock; - int lo_sock; int filter; sockaddr_in link_address; sockaddr_in link_address_netmask; @@ -123,7 +123,13 @@ typedef enum { DHCPv4_RELAY_CONFIG_UNKNOWN, DHCPv4_RELAY_CONFIG_UPDATE, DHCPv4_RELAY_INTERFACE_UPDATE, - DHCPv4_RELAY_METADATA_UPDATE + DHCPv4_RELAY_VLAN_MEMBER_UPDATE, + DHCPv4_RELAY_VLAN_INTERFACE_UPDATE, + DHCPv4_SERVER_RELAY_CONFIG_UPDATE, + DHCPv4_SERVER_FEATURE_UPDATE, + DHCPv4_SERVER_IP_UPDATE, + DHCPv4_SERVER_IP_DELETE, + DHCPv4_RELAY_DUAL_TOR_UPDATE } event_type; struct event_config { @@ -131,6 +137,17 @@ struct event_config { void *msg; }; +struct vlan_member_config { + std::string vlan; + std::string interface; + bool is_add; +}; + +struct vlan_interface_config { + std::string vlan; + std::string vrf; +}; + /** * @code sock_open(const struct sock_fprog *fprog); * diff --git a/dhcp4relay/src/dhcp4relay_mgr.cpp b/dhcp4relay/src/dhcp4relay_mgr.cpp index bc012ea..a1551c6 100644 --- a/dhcp4relay/src/dhcp4relay_mgr.cpp +++ b/dhcp4relay/src/dhcp4relay_mgr.cpp @@ -4,7 +4,7 @@ #include constexpr auto DEFAULT_TIMEOUT_MSEC = 1000; -static std::unordered_map vlans_copy; +std::unordered_map vlans_copy; #ifdef UNIT_TEST using namespace swss; @@ -12,7 +12,14 @@ using namespace swss; std::string host_mac_addr; std::string hostname = "sonic"; +uint32_t deployment_id; +bool is_dualTor = false; +bool feature_dhcp_server_enabled = false; +std::shared_ptr config_db_dhcp_server_ipv4_ptr = NULL; +std::shared_ptr state_db_dhcp_server_ipv4_ip_ptr = NULL; +std::shared_ptr config_db_relaymgr_table_ptr = NULL; +std::string global_dhcp_server_ip; /** * @brief Initializes the configuration listener for the DHCP manager. * @@ -48,19 +55,32 @@ void DHCPMgr::initialize_config_listner() { */ void DHCPMgr::handle_swss_notification() { std::shared_ptr config_db_ptr = std::make_shared("CONFIG_DB", 0); - swss::SubscriberStateTable config_db_relaymgr_table(config_db_ptr.get(), "DHCPV4_RELAY"); + std::shared_ptr state_db_ptr = std::make_shared("STATE_DB", 0); + config_db_relaymgr_table_ptr = std::make_shared(config_db_ptr.get(), "DHCPV4_RELAY"); swss::SubscriberStateTable config_db_interface_table(config_db_ptr.get(), "INTERFACE"); swss::SubscriberStateTable config_db_loopback_table(config_db_ptr.get(), "LOOPBACK_INTERFACE"); swss::SubscriberStateTable config_db_portchannel_table(config_db_ptr.get(), "PORTCHANNEL_INTERFACE"); swss::SubscriberStateTable config_db_device_metadata_table(config_db_ptr.get(), "DEVICE_METADATA"); + swss::SubscriberStateTable config_db_vlan_member_table(config_db_ptr.get(), "VLAN_MEMBER"); + swss::SubscriberStateTable config_db_vlan_interface_table(config_db_ptr.get(), "VLAN_INTERFACE"); + swss::SubscriberStateTable config_db_feature_table(config_db_ptr.get(), "FEATURE"); + swss::SubscriberStateTable config_db_vlan_table(config_db_ptr.get(), "VLAN"); + config_db_dhcp_server_ipv4_ptr = std::make_shared(config_db_ptr.get(), "DHCP_SERVER_IPV4"); + state_db_dhcp_server_ipv4_ip_ptr = std::make_shared(state_db_ptr.get(), "DHCP_SERVER_IPV4_SERVER_IP"); std::deque entries; swss::Select swss_select; - swss_select.addSelectable(&config_db_relaymgr_table); + swss_select.addSelectable(config_db_relaymgr_table_ptr.get()); swss_select.addSelectable(&config_db_interface_table); swss_select.addSelectable(&config_db_loopback_table); swss_select.addSelectable(&config_db_portchannel_table); swss_select.addSelectable(&config_db_device_metadata_table); + swss_select.addSelectable(&config_db_vlan_member_table); + swss_select.addSelectable(&config_db_vlan_interface_table); + swss_select.addSelectable(&config_db_feature_table); + swss_select.addSelectable(&config_db_vlan_table); + swss_select.addSelectable(config_db_dhcp_server_ipv4_ptr.get()); + swss_select.addSelectable(state_db_dhcp_server_ipv4_ip_ptr.get()); while (!stop_thread) { swss::Selectable *selectable; @@ -76,22 +96,46 @@ void DHCPMgr::handle_swss_notification() { continue; } - if (selectable == static_cast(&config_db_relaymgr_table)) { - config_db_relaymgr_table.pops(entries); - process_relay_notification(entries); - } else if (selectable == static_cast(&config_db_interface_table)) { - config_db_interface_table.pops(entries); - process_interface_notification(entries); - } else if (selectable == static_cast(&config_db_loopback_table)) { - config_db_loopback_table.pops(entries); - process_interface_notification(entries); - } else if (selectable == static_cast(&config_db_portchannel_table)) { - config_db_portchannel_table.pops(entries); - process_interface_notification(entries); - } else if (selectable == static_cast(&config_db_device_metadata_table)) { + if (!feature_dhcp_server_enabled) { + if (config_db_relaymgr_table_ptr && selectable == config_db_relaymgr_table_ptr.get()) { + config_db_relaymgr_table_ptr->pops(entries); + process_relay_notification(entries); + } else if (selectable == static_cast(&config_db_interface_table)) { + config_db_interface_table.pops(entries); + process_interface_notification(entries); + } else if (selectable == static_cast(&config_db_loopback_table)) { + config_db_loopback_table.pops(entries); + process_interface_notification(entries); + } else if (selectable == static_cast(&config_db_portchannel_table)) { + config_db_portchannel_table.pops(entries); + process_interface_notification(entries); + } + } else { + if (config_db_dhcp_server_ipv4_ptr && selectable == config_db_dhcp_server_ipv4_ptr.get()) { + config_db_dhcp_server_ipv4_ptr->pops(entries); + process_dhcp_server_ipv4_notification(entries); + } else if (state_db_dhcp_server_ipv4_ip_ptr && selectable == state_db_dhcp_server_ipv4_ip_ptr.get()) { + state_db_dhcp_server_ipv4_ip_ptr->pops(entries); + process_dhcp_server_ipv4_ip_notification(entries, swss_select, config_db_ptr); + } + } + + if (selectable == static_cast(&config_db_device_metadata_table)) { config_db_device_metadata_table.pops(entries); process_device_metadata_notification(entries); - } + } else if (selectable == static_cast(&config_db_vlan_member_table)) { + config_db_vlan_member_table.pops(entries); + process_vlan_member_notification(entries); + } else if (selectable == static_cast(&config_db_vlan_interface_table)) { + config_db_vlan_interface_table.pops(entries); + process_vlan_interface_notification(entries); + } else if (selectable == static_cast(&config_db_feature_table)) { + config_db_feature_table.pops(entries); + process_feature_notification(entries, swss_select, config_db_ptr, state_db_ptr); + } else if (selectable == static_cast(&config_db_vlan_table)) { + config_db_vlan_table.pops(entries); + process_vlan_notification(entries); + } } } @@ -110,10 +154,14 @@ void DHCPMgr::process_device_metadata_notification(std::deque field_values = kfvFieldsValues(entry); + std::string operation = kfvOp(entry); if (key != "localhost") { continue; } + bool subtype_found = false; + bool send_dualTor_event = false; + std::string subtype_value; for (auto &field : field_values) { std::string f = fvField(field); @@ -122,8 +170,49 @@ void DHCPMgr::process_device_metadata_notification(std::deque(std::stoul(v)); + } else if (f == "subtype") { + subtype_found = true; + subtype_value = v; + } + + // Handle is_dualToR logic + if (subtype_found && subtype_value == "DualToR") { + is_dualTor = true; + send_dualTor_event = true; + } else if (is_dualTor) { + // Covers both 'subtype' deleted and any value other than "DualToR" + is_dualTor = false; + send_dualTor_event = true; } + + if (send_dualTor_event) { + relay_config *relay_msg = nullptr; + try { + relay_msg = new relay_config(); + } catch (const std::bad_alloc &e) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Memory allocation failed: %s", e.what()); + return; + } + + if (is_dualTor) { + relay_msg->is_add = true; + } else { + relay_msg->is_add = false; + } + + event_config event; + event.type = DHCPv4_RELAY_DUAL_TOR_UPDATE; + event.msg = static_cast(relay_msg); + // Write the pointer address to the pipe + if (write(config_pipe[1], &event, sizeof(event)) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to write to config update pipe: %s", strerror(errno)); + delete relay_msg; + } + } } /* Re-set hostname to default value if hostname is deleted */ if (hostname.length() == 0) { @@ -260,9 +349,17 @@ void DHCPMgr::process_relay_notification(std::dequevrf.length() == 0) { - relay_msg->vrf = "default"; + std::string value; + std::shared_ptr config_db = std::make_shared("CONFIG_DB", 0); + std::shared_ptr vlan_intf_tbl = std::make_shared(config_db.get(), CFG_VLAN_INTF_TABLE_NAME); + vlan_intf_tbl->hget(vlan, "vrf_name", value); + if (value.size() <= 0) { + relay_msg->vrf = "default"; + } else { + relay_msg->vrf = value; + } } // Update the vlan cache entry @@ -292,6 +389,420 @@ void DHCPMgr::process_relay_notification(std::deque &entries, + swss::Select &select, std::shared_ptr config_db_ptr, + std::shared_ptr state_db_ptr) { + for (auto &entry : entries) { + if (kfvKey(entry) != "dhcp_server") { + continue; + } + + std::string state; + for (auto &field : kfvFieldsValues(entry)) { + if (fvField(field) == "state") { + state = fvValue(field); + break; + } + } + + if (state == "enabled" && !feature_dhcp_server_enabled) { + //Delete the existing vlan configs in main thread + event_config event; + event.type = DHCPv4_SERVER_FEATURE_UPDATE; + + if (write(config_pipe[1], &event, sizeof(event)) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to send delete event for dhcp_server feature update"); + return; + } + vlans_copy.clear(); + feature_dhcp_server_enabled = true; + + if (config_db_dhcp_server_ipv4_ptr) { + select.removeSelectable(config_db_dhcp_server_ipv4_ptr.get()); + } + if (state_db_dhcp_server_ipv4_ip_ptr) { + select.removeSelectable(state_db_dhcp_server_ipv4_ip_ptr.get()); + } + + config_db_dhcp_server_ipv4_ptr = std::make_shared(config_db_ptr.get(), "DHCP_SERVER_IPV4"); + state_db_dhcp_server_ipv4_ip_ptr = std::make_shared(state_db_ptr.get(), "DHCP_SERVER_IPV4_SERVER_IP"); + + select.addSelectable(config_db_dhcp_server_ipv4_ptr.get()); + select.addSelectable(state_db_dhcp_server_ipv4_ip_ptr.get()); + } else if (state == "disabled" && feature_dhcp_server_enabled) { + syslog(LOG_INFO, "[DHCPV4_RELAY] Disabling DHCP server auto-config mode and cleaning up."); + feature_dhcp_server_enabled = false; + global_dhcp_server_ip.clear(); + vlans_copy.clear(); + //Delete the old auto generated relay config in main thread + event_config event; + event.type = DHCPv4_SERVER_FEATURE_UPDATE; + + if (write(config_pipe[1], &event, sizeof(event)) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to send delete event for dhcp_server feature update"); + return; + } + + //re-add the dhcp relay listeners + if (config_db_relaymgr_table_ptr) { + select.removeSelectable(config_db_relaymgr_table_ptr.get()); + } + config_db_relaymgr_table_ptr = std::make_shared(config_db_ptr.get(), "DHCPV4_RELAY"); + select.addSelectable(config_db_relaymgr_table_ptr.get()); + } + } +} + +/** + * @brief Processes the dhcp_server_ip entry and stores the IP in the global parameter. + * + * This method iterates over a deque of dhcp_server_ip configuration entries, parses each entry, + * if the entry is for 'eth0' then only it will process the entry. + * If the operation is "SET" then it will stores the IP in global parameter and restart the dhcp_server + * related as it is the new configuration of the IP. + * If the operation is "DEL" then it will send the delete event to main thread to remove all the + * auto configured dhcp_server config, as without IP, the relay config can't be formed. + * + * The method will handle the clean up for the vlan cache entries and also logs relevant information + * and errors using syslog. + * + * @param entries A deque of KeyOpFieldsValuesTuple objects representing dhcp_server_ip table notifications. + * config_db_ptr It represents the pointer for the CONFIG_DB + */ +void DHCPMgr::process_dhcp_server_ipv4_ip_notification(std::deque &entries, + swss::Select &select, std::shared_ptr config_db_ptr) { + bool is_modify = false; + + for (auto &entry : entries) { + std::string server_intf = kfvKey(entry); + std::string operation = kfvOp(entry); + + if (server_intf != "eth0") { + continue; + } + + if (operation == "SET") { + std::string server_ip; + for (auto &fv : kfvFieldsValues(entry)) { + if (fvField(fv) == "ip") { + server_ip = fvValue(fv); + break; + } + } + if (server_ip.empty()) { + syslog(LOG_ERR, "[DHCPV4_RELAY] dhcp_server IP is not present in state DB"); + return; + } + //modification case + if (!global_dhcp_server_ip.empty() && (global_dhcp_server_ip != server_ip)) { + event_config event; + event.type = DHCPv4_SERVER_IP_UPDATE; + + if (write(config_pipe[1], &event, sizeof(event)) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to send delete event for dhcp_server IP update"); + return; + } + is_modify = true; + } + global_dhcp_server_ip = server_ip; + //Since the server IP see newly added, restart the listener for the dhcp_server config. + if (!is_modify) { + syslog(LOG_INFO, "[DHCPV4_RELAY] Restarting the dhcp_server listener"); + if (config_db_dhcp_server_ipv4_ptr) { + select.removeSelectable(config_db_dhcp_server_ipv4_ptr.get()); + } + config_db_dhcp_server_ipv4_ptr = std::make_shared(config_db_ptr.get(), "DHCP_SERVER_IPV4"); + select.addSelectable(config_db_dhcp_server_ipv4_ptr.get()); + } + } else { + //DHCP server IP deletion case, need to remove the existing configs in main thread + event_config event; + event.type = DHCPv4_SERVER_IP_DELETE; + + if (write(config_pipe[1], &event, sizeof(event)) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to send delete event for dhcp_server IP delete"); + return; + } + global_dhcp_server_ip.clear(); + vlans_copy.clear(); + } + } +} + +void DHCPMgr::process_vlan_member_notification(std::deque &entries) { + for (auto &entry : entries) { + std::string key = kfvKey(entry); + std::string operation = kfvOp(entry); + + size_t pos = key.find('|'); + if (pos == std::string::npos) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Invalid string format"); + return; + } + + std::string vlan = key.substr(0, pos); + std::string interface = key.substr(pos + 1); + + //If the vlan is not configured in DHCPV4 table then skip the entry. + if (vlans_copy.find(vlan) == vlans_copy.end()) { + continue; + } + + vlan_member_config *msg = nullptr; + try { + msg = new vlan_member_config(); + } catch (const std::bad_alloc &e) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Memory allocation failed: %s", e.what()); + return; + } + + msg->vlan = vlan; + msg->interface = interface; + + if (operation == "SET") { + msg->is_add = true; + } else { + msg->is_add = false; + } + + event_config event; + event.type = DHCPv4_RELAY_VLAN_MEMBER_UPDATE; + event.msg = static_cast(msg); + + if (write(config_pipe[1], &event, sizeof(event)) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to send vlan member update for vlan %s", vlan.c_str()); + delete msg; + } + } +} + +void DHCPMgr::process_vlan_interface_notification(std::deque &entries) { + for (auto &entry : entries) { + std::string key = kfvKey(entry); + + std::string vlan; + std::string vrf; + size_t pos = key.find('|'); + if (pos == std::string::npos) { + vlan = key; + vrf = "default"; + for (auto &fv : kfvFieldsValues(entry)) { + if (fvField(fv) == "vrf_name") { + vrf = fvValue(fv); + break; + } + } + } else { + vlan = key.substr(0, pos); + } + + //If the vlan is not configured in DHCPV4 table then skip the entry. + if (vlans_copy.find(vlan) == vlans_copy.end()) { + continue; + } + + vlan_interface_config *msg = nullptr; + try { + msg = new vlan_interface_config(); + } catch (const std::bad_alloc &e) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Memory allocation failed: %s", e.what()); + return; + } + msg->vlan = vlan; + msg->vrf = vrf; + + event_config event; + event.type = DHCPv4_RELAY_VLAN_INTERFACE_UPDATE; + event.msg = static_cast(msg); + + if (write(config_pipe[1], &event, sizeof(event)) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to send vlan interface update for vlan %s", vlan.c_str()); + delete msg; + } + + } +} + +/** + * @brief Processes the dhcp_server table entry to form the dhcp_relay config. + * + * This method iterates over a deque of dhcp_server configuration entries, parses each entry, + * If the operation is "SET" then based on the "state" value it will proceed entry, + * it will adds the vlan and server IP to for the relay_config and send the event to main thread. + * If the server IP is not updated then it will get the entry from DB and fill it. + * related as it is the new configuration of the IP. + * If the Vlan is not present in the VLAN table then it will not send the config event to main thread. + * If the operation is "DEL" then it will send the delete entry event to the main thread. + * + * The method will handle the updating the vlan cache entries and also logs relevant information + * and errors using syslog. + * + * @param entries A deque of KeyOpFieldsValuesTuple objects representing dhcp_server table notifications. + */ +void DHCPMgr::process_dhcp_server_ipv4_notification(std::deque &entries) { + std::shared_ptr config_db = std::make_shared("CONFIG_DB", 0); + swss::Table vlan_tbl(config_db.get(), "VLAN"); + + for (auto &entry : entries) { + std::string vlan = kfvKey(entry); + std::string operation = kfvOp(entry); + + relay_config *relay_msg = nullptr; + try { + relay_msg = new relay_config(); + } catch (const std::bad_alloc &e) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Memory allocation failed: %s", e.what()); + return; + } + + relay_msg->vlan = vlan; + + if (operation == "SET") { + std::string state; + for (auto &fv : kfvFieldsValues(entry)) { + if (fvField(fv) == "state") { + state = fvValue(fv); + break; + } + } + + if (state == "enabled") { + if (global_dhcp_server_ip.empty()) { + std::shared_ptr state_db_ptr = std::make_shared("STATE_DB", 0); + swss::Table ip_tbl(state_db_ptr.get(), "DHCP_SERVER_IPV4_SERVER_IP"); + + std::string ip; + ip_tbl.hget("eth0", "ip", ip); + if (!ip.empty()) { + global_dhcp_server_ip = ip; + syslog(LOG_INFO, "[DHCPV4_RELAY] Fetched DHCPv4 server IP from STATE_DB: %s", ip.c_str()); + } else { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to get DHCPv4 server IP from STATE_DB"); + continue; + } + } + relay_msg->is_add = true; + relay_msg->servers.push_back(global_dhcp_server_ip); + relay_msg->vrf = "default"; + } else if (state == "disabled") { + relay_msg->is_add = false; //In case of modify in state field need to delete the entry + } + } else { + relay_msg->is_add = false; + } + + // Update the vlan cache entry + if (relay_msg->is_add) { + vlans_copy[relay_msg->vlan] = *relay_msg; + } else { + vlans_copy.erase(relay_msg->vlan); + } + + /*Validation to check vlan is present in VLAN table or not */ + std::string value; + if (!vlan_tbl.hget(vlan, "vlanid", value)) { + delete relay_msg; + continue; + } + + event_config event; + event.type = DHCPv4_SERVER_RELAY_CONFIG_UPDATE; + event.msg = static_cast(relay_msg); + + if (write(config_pipe[1], &event, sizeof(event)) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to send vlan table update for VLAN %s", vlan.c_str()); + delete relay_msg; + } + } +} + +/** + * @brief Processes the vlan table entry. + * + * This method iterates over a deque of vlan configuration entries, parses each entry, + * If any dhcp_relay/dhcp_server entry exists in the vlan cache list then based on the + * operation "SET" or "DEL" it will send the update event to main thread to process that entry. + * + * @param entries A deque of KeyOpFieldsValuesTuple objects representing vlan table notifications. + */ +void DHCPMgr::process_vlan_notification(std::deque &entries) { + for (auto &entry : entries) { + std::string vlan = kfvKey(entry); + std::string operation = kfvOp(entry); + + //If the vlan is not configured in DHCPV4 table then skip the entry. + if (vlans_copy.find(vlan) == vlans_copy.end()) { + continue; + } + + relay_config *relay_msg = nullptr; + try { + relay_msg = new relay_config(); + } catch (const std::bad_alloc &e) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Memory allocation failed: %s", e.what()); + return; + } + + relay_msg->vlan = vlan; + + if (operation == "SET") { + *relay_msg = vlans_copy[relay_msg->vlan]; + relay_msg->is_add = true; + } else { + relay_msg->is_add = false; + } + + event_config event; + if (feature_dhcp_server_enabled) { + event.type = DHCPv4_SERVER_RELAY_CONFIG_UPDATE; + } else { + event.type = DHCPv4_RELAY_CONFIG_UPDATE; + } + event.msg = static_cast(relay_msg); + + if (write(config_pipe[1], &event, sizeof(event)) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to send vlan update event for vlan %s", vlan.c_str()); + delete relay_msg; + } + } +} + +/** + * @code void DHCPMgr::stop_db_updates(); + * + * @brief Method to stop thread which will be listening to the DB updates.. + * + * @return none + */ + +void DHCPMgr::stop_db_updates() { + stop_thread = true; +} + +/** + * @code DHCPMgr::~DHCPMgr() + * + * @brief Destructor. + * + * @return none + */ DHCPMgr::~DHCPMgr() { - stop_thread = true; + stop_db_updates(); } diff --git a/dhcp4relay/src/dhcp4relay_mgr.h b/dhcp4relay/src/dhcp4relay_mgr.h index e60934d..da2d1ae 100644 --- a/dhcp4relay/src/dhcp4relay_mgr.h +++ b/dhcp4relay/src/dhcp4relay_mgr.h @@ -24,7 +24,17 @@ class DHCPMgr { void initialize_config_listner(); void handle_swss_notification(); + void stop_db_updates(); void process_relay_notification(std::deque &entries); void process_interface_notification(std::deque &entries); void process_device_metadata_notification(std::deque &entries); + void process_vlan_member_notification(std::deque &entries); + void process_vlan_interface_notification(std::deque &entries); + void process_feature_notification(std::deque &entries, + swss::Select &select, std::shared_ptr config_db_ptr, + std::shared_ptr state_db_ptr); + void process_dhcp_server_ipv4_ip_notification(std::deque &entries, + swss::Select &select, std::shared_ptr config_db_ptr); + void process_dhcp_server_ipv4_notification(std::deque &entries); + void process_vlan_notification(std::deque &entries); }; diff --git a/dhcp4relay/test/database_config.json b/dhcp4relay/test/database_config.json index b40e808..8ab44e2 100644 --- a/dhcp4relay/test/database_config.json +++ b/dhcp4relay/test/database_config.json @@ -12,6 +12,11 @@ } }, "DATABASES" : { + "COUNTERS_DB" : { + "id" : 2, + "separator": "|", + "instance" : "redis" + }, "CONFIG_DB" : { "id" : 4, "separator": "|", diff --git a/dhcp4relay/test/mock_relay.cpp b/dhcp4relay/test/mock_relay.cpp index 04f775f..15ee4da 100644 --- a/dhcp4relay/test/mock_relay.cpp +++ b/dhcp4relay/test/mock_relay.cpp @@ -23,11 +23,15 @@ using namespace swss; extern std::string host_mac_addr; extern std::string hostname; +extern uint32_t deployment_id; +extern bool feature_dhcp_server_enabled; +extern std::unordered_map vlans_copy; +extern std::string global_dhcp_server_ip; MOCK_GLOBAL_FUNC1(getifaddrs, int(struct ifaddrs **)); MOCK_GLOBAL_FUNC1(freeifaddrs, void(struct ifaddrs *)); MOCK_GLOBAL_FUNC3(write, ssize_t(int, const void*, size_t)); -MOCK_GLOBAL_FUNC4(send_udp, bool(int, uint8_t *, struct sockaddr_in, uint32_t)); +MOCK_GLOBAL_FUNC6(send_udp, bool(int, uint8_t *, struct sockaddr_in, uint32_t, in_addr, bool)); void encode_relay_option(pcpp::DhcpLayer *dhcp_pkt, relay_config *config); void to_client(pcpp::DhcpLayer* dhcp_pkt, std::unordered_map *vlans, @@ -176,7 +180,6 @@ TEST(prepareConfig, prepare_vlan_sockets) config.vlan = "Vlan200"; EXPECT_EQ(prepare_vlan_sockets(config), 0); EXPECT_GE(config.client_sock, 0); - EXPECT_GE(config.lo_sock, 0); } TEST(prepareConfig, prepare_vrf_sockets) { @@ -290,7 +293,6 @@ TEST(relayConfig, handle_vlan_events) { EXPECT_EQ(vrf_sock_map["default"].ref_count, 1); EXPECT_GE(vlans["Vlan200"].client_sock, 0); - EXPECT_GE(vlans["Vlan200"].lo_sock, 0); /*Vlan deletion.*/ relay_config *config_del = new relay_config(); @@ -298,7 +300,6 @@ TEST(relayConfig, handle_vlan_events) { config_del->is_add = false; vlans["Vlan200"].client_sock = -1; - vlans["Vlan200"].lo_sock = -1; vlans["Vlan200"].vrf_sock = -1; event.msg = static_cast(config_del); @@ -335,6 +336,13 @@ TEST(relayConfig, handle_interface_events) { ASSERT_TRUE(vlans.find("Vlan100") != vlans.end()); EXPECT_EQ(vlans["Vlan100"].src_intf_sel_addr.sin_family, AF_INET); EXPECT_EQ(vlans["Vlan100"].src_intf_sel_addr.sin_addr.s_addr, inet_addr("192.168.1.1")); + + event.type = DHCPv4_SERVER_FEATURE_UPDATE; + event.msg = NULL; + ASSERT_NE(write(pipe_fds[1], &event, sizeof(event)), -1); + + config_event_callback(pipe_fds[0], 0, &vlans); + close(pipe_fds[0]); close(pipe_fds[1]); } @@ -372,8 +380,20 @@ TEST(DHCPMgrTest, initialize_config_listner) { swss::Table loopback_intf_table(config_db.get(), "LOOPBACK_INTERFACE"); swss::Table portchannel_intf_table(config_db.get(), "PORTCHANNEL_INTERFACE"); swss::Table metadata_table(config_db.get(), "DEVICE_METADATA"); + swss::Table vlan_member_table(config_db.get(), "VLAN_MEMBER"); + swss::Table vlan_interface_table(config_db.get(), "VLAN_INTERFACE"); + + std::string vlan_member_key = "Vlan200|Ethernet8"; + std::vector> vlan_member_values = { + {"tagging_mode", "untagged"}, + }; + + std::string vlan_interface_key = "Vlan200|200.200.200.1/24"; + std::vector> vlan_interface_values = { + {"vrf_name", "VrfRed"}, + }; - std::string dhcp_key = "Vlan200"; + std::string vlan = "Vlan200"; std::vector> dhcp_values = { {"dhcpv4_servers", "1.1.1.1,1.1.1.2"}, {"server_vrf", "VrfRed"}, @@ -384,7 +404,7 @@ TEST(DHCPMgrTest, initialize_config_listner) { {"vrf_selection", "enable"} }; - dhcp_table.set(dhcp_key, dhcp_values); + dhcp_table.set(vlan, dhcp_values); std::string intf_key = "Ethernet4|192.168.1.1/24"; std::vector> intf_values = { @@ -403,14 +423,122 @@ TEST(DHCPMgrTest, initialize_config_listner) { metadata_table.set("host", metadata_values); metadata_table.set(metadata_key, metadata_values); + vlan_member_table.set(vlan_member_key, vlan_member_values); + vlan_interface_table.set(vlan_interface_key, intf_values); + vlan_interface_table.set(vlan, vlan_interface_values); + std::this_thread::sleep_for(std::chrono::seconds(1)); + dhcpMgr.stop_db_updates(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); +} - std::this_thread::sleep_for(std::chrono::seconds(2)); +TEST(DHCPMgrTest, process_vlan_events) { + DHCPMgr dhcpMgr; + EXPECT_GLOBAL_CALL(write, write(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(-1)); + vlans_copy.clear(); + relay_config *config = new relay_config(); + config->vlan = "Vlan100"; + config->is_add = true; + vlans_copy["Vlan100"] = *config; + std::deque entries; + entries.emplace_back("Vlan100", "SET", std::vector{}); + dhcpMgr.process_vlan_notification(entries); } -void mac_to_string(const uint8_t mac[6], char *out_str, char sep = ':') { - sprintf(out_str, "%02X%c%02X%c%02X%c%02X%c%02X%c%02X", - mac[0], sep, mac[1], sep, mac[2], sep, - mac[3], sep, mac[4], sep, mac[5]); +TEST(DHCPMgrTest, dhcp_server_feature_enable) { + DHCPMgr dhcpMgr; + EXPECT_GLOBAL_CALL(write, write(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(0)); + dhcpMgr.initialize_config_listner(); + + std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); + std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); + + swss::Table feature_table(config_db.get(), "FEATURE"); + swss::Table vlan_table(config_db.get(), "VLAN"); + swss::Table dhcp_server_table(config_db.get(), "DHCP_SERVER_IPV4"); + swss::Table dhcp_server_ip_table(state_db.get(), "DHCP_SERVER_IPV4_SERVER_IP"); + + std::string vlan = "Vlan200"; + std::vector> enable_dhcp_server = { + {"state", "enabled"}, + }; + + std::vector> server_ip_values = { + {"ip", "240.127.1.2"}, + }; + + std::vector> vlan_values = { + {"vlanid", "200"}, + }; + + vlan_table.set(vlan, vlan_values); + dhcp_server_ip_table.set("eth0", server_ip_values); + dhcp_server_table.set(vlan, enable_dhcp_server); + feature_table.set("dhcp_server", enable_dhcp_server); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + dhcpMgr.stop_db_updates(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + EXPECT_EQ(global_dhcp_server_ip, "240.127.1.2"); + feature_dhcp_server_enabled = false; + global_dhcp_server_ip.clear(); +} + +TEST(DHCPMgrTest, dhcp_server_feature_disable) { + DHCPMgr dhcpMgr; + EXPECT_GLOBAL_CALL(write, write(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(0)); + dhcpMgr.initialize_config_listner(); + std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); + + swss::Table feature_table(config_db.get(), "FEATURE"); + std::vector> disable_dhcp_server = { + {"state", "disabled"}, + }; + feature_dhcp_server_enabled = true; + feature_table.set("dhcp_server", disable_dhcp_server); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + dhcpMgr.stop_db_updates();; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + feature_dhcp_server_enabled = false; +} + +TEST(DHCPMgrTest, dhcp_server_ip_modification) { + DHCPMgr dhcpMgr; + EXPECT_GLOBAL_CALL(write, write(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(0)); + global_dhcp_server_ip = "240.127.1.3"; + std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); + std::deque entries; + swss::Select select; + entries.emplace_back("eth0", "SET", std::vector{ + {"ip", "240.127.1.2"} + }); + + dhcpMgr.process_dhcp_server_ipv4_ip_notification(entries, select, config_db); + EXPECT_EQ(global_dhcp_server_ip, "240.127.1.2"); +} + +TEST(DHCPMgrTest, dhcp_server_ip_deletion) { + DHCPMgr dhcpMgr; + EXPECT_GLOBAL_CALL(write, write(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(0)); + std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); + std::deque entries; + swss::Select select; + entries.emplace_back("eth0", "DEL", std::vector{}); + + dhcpMgr.process_dhcp_server_ipv4_ip_notification(entries, select,config_db); + EXPECT_TRUE(global_dhcp_server_ip.empty()); + EXPECT_TRUE(vlans_copy.empty()); } TEST(DHCPRelayTest, encode_relay_option) { @@ -534,7 +662,8 @@ TEST(DHCPRelayTest, to_client) { struct ifaddrs *mock_ifaddrs = CreateMockIfaddrs("192.168.1.1", "255.255.255.0", "Vlan100", "192.168.1.2", "Ethernet4"); EXPECT_GLOBAL_CALL(getifaddrs, getifaddrs(_)).WillOnce(DoAll(testing::SetArgPointee<0>(mock_ifaddrs), Return(0))); EXPECT_GLOBAL_CALL(freeifaddrs, freeifaddrs(_)).Times(1); - EXPECT_GLOBAL_CALL(send_udp, send_udp(_, _, _, _)).WillOnce([](int sock, uint8_t* hdr, struct sockaddr_in target, uint32_t n) { + EXPECT_GLOBAL_CALL(send_udp, send_udp(_, _, _, _, _, _)).WillOnce([] + (int sock, uint8_t* hdr, struct sockaddr_in target, uint32_t len, in_addr src_ip, bool use_src_ip) { pcpp::dhcp_header* dhcp_hdr = (pcpp::dhcp_header*)hdr; EXPECT_EQ((dhcp_hdr->opCode), 1); EXPECT_EQ((dhcp_hdr->hops), 1); @@ -581,7 +710,8 @@ TEST(DHCPRelayTest, from_client) { //struct ifaddrs *mock_ifaddrs = CreateMockIfaddrs("192.168.1.1", "255.255.255.0", "Vlan100", "192.168.1.2", "Ethernet4"); //EXPECT_GLOBAL_CALL(getifaddrs, getifaddrs(_)).WillOnce(DoAll(testing::SetArgPointee<0>(mock_ifaddrs), Return(0))); //EXPECT_GLOBAL_CALL(freeifaddrs, freeifaddrs(_)).Times(1); - EXPECT_GLOBAL_CALL(send_udp, send_udp(_, _, _, _)).WillOnce([](int sock, uint8_t* hdr, struct sockaddr_in target, uint32_t n) { + EXPECT_GLOBAL_CALL(send_udp, send_udp(_, _, _, _, _, _)).WillOnce([] + (int sock, uint8_t* hdr, struct sockaddr_in target, uint32_t len, in_addr src_ip, bool use_src_ip) { pcpp::dhcp_header* dhcp_hdr = (pcpp::dhcp_header*)hdr; EXPECT_EQ((dhcp_hdr->opCode), 0); EXPECT_EQ((dhcp_hdr->hops), 1); diff --git a/dhcp4relay/test/mock_relay_stats.cpp b/dhcp4relay/test/mock_relay_stats.cpp index 4274198..60e944f 100644 --- a/dhcp4relay/test/mock_relay_stats.cpp +++ b/dhcp4relay/test/mock_relay_stats.cpp @@ -174,8 +174,8 @@ TEST_F(DHCPCounter_table_test, DBUpdate_loop_integration) { // Stop the updates counter_table->stop_db_updates(); - std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); - swss::Table cntr_table(state_db.get(), "DHCPV4_COUNTER_TABLE"); + std::shared_ptr cntrs_db = std::make_shared ("COUNTERS_DB", 0); + swss::Table cntr_table(cntrs_db.get(), "COUNTERS_DHCPV4"); std::vector existing_rx_fields; std::vector existing_tx_fields; cntr_table.get(interface+"|RX", existing_rx_fields); From 7a886d7d17165db0d8249446e04589f374335523 Mon Sep 17 00:00:00 2001 From: Akshath TR Date: Mon, 21 Jul 2025 10:05:27 -0700 Subject: [PATCH 04/17] Addressing upstream review comments --- dhcp4relay/Makefile | 32 ++- dhcp4relay/src/dhcp4relay.cpp | 54 +++- dhcp4relay/src/dhcp4relay.h | 11 +- dhcp4relay/src/dhcp4relay_mgr.cpp | 44 ++++ dhcp4relay/src/dhcp4relay_mgr.h | 1 + dhcp4relay/src/dhcp4relay_stats.cpp | 78 +++--- dhcp4relay/test/mock_relay.cpp | 243 ++++++++++++++++-- dhcp4relay/test/mock_relay.h | 2 + ...-dhcpv4-relay-accept-random-src-port.patch | 15 ++ patch/series | 1 + pcappp_v24.09.zip | Bin 0 -> 16988791 bytes 11 files changed, 406 insertions(+), 75 deletions(-) create mode 100644 patch/0001-dhcpv4-relay-accept-random-src-port.patch create mode 100644 patch/series create mode 100644 pcappp_v24.09.zip diff --git a/dhcp4relay/Makefile b/dhcp4relay/Makefile index 2cc03de..98ca677 100644 --- a/dhcp4relay/Makefile +++ b/dhcp4relay/Makefile @@ -17,8 +17,8 @@ LD_PCAPPLUSPLUS_LIB := -lPcap++ -lPacket++ -lCommon++ #pcap plus plus zip file pcappp_v24.09.zip PCAPPPVAR := 24.09 -PCAPPP_URL = https://github.com/seladb/PcapPlusPlus/releases/download/v${PCAPPPVAR}/pcapplusplus-${PCAPPPVAR}-ubuntu-20.04-gcc-9.4.0-x86_64.tar.gz -PCAPPLUSPLUS_DIR := $(WORKING_DIR)/pcapplusplus-$(PCAPPPVAR)-ubuntu-20.04-gcc-9.4.0-x86_64 +PCAPPPZIP_FILE := pcappp_v${PCAPPPVAR}.zip +PCAPPLUSPLUS_DIR := $(WORKING_DIR)/PcapPlusPlus-$(PCAPPPVAR) PCAPPP_DONE = $(WORKING_DIR)/pcappp.stamp INCLUDE_DIR = $(PCAPPLUSPLUS_DIR)/include LIB_DIR = $(PCAPPLUSPLUS_DIR)/lib @@ -33,10 +33,28 @@ PWD := $(shell pwd) .PHONY: $(PCAPPP_DONE) $(PCAPPP_DONE): - wget $(PCAPPP_URL) -O pcapplusplus.tar.gz - tar -xzf pcapplusplus.tar.gz - touch $@ - + + # Remove stale files + rm -rf $(PCAPPLUSPLUS_DIR) + + unzip ${PCAPPPZIP_FILE} + pushd $(PCAPPLUSPLUS_DIR) + + # Create a git repository here for stg to apply patches + git init + git add -f * + git commit -qm "initial commit" + + # Apply patches + stg init + stg import -s ../patch/series + popd + + cd $(PCAPPLUSPLUS_DIR) && cmake -S . -B build && cmake --build build + cd build && make && sudo cmake --install . + + touch $@ + all: $(DHCP4RELAY_TARGET) $(DHCP4RELAY_TEST_TARGET) -include src/subdir.mk @@ -79,7 +97,7 @@ uninstall: $(RM) $(DESTDIR)/usr/sbin/$(notdir $(DHCP4RELAY_TARGET)) clean: - -$(RM) $(BUILD_DIR) $(BUILD_TEST_DIR) *.html *.xml pcap* + -$(RM) $(BUILD_DIR) $(BUILD_TEST_DIR) *.html *.xml $(FIND) . -name *.gcda -exec rm -f {} \; $(FIND) . -name *.gcno -exec rm -f {} \; $(FIND) . -name *.gcov -exec rm -f {} \; diff --git a/dhcp4relay/src/dhcp4relay.cpp b/dhcp4relay/src/dhcp4relay.cpp index e14c272..e0a8e64 100644 --- a/dhcp4relay/src/dhcp4relay.cpp +++ b/dhcp4relay/src/dhcp4relay.cpp @@ -24,7 +24,6 @@ struct event_base *base; struct event *ev_sigint; struct event *ev_sigterm; static std::string vlan_member = "VLAN_MEMBER|"; -static std::string counter_table = "DHCPv4_COUNTER_TABLE|"; extern std::string host_mac_addr; extern std::string hostname; @@ -463,11 +462,6 @@ void encode_relay_option(pcpp::DhcpLayer *dhcp_pkt, relay_config *config) { std::string intf_alias; if (phy_interface_alias_map.find(config->phy_interface) != phy_interface_alias_map.end()) { intf_alias = phy_interface_alias_map[config->phy_interface]; - } else { - std::shared_ptr port_tbl = - std::make_shared(config->config_db.get(), CFG_PORT_TABLE_NAME); - port_tbl->hget(config->phy_interface.c_str(), "alias", intf_alias); - phy_interface_alias_map[config->phy_interface] = intf_alias; } /* Encode circuit ID sub-option */ @@ -508,7 +502,9 @@ void encode_relay_option(pcpp::DhcpLayer *dhcp_pkt, relay_config *config) { /* Encode VSS sub-option 151 if client is not default VRF */ /* | 151 | vrf_len | 0 | vrf_name | */ uint8_t vss_buf[32] = {0}; - if (config->vrf_selection_opt == "enable") { + /* Enable VSS only if client and server are in two different VRF's */ + if ((config->vrf_selection_opt == "enable") && (vrf != "default") && + (config->vrf != vrf)) { uint8_t zero_encode = 0; memcpy(vss_buf, &zero_encode, sizeof(uint8_t)); memcpy((vss_buf + 1), (uint8_t *)vrf.c_str(), (uint8_t)vrf.length()); @@ -580,8 +576,16 @@ void from_client(pcpp::DhcpLayer *dhcp_pkt, relay_config &config) { } } + /* Drop the packet if the hop count exceeds the configured maximum. */ + if (dhcp_pkt->getDhcpHeader()->hops >= config.max_hop_count) { + syslog(LOG_NOTICE, "[DHCPV4_RELAY] Dropping packet: hop count %d exceeds max allowed %d\n", + dhcp_pkt->getDhcpHeader()->hops, config.max_hop_count); + // increment drop counter + dhcp_cntr_table.increment_counter(config.vlan, "TX", DHCPv4_MESSAGE_TYPE_DROP); + return; + } + /* Increase the hop count */ - /*TODO: check hops count is less than max hops count allowed */ dhcp_pkt->getDhcpHeader()->hops = dhcp_pkt->getDhcpHeader()->hops + 1; int sock = config.vrf_sock; uint32_t index = 0; @@ -611,17 +615,28 @@ void from_client(pcpp::DhcpLayer *dhcp_pkt, relay_config &config) { uint8_t *decode_tlv(const uint8_t *buf, uint8_t t, uint8_t &l, uint32_t options_total_size) { uint8_t *temp = (uint8_t *)buf; + uint32_t offset = 0; uint8_t len = 0; - while (temp && (len < options_total_size)) { + while (temp && ((offset + DHCP_SUB_OPT_TLV_HEADER_LEN) <= options_total_size)) { len = *(temp + DHCP_SUB_OPT_TLV_LENGTH_OFFSET); + if ((offset + DHCP_SUB_OPT_TLV_LENGTH_OFFSET + len) > options_total_size) { + /* Malformed packet */ + syslog(LOG_ERR, "[DHCPV4_INFO] Failed to decode realy agent sub-option %d" + " exceeded total option len %d offset %d sub-option len %d\n", + t, options_total_size, offset, len); + l = 0; + return NULL; + } if (t == *temp) { syslog(LOG_INFO, "[DHCPV4_INFO] Decoding realy agent sub-option %d of len %d\n", t, len); l = len; return (temp + DHCP_SUB_OPT_TLV_HEADER_LEN); } + offset += (len + DHCP_SUB_OPT_TLV_HEADER_LEN); temp += (len + DHCP_SUB_OPT_TLV_HEADER_LEN); } + l = 0; return NULL; } @@ -889,7 +904,7 @@ void pkt_in_callback(evutil_socket_t fd, short event, void *arg) { auto itr = std::find(interface_list.begin(), interface_list.end(), intf); /* To avoid duplicate packets, we are only processing packets from interface in PORT_TABLE and packets from VXLAN interface */ - if ((itr == interface_list.end()) && (intf.find("VXLAN") != std::string::npos)) { + if ((itr == interface_list.end()) && (intf.rfind("VXLAN", 0) != 0)) { continue; } @@ -980,6 +995,7 @@ void pkt_in_callback(evutil_socket_t fd, short event, void *arg) { if (vlan_str.empty()) { continue; } + auto config_itr = vlans->find(vlan_str); if (config_itr == vlans->end()) { syslog(LOG_WARNING, "[DHCPV4_RELAY] Config not found for vlan %s\n", intf.c_str()); @@ -1339,6 +1355,24 @@ void config_event_callback(evutil_socket_t fd, short event, void *arg) { } delete relay_msg; } + } else if (received_event.type == DHCPv4_RELAY_PORT_UPDATE) { + port_config *port_msg = static_cast(received_event.msg); + if (port_msg) { + syslog(LOG_INFO, "[DHCPV4_RELAY] Updating interface %s event", port_msg->phy_interface.c_str()); + if (port_msg->is_add) { + if (std::find(interface_list.begin(), interface_list.end(), port_msg->phy_interface) == interface_list.end()) { + interface_list.push_back(port_msg->phy_interface); + } + phy_interface_alias_map[port_msg->phy_interface] = port_msg->alias; + } else { + auto it = std::find(interface_list.begin(), interface_list.end(), port_msg->phy_interface); + if (it != interface_list.end()) { + interface_list.erase(it); + } + phy_interface_alias_map.erase(port_msg->phy_interface); + } + delete port_msg; + } } } else { syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to read config update: expected %lu bytes, got %zd bytes", sizeof(received_event), bytes_read); diff --git a/dhcp4relay/src/dhcp4relay.h b/dhcp4relay/src/dhcp4relay.h index 90c8200..2577801 100644 --- a/dhcp4relay/src/dhcp4relay.h +++ b/dhcp4relay/src/dhcp4relay.h @@ -31,6 +31,7 @@ #define MAX_DHCP_PKT_SIZE 1472 // 1500 - (IP + UDP)headers #define MAC_ADDR_STR_LEN 17 #define DHCP_MAGIC_NUMBER 0x63538263 +#define MAX_HOP_COUNT 16 #define BOOTPREQUEST 1 #define BOOTPREPLY 2 @@ -112,6 +113,7 @@ struct relay_config { std::string server_id_override_opt; std::string vrf_selection_opt; std::string agent_relay_mode; + uint8_t max_hop_count = MAX_HOP_COUNT; std::vector servers; std::vector servers_sock; bool is_interface_id; @@ -129,7 +131,8 @@ typedef enum { DHCPv4_SERVER_FEATURE_UPDATE, DHCPv4_SERVER_IP_UPDATE, DHCPv4_SERVER_IP_DELETE, - DHCPv4_RELAY_DUAL_TOR_UPDATE + DHCPv4_RELAY_DUAL_TOR_UPDATE, + DHCPv4_RELAY_PORT_UPDATE } event_type; struct event_config { @@ -148,6 +151,12 @@ struct vlan_interface_config { std::string vrf; }; +struct port_config { + std::string phy_interface; + std::string alias; + bool is_add; +}; + /** * @code sock_open(const struct sock_fprog *fprog); * diff --git a/dhcp4relay/src/dhcp4relay_mgr.cpp b/dhcp4relay/src/dhcp4relay_mgr.cpp index a1551c6..c92b47d 100644 --- a/dhcp4relay/src/dhcp4relay_mgr.cpp +++ b/dhcp4relay/src/dhcp4relay_mgr.cpp @@ -67,6 +67,7 @@ void DHCPMgr::handle_swss_notification() { swss::SubscriberStateTable config_db_vlan_table(config_db_ptr.get(), "VLAN"); config_db_dhcp_server_ipv4_ptr = std::make_shared(config_db_ptr.get(), "DHCP_SERVER_IPV4"); state_db_dhcp_server_ipv4_ip_ptr = std::make_shared(state_db_ptr.get(), "DHCP_SERVER_IPV4_SERVER_IP"); + swss::SubscriberStateTable config_db_port_table(config_db_ptr.get(), "PORT"); std::deque entries; swss::Select swss_select; @@ -81,6 +82,7 @@ void DHCPMgr::handle_swss_notification() { swss_select.addSelectable(&config_db_vlan_table); swss_select.addSelectable(config_db_dhcp_server_ipv4_ptr.get()); swss_select.addSelectable(state_db_dhcp_server_ipv4_ip_ptr.get()); + swss_select.addSelectable(&config_db_port_table); while (!stop_thread) { swss::Selectable *selectable; @@ -135,6 +137,9 @@ void DHCPMgr::handle_swss_notification() { } else if (selectable == static_cast(&config_db_vlan_table)) { config_db_vlan_table.pops(entries); process_vlan_notification(entries); + } else if (selectable == static_cast(&config_db_port_table)) { + config_db_port_table.pops(entries); + process_port_notification(entries); } } } @@ -345,6 +350,8 @@ void DHCPMgr::process_relay_notification(std::dequevrf_selection_opt = v; } else if (f == "agent_relay_mode") { relay_msg->agent_relay_mode = v; + } else if (f == "max_hop_count") { + relay_msg->max_hop_count = static_cast(std::stoi(v)); } syslog(LOG_DEBUG, "[DHCPV4_RELAY] key: %s, Operation: %s, f: %s, v: %s", vlan.c_str(), operation.c_str(), f.c_str(), v.c_str()); } @@ -784,6 +791,43 @@ void DHCPMgr::process_vlan_notification(std::deque } } +void DHCPMgr::process_port_notification(std::deque &entries) { + for (auto &entry : entries) { + std::string interface = kfvKey(entry); + std::string operation = kfvOp(entry); + std::vector fv = kfvFieldsValues(entry); + port_config *port_msg = nullptr; + try { + port_msg = new port_config(); + } catch (const std::bad_alloc &e) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Memory allocation failed: %s", e.what()); + return; + } + + port_msg->phy_interface = interface; + if (operation == "SET") { + port_msg->is_add = true; + for (auto &fv : kfvFieldsValues(entry)) { + if (fvField(fv) == "alias") { + port_msg->alias = fvValue(fv); + break; + } + } + } else { + port_msg->is_add = false; + } + + event_config event; + event.type = DHCPv4_RELAY_PORT_UPDATE; + event.msg = static_cast(port_msg); + + if (write(config_pipe[1], &event, sizeof(event)) == -1) { + syslog(LOG_ERR, "[DHCPV4_RELAY] Failed to send port table update for interface %s", interface.c_str()); + delete port_msg; + } + } +} + /** * @code void DHCPMgr::stop_db_updates(); * diff --git a/dhcp4relay/src/dhcp4relay_mgr.h b/dhcp4relay/src/dhcp4relay_mgr.h index da2d1ae..15065d4 100644 --- a/dhcp4relay/src/dhcp4relay_mgr.h +++ b/dhcp4relay/src/dhcp4relay_mgr.h @@ -37,4 +37,5 @@ class DHCPMgr { swss::Select &select, std::shared_ptr config_db_ptr); void process_dhcp_server_ipv4_notification(std::deque &entries); void process_vlan_notification(std::deque &entries); + void process_port_notification(std::deque &entries); }; diff --git a/dhcp4relay/src/dhcp4relay_stats.cpp b/dhcp4relay/src/dhcp4relay_stats.cpp index 778bcaa..3c52dfd 100644 --- a/dhcp4relay/src/dhcp4relay_stats.cpp +++ b/dhcp4relay/src/dhcp4relay_stats.cpp @@ -48,6 +48,41 @@ std::unordered_map DHCPCounter_table::get_counters_da return interfaces_cntr_table; } +/** + * @brief Helper function to update RX/TX counters in the DB for a given interface and direction. + * + * @param cntr_table Shared pointer to the swss::Table for updating the DB. + * @param interface Name of the interface. + * @param direction "RX" or "TX". + * @param counters_map Reference to the map of counters (either RX or TX). + */ +void update_interface_counters_in_db( + std::shared_ptr cntr_table, + const std::string& interface, + const std::string& direction, + const std::unordered_map counters_map) +{ + std::vector existing_fields; + std::string key = interface + swss::TableBase::getTableSeparator(COUNTERS_DB) + direction; + cntr_table->get(key, existing_fields); + + std::vector fields; + std::unordered_map counter_map; + + // Populate existing counters + for (const auto& field : existing_fields) { + counter_map[fvField(field)] = std::stoi(fvValue(field)); + } + + // Update with new values + for (const auto& [type, value] : counters_map) { + counter_map[type] += value; + fields.emplace_back(type, std::to_string(counter_map[type])); + } + + cntr_table->set(key, fields); +} + /** * @code DHCPCounter_table::db_update_loop(); * @@ -79,47 +114,8 @@ void DHCPCounter_table::db_update_loop() { 3. Populate to DB */ for (const auto& [interface, counters] : interfaces_copy) { - // RX counters - std::vector existing_rx_fields; - std::string rx_key = interface + "|RX"; - cntr_table->get(rx_key, existing_rx_fields); - - std::vector rx_fields; - std::unordered_map rx_counter_map; - - // Populate existing counters - for (const auto& field : existing_rx_fields) { - rx_counter_map[fvField(field)] = std::stoi(fvValue(field)); - } - - // Update with new values - for (const auto& [type, value] : counters.RX) { - rx_counter_map[type] += value; - rx_fields.emplace_back(type, std::to_string(rx_counter_map[type])); - } - - cntr_table->set(rx_key, rx_fields); - - // TX counters (similar logic) - std::vector existing_tx_fields; - std::string tx_key = interface + "|TX"; - cntr_table->get(tx_key, existing_tx_fields); - - std::vector tx_fields; - std::unordered_map tx_counter_map; - - // Populate existing counters - for (const auto& field : existing_tx_fields) { - tx_counter_map[fvField(field)] = std::stoi(fvValue(field)); - } - - // Update with new values - for (const auto& [type, value] : counters.TX) { - tx_counter_map[type] += value; - tx_fields.emplace_back(type, std::to_string(tx_counter_map[type])); - } - - cntr_table->set(tx_key, tx_fields); + update_interface_counters_in_db(cntr_table, interface, "RX", counters.RX); + update_interface_counters_in_db(cntr_table, interface, "TX", counters.TX); } // Update local changes after syncing to Redis diff --git a/dhcp4relay/test/mock_relay.cpp b/dhcp4relay/test/mock_relay.cpp index 15ee4da..54a6fd5 100644 --- a/dhcp4relay/test/mock_relay.cpp +++ b/dhcp4relay/test/mock_relay.cpp @@ -337,16 +337,157 @@ TEST(relayConfig, handle_interface_events) { EXPECT_EQ(vlans["Vlan100"].src_intf_sel_addr.sin_family, AF_INET); EXPECT_EQ(vlans["Vlan100"].src_intf_sel_addr.sin_addr.s_addr, inet_addr("192.168.1.1")); + event.type = DHCPv4_SERVER_IP_UPDATE; + global_dhcp_server_ip = "192.168.1.1"; + ASSERT_NE(write(pipe_fds[1], &event, sizeof(event)), -1); + config_event_callback(pipe_fds[0], 0, &vlans); + + EXPECT_EQ(vlans["Vlan100"].servers_sock[0].sin_family, AF_INET); + EXPECT_EQ(vlans["Vlan100"].servers_sock[0].sin_port, htons(67)); + EXPECT_EQ(vlans["Vlan100"].servers_sock[0].sin_addr.s_addr, inet_addr("192.168.1.1")); + event.type = DHCPv4_SERVER_FEATURE_UPDATE; event.msg = NULL; + ASSERT_NE(write(pipe_fds[1], &event, sizeof(event)), -1); + config_event_callback(pipe_fds[0], 0, &vlans); + + close(pipe_fds[0]); + close(pipe_fds[1]); +} + +TEST(relayConfig, handle_vlan_member_events) { + int pipe_fds[2]; + EXPECT_GLOBAL_CALL(write, write(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(RealWrite)); + ASSERT_NE(pipe(pipe_fds), -1); + + std::unordered_map vlans; + vlans["Vlan100"].vlan = "Vlan100"; + vlans["Vlan100"].client_sock = -1; + vlans["Vlan100"].is_add = true; + + vlan_member_config *vlan_config = new vlan_member_config(); + + vlan_config->is_add = true; + vlan_config->interface = "Ethernet12"; + vlan_config->vlan = "Vlan100"; + + event_config event; + event.type = DHCPv4_RELAY_VLAN_MEMBER_UPDATE; + event.msg = static_cast(vlan_config); + + ASSERT_NE(write(pipe_fds[1], &event, sizeof(event)), -1); + + config_event_callback(pipe_fds[0], 0, &vlans); + + EXPECT_EQ(vlan_map["Ethernet12"], "Vlan100"); + EXPECT_GE(vlans["Vlan100"].client_sock, 0); + + vlan_member_config *vlan_config_del = new vlan_member_config(); + + vlans["Vlan100"].client_sock = -1; + vlan_config_del->is_add = false ; + vlan_config_del->interface = "Ethernet12"; + vlan_config_del->vlan = "Vlan100"; + + event.msg = static_cast(vlan_config_del); + + ASSERT_NE(write(pipe_fds[1], &event, sizeof(event)), -1); + + config_event_callback(pipe_fds[0], 0, &vlans); + + EXPECT_NE(vlan_map["Ethernet12"], "Vlan100"); + EXPECT_GE(vlans["Vlan100"].client_sock, 0); + + close(pipe_fds[0]); + close(pipe_fds[1]); +} + +TEST(relayConfig, handle_vlan_interface_events) { + int pipe_fds[2]; + EXPECT_GLOBAL_CALL(write, write(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(RealWrite)); + ASSERT_NE(pipe(pipe_fds), -1); + + std::unordered_map vlans; + vlans["Vlan100"].vlan = "Vlan100"; + vlans["Vlan100"].is_add = true; + + vlan_interface_config *vlan_config = new vlan_interface_config(); + + vlan_config->vlan = "Vlan100"; + vlan_config->vrf = "VrfRed"; + + event_config event; + event.type = DHCPv4_RELAY_VLAN_INTERFACE_UPDATE; + event.msg = static_cast(vlan_config); + ASSERT_NE(write(pipe_fds[1], &event, sizeof(event)), -1); config_event_callback(pipe_fds[0], 0, &vlans); + EXPECT_EQ(vlan_vrf_map["Vlan100"], "VrfRed"); + + vlan_interface_config *vlan_intf_config = new vlan_interface_config(); + + vlans["Vlan100"].client_sock = -1; + vlan_intf_config->vlan = "Vlan100"; + + event.msg = static_cast(vlan_intf_config); + + ASSERT_NE(write(pipe_fds[1], &event, sizeof(event)), -1); + + config_event_callback(pipe_fds[0], 0, &vlans); + + EXPECT_GE(vlans["Vlan100"].client_sock, 0); + close(pipe_fds[0]); close(pipe_fds[1]); } +TEST(relayConfig, handle_port_table_events) { + int pipe_fds[2]; + EXPECT_GLOBAL_CALL(write, write(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(RealWrite)); + ASSERT_NE(pipe(pipe_fds), -1); + std::unordered_map vlans; + + port_config *port_msg = new port_config(); + + port_msg->phy_interface = "Ethernet12"; + port_msg->alias = "eth12"; + port_msg->is_add = true; + + event_config event; + event.type = DHCPv4_RELAY_PORT_UPDATE; + event.msg = static_cast(port_msg); + + ASSERT_NE(write(pipe_fds[1], &event, sizeof(event)), -1); + + config_event_callback(pipe_fds[0], 0, &vlans); + + EXPECT_EQ(interface_list[0], "Ethernet12"); + EXPECT_EQ(phy_interface_alias_map["Ethernet12"], "eth12"); + + port_config *port_msg_del = new port_config(); + + port_msg_del->phy_interface = "Ethernet12"; + port_msg_del->is_add = false; + + event.msg = static_cast(port_msg_del); + + ASSERT_NE(write(pipe_fds[1], &event, sizeof(event)), -1); + + config_event_callback(pipe_fds[0], 0, &vlans); + EXPECT_EQ(std::find(interface_list.begin(), interface_list.end(), "Ethernet12"), interface_list.end()); + EXPECT_EQ(phy_interface_alias_map.count("Ethernet12"), 0); + + close(pipe_fds[0]); + close(pipe_fds[1]); +} TEST(relay, signal_init) { signal_init(); EXPECT_NE((uintptr_t)ev_sigint, NULL); @@ -382,6 +523,7 @@ TEST(DHCPMgrTest, initialize_config_listner) { swss::Table metadata_table(config_db.get(), "DEVICE_METADATA"); swss::Table vlan_member_table(config_db.get(), "VLAN_MEMBER"); swss::Table vlan_interface_table(config_db.get(), "VLAN_INTERFACE"); + swss::Table port_table(config_db.get(), "PORT"); std::string vlan_member_key = "Vlan200|Ethernet8"; std::vector> vlan_member_values = { @@ -401,7 +543,8 @@ TEST(DHCPMgrTest, initialize_config_listner) { {"agent_relay_mode", "discard"}, {"link_selection", "enable"}, {"server_id_override", "enable"}, - {"vrf_selection", "enable"} + {"vrf_selection", "enable"}, + {"max_hop_count", "16"} }; dhcp_table.set(vlan, dhcp_values); @@ -421,14 +564,19 @@ TEST(DHCPMgrTest, initialize_config_listner) { {"mac", "00:11:22:33:44:55"} }; + std::vector> port_values = { + {"alias", "eth12"}, + }; metadata_table.set("host", metadata_values); metadata_table.set(metadata_key, metadata_values); vlan_member_table.set(vlan_member_key, vlan_member_values); vlan_interface_table.set(vlan_interface_key, intf_values); vlan_interface_table.set(vlan, vlan_interface_values); + port_table.set("Ethernet12", port_values); std::this_thread::sleep_for(std::chrono::seconds(1)); dhcpMgr.stop_db_updates(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_EQ(vlans_copy[vlan].max_hop_count, 16); } TEST(DHCPMgrTest, process_vlan_events) { @@ -557,11 +705,8 @@ TEST(DHCPRelayTest, encode_relay_option) { pcpp::DhcpLayer dhcpLayer(pcpp::DHCP_DISCOVER, clientMac); dhcpLayer.getDhcpHeader()->hops = 1; - Table portTbl(config_db.get(), CFG_PORT_TABLE_NAME); - - std::vector fvVector; - fvVector.emplace_back("alias", "eth12"); - portTbl.set("Ethernet12", fvVector); + interface_list.push_back("Ethernet12"); + phy_interface_alias_map["Ethernet12"] = "eth12"; relay_config config = {}; config.config_db = config_db; @@ -619,6 +764,78 @@ TEST(DHCPRelayTest, encode_relay_option) { EXPECT_EQ(memcmp(vss_buf, vrf_ptr, 6), 0); } +TEST(DHCPRelayTest, encode_relay_option_server_client_same_vrf) { + std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); + pcpp::EthLayer ethLayer(pcpp::MacAddress("00:13:72:25:fa:cd"), pcpp::MacAddress("00:e0:b1:49:39:02")); + + pcpp::IPv4Address srcIp("172.22.178.234"); + pcpp::IPv4Address dstIp("10.10.8.240"); + pcpp::IPv4Layer ipLayer(srcIp, dstIp); + ipLayer.getIPv4Header()->ipId = htobe16(20370); + ipLayer.getIPv4Header()->timeToLive = 128; + + pcpp::UdpLayer udpLayer((uint16_t)67, (uint16_t)67); + + pcpp::MacAddress clientMac(std::string("00:0e:86:11:c0:75")); + pcpp::DhcpLayer dhcpLayer(pcpp::DHCP_DISCOVER, clientMac); + dhcpLayer.getDhcpHeader()->hops = 1; + + interface_list.push_back("Ethernet12"); + phy_interface_alias_map["Ethernet12"] = "eth12"; + + relay_config config = {}; + config.config_db = config_db; + config.phy_interface = "Ethernet12"; + config.vlan = "Vlan10"; + config.vrf = "Vrf01"; + config.link_selection_opt = "enable"; + config.server_id_override_opt = "enable"; + config.link_address.sin_addr.s_addr = inet_addr("192.168.10.10"); + config.link_address_netmask.sin_addr.s_addr = inet_addr("255.255.255.0"); + config.vrf_selection_opt = "enable"; + vlan_vrf_map["Vlan10"] = "Vrf01"; + + hostname = "cisco"; + host_mac_addr = "12:32:54:24:95:36"; + + encode_relay_option(&dhcpLayer, &config); + + auto agent_option = dhcpLayer.getOptionData(pcpp::DHCPOPT_DHCP_AGENT_OPTIONS); + auto options_ptr = agent_option.getValue(); + auto agent_option_size = agent_option.getDataSize(); + EXPECT_NE((uintptr_t)options_ptr, NULL); + + uint8_t circuit_id_len = 0; + auto circuit_id_ptr = decode_tlv((const uint8_t *)options_ptr, OPTION82_SUBOPT_CIRCUIT_ID, + circuit_id_len, agent_option_size); + std::string circuit_id((const char*)circuit_id_ptr, circuit_id_len); + + EXPECT_EQ(circuit_id, "cisco:eth12:Vlan10"); + uint8_t remote_id_len = 0; + auto remote_id_ptr = decode_tlv((const uint8_t *)options_ptr, OPTION82_SUBOPT_REMOTE_ID, + remote_id_len, agent_option_size); + + EXPECT_EQ(memcmp(host_mac_addr.c_str(), remote_id_ptr, 17), 0); + + uint8_t link_sel_len = 0; + auto link_sel_ip_ptr = decode_tlv((const uint8_t *)options_ptr, OPTION82_SUBOPT_LINK_SELECTION, + link_sel_len, agent_option_size); + auto link_sel_ip = *((uint32_t *)link_sel_ip_ptr); + EXPECT_EQ((config.link_address.sin_addr.s_addr & config.link_address_netmask.sin_addr.s_addr), link_sel_ip); + + auto srv_ovr_ride = decode_tlv((const uint8_t *)options_ptr, OPTION82_SUBOPT_SERVER_OVERRIDE, + link_sel_len, agent_option_size); + auto srv_ip = *((uint32_t *)srv_ovr_ride); + EXPECT_EQ(srv_ip, config.link_address.sin_addr.s_addr); + + uint8_t vrf_len = 0; + uint8_t *vrf_ptr = NULL; + vrf_ptr = decode_tlv((const uint8_t *)options_ptr, OPTION82_SUBOPT_VIRTUAL_SUBNET, + vrf_len, agent_option_size); + + EXPECT_EQ((uintptr_t)vrf_ptr, NULL); +} + TEST(DHCPRelayTest, to_client) { std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); pcpp::EthLayer ethLayer(pcpp::MacAddress("00:13:72:25:fa:cd"), pcpp::MacAddress("00:e0:b1:49:39:02")); @@ -638,11 +855,8 @@ TEST(DHCPRelayTest, to_client) { dhcpLayer.getDhcpHeader()->gatewayIpAddress = inet_addr("192.168.1.1"); dhcpLayer.getDhcpHeader()->opCode = 1; - Table portTbl(config_db.get(), CFG_PORT_TABLE_NAME); - - std::vector fvVector; - fvVector.emplace_back("alias", "eth12"); - portTbl.set("Ethernet12", fvVector); + interface_list.push_back("Ethernet12"); + phy_interface_alias_map["Ethernet12"] = "eth12"; relay_config config = {}; config.config_db = config_db; @@ -682,11 +896,8 @@ TEST(DHCPRelayTest, from_client) { dhcpLayer.getDhcpHeader()->gatewayIpAddress = inet_addr("192.168.1.1"); dhcpLayer.getDhcpHeader()->opCode = 0; - Table portTbl(config_db.get(), CFG_PORT_TABLE_NAME); - - std::vector fvVector; - fvVector.emplace_back("alias", "eth12"); - portTbl.set("Ethernet12", fvVector); + interface_list.push_back("Ethernet12"); + phy_interface_alias_map["Ethernet12"] = "eth12"; relay_config config = {}; config.config_db = config_db; diff --git a/dhcp4relay/test/mock_relay.h b/dhcp4relay/test/mock_relay.h index 4aa7465..386dc41 100644 --- a/dhcp4relay/test/mock_relay.h +++ b/dhcp4relay/test/mock_relay.h @@ -15,3 +15,5 @@ extern std::unordered_map vlan_map; extern std::unordered_map vlan_vrf_map; extern swss::Select swssSelect; extern std::unordered_map vrf_sock_map; +extern std::unordered_map phy_interface_alias_map; +extern std::vector interface_list; diff --git a/patch/0001-dhcpv4-relay-accept-random-src-port.patch b/patch/0001-dhcpv4-relay-accept-random-src-port.patch new file mode 100644 index 0000000..f0729b6 --- /dev/null +++ b/patch/0001-dhcpv4-relay-accept-random-src-port.patch @@ -0,0 +1,15 @@ +diff --git a/PcapPlusPlus-24.09/Packet++/header/DhcpLayer.h b/PcapPlusPlus-24.09/Packet++/header/DhcpLayer.h +index 435becb..282d739 100644 +--- a/PcapPlusPlus-24.09/Packet++/header/DhcpLayer.h ++++ b/PcapPlusPlus-24.09/Packet++/header/DhcpLayer.h +@@ -881,8 +881,8 @@ namespace pcpp + + bool DhcpLayer::isDhcpPorts(uint16_t portSrc, uint16_t portDst) + { +- return ((portSrc == 68 && portDst == 67) || (portSrc == 67 && portDst == 68) || +- (portSrc == 67 && portDst == 67)); ++ // src port can be any ephemeral port so removing the check ++ return ((portDst == 67) || (portDst == 68)); + } + + } // namespace pcpp diff --git a/patch/series b/patch/series new file mode 100644 index 0000000..ec2293e --- /dev/null +++ b/patch/series @@ -0,0 +1 @@ +0001-dhcpv4-relay-accept-random-src-port.patch diff --git a/pcappp_v24.09.zip b/pcappp_v24.09.zip new file mode 100644 index 0000000000000000000000000000000000000000..cdbbd6ce9d80e1f1eadb1d30f770841465b393e6 GIT binary patch literal 16988791 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