diff --git a/.azure-pipelines/build.yml b/.azure-pipelines/build.yml index 1702ef8..758e8a6 100644 --- a/.azure-pipelines/build.yml +++ b/.azure-pipelines/build.yml @@ -30,6 +30,8 @@ jobs: sudo apt-get install -y dotnet-sdk-6.0 displayName: install .Net - script: | + set -ex + sudo apt-get update sudo apt-get install -y \ libboost-system1.71-dev \ libboost-thread1.71-dev \ diff --git a/src/configInterface.cpp b/src/configInterface.cpp index d0801af..6ea9cc3 100644 --- a/src/configInterface.cpp +++ b/src/configInterface.cpp @@ -9,6 +9,8 @@ bool pollSwssNotifcation = true; std::shared_ptr mSwssThreadPtr; swss::Select swssSelect; +extern bool dual_tor_sock; + /** * @code void initialize_swss() * @@ -116,6 +118,12 @@ void handleRelayNotification(swss::SubscriberStateTable &ipHelpersTable, std::un void processRelayNotification(std::deque &entries, std::unordered_map &vlans) { std::vector servers; + bool option_79_default = true; + bool interface_id_default = false; + + if (dual_tor_sock) { + interface_id_default = true; + } for (auto &entry: entries) { std::string vlan = kfvKey(entry); @@ -123,8 +131,8 @@ void processRelayNotification(std::deque &entries, std::vector fieldValues = kfvFieldsValues(entry); relay_config intf; - intf.is_option_79 = true; - intf.is_interface_id = false; + intf.is_option_79 = option_79_default; + intf.is_interface_id = interface_id_default; intf.interface = vlan; intf.mux_key = ""; intf.state_db = nullptr; diff --git a/src/configInterface.h b/src/configInterface.h index 94a0578..38f3ee2 100644 --- a/src/configInterface.h +++ b/src/configInterface.h @@ -11,6 +11,7 @@ struct swssNotification { std::unordered_map vlans; swss::SubscriberStateTable *ipHelpersTable; }; + /** * @code void initialize_swss() * diff --git a/src/main.cpp b/src/main.cpp index 9da3cb8..54fc75c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,23 +4,32 @@ #include "configInterface.h" bool dual_tor_sock = false; +char loopback[IF_NAMESIZE] = "Loopback0"; static void usage() { - printf("Usage: ./dhcp6relay {-d}\n"); - printf("\t-d: enable dual tor option\n"); + printf("Usage: ./dhcp6relay [-u ]\n"); + printf("\tloopback interface: is the loopback interface for dual tor setup\n"); } int main(int argc, char *argv[]) { - if (argc > 1) { + if (argc > 2) { switch (argv[1][1]) { - case 'd': + case 'u': + if (strlen(argv[2]) != 0 && strlen(argv[2]) < IF_NAMESIZE) { + std::memset(loopback, 0, IF_NAMESIZE); + std::memcpy(loopback, argv[2], strlen(argv[2])); + } else { + syslog(LOG_ERR, "loopback interface name over length %d.\n", IF_NAMESIZE); + return 1; + } dual_tor_sock = true; break; default: fprintf(stderr, "%s: Unknown option\n", basename(argv[0])); usage(); + return 0; } } try { diff --git a/src/relay.cpp b/src/relay.cpp index 64390a9..9310d35 100644 --- a/src/relay.cpp +++ b/src/relay.cpp @@ -14,8 +14,6 @@ #define BUFFER_SIZE 9200 -struct event *listen_event; -struct event *server_listen_event; struct event_base *base; struct event *ev_sigint; struct event *ev_sigterm; @@ -71,6 +69,9 @@ std::map counterMap = {{DHCPv6_MESSAGE_TYPE_UNKNOWN, "Unknown" /* interface to vlan mapping */ std::unordered_map vlan_map; +/* ipv6 address to vlan name mapping */ +std::unordered_map addr_vlan_map; + /** * @code initialize_counter(std::shared_ptr state_db, std::string counterVlan); * @@ -111,6 +112,7 @@ void initialize_counter(std::shared_ptr state_db, std::string * @return none */ void update_counter(std::shared_ptr state_db, std::string counterVlan, uint8_t msg_type) { + counters[msg_type]++; state_db->hset(counterVlan, counterMap.find(msg_type)->second, toString(counters[msg_type])); } @@ -130,6 +132,19 @@ std::string toString(uint64_t count) { return countValue; } +/** + * @code bool inline isIPv6Zero(const in6_addr *addr) + * + * @brief check if ipv6 address is zero + * + * @param addr ipv6 address + * + * @return bool + */ +bool inline isIPv6Zero(const in6_addr *addr) { + return (memcmp(addr, &in6addr_any, sizeof(in6addr_any)) == 0); +} + /** * @code const struct ether_header *parse_ether_frame(const uint8_t *buffer, const uint8_t **out_end); * @@ -215,9 +230,7 @@ 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) { auto option = (const struct dhcpv6_option *)buffer; - uint8_t size = 4; // option-code + option-len - size += *(uint16_t *)(buffer); - (*out_end) = buffer + size + ntohs(option->option_length); + (*out_end) = buffer + sizeof(struct dhcpv6_option) + ntohs(option->option_length); return option; } @@ -225,7 +238,6 @@ const struct dhcpv6_option *parse_dhcpv6_opt(const uint8_t *buffer, const uint8_ void process_sent_msg(relay_config *config, uint8_t msg_type) { std::string counterVlan = counter_table; if (counterMap.find(msg_type) != counterMap.end()) { - counters[msg_type]++; update_counter(config->state_db, counterVlan.append(config->interface), msg_type); } else { syslog(LOG_WARNING, "unexpected message type %d(0x%x)\n", msg_type, msg_type); @@ -269,6 +281,9 @@ int sock_open(const struct sock_fprog *fprog) return -1; } + evutil_make_listen_socket_reuseable(s); + evutil_make_socket_nonblocking(s); + struct sockaddr_ll sll = { .sll_family = AF_PACKET, .sll_protocol = htons(ETH_P_ALL), @@ -306,22 +321,22 @@ int sock_open(const struct sock_fprog *fprog) } /** - * @code prepare_relay_config(relay_config &interface_config, int local_sock, int filter); + * @code prepare_relay_config(relay_config &interface_config, int gua_sock, int filter); * * @brief prepare for specified relay interface config: server and link address * * @param interface_config pointer to relay config to be prepared - * @param local_sock L3 socket used for relaying messages + * @param gua_sock L3 socket used for relaying messages * @param filter socket attached with filter * * @return none */ -void prepare_relay_config(relay_config &interface_config, int local_sock, int filter) { +void prepare_relay_config(relay_config &interface_config, int gua_sock, int filter) { struct ifaddrs *ifa, *ifa_tmp; sockaddr_in6 non_link_local; sockaddr_in6 link_local; - interface_config.local_sock = local_sock; + interface_config.gua_sock = gua_sock; interface_config.filter = filter; for(auto server: interface_config.servers) { @@ -360,38 +375,91 @@ void prepare_relay_config(relay_config &interface_config, int local_sock, int fi if(!IN6_IS_ADDR_LINKLOCAL(&non_link_local.sin6_addr)) { interface_config.link_address = non_link_local; - } - else { + } else { interface_config.link_address = link_local; } + char ipv6_str[INET6_ADDRSTRLEN] = {}; + inet_ntop(AF_INET6, &interface_config.link_address.sin6_addr, ipv6_str, INET6_ADDRSTRLEN); + addr_vlan_map[std::string(ipv6_str)] = interface_config.interface; +} + +int prepare_lo_socket(const char *lo) { + struct ifaddrs *ifa, *ifa_tmp; + sockaddr_in6 gua = {0}; + int lo_sock = -1; + + if ((lo_sock = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) { + syslog(LOG_ERR, "socket: Failed to create gua socket on interface %s\n", lo); + return -1; + } + + evutil_make_listen_socket_reuseable(lo_sock); + evutil_make_socket_nonblocking(lo_sock); + + if (getifaddrs(&ifa) == -1) { + syslog(LOG_WARNING, "getifaddrs: Unable to get network interfaces with %s\n", strerror(errno)); + } + bool bind_gua = false; + ifa_tmp = ifa; + while (ifa_tmp) { + if (ifa_tmp->ifa_addr && (ifa_tmp->ifa_addr->sa_family == AF_INET6)) { + if (strcmp(ifa_tmp->ifa_name, lo) == 0) { + struct sockaddr_in6 *in6 = (struct sockaddr_in6*) ifa_tmp->ifa_addr; + if (!IN6_IS_ADDR_LINKLOCAL(&in6->sin6_addr)) { + bind_gua = true; + gua = *in6; + gua.sin6_family = AF_INET6; + gua.sin6_port = htons(RELAY_PORT); + break; + } + } + } + ifa_tmp = ifa_tmp->ifa_next; + } + freeifaddrs(ifa); + + if (!bind_gua || bind(lo_sock, (sockaddr *)&gua, sizeof(gua)) == -1) { + syslog(LOG_ERR, "bind: Failed to bind socket on interface %s with %s\n", lo, strerror(errno)); + (void) close(lo_sock); + return -1; + } + + return lo_sock; } /** - * @code prepare_socket(int &local_sock, int &server_sock, relay_config &config); + * @code prepare_vlan_sockets(int &gua_sock, int &lla_sock, relay_config &config); * - * @brief prepare L3 socket for sending + * @brief prepare vlan l3 socket for sending * - * @param local_sock pointer to socket binded to global address for relaying client message to server and listening for server message - * @param server_sock pointer to socket binded to link_local address for relaying server message to client + * @param gua_sock socket binded to global address for relaying client message to server and listening for server message + * @param lla_sock socket binded to link_local address for relaying server message to client * - * @return none + * @return int */ -void prepare_socket(int &local_sock, int &server_sock, relay_config &config) { +int prepare_vlan_sockets(int &gua_sock, int &lla_sock, relay_config &config) { struct ifaddrs *ifa, *ifa_tmp; - sockaddr_in6 addr = {0}; - sockaddr_in6 ll_addr = {0}; + sockaddr_in6 gua = {0}, lla = {0}; - if ((local_sock = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) { - syslog(LOG_ERR, "socket: Failed to create socket on interface %s\n", config.interface.c_str()); + if ((gua_sock = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) { + syslog(LOG_ERR, "socket: Failed to create gua socket on interface %s\n", config.interface.c_str()); + return -1; } - if ((server_sock = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) { - syslog(LOG_ERR, "socket: Failed to create socket on interface %s\n", config.interface.c_str()); + if ((lla_sock = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) { + syslog(LOG_ERR, "socket: Failed to create lla socket on interface %s\n", config.interface.c_str()); + close(gua_sock); + return -1; } + evutil_make_listen_socket_reuseable(gua_sock); + evutil_make_socket_nonblocking(gua_sock); + evutil_make_listen_socket_reuseable(lla_sock); + evutil_make_socket_nonblocking(lla_sock); + int retry = 0; - bool bind_addr = false; - bool bind_ll_addr = false; + bool bind_gua = false; + bool bind_lla = false; do { if (getifaddrs(&ifa) == -1) { syslog(LOG_WARNING, "getifaddrs: Unable to get network interfaces with %s\n", strerror(errno)); @@ -399,19 +467,20 @@ void prepare_socket(int &local_sock, int &server_sock, relay_config &config) { else { ifa_tmp = ifa; while (ifa_tmp) { - if (ifa_tmp->ifa_addr && (ifa_tmp->ifa_addr->sa_family == AF_INET6) && (strcmp(ifa_tmp->ifa_name, config.interface.c_str()) == 0)) { - struct sockaddr_in6 *in6 = (struct sockaddr_in6*) ifa_tmp->ifa_addr; - if(!IN6_IS_ADDR_LINKLOCAL(&in6->sin6_addr)) { - bind_addr = true; - in6->sin6_family = AF_INET6; - in6->sin6_port = htons(RELAY_PORT); - addr = *in6; - } - if(IN6_IS_ADDR_LINKLOCAL(&in6->sin6_addr)) { - bind_ll_addr = true; - in6->sin6_family = AF_INET6; - in6->sin6_port = htons(RELAY_PORT); - ll_addr = *in6; + if (ifa_tmp->ifa_addr && (ifa_tmp->ifa_addr->sa_family == AF_INET6)) { + if (strcmp(ifa_tmp->ifa_name, config.interface.c_str()) == 0) { + struct sockaddr_in6 *in6 = (struct sockaddr_in6*) ifa_tmp->ifa_addr; + if (!IN6_IS_ADDR_LINKLOCAL(&in6->sin6_addr)) { + bind_gua = true; + gua = *in6; + gua.sin6_family = AF_INET6; + gua.sin6_port = htons(RELAY_PORT); + } else { + bind_lla = true; + lla = *in6; + lla.sin6_family = AF_INET6; + lla.sin6_port = htons(RELAY_PORT); + } } } ifa_tmp = ifa_tmp->ifa_next; @@ -419,7 +488,7 @@ void prepare_socket(int &local_sock, int &server_sock, relay_config &config) { freeifaddrs(ifa); } - if (bind_addr && bind_ll_addr) { + if (bind_gua && bind_lla) { break; } @@ -427,13 +496,22 @@ void prepare_socket(int &local_sock, int &server_sock, relay_config &config) { sleep(5); } while (retry < 6); - if ((!bind_addr) || (bind(local_sock, (sockaddr *)&addr, sizeof(addr)) == -1)) { - syslog(LOG_ERR, "bind: Failed to bind socket to global ipv6 address on interface %s after %d retries with %s\n", config.interface.c_str(), retry, strerror(errno)); + if ((!bind_gua) || (bind(gua_sock, (sockaddr *)&gua, sizeof(gua)) == -1)) { + syslog(LOG_ERR, "bind: Failed to bind socket to global ipv6 address on interface %s after %d retries with %s\n", + config.interface.c_str(), retry, strerror(errno)); + close(gua_sock); + close(lla_sock); + return -1; } - if ((!bind_ll_addr) || (bind(server_sock, (sockaddr *)&ll_addr, sizeof(addr)) == -1)) { - syslog(LOG_ERR, "bind: Failed to bind socket to link local ipv6 address on interface %s after %d retries with %s\n", config.interface.c_str(), retry, strerror(errno)); + if ((!bind_lla) || (bind(lla_sock, (sockaddr *)&lla, sizeof(lla)) == -1)) { + syslog(LOG_ERR, "bind: Failed to bind socket to link local ipv6 address on interface %s after %d retries with %s\n", + config.interface.c_str(), retry, strerror(errno)); + close(gua_sock); + close(lla_sock); + return -1; } + return 0; } @@ -451,7 +529,7 @@ void prepare_socket(int &local_sock, int &server_sock, relay_config &config) { * * @return none */ -void relay_client(int sock, const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, const ether_header *ether_hdr, relay_config *config) { +void relay_client(const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, const ether_header *ether_hdr, relay_config *config) { static uint8_t buffer[BUFFER_SIZE]; auto current_buffer_position = buffer; dhcpv6_relay_msg new_message; @@ -496,6 +574,10 @@ void relay_client(int sock, const uint8_t *msg, int32_t len, const ip6_hdr *ip_h relay_forward(current_buffer_position, parse_dhcpv6_hdr(msg), dhcp_message_length); current_buffer_position += dhcp_message_length + sizeof(dhcpv6_option); + int sock = config->gua_sock; + if (dual_tor_sock) { + sock = config->lo_sock; + } for(auto server: config->servers_sock) { if(send_udp(sock, buffer, server, current_buffer_position - buffer)) { process_sent_msg(config, new_message.msg_type); @@ -504,11 +586,10 @@ void relay_client(int sock, const uint8_t *msg, int32_t len, const ip6_hdr *ip_h } /** - * @code relay_relay_forw(int sock, const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, relay_config *config) + * @code relay_relay_forw(const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, relay_config *config) * * @brief construct a relay-forward message encapsulated relay-forward message * - * @param sock L3 socket for sending data to servers * @param msg pointer to dhcpv6 message header position * @param len size of data received * @param ip_hdr pointer to IPv6 header @@ -516,7 +597,7 @@ void relay_client(int sock, const uint8_t *msg, int32_t len, const ip6_hdr *ip_h * * @return none */ -void relay_relay_forw(int sock, const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, relay_config *config) { +void relay_relay_forw(const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, relay_config *config) { static uint8_t buffer[BUFFER_SIZE]; dhcpv6_relay_msg new_message; auto current_buffer_position = buffer; @@ -534,10 +615,29 @@ void relay_relay_forw(int sock, const uint8_t *msg, int32_t len, const ip6_hdr * memcpy(current_buffer_position, &new_message, sizeof(dhcpv6_relay_msg)); current_buffer_position += sizeof(dhcpv6_relay_msg); + // insert option82 for new relay-forward packet, we need this information + // to get original relay-forward source interface for accurate counting in dualtor scenario + // is_interface_id is by-default enabled in dualtor scenario + if(config->is_interface_id) { + interface_id_option intf_id; + intf_id.option_code = htons(OPTION_INTERFACE_ID); + intf_id.option_length = htons(sizeof(in6_addr)); + intf_id.interface_id = config->link_address.sin6_addr; + if ((unsigned)(current_buffer_position + sizeof(linklayer_addr_option) - buffer) > sizeof(buffer)) { + return; + } + memcpy(current_buffer_position, &intf_id, sizeof(interface_id_option)); + current_buffer_position += sizeof(interface_id_option); + } + auto dhcp_message_length = len; relay_forward(current_buffer_position, parse_dhcpv6_hdr(msg), dhcp_message_length); current_buffer_position += dhcp_message_length + sizeof(dhcpv6_option); + int sock = config->gua_sock; + if (dual_tor_sock) { + sock = config->lo_sock; + } for(auto server: config->servers_sock) { if(send_udp(sock, buffer, server, current_buffer_position - buffer)) { process_sent_msg(config, new_message.msg_type); @@ -546,18 +646,17 @@ void relay_relay_forw(int sock, const uint8_t *msg, int32_t len, const ip6_hdr * } /** - * @code relay_relay_reply(int sock, const uint8_t *msg, int32_t len, relay_config *configs); + * @code relay_relay_reply(const uint8_t *msg, int32_t len, relay_config *configs); * * @brief relay and unwrap a relay-reply message * - * @param sock L3 socket for sending data to servers * @param msg pointer to dhcpv6 message header position * @param len size of data received * @param config relay interface config * * @return none */ - void relay_relay_reply(int sock, const uint8_t *msg, int32_t len, relay_config *config) { + void relay_relay_reply(const uint8_t *msg, int32_t len, relay_config *config) { static uint8_t buffer[BUFFER_SIZE]; uint8_t type = 0; struct sockaddr_in6 target_addr; @@ -591,6 +690,13 @@ void relay_relay_forw(int sock, const uint8_t *msg, int32_t len, const ip6_hdr * target_addr.sin6_flowinfo = 0; target_addr.sin6_port = htons(CLIENT_PORT); target_addr.sin6_scope_id = if_nametoindex(config->interface.c_str()); + int sock = config->lla_sock; + if (isIPv6Zero(&dhcp_relay_header->link_address)) { + // In this case, it's multi-level relay + if (!IN6_IS_ADDR_LINKLOCAL(&dhcp_relay_header->peer_address)) + sock = config->gua_sock; + target_addr.sin6_port = htons(RELAY_PORT); + } if(send_udp(sock, buffer, target_addr, current_buffer_position - buffer)) { process_sent_msg(config, type); @@ -738,11 +844,10 @@ void client_packet_handler(uint8_t *buffer, ssize_t length, struct relay_config } auto option_position = current_position + sizeof(struct dhcpv6_msg); - switch (msg->msg_type) { case DHCPv6_MESSAGE_TYPE_RELAY_FORW: { - relay_relay_forw(config->local_sock, current_position, ntohs(udp_header->len) - sizeof(udphdr), ip_header, config); + relay_relay_forw(current_position, ntohs(udp_header->len) - sizeof(udphdr), ip_header, config); break; } case DHCPv6_MESSAGE_TYPE_SOLICIT: @@ -758,15 +863,13 @@ void client_packet_handler(uint8_t *buffer, ssize_t length, struct relay_config auto option = parse_dhcpv6_opt(option_position, &tmp); option_position = tmp; if (ntohs(option->option_code) > DHCPv6_OPTION_LIMIT) { - counters[DHCPv6_MESSAGE_TYPE_MALFORMED]++; update_counter(config->state_db, counterVlan.append(config->interface), DHCPv6_MESSAGE_TYPE_MALFORMED); syslog(LOG_WARNING, "DHCPv6 option is invalid or contains malformed payload from %s\n", ifname.c_str()); return; } } - counters[msg->msg_type]++; update_counter(config->state_db, counterVlan.append(config->interface), msg->msg_type); - relay_client(config->local_sock, current_position, ntohs(udp_header->len) - sizeof(udphdr), ip_header, ether_header, config); + relay_client(current_position, ntohs(udp_header->len) - sizeof(udphdr), ip_header, ether_header, config); break; } default: @@ -777,6 +880,122 @@ void client_packet_handler(uint8_t *buffer, ssize_t length, struct relay_config } } +/** + * @code struct relay_config * + * get_relay_int_from_relay_msg(const uint8_t *msg, int32_t len, + * std::unordered_map *vlans) + * + * @brief get relay interface info from relay message + * + * @param addr ipv6 address + * + * @return bool + */ +struct relay_config * +get_relay_int_from_relay_msg(const uint8_t *msg, int32_t len, std::unordered_map *vlans) { + auto current_position = msg; + auto dhcp_relay_header = parse_dhcpv6_relay(msg); + interface_id_option intf_id; + + std::memset(&intf_id, 0, sizeof(interface_id_option)); + current_position += sizeof(struct dhcpv6_relay_msg); + while ((current_position - msg) < len) { + const uint8_t *tmp = NULL; + auto option = parse_dhcpv6_opt(current_position, &tmp); + current_position = tmp; + if (current_position - msg > len) { + break; + } + switch (ntohs(option->option_code)) { + case OPTION_INTERFACE_ID: { + intf_id.option_code = OPTION_INTERFACE_ID; + intf_id.option_length = ntohs(option->option_length); + std::memcpy(&intf_id.interface_id, ((uint8_t *)option) + sizeof(struct dhcpv6_option), intf_id.option_length); + break; + } + default: + break; + } + } + + in6_addr address = in6addr_any; + if (!isIPv6Zero(&intf_id.interface_id)) { + std::memcpy(&address, &intf_id.interface_id, sizeof(in6_addr)); + } else { + std::memcpy(&address, &dhcp_relay_header->link_address, sizeof(in6_addr)); + } + // multi-level relay agents + if (isIPv6Zero(&address)) { + return NULL; + } + + char ipv6_str[INET6_ADDRSTRLEN] = {}; + inet_ntop(AF_INET6, &address, ipv6_str, INET6_ADDRSTRLEN); + auto v6_string = std::string(ipv6_str); + if (addr_vlan_map.find(v6_string) == addr_vlan_map.end()) { + syslog(LOG_WARNING, "DHCPv6 type %d can't find vlan info from link address %s\n", + dhcp_relay_header->msg_type, ipv6_str); + return NULL; + } + + auto vlan_name = addr_vlan_map[v6_string]; + if (vlans->find(vlan_name) == vlans->end()) { + syslog(LOG_WARNING, "DHCPv6 can't find vlan %s config\n", vlan_name.c_str()); + return NULL; + } + return &vlans->find(vlan_name)->second; +} + +/** + * @code void server_callback_dualtor(evutil_socket_t fd, short event, void *arg); + * + * @brief callback for libevent that is called everytime data is received at the loopback socket + * + * @param fd loopback socket + * @param event libevent triggered event + * @param arg callback argument provided by user + * + * @return none + */ +void server_callback_dualtor(evutil_socket_t fd, short event, void *arg) { + auto vlans = reinterpret_cast *>(arg); + sockaddr_in6 from; + socklen_t len = sizeof(from); + int32_t pkts_num = 0; + static uint8_t message_buffer[BUFFER_SIZE]; + + while (pkts_num++ < BATCH_SIZE) { + std::string counterVlan = counter_table; + auto buffer_sz = recvfrom(fd, message_buffer, BUFFER_SIZE, 0, (sockaddr *)&from, &len); + if (buffer_sz <= 0) { + if (errno != EAGAIN) { + syslog(LOG_ERR, "recv: Failed to receive data from server: %s\n", strerror(errno)); + } + return; + } + + if (buffer_sz < (int32_t)sizeof(struct dhcpv6_msg)) { + syslog(LOG_WARNING, "Invalid DHCPv6 packet length %ld, no space for dhcpv6 msg header\n", buffer_sz); + continue; + } + + auto msg = parse_dhcpv6_hdr(message_buffer); + + if (msg->msg_type != DHCPv6_MESSAGE_TYPE_RELAY_REPL) { + syslog(LOG_WARNING, "Invalid DHCPv6 message type %d received on loopback interface\n", msg->msg_type); + continue; + } + auto config = get_relay_int_from_relay_msg(message_buffer, buffer_sz, vlans); + if (!config) { + syslog(LOG_WARNING, "Invalid DHCPv6 header content on loopback socket, packet will be dropped\n"); + continue; + } + update_counter(config->state_db, counterVlan.append(std::string(loopback)), msg->msg_type); + + relay_relay_reply(message_buffer, buffer_sz, config); + } +} + /** * @code void server_callback(evutil_socket_t fd, short event, void *arg); @@ -795,10 +1014,10 @@ void server_callback(evutil_socket_t fd, short event, void *arg) { socklen_t len = sizeof(from); int32_t pkts_num = 0; static uint8_t message_buffer[BUFFER_SIZE]; - std::string counterVlan = counter_table; while (pkts_num++ < BATCH_SIZE) { - auto buffer_sz = recvfrom(config->local_sock, message_buffer, BUFFER_SIZE, 0, (sockaddr *)&from, &len); + std::string counterVlan = counter_table; + auto buffer_sz = recvfrom(config->gua_sock, message_buffer, BUFFER_SIZE, 0, (sockaddr *)&from, &len); if (buffer_sz <= 0) { if (errno != EAGAIN) { syslog(LOG_ERR, "recv: Failed to receive data from server: %s\n", strerror(errno)); @@ -817,13 +1036,11 @@ void server_callback(evutil_socket_t fd, short event, void *arg) { update_counter(config->state_db, counterVlan.append(config->interface), DHCPv6_MESSAGE_TYPE_UNKNOWN); syslog(LOG_WARNING, "Unknown DHCPv6 message type %d\n", msg->msg_type); continue; - } else { - counters[msg->msg_type]++; } update_counter(config->state_db, counterVlan.append(config->interface), msg->msg_type); if (msg->msg_type == DHCPv6_MESSAGE_TYPE_RELAY_REPL) { - relay_relay_reply(config->server_sock, message_buffer, buffer_sz, config); + relay_relay_reply(message_buffer, buffer_sz, config); } } } @@ -922,7 +1139,7 @@ void loop_relay(std::unordered_map &vlans) { std::vector sockets; base = event_base_new(); if(base == NULL) { - syslog(LOG_ERR, "libevent: Failed to create base\n"); + syslog(LOG_ERR, "libevent: Failed to create event base\n"); } std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); @@ -934,21 +1151,41 @@ void loop_relay(std::unordered_map &vlans) { auto filter = sock_open(ðer_relay_fprog); if (filter != -1) { sockets.push_back(filter); - listen_event = event_new(base, filter, EV_READ|EV_PERSIST, client_callback, - reinterpret_cast(&vlans)); - if (listen_event == NULL) { - syslog(LOG_ERR, "libevent: Failed to create client listen libevent\n"); + 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(listen_event, NULL); - syslog(LOG_INFO, "libevent: add filter socket event\n"); + event_add(event, NULL); + syslog(LOG_INFO, "libevent: Add client listen socket event\n"); } else { - syslog(LOG_ALERT, "Failed to create relay filter socket"); + syslog(LOG_ERR, "Failed to create client listen socket"); exit(EXIT_FAILURE); } + int lo_sock = -1; + if (dual_tor_sock) { + 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); + } + } + for(auto &vlan : vlans) { - int local_sock = 0; - int server_sock = 0; + int gua_sock = 0; + int lla_sock = 0; vlan.second.config_db = config_db; vlan.second.mux_table = mStateDbMuxTablePtr; vlan.second.state_db = state_db; @@ -958,28 +1195,28 @@ void loop_relay(std::unordered_map &vlans) { std::string counterVlan = counter_table; initialize_counter(vlan.second.state_db, counterVlan.append(vlan.second.interface)); - prepare_socket(local_sock, server_sock, vlan.second); - - vlan.second.local_sock = local_sock; - vlan.second.server_sock = server_sock; - - sockets.push_back(local_sock); - sockets.push_back(server_sock); - prepare_relay_config(vlan.second, local_sock, filter); - - evutil_make_listen_socket_reuseable(filter); - evutil_make_socket_nonblocking(filter); - - evutil_make_listen_socket_reuseable(local_sock); - evutil_make_socket_nonblocking(local_sock); + 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); - server_listen_event = event_new(base, local_sock, EV_READ|EV_PERSIST, server_callback, &(vlan.second)); - - if (server_listen_event == NULL) { - syslog(LOG_ERR, "libevent: Failed to create server listen libevent\n"); + 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); } - event_add(server_listen_event, NULL); - syslog(LOG_INFO, "libevent: add server listen socket for %s\n", vlan.first.c_str()); } if((signal_init() == 0) && signal_start() == 0) { diff --git a/src/relay.h b/src/relay.h index 240db54..3b56bd8 100644 --- a/src/relay.h +++ b/src/relay.h @@ -34,6 +34,7 @@ #define BATCH_SIZE 64 extern bool dual_tor_sock; +extern char loopback[IF_NAMESIZE]; /* DHCPv6 message types */ typedef enum @@ -58,8 +59,9 @@ typedef enum } dhcp_message_type_t; struct relay_config { - int local_sock; - int server_sock; + int gua_sock; + int lla_sock; + int lo_sock; int filter; sockaddr_in6 link_address; std::shared_ptr state_db; @@ -75,7 +77,7 @@ struct relay_config { /* DHCPv6 messages and options */ -struct dhcpv6_msg { +struct PACKED dhcpv6_msg { uint8_t msg_type; uint8_t xid[3]; }; @@ -87,19 +89,18 @@ struct PACKED dhcpv6_relay_msg { struct in6_addr peer_address; }; - -struct dhcpv6_option { +struct PACKED dhcpv6_option { uint16_t option_code; uint16_t option_length; }; -struct linklayer_addr_option { +struct PACKED linklayer_addr_option { uint16_t option_code; uint16_t option_length; uint16_t link_layer_type; }; -struct interface_id_option { +struct PACKED interface_id_option { uint16_t option_code; uint16_t option_length; in6_addr interface_id; // to accomodate dual-tor, this opaque value is set to carry relay interface's global ipv6 address @@ -116,30 +117,31 @@ struct interface_id_option { */ int sock_open(const struct sock_fprog *fprog); + /** - * @code prepare_socket(int *local_sock, int *server_sock, relay_config *config); + * @code prepare_vlan_sockets(int &gua_sock, int &lla_sock, relay_config &config); * - * @brief prepare L3 socket for sending + * @brief prepare vlan l3 socket for sending * - * @param local_sock pointer to socket binded to global address for relaying client message to server and listening for server message - * @param server_sock pointer to socket binded to link_local address for relaying server message to client + * @param gua_sock socket binded to global address for relaying client message to server and listening for server message + * @param lla_sock socket binded to link_local address for relaying server message to client * - * @return none + * @return int */ -void prepare_socket(int *local_sock, int *server_sock, relay_config *config); +int prepare_vlan_sockets(int &gua_sock, int &lla_sock, relay_config &config); /** - * @code prepare_relay_config(relay_config &interface_config, int local_sock, int filter); + * @code prepare_relay_config(relay_config &interface_config, int gua_sock, int filter); * * @brief prepare for specified relay interface config: server and link address * * @param interface_config pointer to relay config to be prepared - * @param local_sock L3 socket used for relaying messages + * @param gua_sock L3 socket used for relaying messages * @param filter socket attached with filter * * @return none */ -void prepare_relay_config(relay_config &interface_config, int local_sock, int filter); +void prepare_relay_config(relay_config &interface_config, int gua_sock, int filter); /** * @code relay_forward(uint8_t *buffer, const struct dhcpv6_msg *msg, uint16_t msg_length); @@ -155,11 +157,10 @@ void prepare_relay_config(relay_config &interface_config, int local_sock, int fi void relay_forward(uint8_t *buffer, const struct dhcpv6_msg *msg, uint16_t msg_length); /** - * @code relay_client(int sock, const uint8_t *msg, int32_t len, ip6_hdr *ip_hdr, const ether_header *ether_hdr, relay_config *config); + * @code relay_client(const uint8_t *msg, int32_t len, ip6_hdr *ip_hdr, const ether_header *ether_hdr, relay_config *config); * * @brief construct relay-forward message * - * @param sock L3 socket for sending data to servers * @param msg pointer to dhcpv6 message header position * @param len size of data received * @param ip_hdr pointer to IPv6 header @@ -168,14 +169,13 @@ void relay_forward(uint8_t *buffer, const struct dhcpv6_msg *msg, uint16_t msg_l * * @return none */ -void relay_client(int sock, const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, const ether_header *ether_hdr, relay_config *config); +void relay_client(const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, const ether_header *ether_hdr, relay_config *config); /** - * @code relay_relay_forw(int sock, const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, relay_config *config) + * @code relay_relay_forw(const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, relay_config *config) * * @brief construct a relay-forward message encapsulated relay-forward message * - * @param sock L3 socket for sending data to servers * @param msg pointer to dhcpv6 message header position * @param len size of data received * @param ip_hdr pointer to IPv6 header @@ -183,21 +183,20 @@ void relay_client(int sock, const uint8_t *msg, int32_t len, const ip6_hdr *ip_h * * @return none */ -void relay_relay_forw(int sock, const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, relay_config *config); +void relay_relay_forw(const uint8_t *msg, int32_t len, const ip6_hdr *ip_hdr, relay_config *config); /** - * @code relay_relay_reply(int sock, const uint8_t *msg, int32_t len, relay_config *configs); + * @code relay_relay_reply(const uint8_t *msg, int32_t len, relay_config *configs); * * @brief relay and unwrap a relay-reply message * - * @param sock L3 socket for sending data to servers * @param msg pointer to dhcpv6 message header position * @param len size of data received * @param config relay interface config * * @return none */ -void relay_relay_reply(int sock, const uint8_t *msg, int32_t len, relay_config *configs); +void relay_relay_reply(const uint8_t *msg, int32_t len, relay_config *configs); /** * @code loop_relay(std::unordered_map &vlans); @@ -424,3 +423,17 @@ void server_callback(evutil_socket_t fd, short event, void *arg); * @return none */ void server_callback_dual_tor(evutil_socket_t fd, short event, void *arg); + +/** + * @code struct relay_config * + * get_relay_int_from_relay_msg(const uint8_t *msg, int32_t len, + * std::unordered_map *vlans) + * + * @brief get relay interface info from relay message + * + * @param addr ipv6 address + * + * @return bool + */ +struct relay_config * +get_relay_int_from_relay_msg(const uint8_t *msg, int32_t len, std::unordered_map *vlans); diff --git a/test/MockRelay.cpp b/test/MockRelay.cpp index 0d14de7..cccf829 100644 --- a/test/MockRelay.cpp +++ b/test/MockRelay.cpp @@ -11,9 +11,11 @@ #include "MockRelay.h" bool dual_tor_sock = false; +char loopback[IF_NAMESIZE] = "Loopback0"; extern struct event_base *base; extern struct event *ev_sigint; extern struct event *ev_sigterm; +extern std::unordered_map addr_vlan_map; static struct sock_filter ether_relay_filter[] = { @@ -311,13 +313,11 @@ TEST(counter, update_counter) 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"); + EXPECT_EQ(*ptr, "1"); } 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, @@ -345,6 +345,9 @@ TEST(relay, relay_client) } std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); config.state_db = state_db; + config.gua_sock = 125; + config.lla_sock = 125; + config.lo_sock = 125; struct ether_header ether_hdr; ether_hdr.ether_shost[0] = 0x5a; @@ -357,9 +360,9 @@ TEST(relay, relay_client) ip6_hdr ip_hdr; std::string s_addr = "2000::3"; - relay_client(mock_sock, msg, msg_len, &ip_hdr, ðer_hdr, &config); + relay_client(msg, msg_len, &ip_hdr, ðer_hdr, &config); - EXPECT_EQ(last_used_sock, 124); + EXPECT_EQ(last_used_sock, 125); auto sent_msg = parse_dhcpv6_relay(sender_buffer); @@ -394,8 +397,6 @@ TEST(relay, relay_client) } 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, @@ -428,12 +429,15 @@ TEST(relay, relay_relay_forw) { } std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); config.state_db = state_db; + config.gua_sock = 125; + config.lla_sock = 125; + config.lo_sock = 125; 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); + relay_relay_forw(msg, msg_len, &ip_hdr, &config); EXPECT_EQ(last_used_sock, 125); @@ -453,8 +457,6 @@ TEST(relay, relay_relay_forw) { 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, @@ -491,15 +493,18 @@ TEST(relay, relay_relay_reply) config.interface = "Vlan1000"; std::shared_ptr state_db = std::make_shared ("STATE_DB", 0); config.state_db = state_db; + config.gua_sock = 125; + config.lla_sock = 125; + config.lo_sock = 125; int local_sock = 1; int filter = 1; prepare_relay_config(config, local_sock, filter); - relay_relay_reply(mock_sock, msg, msg_len, &config); + relay_relay_reply(msg, msg_len, &config); - EXPECT_EQ(last_used_sock, 123); + EXPECT_EQ(last_used_sock, 125); uint8_t expected_bytes[] = { 0x07, 0x4f, 0x6d, 0x04, 0x00, 0x03, 0x00, 0x28, @@ -559,3 +564,50 @@ TEST(relay, dhcp6relay_stop) { event_base_free(base); base = NULL; } + +TEST(relay, get_relay_int_from_relay_msg) { + struct relay_config config{}; + std::string lla_str = "fc02:1000::1"; + std::unordered_map vlans; + + config.is_option_79 = true; + config.is_interface_id = true; + inet_pton(AF_INET6, lla_str.c_str(), &config.link_address.sin6_addr); + config.servers.push_back("fc02:2000::1"); + config.servers.push_back("fc02:2000::2"); + config.interface = "Vlan1000"; + + vlans["Vlan1000"] = config; + addr_vlan_map[lla_str] = "Vlan1000"; + + uint8_t relay_reply_with_opt18[] = { + 0x0d,0x00,0xfc,0x02,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x01,0xfe,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x12,0x70,0xfd,0xff,0xfe,0xcb, + 0x0c,0x06,0x00,0x09,0x00,0x04,0x07,0x00,0x30,0x39,0x00,0x12,0x00,0x10,0xfc,0x02, + 0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01 + }; + + auto cfg = get_relay_int_from_relay_msg(relay_reply_with_opt18, sizeof(relay_reply_with_opt18), &vlans); + EXPECT_NE((uintptr_t)cfg, NULL); + EXPECT_EQ(cfg->interface, "Vlan1000"); + + uint8_t relay_reply_without_opt18[] = { + 0x0d,0x00,0xfc,0x02,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x01,0xfe,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x12,0x70,0xfd,0xff,0xfe,0xcb, + 0x0c,0x06,0x00,0x09,0x00,0x04,0x07,0x00,0x30,0x39 + }; + + cfg = get_relay_int_from_relay_msg(relay_reply_without_opt18, sizeof(relay_reply_without_opt18), &vlans); + EXPECT_NE((uintptr_t)cfg, NULL); + EXPECT_EQ(cfg->interface, "Vlan1000"); + + uint8_t relay_reply_without_opt18_linkaddr_zero[] = { + 0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xfe,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x12,0x70,0xfd,0xff,0xfe,0xcb, + 0x0c,0x06,0x00,0x09,0x00,0x04,0x07,0x00,0x30,0x39 + }; + + cfg = get_relay_int_from_relay_msg(relay_reply_without_opt18_linkaddr_zero, + sizeof(relay_reply_without_opt18_linkaddr_zero), &vlans); + EXPECT_EQ((uintptr_t)cfg, NULL); +} \ No newline at end of file diff --git a/test/main.cpp b/test/main.cpp index dfca267..6b3b209 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -1,4 +1,5 @@ #include "gtest/gtest.h" +#include #include #include