From 30e51c462cc2b4e22ff4c9ae3d7b282fb8db681f Mon Sep 17 00:00:00 2001 From: jcaiMR Date: Tue, 22 Aug 2023 10:12:15 +0000 Subject: [PATCH 1/4] dhcpv6 per interface counter support --- .azure-pipelines/build.yml | 5 +- .github/workflows/codeql-analysis.yml | 1 + Makefile | 2 +- lgtm.yml | 1 + src/relay.cpp | 436 +++++++++++++++++++------- src/relay.h | 101 +++++- test/mock_relay.cpp | 184 +++++++++-- 7 files changed, 590 insertions(+), 140 deletions(-) diff --git a/.azure-pipelines/build.yml b/.azure-pipelines/build.yml index b854bc6..3d18e76 100644 --- a/.azure-pipelines/build.yml +++ b/.azure-pipelines/build.yml @@ -44,6 +44,7 @@ jobs: libnl-route-3-dev \ libnl-genl-3-dev \ libnl-nf-3-dev \ + libjsoncpp-dev \ redis-server sudo sed -ri 's/^# unixsocket/unixsocket/' /etc/redis/redis.conf sudo sed -ri 's/^unixsocketperm .../unixsocketperm 777/' /etc/redis/redis.conf @@ -73,8 +74,8 @@ jobs: ${{ else }}: artifact: common-lib.${{ parameters.arch }} patterns: | - target/debs/buster/libyang-*.deb - target/debs/buster/libyang_*.deb + target/debs/bullseye/libyang-*.deb + target/debs/bullseye/libyang_*.deb displayName: "Download libyang from common lib" - script: | set -ex diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 394844d..7a506b7 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -50,6 +50,7 @@ jobs: libnl-nf-3-dev \ libnl-genl-3-dev \ libgmock-dev \ + libjsoncpp-dev \ dh-exec \ swig3.0 \ uuid-dev \ diff --git a/Makefile b/Makefile index eb77866..df797bb 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ MKDIR := mkdir MV := mv FIND := find GCOVR := gcovr -override LDLIBS += -levent -lhiredis -lswsscommon -pthread -lboost_thread -lboost_system +override LDLIBS += -levent -lhiredis -lswsscommon -pthread -lboost_thread -lboost_system -ljsoncpp 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 diff --git a/lgtm.yml b/lgtm.yml index 2deaff7..f71d288 100644 --- a/lgtm.yml +++ b/lgtm.yml @@ -11,6 +11,7 @@ extraction: - "libnl-nf-3-dev" - "libnl-genl-3-dev" - "libgmock-dev" + - "libjsoncpp-dev" - "dh-exec" - "swig3.0" - "uuid-dev" diff --git a/src/relay.cpp b/src/relay.cpp index 60de068..fbc31a0 100644 --- a/src/relay.cpp +++ b/src/relay.cpp @@ -1,10 +1,9 @@ #include #include -#include #include -#include -#include +#include #include +#include #include "configdb.h" #include "sonicv2connector.h" @@ -19,33 +18,53 @@ static std::string counter_table = "DHCPv6_COUNTER_TABLE|"; static uint8_t client_recv_buffer[BUFFER_SIZE]; static uint8_t server_recv_buffer[BUFFER_SIZE]; +static uint8_t outbond_packet_buffer[BUFFER_SIZE]; /* DHCPv6 filter */ -/* sudo tcpdump -dd "inbound and ip6 dst ff02::1:2 && udp dst port 547" */ -static struct sock_filter ether_relay_filter[] = { +/* sudo tcpdump -dd "inbound and ip6 and udp and (port 546 or port 547)" */ +static struct sock_filter inbond_filter[] = { { 0x28, 0, 0, 0xfffff004 }, - { 0x15, 15, 0, 0x00000004 }, + { 0x15, 11, 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 }, + { 0x15, 0, 9, 0x000086dd }, { 0x30, 0, 0, 0x00000014 }, - { 0x15, 0, 3, 0x00000011 }, + { 0x15, 0, 7, 0x00000011 }, + { 0x28, 0, 0, 0x00000036 }, + { 0x15, 4, 0, 0x00000222 }, + { 0x15, 3, 0, 0x00000223 }, { 0x28, 0, 0, 0x00000038 }, + { 0x15, 1, 0, 0x00000222 }, { 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 +const struct sock_fprog inbond_filter_fprog = { + lengthof(inbond_filter), + inbond_filter +}; + + +/* sudo tcpdump -dd "outbound and ip6 and udp and (port 546 or port 547)" */ +static struct sock_filter outbond_filter[] = { + { 0x28, 0, 0, 0xfffff004 }, + { 0x15, 0, 11, 0x00000004 }, + { 0x28, 0, 0, 0x0000000c }, + { 0x15, 0, 9, 0x000086dd }, + { 0x30, 0, 0, 0x00000014 }, + { 0x15, 0, 7, 0x00000011 }, + { 0x28, 0, 0, 0x00000036 }, + { 0x15, 4, 0, 0x00000222 }, + { 0x15, 3, 0, 0x00000223 }, + { 0x28, 0, 0, 0x00000038 }, + { 0x15, 1, 0, 0x00000222 }, + { 0x15, 0, 1, 0x00000223 }, + { 0x6, 0, 0, 0x00040000 }, + { 0x6, 0, 0, 0x00000000 }, +}; +const struct sock_fprog outbond_filter_fprog = { + lengthof(outbond_filter), + outbond_filter }; /* DHCPv6 counter name map */ @@ -70,6 +89,9 @@ std::map counterMap = { /* interface to vlan mapping */ std::unordered_map vlan_map; +/* interface to port-channel mapping */ +std::unordered_map portchan_map; + /* ipv6 address to vlan name mapping */ std::unordered_map addr_vlan_map; @@ -260,6 +282,33 @@ bool DHCPv6Msg::UnmarshalBinary(const uint8_t *packet, uint16_t len) { return true; } +/** + * @code void gen_counter_json_str(dhcp_message_type_t type, uint64_t cnt) + * + * @brief generate counter json string based on the value in counterMap + * + * @return counter json string + */ +std::string gen_counter_json_str(dhcp_message_type_t type, uint64_t cnt) { + std::string init_value; + + init_value.append("{"); + for (int i = 0; i < DHCPv6_MESSAGE_TYPE_COUNT; i++) { + std::string json_str; + if (i == type) { + json_str = "'" + counterMap[i] + "'"+ ":" + "'" + std::to_string(cnt) + "'"; + } else { + json_str = "'" + counterMap[i] + "'"+ ":" + "'0'"; + } + init_value.append(json_str); + if (i + 1 < DHCPv6_MESSAGE_TYPE_COUNT) { + init_value.append(","); + } + } + init_value.append("}"); + return init_value; +} + /** * @code initialize_counter(std::shared_ptr state_db, std::string &ifname); * @@ -272,35 +321,61 @@ bool DHCPv6Msg::UnmarshalBinary(const uint8_t *packet, uint16_t len) { */ void initialize_counter(std::shared_ptr state_db, std::string &ifname) { std::string table_name = counter_table + ifname; - for (auto &intr : counterMap) { - state_db->hset(table_name, intr.second, toString(0)); - } + + auto init_value = gen_counter_json_str(DHCPv6_MESSAGE_TYPE_UNKNOWN, 0); + state_db->del(table_name); + state_db->hset(table_name, "RX", init_value); + state_db->hset(table_name, "TX", init_value); } /** - * @code void increase_counter(std::shared_ptr state_db, std::string &ifname, uint8_t msg_type); + * @code void increase_counter(swss::DBConnector *state_db, std::string &ifname, + * uint8_t msg_type, dhcpv6_pkt_dir_t dir); * * @brief increase the counter in state_db with count of each DHCPv6 message types * - * @param std::shared_ptr state_db, state_db connector pointer + * @param state_db state_db connector pointer * @param ifname interface name * @param msg_type dhcpv6 message type to be increased in counter + * @param dir packet direction * * @return none */ -void increase_counter(std::shared_ptr state_db, std::string &ifname, uint8_t msg_type) { +void increase_counter(swss::DBConnector *state_db, std::string &ifname, uint8_t msg_type, dhcpv6_pkt_dir_t dir) { if (counterMap.find(msg_type) == counterMap.end()) { syslog(LOG_WARNING, "Unexpected message type %d(0x%x)\n", msg_type, msg_type); return; } std::string table_name = counter_table + ifname; std::string type = counterMap.find(msg_type)->second; - auto count_str = state_db->hget(table_name, type); - if (count_str == nullptr) { - state_db->hset(table_name, type, toString(1)); + auto counters_json = state_db->hget(table_name, (dir == DHCPV6_RX) ? "RX" : "TX"); + if (counters_json == nullptr) { + auto json_string = gen_counter_json_str(static_cast(msg_type), 1); + state_db->hset(table_name, (dir == DHCPV6_RX) ? "RX" : "TX", json_string); } else { - auto count = atoll(count_str.get()->c_str()); - state_db->hset(table_name, type, toString(count + 1)); + Json::Value root; + Json::CharReaderBuilder builder; + Json::StreamWriterBuilder wbuilder; + JSONCPP_STRING err; + + std::replace(counters_json.get()->begin(), counters_json.get()->end(), '\'', '\"'); + auto json_begin = counters_json.get()->c_str(); + auto json_end = json_begin + counters_json.get()->length(); + const std::unique_ptr reader(builder.newCharReader()); + if (reader->parse(json_begin, json_end, &root, &err)) { + if (root.isMember(type)) { + std::string cnt_string = root[type].asString(); + auto cnt = std::stoull(cnt_string) + 1; + root[type] = Json::Value(std::to_string(cnt)); + } else { + root[type] = Json::Value(std::to_string(1)); + } + wbuilder["indentation"] = ""; // whitespace-less output + const std::string document = Json::writeString(wbuilder, root); + state_db->hset(table_name, (dir == DHCPV6_RX) ? "RX" : "TX", document); + } else { + syslog(LOG_WARNING, "failed to parse counter json: %s, %s", json_begin, err.c_str()); + } } } @@ -397,7 +472,7 @@ const struct dhcpv6_relay_msg *parse_dhcpv6_relay(const uint8_t *buffer) { } /** - * @code sock_open(const struct sock_fprog *fprog); + * @code prepare_raw_socket(const struct sock_fprog *fprog); * * @brief prepare L2 socket to attach to "udp and port 547" filter * @@ -405,9 +480,8 @@ const struct dhcpv6_relay_msg *parse_dhcpv6_relay(const uint8_t *buffer) { * * @return socket descriptor */ -int sock_open(const struct sock_fprog *fprog) +int prepare_raw_socket(const struct sock_fprog *fprog) { - int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (s == -1) { syslog(LOG_ERR, "socket: Failed to create socket\n"); @@ -449,7 +523,7 @@ int sock_open(const struct sock_fprog *fprog) } else { syslog(LOG_INFO, "setsockopt: change raw socket recv buffer size from %d to %d\n", optval, optval_new); } - + syslog(LOG_INFO, "RAW Socket:%d successfully initialized !!!\n", s); return s; } @@ -470,7 +544,7 @@ void prepare_relay_config(relay_config &interface_config, int gua_sock, int filt sockaddr_in6 link_local; interface_config.gua_sock = gua_sock; - interface_config.filter = filter; + interface_config.filter = filter; for(auto server: interface_config.servers) { sockaddr_in6 tmp; @@ -566,7 +640,7 @@ int prepare_lo_socket(const char *lo) { (void) close(lo_sock); return -1; } - + syslog(LOG_INFO, "%s socket %d successfully initialized !!!\n", lo, lo_sock); return lo_sock; } @@ -654,6 +728,8 @@ int prepare_vlan_sockets(int &gua_sock, int &lla_sock, relay_config &config) { close(lla_sock); return -1; } + syslog(LOG_INFO, "%s GUA Socket:%d, LLA Socket:%d, successfully initialized !!!\n", + config.interface.c_str(), gua_sock, lla_sock); return 0; } @@ -679,11 +755,11 @@ void relay_client(const uint8_t *msg, uint16_t len, const ip6_hdr *ip_hdr, const if (!result) { char addr_str[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &ip_hdr->ip6_src, addr_str, INET6_ADDRSTRLEN); - increase_counter(config->state_db, config->interface, DHCPv6_MESSAGE_TYPE_MALFORMED); + increase_counter(config->state_db.get(), config->interface, DHCPv6_MESSAGE_TYPE_MALFORMED, DHCPV6_RX); syslog(LOG_WARNING, "DHCPv6 option is invalid or contains malformed payload from %s\n", addr_str); return; } - increase_counter(config->state_db, config->interface, dhcpv6.m_msg_hdr.msg_type); + increase_counter(config->state_db.get(), config->interface, dhcpv6.m_msg_hdr.msg_type, DHCPV6_RX); /* generate relay packet */ class RelayMsg relay; @@ -723,7 +799,7 @@ void relay_client(const uint8_t *msg, uint16_t len, const ip6_hdr *ip_hdr, const } for(auto server: config->servers_sock) { if(send_udp(sock, relay_pkt, server, relay_pkt_len)) { - increase_counter(config->state_db, config->interface, DHCPv6_MESSAGE_TYPE_RELAY_FORW); + increase_counter(config->state_db.get(), config->interface, DHCPv6_MESSAGE_TYPE_RELAY_FORW, DHCPV6_TX); } } } @@ -750,6 +826,7 @@ void relay_relay_forw(const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, re addr_str, dhcp_relay_header->hop_count); return; } + increase_counter(config->state_db.get(), config->interface, DHCPv6_MESSAGE_TYPE_RELAY_FORW, DHCPV6_RX); RelayMsg relay; relay.m_msg_hdr.msg_type = DHCPv6_MESSAGE_TYPE_RELAY_FORW; @@ -784,7 +861,7 @@ void relay_relay_forw(const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, re } for(auto server: config->servers_sock) { if(send_udp(sock, send_buffer, server, send_buffer_len)) { - increase_counter(config->state_db, config->interface, DHCPv6_MESSAGE_TYPE_RELAY_FORW); + increase_counter(config->state_db.get(), config->interface, DHCPv6_MESSAGE_TYPE_RELAY_FORW, DHCPV6_TX); } } } @@ -805,14 +882,14 @@ void relay_relay_forw(const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, re class RelayMsg relay; auto result = relay.UnmarshalBinary(msg, len); if (!result) { - increase_counter(config->state_db, config->interface, DHCPv6_MESSAGE_TYPE_MALFORMED); + increase_counter(config->state_db.get(), config->interface, DHCPv6_MESSAGE_TYPE_MALFORMED, DHCPV6_RX); syslog(LOG_WARNING, "Relay-reply option is invalid or contains malformed payload\n"); return; } auto opt_value = relay.m_option_list.Get(OPTION_RELAY_MSG); if (opt_value.empty()) { - increase_counter(config->state_db, config->interface, DHCPv6_MESSAGE_TYPE_UNKNOWN); + increase_counter(config->state_db.get(), config->interface, DHCPv6_MESSAGE_TYPE_UNKNOWN, DHCPV6_RX); syslog(LOG_WARNING, "Option relay-msg not found"); return; } @@ -829,7 +906,7 @@ void relay_relay_forw(const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, re int sock = config->lla_sock; if (isIPv6Zero(relay.m_msg_hdr.link_address)) { - // relay.m_msg_hdr is packed member, use a temp variable for unaligned case + /* relay.m_msg_hdr is packed member, use a temp variable for unaligned case */ struct in6_addr peer_addr = relay.m_msg_hdr.peer_address; if (!IN6_IS_ADDR_LINKLOCAL(&peer_addr)) sock = config->gua_sock; @@ -837,12 +914,13 @@ void relay_relay_forw(const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, re } if(send_udp(sock, dhcpv6, target_addr, length)) { - increase_counter(config->state_db, config->interface, msg_type); + increase_counter(config->state_db.get(), config->interface, msg_type, DHCPV6_TX); } } /** - * @code update_vlan_mapping(std::string vlan, std::shared_ptr cfgdb); + * @code update_vlan_mapping(std::string vlan, std::shared_ptr cfgdb, + * std::shared_ptr statdb); * * @brief build vlan member interface to vlan mapping table * @@ -851,7 +929,8 @@ void relay_relay_forw(const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, re * * @return none */ -void update_vlan_mapping(std::string vlan, std::shared_ptr cfgdb) { +void update_vlan_mapping(std::string vlan, std::shared_ptr cfgdb, + std::shared_ptr statdb) { auto match_pattern = std::string("VLAN_MEMBER|") + vlan + std::string("|*"); auto keys = cfgdb->keys(match_pattern); for (auto &itr : keys) { @@ -859,7 +938,54 @@ void update_vlan_mapping(std::string vlan, std::shared_ptr cf auto interface = itr.substr(found + 1); vlan_map[interface] = vlan; syslog(LOG_INFO, "Add <%s, %s> into interface vlan map\n", interface.c_str(), vlan.c_str()); + initialize_counter(statdb, interface); } + initialize_counter(statdb, vlan); +} + +/** + * @code update_portchannel_mapping(std::shared_ptr cfgdb, + * std::shared_ptr statdb); + * + * @brief build portchannel member interface mapping table + * + * @param cfgdb config db connection + * @param statdb state db connection + * + * @return none + */ +void update_portchannel_mapping(std::shared_ptr cfgdb, std::shared_ptr statdb) { + auto match_pattern = std::string("PORTCHANNEL_MEMBER|*"); + auto keys = cfgdb->keys(match_pattern); + std::unordered_map portchannels; + for (auto &itr : keys) { + auto first = itr.find_first_of('|'); + auto second = itr.find_last_of('|'); + auto portchannel = itr.substr(first + 1, second - first - 1); + auto interface = itr.substr(second + 1); + portchan_map[interface] = portchannel; + portchannels[portchannel] = true; + syslog(LOG_INFO, "Add <%s, %s> into interface port-channel map\n", interface.c_str(), portchannel.c_str()); + initialize_counter(statdb, interface); + } + for (auto &itr : portchannels) { + std::string ifname = itr.first; + initialize_counter(statdb, ifname); + } +} + +/** + * @code update_loopback_mapping(std::string &ifname, std::shared_ptr statdb); + * + * @brief update loopback interface mapping, currently only counter related initialization + * + * @param ifname loopback interface name + * @param statdb state db connection + * + * @return none + */ +void update_loopback_mapping(std::string &ifname, std::shared_ptr statdb) { + initialize_counter(statdb, ifname); } /** @@ -896,8 +1022,12 @@ void client_callback(evutil_socket_t fd, short event, void *arg) { std::string intf(interfaceName); auto vlan = vlan_map.find(intf); if (vlan == vlan_map.end()) { - if (intf.find(CLIENT_IF_PREFIX) != std::string::npos) { - syslog(LOG_WARNING, "Invalid input interface %s\n", interfaceName); + if (intf.find(CLIENT_IF_PREFIX) == std::string::npos) { + continue; + } + if (portchan_map.find(intf) != portchan_map.end()) { + /* update portchannel member interface rx counter */ + packet_counting_handler(client_recv_buffer, buffer_sz, intf, vlans->begin()->second.state_db.get(), DHCPV6_RX); } continue; } @@ -907,6 +1037,8 @@ void client_callback(evutil_socket_t fd, short event, void *arg) { continue; } auto config = config_itr->second; + /* update vlan member interface rx counter */ + packet_counting_handler(client_recv_buffer, buffer_sz, intf, config.state_db.get(), DHCPV6_RX); if (dual_tor_sock) { std::string state; config.mux_table->hget(intf, "state", state); @@ -919,6 +1051,102 @@ void client_callback(evutil_socket_t fd, short event, void *arg) { } } +/** + * @code outbond_callback(evutil_socket_t fd, short event, void *arg); + * + * @brief callback for outbond socket, only for counting purpose + * + * @param fd outbond socket + * @param event libevent triggered event + * @param arg callback argument provided by user + * + * @return none + */ +void outbond_callback(evutil_socket_t fd, short event, void *arg) { + auto state_db = static_cast(arg); + struct sockaddr_ll sll; + socklen_t slen = sizeof(sll); + int pkts_num = 0; + + while (pkts_num++ < BATCH_SIZE) { + auto buffer_sz = recvfrom(fd, outbond_packet_buffer, BUFFER_SIZE, 0, (struct sockaddr *)&sll, &slen); + if (buffer_sz <= 0) { + if (errno != EAGAIN) { + syslog(LOG_ERR, "recv: Failed to receive data at filter socket: %s\n", strerror(errno)); + } + return; + } + char interfaceName[IF_NAMESIZE]; + if (if_indextoname(sll.sll_ifindex, interfaceName) == NULL) { + syslog(LOG_WARNING, "Invalid output interface index %d\n", sll.sll_ifindex); + continue; + } + /* Only count TX packets for vlan and portchannel member interfaces. + * For portchannel interfaces, increase based on member interfaces counter. + * For vlan interfaces, increase in relay service function handlers. + */ + std::string intf(interfaceName); + auto vlan = vlan_map.find(intf); + if (vlan == vlan_map.end()) { + if (intf.find(CLIENT_IF_PREFIX) == std::string::npos) { + continue; + } + if (portchan_map.find(intf) != portchan_map.end()) { + packet_counting_handler(outbond_packet_buffer, buffer_sz, intf, state_db, DHCPV6_TX); + } + continue; + } + packet_counting_handler(outbond_packet_buffer, buffer_sz, intf, state_db, DHCPV6_TX); + } +} + +/** + * @code packet_counting_handler(uint8_t *buffer, ssize_t length, std::string &ifname, + swss::DBConnector *state_db, dhcpv6_pkt_dir_t dir); + * + * @brief packet couting handler + * + * @param buffer packet buffer + * @param length packet length + * @param ifname vlan member interface name + * @param state_db state db pointer + * @param dir packet direction + * + * @return none + */ +void packet_counting_handler(uint8_t *buffer, ssize_t length, std::string &ifname, + swss::DBConnector *state_db, dhcpv6_pkt_dir_t dir) { + auto buffer_end = buffer + length; + const uint8_t *current_position = buffer; + const uint8_t *prev = NULL; + + parse_ether_frame(current_position, ¤t_position); + auto ip6_header = parse_ip6_hdr(current_position, ¤t_position); + prev = current_position; + if (ip6_header->ip6_ctlun.ip6_un1.ip6_un1_nxt != IPPROTO_UDP) { + const struct ip6_ext *ext_header; + do { + ext_header = (const struct ip6_ext *)current_position; + current_position += ext_header->ip6e_len; + if((current_position == prev) || + (current_position + sizeof(*ext_header) >= buffer_end)) { + return; + } + prev = current_position; + } + while (ext_header->ip6e_nxt != IPPROTO_UDP); + } + parse_udp(current_position, ¤t_position); + auto msg = parse_dhcpv6_hdr(current_position); + /* Extra counting for port channel interface. + * No need worry for vlan interface counting, it will be handled in relay service paths. + */ + if (portchan_map.find(ifname) != portchan_map.end()) { + increase_counter(state_db, portchan_map[ifname], msg->msg_type, dir); + } + increase_counter(state_db, ifname, msg->msg_type, dir); +} + /** * @code client_packet_handler(uint8_t *buffer, ssize_t length, struct relay_config *config, std::string &ifname); * @@ -964,8 +1192,8 @@ void client_packet_handler(uint8_t *buffer, ssize_t length, struct relay_config auto msg = parse_dhcpv6_hdr(current_position); // RFC3315 only if (msg->msg_type < DHCPv6_MESSAGE_TYPE_SOLICIT || msg->msg_type > DHCPv6_MESSAGE_TYPE_RELAY_REPL) { - increase_counter(config->state_db, config->interface, DHCPv6_MESSAGE_TYPE_UNKNOWN); - syslog(LOG_WARNING, "Unknown DHCPv6 message type %d from %s\n", msg->msg_type, ifname.c_str()); + increase_counter(config->state_db.get(), config->interface, DHCPv6_MESSAGE_TYPE_UNKNOWN, DHCPV6_RX); + syslog(LOG_WARNING, "Unknown DHCPv6 message type %d from %s:%s\n", msg->msg_type, ifname.c_str(), config->interface.c_str()); return; } @@ -1088,7 +1316,7 @@ void server_callback_dualtor(evutil_socket_t fd, short event, void *arg) { continue; } auto loopback_str = std::string(loopback); - increase_counter(config->state_db, loopback_str, msg_type); + increase_counter(config->state_db.get(), loopback_str, msg_type, DHCPV6_RX); relay_relay_reply(server_recv_buffer, buffer_sz, config); } } @@ -1127,12 +1355,12 @@ void server_callback(evutil_socket_t fd, short event, void *arg) { auto msg_type = parse_dhcpv6_hdr(server_recv_buffer)->msg_type; // RFC3315 only if (msg_type < DHCPv6_MESSAGE_TYPE_SOLICIT || msg_type > DHCPv6_MESSAGE_TYPE_RELAY_REPL) { - increase_counter(config->state_db, config->interface, DHCPv6_MESSAGE_TYPE_UNKNOWN); + increase_counter(config->state_db.get(), config->interface, DHCPv6_MESSAGE_TYPE_UNKNOWN, DHCPV6_RX); syslog(LOG_WARNING, "Unknown DHCPv6 message type %d\n", msg_type); continue; } - increase_counter(config->state_db, config->interface, msg_type); + increase_counter(config->state_db.get(), config->interface, msg_type, DHCPV6_RX); if (msg_type == DHCPv6_MESSAGE_TYPE_RELAY_REPL) { relay_relay_reply(server_recv_buffer, buffer_sz, config); } @@ -1222,6 +1450,31 @@ void dhcp6relay_stop() event_base_loopexit(base, NULL); } +/** + * @code prepare_socket_callback(event_base *base, int socket, + * void (*cb)(evutil_socket_t, short, void *), void *arg); + * + * @brief Set socket read event callback + * + * @param base libevent base + * @param socket target socket + * @param cb callback function + * @param arg callback function argument + * + */ +void prepare_socket_callback(event_base *base, int socket, void (*cb)(evutil_socket_t, short, void *), void *arg) { + if (socket == -1) { + syslog(LOG_ERR, "Invalid socket id\n"); + exit(EXIT_FAILURE); + } + auto event = event_new(base, socket, EV_READ|EV_PERSIST, cb, arg); + if (event == NULL) { + syslog(LOG_ERR, "libevent: Failed to create event for socket %d\n", socket); + exit(EXIT_FAILURE); + } + event_add(event, NULL); +} + /** * @code loop_relay(std::unordered_map &vlans); * @@ -1243,41 +1496,25 @@ void loop_relay(std::unordered_map &vlans) { state_db.get(), "HW_MUX_CABLE_TABLE" ); - auto filter = sock_open(ðer_relay_fprog); - if (filter != -1) { - sockets.push_back(filter); - auto event = event_new(base, filter, EV_READ|EV_PERSIST, client_callback, - reinterpret_cast(&vlans)); - if (event == NULL) { - syslog(LOG_ERR, "libevent: Failed to create client listen event\n"); - exit(EXIT_FAILURE); - } - event_add(event, NULL); - syslog(LOG_INFO, "libevent: Add client listen socket event\n"); - } else { - syslog(LOG_ERR, "Failed to create client listen socket"); - exit(EXIT_FAILURE); - } + auto in_filter = prepare_raw_socket(&inbond_filter_fprog); + prepare_socket_callback(base, in_filter, client_callback, reinterpret_cast(&vlans)); + sockets.push_back(in_filter); + + auto out_filter = prepare_raw_socket(&outbond_filter_fprog); + prepare_socket_callback(base, out_filter, outbond_callback, reinterpret_cast(state_db.get())); + sockets.push_back(out_filter); int lo_sock = -1; if (dual_tor_sock) { + std::string lo_string(loopback); + update_loopback_mapping(lo_string, state_db); lo_sock = prepare_lo_socket(loopback); - if (lo_sock != -1) { - sockets.push_back(lo_sock); - auto event = event_new(base, lo_sock, EV_READ|EV_PERSIST, server_callback_dualtor, - reinterpret_cast(&vlans)); - if (event == NULL) { - syslog(LOG_ERR, "libevent: Failed to create dualtor loopback listen event\n"); - exit(EXIT_FAILURE); - } - event_add(event, NULL); - syslog(LOG_INFO, "libevent: Add dualtor loopback socket event\n"); - } else{ - syslog(LOG_ERR, "Failed to create dualtor loopback listen socket"); - exit(EXIT_FAILURE); - } + prepare_socket_callback(base, lo_sock, server_callback_dualtor, reinterpret_cast(&vlans)); + sockets.push_back(lo_sock); } + update_portchannel_mapping(config_db, state_db); + for(auto &vlan : vlans) { int gua_sock = 0; int lla_sock = 0; @@ -1286,31 +1523,18 @@ void loop_relay(std::unordered_map &vlans) { vlan.second.state_db = state_db; vlan.second.mux_key = vlan_member + vlan.second.interface + "|"; - update_vlan_mapping(vlan.first, config_db); - - initialize_counter(vlan.second.state_db, vlan.second.interface); + update_vlan_mapping(vlan.first, config_db, state_db); - if (prepare_vlan_sockets(gua_sock, lla_sock, vlan.second) != -1) { - vlan.second.gua_sock = gua_sock; - vlan.second.lla_sock = lla_sock; - vlan.second.lo_sock = lo_sock; - - sockets.push_back(gua_sock); - sockets.push_back(lla_sock); - prepare_relay_config(vlan.second, gua_sock, filter); - if (!dual_tor_sock) { - auto event = event_new(base, gua_sock, EV_READ|EV_PERSIST, - server_callback, &(vlan.second)); - if (event == NULL) { - syslog(LOG_ERR, "libevent: Failed to create server listen libevent\n"); - } - event_add(event, NULL); - syslog(LOG_INFO, "libevent: add server listen socket for %s\n", vlan.first.c_str()); - } - } else { - syslog(LOG_ERR, "Failed to create dualtor loopback listen socket"); - exit(EXIT_FAILURE); - } + assert(prepare_vlan_sockets(gua_sock, lla_sock, vlan.second) != -1); + + vlan.second.gua_sock = gua_sock; + vlan.second.lla_sock = lla_sock; + vlan.second.lo_sock = lo_sock; + + prepare_relay_config(vlan.second, gua_sock, in_filter); + prepare_socket_callback(base, gua_sock, server_callback, &(vlan.second)); + sockets.push_back(gua_sock); + sockets.push_back(lla_sock); } if(signal_init() == 0 && signal_start() == 0) { diff --git a/src/relay.h b/src/relay.h index 5fb009d..fc63db3 100644 --- a/src/relay.h +++ b/src/relay.h @@ -11,7 +11,10 @@ #include #include #include +#include +#include #include +#include #include #include "dbconnector.h" #include "table.h" @@ -61,6 +64,15 @@ typedef enum DHCPv6_MESSAGE_TYPE_COUNT } dhcp_message_type_t; +/** packet direction */ +typedef enum +{ + DHCPV6_RX, /** RX DHCPV6 packet */ + DHCPV6_TX, /** TX DHCPV6 packet */ + + DHCPV6_DIR_COUNT +} dhcpv6_pkt_dir_t; + struct relay_config { int gua_sock; int lla_sock; @@ -158,15 +170,15 @@ class DHCPv6Msg: public Options { }; /** - * @code sock_open(const struct sock_fprog *fprog); + * @code prepare_raw_socket(const struct sock_fprog *fprog); * - * @brief prepare L2 socket to attach to "udp and port 547" filter + * @brief prepare raw socket filter * - * @param fprog bpf filter "udp and port 547" + * @param fprog bpf filter * * @return socket descriptor */ -int sock_open(const struct sock_fprog *fprog); +int prepare_raw_socket(const struct sock_fprog *fprog); /** * @code prepare_lo_socket(const char *lo); @@ -337,17 +349,20 @@ void shutdown_relay(); void initialize_counter(std::shared_ptr state_db, std::string &ifname); /** - * @code void increase_counter(shared_ptr, std::string ifname, uint8_t msg_type); + * @code void increase_counter(swss::DBConnector *state_db, std::string ifname, + * uint8_t msg_type, dhcpv6_pkt_dir_t dir); * * @brief increase the counter in state_db with count of each DHCPv6 message type * * @param shared_ptr state_db state_db connector * @param ifname interface name * @param msg_type dhcpv6 message type to be increased in counter + * @param dir dhcpv6 packet direction * * @return none */ -void increase_counter(std::shared_ptr state_db, std::string &ifname, uint8_t msg_type); +void increase_counter(swss::DBConnector *state_db, std::string &ifname, + uint8_t msg_type, dhcpv6_pkt_dir_t dir); /* Helper functions */ @@ -423,7 +438,8 @@ const struct dhcpv6_msg *parse_dhcpv6_hdr(const uint8_t *buffer); const struct dhcpv6_relay_msg *parse_dhcpv6_relay(const uint8_t *buffer); /** - * @code update_vlan_mapping(std::string vlan, std::shared_ptr cfgdb); + * @code update_vlan_mapping(std::string vlan, std::shared_ptr cfgdb, + * std::shared_ptr statdb); * * @brief build vlan member interface to vlan mapping table * @@ -432,7 +448,33 @@ const struct dhcpv6_relay_msg *parse_dhcpv6_relay(const uint8_t *buffer); * * @return none */ -void update_vlan_mapping(std::string vlan, std::shared_ptr cfgdb); +void update_vlan_mapping(std::string vlan, std::shared_ptr cfgdb, + std::shared_ptr statdb); + +/** + * @code update_portchannel_mapping(std::shared_ptr cfgdb, + * std::shared_ptr statdb); + * + * @brief build portchannel member interface mapping table + * + * @param cfgdb config db connection + * @param statdb state db connection + * + * @return none + */ +void update_portchannel_mapping(std::shared_ptr cfgdb, std::shared_ptr statdb); + +/** + * @code update_loopback_mapping(std::string &ifname, std::shared_ptr statdb); + * + * @brief update loopback interface mapping, currently only counter related initialization + * + * @param ifname loopback interface name + * @param statdb state db connection + * + * @return none + */ +void update_loopback_mapping(std::string &ifname, std::shared_ptr statdb); /** * @code client_callback(evutil_socket_t fd, short event, void *arg); @@ -474,3 +516,46 @@ void client_packet_handler(uint8_t *buffer, ssize_t length, struct relay_config */ void server_callback(evutil_socket_t fd, short event, void *arg); +/** + * @code outbond_callback(evutil_socket_t fd, short event, void *arg); + * + * @brief callback for outbond socket, only for counting purpose + * + * @param fd outbond socket + * @param event libevent triggered event + * @param arg callback argument provided by user + * + * @return none + */ +void outbond_callback(evutil_socket_t fd, short event, void *arg); + +/** + * @code packet_counting_handler(uint8_t *buffer, ssize_t length, std::string &ifname, + swss::DBConnector *state_db, dhcpv6_pkt_dir_t dir); + * + * @brief packet couting handler + * + * @param buffer packet buffer + * @param length packet length + * @param ifname vlan member interface name + * @param state_db state db pointer + * @param dir packet direction + * + * @return none + */ +void packet_counting_handler(uint8_t *buffer, ssize_t length, std::string &ifname, + swss::DBConnector *state_db, dhcpv6_pkt_dir_t rx); + +/** + * @code prepare_socket_callback(event_base *base, int socket, + * void (*cb)(evutil_socket_t, short, void *), void *arg); + * + * @brief Set socket read event callback + * + * @param base libevent base + * @param socket target socket + * @param cb callback function + * @param arg callback function argument + * + */ +void prepare_socket_callback(event_base *base, int socket, void (*cb)(evutil_socket_t, short, void *), void *arg); diff --git a/test/mock_relay.cpp b/test/mock_relay.cpp index c4be1ee..43205c1 100644 --- a/test/mock_relay.cpp +++ b/test/mock_relay.cpp @@ -6,9 +6,10 @@ #include #include #include +#include + #include "gtest/gtest.h" #include "gmock/gmock.h" - #include "mock_relay.h" using namespace ::testing; @@ -190,7 +191,7 @@ TEST(parsePacket, parse_dhcpv6_relay) EXPECT_GE("fe80::58df:a801:acb7:886", peer_addr.append(peer)); } -TEST(sock, sock_open) +TEST(sock, prepare_raw_socket) { struct sock_filter ether_relay_filter[] = { { 0x6, 0, 0, 0x00040000 }, @@ -199,13 +200,13 @@ TEST(sock, sock_open) lengthof(ether_relay_filter), ether_relay_filter }; - EXPECT_GE(sock_open(ðer_relay_fprog), 0); + EXPECT_GE(prepare_raw_socket(ðer_relay_fprog), 0); } -TEST(sock, sock_open_invalid_filter) +TEST(sock, prepare_raw_socket_invalid_filter) { const struct sock_fprog ether_relay_fprog = {0,{}}; - EXPECT_EQ(sock_open(ðer_relay_fprog), -1); + EXPECT_EQ(prepare_raw_socket(ðer_relay_fprog), -1); } TEST(helper, send_udp) @@ -320,29 +321,53 @@ TEST(counter, initialize_counter) std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); std::string ifname = "Vlan1000"; initialize_counter(state_db, ifname); - 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")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "RX")); + EXPECT_TRUE(state_db->hexists("DHCPv6_COUNTER_TABLE|Vlan1000", "TX")); } TEST(counter, increase_counter) { std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); - state_db->hset("DHCPv6_COUNTER_TABLE|Vlan1000", "Solicit", "0"); std::string ifname = "Vlan1000"; - increase_counter(state_db, ifname, 1); - std::shared_ptr output = state_db->hget("DHCPv6_COUNTER_TABLE|Vlan1000", "Solicit"); - std::string *ptr = output.get(); - EXPECT_EQ(*ptr, "1"); + Json::Value root; + Json::CharReaderBuilder builder; + std::string *output; + const std::unique_ptr reader(builder.newCharReader()); + + // case 1, invalid packet type + ASSERT_NO_THROW(increase_counter(state_db.get(), ifname, DHCPv6_MESSAGE_TYPE_COUNT, DHCPV6_RX)); + + // case 2, counter table not initialized before increase_counter + increase_counter(state_db.get(), ifname, DHCPv6_MESSAGE_TYPE_SOLICIT, DHCPV6_RX); + auto output_ptr1 = state_db->hget("DHCPv6_COUNTER_TABLE|Vlan1000", "RX"); + EXPECT_NE(output_ptr1, nullptr); + output = output_ptr1.get(); + std::replace(output->begin(), output->end(), '\'', '\"'); + auto json_begin = output->c_str(); + auto json_end = json_begin + output->length(); + reader->parse(json_begin, json_end, &root, NULL); + EXPECT_EQ(root["Solicit"], "1"); + EXPECT_EQ(root["Advertise"], "0"); + + // case 3, counter table initialized before increase_counter + initialize_counter(state_db, ifname); + increase_counter(state_db.get(), ifname, DHCPv6_MESSAGE_TYPE_SOLICIT, DHCPV6_RX); + auto output_ptr2 = state_db->hget("DHCPv6_COUNTER_TABLE|Vlan1000", "RX"); + EXPECT_NE(output_ptr2, nullptr); + output = output_ptr2.get(); + std::replace(output->begin(), output->end(), '\'', '\"'); + json_begin = output->c_str(); + json_end = json_begin + output->length(); + reader->parse(json_begin, json_end, &root, NULL); + EXPECT_EQ(root["Solicit"], "1"); + EXPECT_EQ(root["Advertise"], "0"); + + // case 4, invalid json string + Json::StreamWriterBuilder wbuilder; + std::string document = Json::writeString(wbuilder, root); + std::replace(document.begin(), document.end(), '\"', ':'); + state_db->hset("DHCPv6_COUNTER_TABLE|Vlan1000", "RX", document); + ASSERT_NO_THROW(increase_counter(state_db.get(), ifname, DHCPv6_MESSAGE_TYPE_SOLICIT, DHCPV6_RX)); } TEST(relay, relay_client) @@ -367,6 +392,7 @@ TEST(relay, relay_client) 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); @@ -631,10 +657,11 @@ TEST(relay, dhcp6relay_stop) { TEST(relay, update_vlan_mapping) { std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); + std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); config_db->hset("VLAN_MEMBER|Vlan1000|Ethernet19", "tagging_mode", "untagged"); config_db->hset("VLAN_MEMBER|Vlan1000|Ethernet20", "tagging_mode", "untagged"); std::string vlan = "Vlan1000"; - update_vlan_mapping(vlan, config_db); + update_vlan_mapping(vlan, config_db, state_db); auto output = config_db->hget("VLAN_MEMBER|Vlan1000|Ethernet19", "tagging_mode"); std::string *ptr = output.get(); @@ -1116,5 +1143,116 @@ TEST(relay, server_callback_dualtor) { ASSERT_NO_THROW(server_callback_dualtor(0, 0, &vlans_in_loop)); } +TEST(relay, update_portchannel_mapping) { + std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); + std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); + config_db->hset("PORTCHANNEL_MEMBER|PortChannel101|Ethernet48", "", ""); + config_db->hset("PORTCHANNEL_MEMBER|PortChannel101|Ethernet52", "", ""); + config_db->hset("PORTCHANNEL_MEMBER|PortChannel102|Ethernet56", "", ""); + + update_portchannel_mapping(config_db, state_db); + + Json::Value root; + Json::CharReaderBuilder builder; + std::string *output; + const std::unique_ptr reader(builder.newCharReader()); + + auto output_ptr1 = state_db->hget("DHCPv6_COUNTER_TABLE|PortChannel101", "RX"); + EXPECT_NE(output_ptr1, nullptr); + output = output_ptr1.get(); + std::replace(output->begin(), output->end(), '\'', '\"'); + auto json_begin = output->c_str(); + auto json_end = json_begin + output->length(); + reader->parse(json_begin, json_end, &root, NULL); + + EXPECT_EQ(root["Unknown"], "0"); + EXPECT_EQ(root["Solicit"], "0"); + EXPECT_EQ(root["Advertise"], "0"); + EXPECT_EQ(root["Malformed"], "0"); + + auto output_ptr2 = state_db->hget("DHCPv6_COUNTER_TABLE|Ethernet52", "TX"); + EXPECT_NE(output_ptr2, nullptr); + output = output_ptr2.get(); + std::replace(output->begin(), output->end(), '\'', '\"'); + json_begin = output->c_str(); + json_end = json_begin + output->length(); + reader->parse(json_begin, json_end, &root, NULL); + + EXPECT_EQ(root["Unknown"], "0"); + EXPECT_EQ(root["Solicit"], "0"); + EXPECT_EQ(root["Advertise"], "0"); + EXPECT_EQ(root["Malformed"], "0"); +} + +TEST(relay, prepare_socket_callback) { + EXPECT_GLOBAL_CALL(event_add, event_add(_, NULL)).Times(1).WillOnce(Return(0)); + auto base = event_base_new(); + ASSERT_NO_THROW(prepare_socket_callback(base, 1, server_callback_dualtor, NULL)); +} + +TEST(relay, outbond_callback) { + std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); + std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); + config_db->hset("PORTCHANNEL_MEMBER|PortChannel101|Ethernet48", "", ""); + config_db->hset("PORTCHANNEL_MEMBER|PortChannel101|Ethernet52", "", ""); + config_db->hset("PORTCHANNEL_MEMBER|PortChannel102|Ethernet56", "", ""); + config_db->hset("VLAN_MEMBER|Vlan1000|Ethernet19", "tagging_mode", "untagged"); + config_db->hset("VLAN_MEMBER|Vlan1000|Ethernet20", "tagging_mode", "untagged"); + std::string vlan = "Vlan1000"; + + update_vlan_mapping(vlan, config_db, state_db); + update_portchannel_mapping(config_db, state_db); + + // simulator normal dhcpv6 packet length + ssize_t msg_len = 129; + + // cover buffer_sz <= 0 + EXPECT_GLOBAL_CALL(recvfrom, recvfrom(_, _, _, _, _, _)).Times(5).WillOnce(Return(0)) + .WillOnce(Return(2)).WillOnce(Return(0)) + .WillOnce(Return(msg_len)).WillOnce(Return(0)); + + char ethernet1[IF_NAMESIZE] = "Ethernet1"; + char ptr[20] = "vlan"; + EXPECT_GLOBAL_CALL(if_indextoname, if_indextoname(_, _)).Times(2).WillOnce(Return(nullptr)) + .WillOnce(DoAll(SetArrayArgument<1>(ethernet1, ethernet1 + IF_NAMESIZE), Return(ptr))); + + ASSERT_NO_THROW(outbond_callback(0, 0, state_db.get())); + // cover 0 < buffer_sz < sizeof(struct dhcpv6_msg) + ASSERT_NO_THROW(outbond_callback(0, 0, state_db.get())); + + ASSERT_NO_THROW(outbond_callback(0, 0, state_db.get())); +} + +TEST(relay, packet_counting_handler) { + uint8_t client_raw_solicit_with_externsion[] = { + 0x33, 0x33, 0x00, 0x01, 0x00, 0x02, 0x08, 0x00, 0x27, 0xfe, 0x8f, 0x95, 0x86, 0xdd, 0x60, 0x00, + 0x00, 0x00, 0x00, 0x44, 0x2c, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, + 0x27, 0xff, 0xfe, 0xfe, 0x8f, 0x95, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, + 0x02, 0x22, 0x02, 0x23, 0x00, 0x3c, 0xad, 0x08, 0x01, 0x10, + 0x08, 0x74, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1c, 0x39, 0xcf, 0x88, 0x08, 0x00, + 0x27, 0xfe, 0x8f, 0x95, 0x00, 0x06, 0x00, 0x04, 0x00, 0x17, 0x00, 0x18, 0x00, 0x08, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x19, 0x00, 0x0c, 0x27, 0xfe, 0x8f, 0x95, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, + 0x15, 0x18 + }; + std::string ifname("Vlan1000"); + std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); + packet_counting_handler(client_raw_solicit_with_externsion, sizeof(client_raw_solicit_with_externsion), + ifname, state_db.get(), DHCPV6_RX); + + Json::Value root; + Json::CharReaderBuilder builder; + const std::unique_ptr reader(builder.newCharReader()); + + auto output_ptr1 = state_db->hget("DHCPv6_COUNTER_TABLE|Vlan1000", "RX"); + EXPECT_NE(output_ptr1, nullptr); + auto output = output_ptr1.get(); + std::replace(output->begin(), output->end(), '\'', '\"'); + auto json_begin = output->c_str(); + auto json_end = json_begin + output->length(); + reader->parse(json_begin, json_end, &root, NULL); + EXPECT_EQ(root["Solicit"], "1"); +} + From 9d1ee9bd3f3bf778d0a9b1c71a4d08c7bcd2021a Mon Sep 17 00:00:00 2001 From: jcaiMR Date: Wed, 6 Sep 2023 06:28:53 +0000 Subject: [PATCH 2/4] fix gmock issue --- test/mock_relay.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/mock_relay.cpp b/test/mock_relay.cpp index 43205c1..a995f67 100644 --- a/test/mock_relay.cpp +++ b/test/mock_relay.cpp @@ -1224,12 +1224,11 @@ TEST(relay, outbond_callback) { } TEST(relay, packet_counting_handler) { - uint8_t client_raw_solicit_with_externsion[] = { + uint8_t client_raw_solicit[] = { 0x33, 0x33, 0x00, 0x01, 0x00, 0x02, 0x08, 0x00, 0x27, 0xfe, 0x8f, 0x95, 0x86, 0xdd, 0x60, 0x00, - 0x00, 0x00, 0x00, 0x44, 0x2c, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, + 0x00, 0x00, 0x00, 0x3c, 0x11, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x27, 0xff, 0xfe, 0xfe, 0x8f, 0x95, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, - 0x02, 0x22, 0x02, 0x23, 0x00, 0x3c, 0xad, 0x08, 0x01, 0x10, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x02, 0x22, 0x02, 0x23, 0x00, 0x3c, 0xad, 0x08, 0x01, 0x10, 0x08, 0x74, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1c, 0x39, 0xcf, 0x88, 0x08, 0x00, 0x27, 0xfe, 0x8f, 0x95, 0x00, 0x06, 0x00, 0x04, 0x00, 0x17, 0x00, 0x18, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0x00, 0x0c, 0x27, 0xfe, 0x8f, 0x95, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, @@ -1237,7 +1236,7 @@ TEST(relay, packet_counting_handler) { }; std::string ifname("Vlan1000"); std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); - packet_counting_handler(client_raw_solicit_with_externsion, sizeof(client_raw_solicit_with_externsion), + packet_counting_handler(client_raw_solicit, sizeof(client_raw_solicit), ifname, state_db.get(), DHCPV6_RX); Json::Value root; From cd6bcd7003cf818c0da2df3270d972bd36f428f2 Mon Sep 17 00:00:00 2001 From: jcaiMR Date: Sun, 10 Sep 2023 11:43:25 +0000 Subject: [PATCH 3/4] fix typo add protection code for message type decode --- src/relay.cpp | 88 +++++++++++++++++++++++++-------------------- src/relay.h | 12 +++---- test/mock_relay.cpp | 25 ++++++------- 3 files changed, 66 insertions(+), 59 deletions(-) diff --git a/src/relay.cpp b/src/relay.cpp index fbc31a0..adb58d3 100644 --- a/src/relay.cpp +++ b/src/relay.cpp @@ -18,12 +18,12 @@ static std::string counter_table = "DHCPv6_COUNTER_TABLE|"; static uint8_t client_recv_buffer[BUFFER_SIZE]; static uint8_t server_recv_buffer[BUFFER_SIZE]; -static uint8_t outbond_packet_buffer[BUFFER_SIZE]; +static uint8_t outbound_packet_buffer[BUFFER_SIZE]; /* DHCPv6 filter */ /* sudo tcpdump -dd "inbound and ip6 and udp and (port 546 or port 547)" */ -static struct sock_filter inbond_filter[] = { +static struct sock_filter inbound_filter[] = { { 0x28, 0, 0, 0xfffff004 }, { 0x15, 11, 0, 0x00000004 }, { 0x28, 0, 0, 0x0000000c }, @@ -39,14 +39,14 @@ static struct sock_filter inbond_filter[] = { { 0x6, 0, 0, 0x00040000 }, { 0x6, 0, 0, 0x00000000 }, }; -const struct sock_fprog inbond_filter_fprog = { - lengthof(inbond_filter), - inbond_filter +const struct sock_fprog inbound_filter_fprog = { + lengthof(inbound_filter), + inbound_filter }; /* sudo tcpdump -dd "outbound and ip6 and udp and (port 546 or port 547)" */ -static struct sock_filter outbond_filter[] = { +static struct sock_filter outbound_filter[] = { { 0x28, 0, 0, 0xfffff004 }, { 0x15, 0, 11, 0x00000004 }, { 0x28, 0, 0, 0x0000000c }, @@ -62,9 +62,9 @@ static struct sock_filter outbond_filter[] = { { 0x6, 0, 0, 0x00040000 }, { 0x6, 0, 0, 0x00000000 }, }; -const struct sock_fprog outbond_filter_fprog = { - lengthof(outbond_filter), - outbond_filter +const struct sock_fprog outbound_filter_fprog = { + lengthof(outbound_filter), + outbound_filter }; /* DHCPv6 counter name map */ @@ -344,7 +344,8 @@ void initialize_counter(std::shared_ptr state_db, std::string void increase_counter(swss::DBConnector *state_db, std::string &ifname, uint8_t msg_type, dhcpv6_pkt_dir_t dir) { if (counterMap.find(msg_type) == counterMap.end()) { syslog(LOG_WARNING, "Unexpected message type %d(0x%x)\n", msg_type, msg_type); - return; + // overwrite to UNKNOWN type for counting + msg_type = DHCPv6_MESSAGE_TYPE_UNKNOWN; } std::string table_name = counter_table + ifname; std::string type = counterMap.find(msg_type)->second; @@ -989,7 +990,7 @@ void update_loopback_mapping(std::string &ifname, std::shared_ptr *>(arg); struct sockaddr_ll sll; socklen_t slen = sizeof(sll); @@ -1025,10 +1026,8 @@ void client_callback(evutil_socket_t fd, short event, void *arg) { if (intf.find(CLIENT_IF_PREFIX) == std::string::npos) { continue; } - if (portchan_map.find(intf) != portchan_map.end()) { - /* update portchannel member interface rx counter */ - packet_counting_handler(client_recv_buffer, buffer_sz, intf, vlans->begin()->second.state_db.get(), DHCPV6_RX); - } + // update uplink Ethernet interfaces rx counters + packet_counting_handler(client_recv_buffer, buffer_sz, intf, vlans->begin()->second.state_db.get(), DHCPV6_RX); continue; } auto config_itr = vlans->find(vlan->second); @@ -1037,7 +1036,7 @@ void client_callback(evutil_socket_t fd, short event, void *arg) { continue; } auto config = config_itr->second; - /* update vlan member interface rx counter */ + /* update downlink vlan member interface rx counters */ packet_counting_handler(client_recv_buffer, buffer_sz, intf, config.state_db.get(), DHCPV6_RX); if (dual_tor_sock) { std::string state; @@ -1052,24 +1051,24 @@ void client_callback(evutil_socket_t fd, short event, void *arg) { } /** - * @code outbond_callback(evutil_socket_t fd, short event, void *arg); + * @code outbound_callback(evutil_socket_t fd, short event, void *arg); * - * @brief callback for outbond socket, only for counting purpose + * @brief callback for outbound socket, only for counting purpose * - * @param fd outbond socket + * @param fd outbound socket * @param event libevent triggered event * @param arg callback argument provided by user * * @return none */ -void outbond_callback(evutil_socket_t fd, short event, void *arg) { +void outbound_callback(evutil_socket_t fd, short event, void *arg) { auto state_db = static_cast(arg); struct sockaddr_ll sll; socklen_t slen = sizeof(sll); int pkts_num = 0; while (pkts_num++ < BATCH_SIZE) { - auto buffer_sz = recvfrom(fd, outbond_packet_buffer, BUFFER_SIZE, 0, (struct sockaddr *)&sll, &slen); + auto buffer_sz = recvfrom(fd, outbound_packet_buffer, BUFFER_SIZE, 0, (struct sockaddr *)&sll, &slen); if (buffer_sz <= 0) { if (errno != EAGAIN) { syslog(LOG_ERR, "recv: Failed to receive data at filter socket: %s\n", strerror(errno)); @@ -1081,9 +1080,9 @@ void outbond_callback(evutil_socket_t fd, short event, void *arg) { syslog(LOG_WARNING, "Invalid output interface index %d\n", sll.sll_ifindex); continue; } - /* Only count TX packets for vlan and portchannel member interfaces. - * For portchannel interfaces, increase based on member interfaces counter. - * For vlan interfaces, increase in relay service function handlers. + /* Only count TX packets downlink and uplink Ethernet interfaces. + * For portchannel interface TX counter, increase in packet_counting_handler. + * For vlan interface counter, increase in relay service function handlers. */ std::string intf(interfaceName); auto vlan = vlan_map.find(intf); @@ -1091,12 +1090,10 @@ void outbond_callback(evutil_socket_t fd, short event, void *arg) { if (intf.find(CLIENT_IF_PREFIX) == std::string::npos) { continue; } - if (portchan_map.find(intf) != portchan_map.end()) { - packet_counting_handler(outbond_packet_buffer, buffer_sz, intf, state_db, DHCPV6_TX); - } + packet_counting_handler(outbound_packet_buffer, buffer_sz, intf, state_db, DHCPV6_TX); continue; } - packet_counting_handler(outbond_packet_buffer, buffer_sz, intf, state_db, DHCPV6_TX); + packet_counting_handler(outbound_packet_buffer, buffer_sz, intf, state_db, DHCPV6_TX); } } @@ -1130,13 +1127,21 @@ void packet_counting_handler(uint8_t *buffer, ssize_t length, std::string &ifnam current_position += ext_header->ip6e_len; if((current_position == prev) || (current_position + sizeof(*ext_header) >= buffer_end)) { + syslog(LOG_WARNING, "Invalid ipv6 extension header packets from %s\n", ifname.c_str()); return; } prev = current_position; } while (ext_header->ip6e_nxt != IPPROTO_UDP); } - parse_udp(current_position, ¤t_position); + auto udp_header = parse_udp(current_position, ¤t_position); + auto udp_len = ntohs(udp_header->len); + if (udp_len < (sizeof(struct udphdr) + sizeof(struct dhcpv6_msg)) || + (current_position + udp_len - sizeof(struct udphdr)) != buffer_end) { + syslog(LOG_WARNING, "Invalid UDP header length from %s\n", ifname.c_str()); + return; + } + auto msg = parse_dhcpv6_hdr(current_position); /* Extra counting for port channel interface. * No need worry for vlan interface counting, it will be handled in relay service paths. @@ -1148,7 +1153,8 @@ void packet_counting_handler(uint8_t *buffer, ssize_t length, std::string &ifnam } /** - * @code client_packet_handler(uint8_t *buffer, ssize_t length, struct relay_config *config, std::string &ifname); + * @code client_packet_handler(uint8_t *buffer, ssize_t length, struct relay_config *config, + * std::string &ifname); * * @brief dhcpv6 client packet handler * @@ -1175,6 +1181,7 @@ void client_packet_handler(uint8_t *buffer, ssize_t length, struct relay_config current_position += ext_header->ip6e_len; if((current_position == prev) || (current_position + sizeof(*ext_header) >= buffer_end)) { + syslog(LOG_WARNING, "Invalid ipv6 extension header packets from %s\n", ifname.c_str()); return; } prev = current_position; @@ -1183,8 +1190,9 @@ void client_packet_handler(uint8_t *buffer, ssize_t length, struct relay_config } auto udp_header = parse_udp(current_position, ¤t_position); - uint16_t udp_len = ntohs(udp_header->len); - if (udp_len < sizeof(struct udphdr) || (current_position - sizeof(struct udphdr) + udp_len) != buffer_end) { + auto udp_len = ntohs(udp_header->len); + if (udp_len < (sizeof(struct udphdr) + sizeof(struct dhcpv6_msg)) || + (current_position + udp_len - sizeof(struct udphdr)) != buffer_end) { syslog(LOG_WARNING, "Invalid UDP header length from %s\n", ifname.c_str()); return; } @@ -1193,7 +1201,8 @@ void client_packet_handler(uint8_t *buffer, ssize_t length, struct relay_config // RFC3315 only if (msg->msg_type < DHCPv6_MESSAGE_TYPE_SOLICIT || msg->msg_type > DHCPv6_MESSAGE_TYPE_RELAY_REPL) { increase_counter(config->state_db.get(), config->interface, DHCPv6_MESSAGE_TYPE_UNKNOWN, DHCPV6_RX); - syslog(LOG_WARNING, "Unknown DHCPv6 message type %d from %s:%s\n", msg->msg_type, ifname.c_str(), config->interface.c_str()); + syslog(LOG_WARNING, "Unknown DHCPv6 message type %d from %s:%s\n", + msg->msg_type, ifname.c_str(), config->interface.c_str()); return; } @@ -1217,7 +1226,8 @@ void client_packet_handler(uint8_t *buffer, ssize_t length, struct relay_config } default: { - syslog(LOG_WARNING, "DHCPv6 client message type %d received from %s was not relayed\n", msg->msg_type, ifname.c_str()); + syslog(LOG_WARNING, "DHCPv6 client message type %d received from %s was not relayed\n", + msg->msg_type, ifname.c_str()); break; } } @@ -1496,12 +1506,12 @@ void loop_relay(std::unordered_map &vlans) { state_db.get(), "HW_MUX_CABLE_TABLE" ); - auto in_filter = prepare_raw_socket(&inbond_filter_fprog); - prepare_socket_callback(base, in_filter, client_callback, reinterpret_cast(&vlans)); + auto in_filter = prepare_raw_socket(&inbound_filter_fprog); + prepare_socket_callback(base, in_filter, inbound_callback, reinterpret_cast(&vlans)); sockets.push_back(in_filter); - auto out_filter = prepare_raw_socket(&outbond_filter_fprog); - prepare_socket_callback(base, out_filter, outbond_callback, reinterpret_cast(state_db.get())); + auto out_filter = prepare_raw_socket(&outbound_filter_fprog); + prepare_socket_callback(base, out_filter, outbound_callback, reinterpret_cast(state_db.get())); sockets.push_back(out_filter); int lo_sock = -1; diff --git a/src/relay.h b/src/relay.h index fc63db3..4b06ca8 100644 --- a/src/relay.h +++ b/src/relay.h @@ -477,7 +477,7 @@ void update_portchannel_mapping(std::shared_ptr cfgdb, std::s void update_loopback_mapping(std::string &ifname, std::shared_ptr statdb); /** - * @code client_callback(evutil_socket_t fd, short event, void *arg); + * @code inbound_callback(evutil_socket_t fd, short event, void *arg); * * @brief callback for libevent that is called everytime data is received at the filter socket * @@ -487,7 +487,7 @@ void update_loopback_mapping(std::string &ifname, std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); std::shared_ptr mux_table = std::make_shared ( state_db.get(), "HW_MUX_CABLE_TABLE" @@ -824,21 +824,21 @@ TEST(relay, client_callback) { .WillOnce(DoAll(SetArrayArgument<1>(ethernet1, ethernet1 + IF_NAMESIZE), Return(ptr))) .WillOnce(DoAll(SetArrayArgument<1>(ethernet3, ethernet3 + IF_NAMESIZE), Return(ptr))); // test buffer_sz <=0 early return - ASSERT_NO_THROW(client_callback(-1, 0, &vlans)); + ASSERT_NO_THROW(inbound_callback(-1, 0, &vlans)); // test buffer_sz > 0, if_indextoname == null early return - ASSERT_NO_THROW(client_callback(-1, 0, &vlans)); + ASSERT_NO_THROW(inbound_callback(-1, 0, &vlans)); // test normal msg but vlan not found - ASSERT_NO_THROW(client_callback(-1, 0, &vlans)); + ASSERT_NO_THROW(inbound_callback(-1, 0, &vlans)); // test normal msg and vlan found - ASSERT_NO_THROW(client_callback(-1, 0, &vlans)); + ASSERT_NO_THROW(inbound_callback(-1, 0, &vlans)); dual_tor_sock = true; // test normal msg and vlan found + dual tor - ASSERT_NO_THROW(client_callback(-1, 0, &vlans)); + ASSERT_NO_THROW(inbound_callback(-1, 0, &vlans)); dual_tor_sock = false; // normal msg but interface mapping missing - ASSERT_NO_THROW(client_callback(-1, 0, &vlans)); + ASSERT_NO_THROW(inbound_callback(-1, 0, &vlans)); } TEST(relay, shutdown_relay) { @@ -1190,7 +1190,7 @@ TEST(relay, prepare_socket_callback) { ASSERT_NO_THROW(prepare_socket_callback(base, 1, server_callback_dualtor, NULL)); } -TEST(relay, outbond_callback) { +TEST(relay, outbound_callback) { std::shared_ptr config_db = std::make_shared ("CONFIG_DB", 0); std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); config_db->hset("PORTCHANNEL_MEMBER|PortChannel101|Ethernet48", "", ""); @@ -1216,11 +1216,11 @@ TEST(relay, outbond_callback) { EXPECT_GLOBAL_CALL(if_indextoname, if_indextoname(_, _)).Times(2).WillOnce(Return(nullptr)) .WillOnce(DoAll(SetArrayArgument<1>(ethernet1, ethernet1 + IF_NAMESIZE), Return(ptr))); - ASSERT_NO_THROW(outbond_callback(0, 0, state_db.get())); + ASSERT_NO_THROW(outbound_callback(0, 0, state_db.get())); // cover 0 < buffer_sz < sizeof(struct dhcpv6_msg) - ASSERT_NO_THROW(outbond_callback(0, 0, state_db.get())); + ASSERT_NO_THROW(outbound_callback(0, 0, state_db.get())); - ASSERT_NO_THROW(outbond_callback(0, 0, state_db.get())); + ASSERT_NO_THROW(outbound_callback(0, 0, state_db.get())); } TEST(relay, packet_counting_handler) { @@ -1252,6 +1252,3 @@ TEST(relay, packet_counting_handler) { reader->parse(json_begin, json_end, &root, NULL); EXPECT_EQ(root["Solicit"], "1"); } - - - From d6f108bb5da267a13a308724543dcfd1974c4dd0 Mon Sep 17 00:00:00 2001 From: jcaiMR <111116206+jcaiMR@users.noreply.github.com> Date: Mon, 11 Sep 2023 10:47:47 +0800 Subject: [PATCH 4/4] Update relay.cpp --- src/relay.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/relay.cpp b/src/relay.cpp index adb58d3..eccb62e 100644 --- a/src/relay.cpp +++ b/src/relay.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include "configdb.h" #include "sonicv2connector.h"