diff --git a/.azure-pipelines/build.yml b/.azure-pipelines/build.yml index 8f6a99e..1702ef8 100644 --- a/.azure-pipelines/build.yml +++ b/.azure-pipelines/build.yml @@ -41,7 +41,12 @@ jobs: libnl-3-dev \ libnl-route-3-dev \ libnl-genl-3-dev \ - libnl-nf-3-dev + libnl-nf-3-dev \ + redis-server + sudo sed -ri 's/^# unixsocket/unixsocket/' /etc/redis/redis.conf + sudo sed -ri 's/^unixsocketperm .../unixsocketperm 777/' /etc/redis/redis.conf + sudo sed -ri 's/redis-server.sock/redis.sock/' /etc/redis/redis.conf + sudo service redis-server start displayName: "Install dependencies" - checkout: self @@ -73,9 +78,13 @@ jobs: - publish: $(Build.ArtifactStagingDirectory) artifact: sonic-dhcp-relay.${{ parameters.arch }} displayName: "Archive dhcp-relay debian packages" + - task: PublishTestResults@2 + inputs: + testResultsFiles: build-test/dhcp6relay-test-test-result.xml - ${{ if and(eq(parameters.arch, 'amd64'), parameters.codeCoverage) }}: - task: PublishCodeCoverageResults@1 inputs: - summaryFileLocation: dhcprelay-test-result.xml + summaryFileLocation: build-test/dhcp6relay-test-code-coverage.xml pathToSources: $(Build.SourcesDirectory) + reportDirectory: $(Build.SourcesDirectory)/build-test codeCoverageTool: 'Cobertura' diff --git a/Makefile b/Makefile index e15b24a..a83ccb2 100644 --- a/Makefile +++ b/Makefile @@ -1,36 +1,66 @@ RM := rm -rf -DHCP6RELAY_TARGET := dhcp6relay +BUILD_DIR := build +BUILD_TEST_DIR := build-test +DHCP6RELAY_TARGET := $(BUILD_DIR)/dhcp6relay +DHCP6RELAY_TEST_TARGET := $(BUILD_TEST_DIR)/dhcp6relay-test CP := cp MKDIR := mkdir MV := mv +FIND := find +GCOVR := gcovr override LDLIBS += -levent -lhiredis -lswsscommon -pthread -lboost_thread -lboost_system override CPPFLAGS += -Wall -std=c++17 -fPIE -I/usr/include/swss override CPPFLAGS += -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@)" +CPPFLAGS_TEST := --coverage -fprofile-arcs -ftest-coverage -fprofile-generate -fsanitize=address +LDLIBS_TEST := --coverage -lgtest -pthread -lstdc++fs -fsanitize=address PWD := $(shell pwd) -all: $(DHCP6RELAY_TARGET) +all: $(DHCP6RELAY_TARGET) $(DHCP6RELAY_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 --include src/subdir.mk +$(BUILD_DIR)/%.o: %.cpp + @mkdir -p $(@D) + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< $(DHCP6RELAY_TARGET): $(OBJS) $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@ -install: - $(MKDIR) -p $(DESTDIR)/usr/sbin - $(MV) $(DHCP6RELAY_TARGET) $(DESTDIR)/usr/sbin +$(BUILD_TEST_DIR)/%.o: %.cpp + @mkdir -p $(@D) + $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(CPPFLAGS_TEST) -c -o $@ $< -deinstall: - $(RM) $(DESTDIR)/usr/sbin/$(DHCP6RELAY_TARGET) - $(RM) -rf $(DESTDIR)/usr/sbin +$(DHCP6RELAY_TEST_TARGET): $(TEST_OBJS) + $(CXX) $(LDFLAGS) $^ $(LDLIBS) $(LDLIBS_TEST) -o $@ -clean: - -$(RM) $(EXECUTABLES) $(OBJS:%.o=%.d) $(OBJS) $(DHCP6RELAY_TARGET) - -@echo ' ' +test: $(DHCP6RELAY_TEST_TARGET) + sudo ASAN_OPTIONS=detect_leaks=0 ./$(DHCP6RELAY_TEST_TARGET) --gtest_output=xml:$(DHCP6RELAY_TEST_TARGET)-test-result.xml || true + $(GCOVR) -r ./ --html --html-details -o $(DHCP6RELAY_TEST_TARGET)-code-coverage.html + $(GCOVR) -r ./ --xml-pretty -o $(DHCP6RELAY_TEST_TARGET)-code-coverage.xml + +install: $(DHCP6RELAY_TARGET) + install -D $(DHCP6RELAY_TARGET) $(DESTDIR)/usr/sbin/$(notdir $(DHCP6RELAY_TARGET)) -.PHONY: all clean dependents +uninstall: + $(RM) $(DESTDIR)/usr/sbin/$(notdir $(DHCP6RELAY_TARGET)) +clean: + -$(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 {} \; + -@echo ' ' +.PHONY: all clean test install uninstall diff --git a/src/configInterface.cpp b/src/configInterface.cpp index 4812ee5..890acd7 100644 --- a/src/configInterface.cpp +++ b/src/configInterface.cpp @@ -7,9 +7,6 @@ constexpr auto DEFAULT_TIMEOUT_MSEC = 1000; bool pollSwssNotifcation = true; std::shared_ptr mSwssThreadPtr; - -std::shared_ptr configDbPtr = std::make_shared ("CONFIG_DB", 0); -swss::SubscriberStateTable ipHelpersTable(configDbPtr.get(), "DHCP_RELAY"); swss::Select swssSelect; /** @@ -22,9 +19,14 @@ swss::Select swssSelect; void initialize_swss(std::vector *vlans) { try { + std::shared_ptr configDbPtr = std::make_shared ("CONFIG_DB", 0); + swss::SubscriberStateTable ipHelpersTable(configDbPtr.get(), "DHCP_RELAY"); swssSelect.addSelectable(&ipHelpersTable); - get_dhcp(vlans); - mSwssThreadPtr = std::make_shared (&handleSwssNotification, vlans); + get_dhcp(vlans, &ipHelpersTable); + struct swssNotification test; + test.vlans = vlans; + test.ipHelpersTable = &ipHelpersTable; + mSwssThreadPtr = std::make_shared (&handleSwssNotification, test); } catch (const std::bad_alloc &e) { syslog(LOG_ERR, "Failed allocate memory. Exception details: %s", e.what()); @@ -52,15 +54,15 @@ void deinitialize_swss() * * @return none */ -void get_dhcp(std::vector *vlans) { +void get_dhcp(std::vector *vlans, swss::SubscriberStateTable *ipHelpersTable) { swss::Selectable *selectable; int ret = swssSelect.select(&selectable, DEFAULT_TIMEOUT_MSEC); if (ret == swss::Select::ERROR) { syslog(LOG_WARNING, "Select: returned ERROR"); } else if (ret == swss::Select::TIMEOUT) { } - if (selectable == static_cast (&ipHelpersTable)) { - handleRelayNotification(ipHelpersTable, vlans); + if (selectable == static_cast (ipHelpersTable)) { + handleRelayNotification(*ipHelpersTable, vlans); } } /** @@ -72,10 +74,10 @@ void get_dhcp(std::vector *vlans) { * * @return none */ -void handleSwssNotification(std::vector *vlans) +void handleSwssNotification(swssNotification test) { while (pollSwssNotifcation) { - get_dhcp(vlans); + get_dhcp(test.vlans, test.ipHelpersTable); } } diff --git a/src/configInterface.h b/src/configInterface.h index 00a9abc..82ba4e9 100644 --- a/src/configInterface.h +++ b/src/configInterface.h @@ -1,8 +1,14 @@ +#pragma once + #include #include "subscriberstatetable.h" #include "select.h" #include "relay.h" +struct swssNotification { + std::vector *vlans; + swss::SubscriberStateTable *ipHelpersTable; +}; /** * @code void initialize_swss() * @@ -28,18 +34,18 @@ void deinitialize_swss(); * * @return none */ -void get_dhcp(std::vector *vlans); +void get_dhcp(std::vector *vlans, swss::SubscriberStateTable *ipHelpersTable); /** - * @code void handleSwssNotification(std::vector *vlans) + * @code void swssNotification test * * @brief main thread for handling SWSS notification * - * @param vlans list of vlans/argument config that contains strings of server and option + * @param test swssNotification that includes list of vlans/argument config that contains strings of server and option * * @return none */ -void handleSwssNotification(std::vector *vlans); +void handleSwssNotification(swssNotification test); /** * @code void handleRelayNotification(swss::SubscriberStateTable &ipHelpersTable, std::vector *vlans) diff --git a/src/relay.cpp b/src/relay.cpp index a82161e..d3ed631 100644 --- a/src/relay.cpp +++ b/src/relay.cpp @@ -22,7 +22,6 @@ struct event *ev_sigterm; static std::string vlan_member = "VLAN_MEMBER|"; static std::string counter_table = "DHCPv6_COUNTER_TABLE|"; -struct database redis_db; /* DHCPv6 filter */ /* sudo tcpdump -dd "inbound and ip6 dst ff02::1:2 && udp dst port 547" */ @@ -221,24 +220,9 @@ const struct dhcpv6_option *parse_dhcpv6_opt(const uint8_t *buffer, const uint8_ return option; } -/** - * @code void send_udp(int sock, uint8_t *buffer, struct sockaddr_in6 target, uint32_t n, relay_config *config, uint8_t msg_type); - * - * @brief send udp packet - * - * @param *buffer message buffer - * @param sockaddr_in6 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 dhcpv6_option end of dhcpv6 message option - */ -void send_udp(int sock, uint8_t *buffer, struct sockaddr_in6 target, uint32_t n, relay_config *config, uint8_t msg_type) { +void process_sent_msg(relay_config *config, uint8_t msg_type) { std::string counterVlan = counter_table; - if(sendto(sock, buffer, n, 0, (const struct sockaddr *)&target, sizeof(target)) == -1) - syslog(LOG_ERR, "sendto: Failed to send to target address\n"); - else if (counterMap.find(msg_type) != counterMap.end()) { + if (counterMap.find(msg_type) != counterMap.end()) { counters[msg_type]++; update_counter(config->state_db, counterVlan.append(config->interface), msg_type); } else { @@ -497,7 +481,9 @@ void relay_client(int sock, const uint8_t *msg, int32_t len, const ip6_hdr *ip_h current_buffer_position += dhcp_message_length + sizeof(dhcpv6_option); for(auto server: config->servers_sock) { - send_udp(sock, buffer, server, current_buffer_position - buffer, config, new_message.msg_type); + if(send_udp(sock, buffer, server, current_buffer_position - buffer)) { + process_sent_msg(config, new_message.msg_type); + } } } @@ -537,7 +523,9 @@ void relay_relay_forw(int sock, const uint8_t *msg, int32_t len, const ip6_hdr * current_buffer_position += dhcp_message_length + sizeof(dhcpv6_option); for(auto server: config->servers_sock) { - send_udp(sock, buffer, server, current_buffer_position - buffer, config, new_message.msg_type); + if(send_udp(sock, buffer, server, current_buffer_position - buffer)) { + process_sent_msg(config, new_message.msg_type); + } } } @@ -589,7 +577,9 @@ void relay_relay_forw(int sock, const uint8_t *msg, int32_t len, const ip6_hdr * target_addr.sin6_port = htons(CLIENT_PORT); target_addr.sin6_scope_id = if_nametoindex(config->interface.c_str()); - send_udp(sock, buffer, target_addr, current_buffer_position - buffer, config, type); + if(send_udp(sock, buffer, target_addr, current_buffer_position - buffer)) { + process_sent_msg(config, type); + } } /** @@ -728,9 +718,9 @@ void callback_dual_tor(evutil_socket_t fd, short event, void *arg) { return; std::string state; std::string intf(interfaceName); - redis_db.muxTable->hget(intf, "state", state); + config->mux_table->hget(intf, "state", state); - if (state != "standby" && redis_db.config_db->exists(key.append(intf))) { + if (state != "standby" && config->config_db->exists(key.append(intf))) { char* ptr = (char *)message_buffer; const uint8_t *current_position = (uint8_t *)ptr; const uint8_t *tmp = NULL; @@ -970,8 +960,6 @@ void loop_relay(std::vector *vlans) { std::shared_ptr mStateDbMuxTablePtr = std::make_shared ( state_db.get(), "HW_MUX_CABLE_TABLE" ); - redis_db.config_db = config_db; - redis_db.muxTable = mStateDbMuxTablePtr; int filter = 0; filter = sock_open(ðer_relay_fprog); @@ -981,6 +969,8 @@ void loop_relay(std::vector *vlans) { relay_config *config = &vlan; int local_sock = 0; int server_sock = 0; + config->config_db = config_db; + config->mux_table = mStateDbMuxTablePtr; config->state_db = state_db; config->mux_key = vlan_member + config->interface + "|"; diff --git a/src/relay.h b/src/relay.h index 8b11ff2..2f99599 100644 --- a/src/relay.h +++ b/src/relay.h @@ -1,7 +1,9 @@ +#pragma once + #include #include #include -#include +#include #include #include #include @@ -9,7 +11,9 @@ #include #include #include - +#include "dbconnector.h" +#include "table.h" +#include "sender.h" #define PACKED __attribute__ ((packed)) @@ -60,15 +64,10 @@ struct relay_config { std::vector servers_sock; bool is_option_79; bool is_interface_id; -}; - -struct database { + std::shared_ptr mux_table; std::shared_ptr config_db; - std::shared_ptr muxTable; - std::shared_ptr counterTable; }; - /* DHCPv6 messages and options */ struct dhcpv6_msg { @@ -357,17 +356,53 @@ const struct dhcpv6_relay_msg *parse_dhcpv6_relay(const uint8_t *buffer); const struct dhcpv6_option *parse_dhcpv6_opt(const uint8_t *buffer, const uint8_t **out_end); /** - * @code void send_udp(int sock, uint8_t *buffer, struct sockaddr_in6 target, uint32_t n, relay_config *config, uint8_t msg_type); + * @code callback(evutil_socket_t fd, short event, void *arg); + * + * @brief callback for libevent that is called everytime data is received at the filter socket + * + * @param fd filter socket + * @param event libevent triggered event + * @param arg callback argument provided by user + * + * @return none + */ +void callback(evutil_socket_t fd, short event, void *arg); + +/** + * @code callback_dual_tor(evutil_socket_t fd, short event, void *arg); + * + * @brief callback for libevent that is called everytime data is received at the filter socket with dual tor option enabled * - * @brief send udp packet + * @param fd filter socket + * @param event libevent triggered event + * @param arg callback argument provided by user * - * @param *buffer message buffer - * @param sockaddr_in6 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 none + */ +void callback_dual_tor(evutil_socket_t fd, short event, void *arg); + +/** + * @code void server_callback(evutil_socket_t fd, short event, void *arg); * - * @return dhcpv6_option end of dhcpv6 message option + * @brief callback for libevent that is called everytime data is received at the server socket + * + * @param fd filter socket + * @param event libevent triggered event + * @param arg callback argument provided by user + * + * @return none */ -void send_udp(int sock, uint8_t *buffer, struct sockaddr_in6 target, uint32_t n, relay_config *config, uint8_t msg_type); +void server_callback(evutil_socket_t fd, short event, void *arg); +/** + * @code void server_callback_dual_tor(evutil_socket_t fd, short event, void *arg); + * + * @brief callback for libevent that is called everytime data is received at the server socket + * + * @param fd filter socket + * @param event libevent triggered event + * @param arg callback argument provided by user + * + * @return none + */ +void server_callback_dual_tor(evutil_socket_t fd, short event, void *arg); diff --git a/src/sender.cpp b/src/sender.cpp new file mode 100644 index 0000000..68b5afa --- /dev/null +++ b/src/sender.cpp @@ -0,0 +1,23 @@ +#include "sender.h" +#include + +/** + * @code bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in6 target, uint32_t n); + * + * @brief send udp packet and return true if successful + * + * @param *buffer message buffer + * @param sockaddr_in6 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_in6 target, uint32_t n) { + if(sendto(sock, buffer, n, 0, (const struct sockaddr *)&target, sizeof(target)) == -1) { + syslog(LOG_ERR, "sendto: Failed to send to target address\n"); + return false; + } + return true; +} diff --git a/src/sender.h b/src/sender.h new file mode 100644 index 0000000..49b3643 --- /dev/null +++ b/src/sender.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +/** + * @code bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in6 target, uint32_t n); + * + * @brief send udp packet and return true if successful + * + * @param *buffer message buffer + * @param sockaddr_in6 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_in6 target, uint32_t n); diff --git a/src/subdir.mk b/src/subdir.mk index aa75ad0..c2f37b1 100644 --- a/src/subdir.mk +++ b/src/subdir.mk @@ -1,4 +1,5 @@ -OBJS += \ -$(PWD)/src/relay.o \ -$(PWD)/src/configInterface.o \ -$(PWD)/src/main.o +SRCS += \ +src/sender.cpp \ +src/relay.cpp \ +src/configInterface.cpp \ +src/main.cpp diff --git a/test/MockRelay.cpp b/test/MockRelay.cpp new file mode 100644 index 0000000..28eee72 --- /dev/null +++ b/test/MockRelay.cpp @@ -0,0 +1,560 @@ +#include +#include +#include +#include +#include +#include +#include +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include "MockRelay.h" + +bool dual_tor_sock = false; +extern struct event_base *base; +extern struct event *ev_sigint; +extern struct event *ev_sigterm; + +static struct sock_filter ether_relay_filter[] = { + + { 0x28, 0, 0, 0xfffff004 }, + { 0x15, 15, 0, 0x00000004 }, + { 0x28, 0, 0, 0x0000000c }, + { 0x15, 0, 13, 0x000086dd }, + { 0x20, 0, 0, 0x00000026 }, + { 0x15, 0, 11, 0xff020000 }, + { 0x20, 0, 0, 0x0000002a }, + { 0x15, 0, 9, 0x00000000 }, + { 0x20, 0, 0, 0x0000002e }, + { 0x15, 0, 7, 0x00000000 }, + { 0x20, 0, 0, 0x00000032 }, + { 0x15, 0, 5, 0x00010002 }, + { 0x30, 0, 0, 0x00000014 }, + { 0x15, 0, 3, 0x00000011 }, + { 0x28, 0, 0, 0x00000038 }, + { 0x15, 0, 1, 0x00000223 }, + { 0x6, 0, 0, 0x00040000 }, + { 0x6, 0, 0, 0x00000000 }, +}; +const struct sock_fprog ether_relay_fprog = { + lengthof(ether_relay_filter), + ether_relay_filter +}; + +/* sudo tcpdump -dd -i lo port 547 */ +static struct sock_filter lo_ether_relay_filter[] = { + + { 0x28, 0, 0, 0x0000000c }, + { 0x15, 0, 8, 0x000086dd }, + { 0x30, 0, 0, 0x00000014 }, + { 0x15, 2, 0, 0x00000084 }, + { 0x15, 1, 0, 0x00000006 }, + { 0x15, 0, 17, 0x00000011 }, + { 0x28, 0, 0, 0x00000036 }, + { 0x15, 14, 0, 0x00000223 }, + { 0x28, 0, 0, 0x00000038 }, + { 0x15, 12, 13, 0x00000223 }, + { 0x15, 0, 12, 0x00000800 }, + { 0x30, 0, 0, 0x00000017 }, + { 0x15, 2, 0, 0x00000084 }, + { 0x15, 1, 0, 0x00000006 }, + { 0x15, 0, 8, 0x00000011 }, + { 0x28, 0, 0, 0x00000014 }, + { 0x45, 6, 0, 0x00001fff }, + { 0xb1, 0, 0, 0x0000000e }, + { 0x48, 0, 0, 0x0000000e }, + { 0x15, 2, 0, 0x00000223 }, + { 0x48, 0, 0, 0x00000010 }, + { 0x15, 0, 1, 0x00000223 }, + { 0x6, 0, 0, 0x00040000 }, + { 0x6, 0, 0, 0x00000000 }, +}; +const struct sock_fprog lo_ether_relay_fprog = { + lengthof(lo_ether_relay_filter), + lo_ether_relay_filter +}; + +TEST(helper, toString) +{ + EXPECT_EQ("0", toString(0)); +} + +TEST(parsePacket, parse_ether_frame) +{ + unsigned char ether[] = { + 0x33, 0x33, 0x00, 0x01, 0x00, 0x02, /* destination address */ + 0xfe, 0x54, 0x00, 0x7e, 0x13, 0x01, /* source address */ + 0x86, 0xdd, 0x60 /* layer3 ipv6 protocol */ + }; + + char *ptr = (char *)ether; + const uint8_t *tmp = NULL; + const uint8_t *current_position = (uint8_t *)ptr; + auto ether_header = parse_ether_frame(current_position, &tmp); + + EXPECT_EQ(0x33, ether_header->ether_dhost[0]); + EXPECT_EQ(0x33, ether_header->ether_dhost[1]); + EXPECT_EQ(0x00, ether_header->ether_dhost[2]); + EXPECT_EQ(0x01, ether_header->ether_dhost[3]); + EXPECT_EQ(0x00, ether_header->ether_dhost[4]); + EXPECT_EQ(0x02, ether_header->ether_dhost[5]); + + EXPECT_EQ(0xfe, ether_header->ether_shost[0]); + EXPECT_EQ(0x54, ether_header->ether_shost[1]); + EXPECT_EQ(0x00, ether_header->ether_shost[2]); + EXPECT_EQ(0x7e, ether_header->ether_shost[3]); + EXPECT_EQ(0x13, ether_header->ether_shost[4]); + EXPECT_EQ(0x01, ether_header->ether_shost[5]); + + EXPECT_EQ(ntohs(ETHERTYPE_IPV6), ether_header->ether_type); +} + +TEST(parsePacket, parse_ip6_hdr) +{ + unsigned char ip6[] = { + 0x60, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x11, 0x40, 0xfe, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x32, 0x20, 0xff, 0xfe, 0xe6, 0x27, 0x00, 0xff, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 +}; + + char *ptr = (char *)ip6; + std::string dest_addr; + std::string src_addr; + char dst[INET6_ADDRSTRLEN]; + char src[INET6_ADDRSTRLEN]; + const uint8_t *current_position = (uint8_t *)ptr; + const uint8_t *tmp = NULL; + + auto ip6_header = parse_ip6_hdr(current_position, &tmp); + inet_ntop(AF_INET6, &ip6_header->ip6_dst, dst, sizeof(dst)); + inet_ntop(AF_INET6, &ip6_header->ip6_src, src, sizeof(src)); + EXPECT_EQ("ff02::1:2", dest_addr.append(dst)); + EXPECT_EQ("fe80::c032:20ff:fee6:2700", src_addr.append(src)); +} + +TEST(parsePacket, parse_udp) +{ + unsigned char udp[] = { /* UDP Header */ + 0x02, 0x22, 0x02, 0x23, 0x00, 0x0c, 0xbd, 0xfd, 0x01, 0x00, + 0x02, 0x22, 0x02, 0x23, 0x00, 0x0c, 0xbd, 0xfd, 0x01, 0x00, + 0x02, 0x22, 0x02, 0x23, 0x00, 0x0c, 0xbd, 0xfd, 0x01, 0x00, + 0x30, 0x39 + }; + + char *ptr = (char *)udp; + const uint8_t *current_position = (uint8_t *)ptr; + const uint8_t *tmp = NULL; + + auto udp_header = parse_udp(current_position, &tmp); + EXPECT_EQ(547, ntohs(udp_header->uh_dport)); + EXPECT_EQ(546, ntohs(udp_header->uh_sport)); + EXPECT_EQ(12, ntohs(udp_header->len)); +} + +TEST(parsePacket, parse_dhcpv6_hdr) +{ + unsigned char dhcpv6_hdr[] = { /* DHCPv6 Header */ + 0x01, 0x00, 0x30, 0x39 + }; + + char *ptr = (char *)dhcpv6_hdr; + const uint8_t *current_position = (uint8_t *)ptr; + + auto msg = parse_dhcpv6_hdr(current_position); + EXPECT_EQ(1, msg->msg_type); +} + +TEST(parsePacket, parse_dhcpv6_relay) +{ + unsigned char relay[] = { /* DHCPv6 Relay-Forward Header */ + 0x0c, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x83, 0xef, 0xff, 0xfe, 0x51, + 0x56, 0x52, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xdf, 0xa8, 0x01, 0xac, 0xb7, + 0x08, 0x86, 0x00, 0x09, 0x00, 0x63 + }; + + char *ptr = (char *)relay; + std::string link_addr; + std::string peer_addr; + char peer[INET6_ADDRSTRLEN]; + char link[INET6_ADDRSTRLEN]; + + const uint8_t *current_position = (uint8_t *)ptr; + auto dhcp_relay_header = parse_dhcpv6_relay(current_position); + inet_ntop(AF_INET6, &dhcp_relay_header->peer_address, peer, sizeof(peer)); + inet_ntop(AF_INET6, &dhcp_relay_header->link_address, link, sizeof(link)); + EXPECT_EQ(0, dhcp_relay_header->hop_count); + EXPECT_EQ(12, dhcp_relay_header->msg_type); + EXPECT_GE("fe80::7683:efff:fe51:5652", link_addr.append(link)); + EXPECT_GE("fe80::58df:a801:acb7:886", peer_addr.append(peer)); +} + +TEST(parsePacket, parse_dhcpv6_opt) +{ + unsigned char relay[] = { /* Relay-Forward Message DHCPv6 Option */ + 0x00, 0x09, 0x00, 0x63, 0x01, 0x34, 0x56, 0x78, 0x00, 0x01, 0x00, 0x0a + }; + + char *ptr = (char *)relay; + const uint8_t *current_position = (uint8_t *)ptr; + const uint8_t *tmp = NULL; + + auto dhcp_relay_header = parse_dhcpv6_opt(current_position, &tmp); + EXPECT_EQ(OPTION_RELAY_MSG, ntohs(dhcp_relay_header->option_code)); + EXPECT_EQ(99, ntohs(dhcp_relay_header->option_length)); +} + +TEST(parsePacket, relay_forward) +{ + unsigned char relay_option[] = { /* DHCPv6 Relay Option */ + 0x00, 0x09, 0x00, 0x63 + }; + char *ptr = (char *)relay_option; + static uint8_t buffer[8]; + auto current_buffer_position = buffer; + const uint8_t *current_position = (uint8_t *)ptr; + + relay_forward(current_buffer_position, parse_dhcpv6_hdr(current_position), 4); + auto option = (const struct dhcpv6_option *)current_buffer_position; + EXPECT_EQ(9, ntohs(option->option_code)); + EXPECT_EQ(4, ntohs(option->option_length)); +} + +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(helper, send_udp) +{ + int sock = 0; + uint8_t buffer[4096]; + struct sockaddr_in6 target; + target.sin6_family = AF_INET6; + target.sin6_flowinfo = 0; + target.sin6_port = htons(RELAY_PORT); + target.sin6_scope_id = 0; + inet_pton(AF_INET6, "::1", &target.sin6_addr); + uint32_t len = 10; + send_udp(sock, buffer, target, len); + EXPECT_EQ(1, sendUdpCount); + sendUdpCount = 0; +} + +TEST(prepareConfig, prepare_relay_config) +{ + int local_sock = 1; + int filter = 1; + struct relay_config config{}; + config.is_option_79 = true; + config.link_address.sin6_addr.__in6_u.__u6_addr8[15] = 0x01; + + struct ip6_hdr ip_hdr; + std::string s_addr = "fe80::1"; + inet_pton(AF_INET6, s_addr.c_str(), &ip_hdr.ip6_src); + + config.servers.push_back("fc02:2000::1"); + config.servers.push_back("fc02:2000::2"); + + config.interface = "Vlan1000"; + std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); + config.state_db = state_db; + + prepare_relay_config(&config, &local_sock, filter); + + char addr1[INET6_ADDRSTRLEN]; + char addr2[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &(config.servers_sock.at(0).sin6_addr), addr1, sizeof(addr1)); + inet_ntop(AF_INET6, &(config.servers_sock.at(1).sin6_addr), addr2, sizeof(addr2)); + std::string s1(addr1); + std::string s2(addr2); + + EXPECT_EQ("fc02:2000::1", s1); + EXPECT_EQ("fc02:2000::2", s2); +} + + +TEST(counter, initialize_counter) +{ + std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); + initialize_counter(state_db, "DHCPv6_COUNTER_TABLE|Vlan1000"); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Unknown")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Solicit")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Advertise")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Request")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Confirm")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Renew")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Rebind")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Reply")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Release")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Decline")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Relay-Forward")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "Relay-Reply")); +} + +TEST(counter, update_counter) +{ + std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); + state_db->hset("DHCPv6_COUNTER_TABLE|Vlan1000", "Solicit", "1"); + update_counter(state_db, "DHCPv6_COUNTER_TABLE|Vlan1000", 1); + std::shared_ptr output = state_db->hget("DHCPv6_COUNTER_TABLE|Vlan1000", "Solicit"); + std::string *ptr = output.get(); + EXPECT_EQ(*ptr, "0"); +} + +TEST(relay, relay_client) +{ + int mock_sock = 124; + + uint8_t msg[] = { + 0x01, 0x2f, 0xf4, 0xc8, 0x00, 0x01, 0x00, 0x0e, + 0x00, 0x01, 0x00, 0x01, 0x25, 0x3a, 0x37, 0xb9, + 0x5a, 0xc6, 0xb0, 0x12, 0xe8, 0xb4, 0x00, 0x06, + 0x00, 0x04, 0x00, 0x17, 0x00, 0x18, 0x00, 0x08, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x0c, + 0xb0, 0x12, 0xe8, 0xb4, 0x00, 0x00, 0x0e, 0x10, + 0x00, 0x00, 0x15, 0x18 + }; + int32_t msg_len = sizeof(msg); + + struct relay_config config{}; + config.is_option_79 = true; + std::vector servers; + servers.push_back("fc02:2000::1"); + servers.push_back("fc02:2000::2"); + for (auto server:servers) { + sockaddr_in6 tmp; + inet_pton(AF_INET6, server.c_str(), &tmp.sin6_addr); + tmp.sin6_family = AF_INET6; + tmp.sin6_flowinfo = 0; + tmp.sin6_port = htons(RELAY_PORT); + tmp.sin6_scope_id = 0; + config.servers_sock.push_back(tmp); + } + std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); + config.state_db = state_db; + + struct ether_header ether_hdr; + ether_hdr.ether_shost[0] = 0x5a; + ether_hdr.ether_shost[1] = 0xc6; + ether_hdr.ether_shost[2] = 0xb0; + ether_hdr.ether_shost[3] = 0x12; + ether_hdr.ether_shost[4] = 0xe8; + ether_hdr.ether_shost[5] = 0xb4; + + ip6_hdr ip_hdr; + std::string s_addr = "2000::3"; + + relay_client(mock_sock, msg, msg_len, &ip_hdr, ðer_hdr, &config); + + EXPECT_EQ(last_used_sock, 124); + + auto sent_msg = parse_dhcpv6_relay(sender_buffer); + + EXPECT_EQ(sent_msg->msg_type, DHCPv6_MESSAGE_TYPE_RELAY_FORW); + EXPECT_EQ(sent_msg->hop_count, 0); + + for (int i = 0; i < 16; i++) { + EXPECT_EQ(sent_msg->link_address.__in6_u.__u6_addr8[i], config.link_address.sin6_addr.__in6_u.__u6_addr8[i]); + EXPECT_EQ(sent_msg->peer_address.__in6_u.__u6_addr8[i], ip_hdr.ip6_src.__in6_u.__u6_addr8[i]); + } + + const uint8_t *current_position = sender_buffer + sizeof(dhcpv6_relay_msg); + + bool link_layer = false; + bool interface_id = false; + while ((current_position - sender_buffer) < valid_byte_count) { + + auto option = parse_dhcpv6_opt(current_position, ¤t_position); + switch (ntohs(option->option_code)) { + case OPTION_RELAY_MSG: + EXPECT_EQ(memcmp(((uint8_t *)option) + sizeof(dhcpv6_option), msg, msg_len), 0); + case OPTION_CLIENT_LINKLAYER_ADDR: + link_layer = true; + case OPTION_INTERFACE_ID: + interface_id = true; + } + } + EXPECT_TRUE(link_layer); + EXPECT_TRUE(interface_id); + EXPECT_GE(sendUdpCount, 1); + sendUdpCount = 0; +} + +TEST(relay, relay_relay_forw) { + int mock_sock = 125; + + uint8_t msg[] = { + 0x0c, 0x00, 0x20, 0x01, 0x0d, 0xb8, 0x01, 0x5a, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x09, 0x00, 0x34, 0x01, 0x2f, + 0xf4, 0xc8, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x01, + 0x00, 0x01, 0x25, 0x3a, 0x37, 0xb9, 0x5a, 0xc6, + 0xb0, 0x12, 0xe8, 0xb4, 0x00, 0x06, 0x00, 0x04, + 0x00, 0x17, 0x00, 0x18, 0x00, 0x08, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x0c, 0xb0, 0x12, + 0xe8, 0xb4, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, + 0x15, 0x18 + }; + int32_t msg_len = sizeof(msg); + + relay_config config{}; + config.link_address.sin6_addr.__in6_u.__u6_addr8[15] = 0x02; + std::vector servers; + servers.push_back("fc02:2000::1"); + servers.push_back("fc02:2000::2"); + for (auto server:servers) { + sockaddr_in6 tmp; + inet_pton(AF_INET6, server.c_str(), &tmp.sin6_addr); + tmp.sin6_family = AF_INET6; + tmp.sin6_flowinfo = 0; + tmp.sin6_port = htons(RELAY_PORT); + tmp.sin6_scope_id = 0; + config.servers_sock.push_back(tmp); + } + std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); + config.state_db = state_db; + + ip6_hdr ip_hdr; + std::string s_addr = "2000::3"; + inet_pton(AF_INET6, s_addr.c_str(), &ip_hdr.ip6_src); + + relay_relay_forw(mock_sock, msg, msg_len, &ip_hdr, &config); + + EXPECT_EQ(last_used_sock, 125); + + auto sent_msg = parse_dhcpv6_relay(sender_buffer); + + EXPECT_EQ(sent_msg->msg_type, DHCPv6_MESSAGE_TYPE_RELAY_FORW); + EXPECT_EQ(sent_msg->hop_count, 1); + + for (int i = 0; i < 16; i++) { + EXPECT_EQ(sent_msg->link_address.__in6_u.__u6_addr8[i], 0); + EXPECT_EQ(sent_msg->peer_address.__in6_u.__u6_addr8[i], ip_hdr.ip6_src.__in6_u.__u6_addr8[i]); + } + + EXPECT_GE(sendUdpCount, 1); + sendUdpCount = 0; +} + +TEST(relay, relay_relay_reply) +{ + int mock_sock = 123; + + uint8_t msg[] = { + 0x0d, 0x00, 0x20, 0x01, 0x0d, 0xb8, 0x01, 0x5a, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x12, 0x00, 0x03, 0x47, 0x69, + 0x32, 0x00, 0x09, 0x00, 0x54, 0x07, 0x4f, 0x6d, + 0x04, 0x00, 0x03, 0x00, 0x28, 0xb0, 0x12, 0xe8, + 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x18, 0x20, 0x01, 0x0d, + 0xb8, 0x01, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x78, 0x00, 0x00, 0x1c, + 0x20, 0x00, 0x00, 0x1d, 0x4c, 0x00, 0x01, 0x00, + 0x0e, 0x00, 0x01, 0x00, 0x01, 0x25, 0x3a, 0x37, + 0xb9, 0x5a, 0xc6, 0xb0, 0x12, 0xe8, 0xb4, 0x00, + 0x02, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x25, + 0x3a, 0x32, 0x33, 0x50, 0xe5, 0x49, 0x50, 0x9e, + 0x40 + }; + int32_t msg_len = sizeof(msg); + + struct relay_config config{}; + config.is_option_79 = true; + + config.link_address.sin6_addr.__in6_u.__u6_addr8[15] = 0x01; + + struct ip6_hdr ip_hdr; + std::string s_addr = "fe80::1"; + inet_pton(AF_INET6, s_addr.c_str(), &ip_hdr.ip6_src); + + config.servers.push_back("fc02:2000::1"); + config.servers.push_back("fc02:2000::2"); + + config.interface = "Vlan1000"; + std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); + config.state_db = state_db; + + int local_sock = 1; + int filter = 1; + + prepare_relay_config(&config, &local_sock, filter); + + relay_relay_reply(mock_sock, msg, msg_len, &config); + + EXPECT_EQ(last_used_sock, 123); + + uint8_t expected_bytes[] = { + 0x07, 0x4f, 0x6d, 0x04, 0x00, 0x03, 0x00, 0x28, + 0xb0, 0x12, 0xe8, 0xb4, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x18, + 0x20, 0x01, 0x0d, 0xb8, 0x01, 0x5a, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x78, + 0x00, 0x00, 0x1c, 0x20, 0x00, 0x00, 0x1d, 0x4c, + 0x00, 0x01, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, + 0x25, 0x3a, 0x37, 0xb9, 0x5a, 0xc6, 0xb0, 0x12, + 0xe8, 0xb4, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x01, + 0x00, 0x01, 0x25, 0x3a, 0x32, 0x33, 0x50, 0xe5, + 0x49, 0x50, 0x9e, 0x40 + }; + + EXPECT_EQ(valid_byte_count, sizeof(expected_bytes)); + + EXPECT_EQ(0, memcmp(sender_buffer, expected_bytes, sizeof(expected_bytes))); + + EXPECT_EQ(last_target.sin6_port, htons(CLIENT_PORT)); + + in6_addr expected_target; + inet_pton(AF_INET6, "fe80::1", &expected_target); + for (int i = 0; i < 16; i++) { + EXPECT_EQ(last_target.sin6_addr.__in6_u.__u6_addr8[i], expected_target.__in6_u.__u6_addr8[i]); + } + + EXPECT_GE(sendUdpCount, 1); + sendUdpCount = 0; +} + +TEST(relay, signal_init) { + signal_init(); + EXPECT_NE((uintptr_t)ev_sigint, NULL); + EXPECT_NE((uintptr_t)ev_sigterm, NULL); +} + +TEST(relay, signal_start) { + signal_init(); + EXPECT_NE((uintptr_t)ev_sigint, NULL); + EXPECT_NE((uintptr_t)ev_sigterm, NULL); + signal_start(); +} + +TEST(relay, signal_callback) { + signal_callback(1, 1, &base); +} + +TEST(relay, dhcp6relay_stop) { + int filter = 1; + struct relay_config config{}; + base = event_base_new(); + struct event* event = event_new(base, filter, EV_READ|EV_PERSIST, callback, &config); + dhcp6relay_stop(); + event_free(event); + event_base_free(base); + base = NULL; +} diff --git a/test/MockRelay.h b/test/MockRelay.h new file mode 100644 index 0000000..4aacb74 --- /dev/null +++ b/test/MockRelay.h @@ -0,0 +1,2 @@ +#include "../src/relay.h" +#include "mock_send.h" diff --git a/test/database_config.json b/test/database_config.json new file mode 100644 index 0000000..afcaf0a --- /dev/null +++ b/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" +} \ No newline at end of file diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..dfca267 --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,28 @@ +#include "gtest/gtest.h" +#include +#include + +std::string database_config = "./test/database_config.json"; + +class DhcpRelayEnvironment : public ::testing::Environment { +public: + // Override this to define how to set up the environment. + void SetUp() override { + // by default , init should be false + EXPECT_FALSE(swss::SonicDBConfig::isInit()); + + // load local config file, init should be true + swss::SonicDBConfig::initialize(database_config); + EXPECT_TRUE(swss::SonicDBConfig::isInit()); + } +}; + +int main(int argc, char* argv[]) +{ + testing::InitGoogleTest(&argc, argv); + // Registers a global test environment, and verifies that the + // registration function returns its argument. + DhcpRelayEnvironment* env = new DhcpRelayEnvironment; + testing::AddGlobalTestEnvironment(env); + return RUN_ALL_TESTS(); +} diff --git a/test/mock_send.cpp b/test/mock_send.cpp new file mode 100644 index 0000000..19a9835 --- /dev/null +++ b/test/mock_send.cpp @@ -0,0 +1,17 @@ +#include "mock_send.h" +#include + +uint8_t sender_buffer[4096]; +int32_t valid_byte_count; +int last_used_sock; +sockaddr_in6 last_target; +int sendUdpCount; + +bool send_udp(int sock, uint8_t *buffer, struct sockaddr_in6 target, uint32_t n) { + last_used_sock = sock; + valid_byte_count = n; + memcpy(sender_buffer, buffer, n); + last_target = target; + sendUdpCount++; + return true; +} diff --git a/test/mock_send.h b/test/mock_send.h new file mode 100644 index 0000000..fb39fb7 --- /dev/null +++ b/test/mock_send.h @@ -0,0 +1,10 @@ +#include +#include +#include +#include "../src/sender.h" + +extern uint8_t sender_buffer[4096]; +extern int32_t valid_byte_count; +extern int last_used_sock; +extern sockaddr_in6 last_target; +extern int sendUdpCount; diff --git a/test/subdir.mk b/test/subdir.mk new file mode 100644 index 0000000..2523d4b --- /dev/null +++ b/test/subdir.mk @@ -0,0 +1,6 @@ +TEST_SRCS += \ +test/mock_send.cpp \ +test/main.cpp \ +src/relay.cpp \ +src/configInterface.cpp \ +test/MockRelay.cpp