| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "sockscripter.h" |
| |
| #include <getopt.h> |
| #include <net/if.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <climits> |
| #include <iostream> |
| |
| #include "addr.h" |
| #include "log.h" |
| #include "src/lib/fxl/strings/string_number_conversions.h" |
| #include "util.h" |
| |
| #define PRINT_SOCK_OPT_VAL(level, name) \ |
| LOG(INFO) << #level "=" << (level) << " " << #name << "=" << name |
| |
| #define SET_SOCK_OPT_VAL(level, name, val) \ |
| { \ |
| if (api_->setsockopt(sockfd_, (level), (name), &(val), sizeof(val)) < 0) { \ |
| LOG(ERROR) << "Failed to set socket option " #level ":" #name "-" \ |
| << "[" << errno << "]" << strerror(errno); \ |
| return false; \ |
| } \ |
| LOG(INFO) << "Set " #level ":" #name " = " << (int)(val); \ |
| return true; \ |
| } |
| |
| #define LOG_SOCK_OPT_VAL(level, name, type_val) \ |
| { \ |
| type_val opt = 0; \ |
| socklen_t opt_len = sizeof(opt); \ |
| if (api_->getsockopt(sockfd_, (level), (name), &(opt), &opt_len) < 0) { \ |
| LOG(ERROR) << "Error-Getting " #level ":" #name "-" \ |
| << "[" << errno << "]" << strerror(errno); \ |
| return false; \ |
| } \ |
| LOG(INFO) << #level ":" #name " is set to " << (int)opt; \ |
| return true; \ |
| } |
| |
| bool TestRepeatCfg::Parse(const std::string& cmd) { |
| command = ""; |
| repeat_count = 1; |
| delay_ms = 0; |
| if (cmd[0] != '{') { |
| return false; |
| } |
| |
| auto cmd_e = cmd.find_first_of('}'); |
| if (cmd_e == std::string::npos) { |
| return false; |
| } |
| command = cmd.substr(1, cmd_e - 1); |
| |
| auto n_t_str = cmd.substr(cmd_e + 1); |
| if (n_t_str.empty()) { |
| return true; |
| } |
| |
| auto n_num_str_i = n_t_str.find("N="); |
| if (n_num_str_i != std::string::npos) { |
| auto n_num_str = n_t_str.substr(n_num_str_i + 2); |
| if (!str2int(n_num_str, &repeat_count)) { |
| LOG(ERROR) << "Error parsing '" << cmd << "': in N=<n>, <n> is not a number!"; |
| return false; |
| } |
| } |
| auto t_num_str_i = n_t_str.find("T="); |
| if (t_num_str_i != std::string::npos) { |
| auto t_num_str = n_t_str.substr(t_num_str_i + 2); |
| if (!str2int(t_num_str, &delay_ms)) { |
| LOG(ERROR) << "Error parsing '" << cmd << "': in T=<t>, <t> is not a number!"; |
| } |
| } |
| return true; |
| } |
| |
| struct SocketType { |
| const char* name; |
| const char* arg; |
| const char* help_str; |
| int domain; |
| int type; |
| } socket_types[] = { |
| {"udp", nullptr, "open new AF_INET SOCK_DGRAM socket", AF_INET, SOCK_DGRAM}, |
| {"udp6", nullptr, "open new AF_INET6 SOCK_DGRAM socket", AF_INET6, SOCK_DGRAM}, |
| {"tcp", nullptr, "open new AF_INET SOCK_STREAM socket", AF_INET, SOCK_STREAM}, |
| {"tcp6", nullptr, "open new AF_INET6 SOCK_STREAM socket", AF_INET6, SOCK_STREAM}, |
| {"raw", "<protocol>", "open new AF_INET SOCK_RAW socket", AF_INET, SOCK_RAW}, |
| {"raw6", "<protocol>", "open new AF_INET6 SOCK_RAW socket", AF_INET6, SOCK_RAW}, |
| }; |
| |
| typedef bool (SockScripter::*SockScripterHandler)(char* arg); |
| const struct Command { |
| // Command name as used on the commandline. |
| const char* name; |
| // Argument description, nullptr if the command doesn't have an argument. |
| const char* opt_arg_descr; |
| // String displayed in the help text. |
| const char* help_str; |
| SockScripterHandler handler; |
| } kCommands[] = { |
| {"close", nullptr, "close socket", &SockScripter::Close}, |
| {"bind", "<bind-ip>:<bind-port>", "bind to the given local address", &SockScripter::Bind}, |
| {"bound", nullptr, "log bound-to-address", &SockScripter::LogBoundToAddress}, |
| {"log-peername", nullptr, "log peer name", &SockScripter::LogPeerAddress}, |
| {"connect", "<connect-ip>:<connect-port>", "connect to the given remote address", |
| &SockScripter::Connect}, |
| {"disconnect", nullptr, "disconnect a connected socket", &SockScripter::Disconnect}, |
| {"set-broadcast", "{0|1}", "set SO_BROADCAST flag", &SockScripter::SetBroadcast}, |
| {"log-broadcast", nullptr, "log SO_BROADCAST option flag", &SockScripter::LogBroadcast}, |
| {"set-bindtodevice", "<if-name-string>", "set SO_BINDTODEVICE", &SockScripter::SetBindToDevice}, |
| {"log-bindtodevice", nullptr, "log SO_BINDTODEVICE option value", |
| &SockScripter::LogBindToDevice}, |
| {"set-reuseaddr", "{0|1}", "set SO_REUSEADDR flag", &SockScripter::SetReuseaddr}, |
| {"log-reuseaddr", nullptr, "log SO_REUSEADDR option value", &SockScripter::LogReuseaddr}, |
| {"set-reuseport", "{0|1}", "set SO_REUSEPORT flag", &SockScripter::SetReuseport}, |
| {"log-reuseport", nullptr, "log SO_REUSEPORT option value", &SockScripter::LogReuseport}, |
| {"set-unicast-ttl", "<ttl-for-IP_TTL>", "set TTL for V4 unicast packets", |
| &SockScripter::SetIpUnicastTTL}, |
| {"log-unicast-ttl", nullptr, "log IP_TTL option value", &SockScripter::LogIpUnicastTTL}, |
| {"set-unicast-hops", "<ttl-for-IPV6_UNICAST_HOPS>", "set hops for V6 unicast packets", |
| &SockScripter::SetIpUnicastHops}, |
| {"log-unicast-hops", nullptr, "log IPV6_UNICAST_HOPS option value", |
| &SockScripter::LogIpUnicastHops}, |
| {"set-mcast-ttl", "<ttl-for-IP_MULTICAST-TTL>", "set TTL for V4 mcast packets", |
| &SockScripter::SetIpMcastTTL}, |
| {"log-mcast-ttl", nullptr, "log IP_MULTICAST_TTL option value", &SockScripter::LogIpMcastTTL}, |
| {"set-mcast-loop4", "{1|0}", "set IP_MULTICAST_LOOP flag", &SockScripter::SetIpMcastLoop4}, |
| {"log-mcast-loop4", nullptr, "log IP_MULTICAST_LOOP option value", |
| &SockScripter::LogIpMcastLoop4}, |
| {"set-mcast-hops", "<ttl-for-IPV6_MULTICAST_HOPS>", "set hops for V6 mcast packets", |
| &SockScripter::SetIpMcastHops}, |
| {"log-mcast-hops", nullptr, "log IPV6_MULTICAST_HOPS option value", |
| &SockScripter::LogIpMcastHops}, |
| {"set-mcast-loop6", "{1|0}", "set IPV6_MULTICAST_LOOP flag", &SockScripter::SetIpMcastLoop6}, |
| {"log-mcast-loop6", nullptr, "log IPV6_MULTICAST_LOOP option value", |
| &SockScripter::LogIpMcastLoop6}, |
| {"set-mcast-if4", "{<local-intf-Addr>}", "set IP_MULTICAST_IF for IPv4 mcast", |
| &SockScripter::SetIpMcastIf4}, |
| {"log-mcast-if4", nullptr, "log IP_MULTICAST_IF option value", &SockScripter::LogIpMcastIf4}, |
| {"set-mcast-if6", "<local-intf-id>", "set IP_MULTICAST_IF6 for IPv6 mcast", |
| &SockScripter::SetIpMcastIf6}, |
| {"log-mcast-if6", nullptr, "log IP_MULTICAST_IF6 option value", &SockScripter::LogIpMcastIf6}, |
| |
| {"set-ipv6-only", "{0|1}", "set IPV6_V6ONLY flag", &SockScripter::SetIpV6Only}, |
| {"log-ipv6-only", nullptr, "log IPV6_V6ONLY option value", &SockScripter::LogIpV6Only}, |
| |
| {"join4", "<mcast-ip>-<local-intf-Addr>", |
| "join IPv4 mcast group (IP_ADD_MEMBERSHIP) on local interface", &SockScripter::Join4}, |
| {"drop4", "<mcast-ip>-<local-intf-Addr>", |
| "drop IPv4 mcast group (IP_DROP_MEMBERSHIP) on local interface", &SockScripter::Drop4}, |
| {"join6", "<mcast-ip>-<local-intf-id>", |
| "join IPv6 mcast group (IPV6_ADD_MEMBERSHIP/IPV6_JOIN_GROUP) on local interface", |
| &SockScripter::Join6}, |
| {"drop6", "<mcast-ip>-<local-intf-id>", |
| "drop IPv6 mcast group (IPV6_DROP_MEMBERSHIP/IPV6_LEAVE_GROUP) on local interface", |
| &SockScripter::Drop6}, |
| {"listen", nullptr, "listen on TCP socket", &SockScripter::Listen}, |
| {"accept", nullptr, "accept on TCP socket", &SockScripter::Accept}, |
| {"sendto", "<send-to-ip>:<send-to-port>", "sendto(send_buf, ip, port)", &SockScripter::SendTo}, |
| {"send", nullptr, "send send_buf on a connected socket", &SockScripter::Send}, |
| {"recvfrom", nullptr, "recvfrom()", &SockScripter::RecvFrom}, |
| {"recvfrom-ping", nullptr, "recvfrom() and ping the packet back to the sender", |
| &SockScripter::RecvFromPing}, |
| {"recv", nullptr, "recv() on a connected TCP socket", &SockScripter::Recv}, |
| {"recv-ping", nullptr, |
| "recv() and ping back the packet on a connected TCP " |
| "socket", |
| &SockScripter::RecvPing}, |
| {"set-send-buf-hex", "\"xx xx ..\" ", "set send-buffer with hex values", |
| &SockScripter::SetSendBufHex}, |
| {"set-send-buf-text", "\"<string>\" ", "set send-buffer with text chars", |
| &SockScripter::SetSendBufText}, |
| {"sleep", "<sleep-secs>", "sleeps", &SockScripter::Sleep}, |
| }; |
| |
| int print_socket_types() { |
| for (const auto& socket_type : socket_types) { |
| std::cout << socket_type.name << " "; |
| } |
| std::cout << std::endl; |
| return 0; |
| } |
| |
| int check_socket_type_has_proto(const char* stype) { |
| for (const auto& socket_type : socket_types) { |
| if (strcmp(stype, socket_type.name) == 0) { |
| if (socket_type.arg) { |
| return 0; |
| } |
| break; |
| } |
| } |
| return 1; |
| } |
| |
| int print_commands_list() { |
| for (const auto& cmd : kCommands) { |
| std::cout << cmd.name << " "; |
| } |
| std::cout << std::endl; |
| return 0; |
| } |
| |
| int check_command_has_args(const char* command) { |
| for (const auto& cmd : kCommands) { |
| if (strcmp(command, cmd.name) == 0) { |
| if (cmd.opt_arg_descr) { |
| return 0; |
| } |
| break; |
| } |
| } |
| return 1; |
| } |
| |
| int usage(const char* name) { |
| std::stringstream socket_types_str; |
| for (const auto& socket_type : socket_types) { |
| socket_types_str << " " << socket_type.name << " " |
| << (socket_type.arg ? socket_type.arg : "") << " : " << socket_type.help_str |
| << "\n"; |
| } |
| |
| std::stringstream cmds_str; |
| for (const auto& cmd : kCommands) { |
| cmds_str << " " << cmd.name << " " << (cmd.opt_arg_descr ? cmd.opt_arg_descr : "") << " : " |
| << cmd.help_str << "\n"; |
| } |
| |
| fprintf(stderr, |
| "\nUsage: %s [-h] [-s] [-p <proto>] [-c] [-a <cmd>] " |
| " {socket-type} {socket-cmds}\n" |
| " -h : this help message\n" |
| " -s : prints available socket-types (for bash completion)\n" |
| " -p <proto> : returns 0 if the given socket-type has a parameter\n" |
| " -c : prints available commands (for bash completion)\n" |
| " -a <cmd> : returns 0 if the given command has a parameter\n" |
| "\n" |
| " socket-type is one of the following:\n%s\n" |
| " socket-cmd is one of the following:\n%s\n" |
| " <local-intf-id> is an integer prefixed by '%%', e.g. '%%1'\n" |
| " <local-intf-Addr> is of the format [<IP>][<ID>], e.g. '192.168.1.166'," |
| "'192.168.1.166%%2', '%%2'\n\n" |
| " A command can be repeated by wrapping it in the following structure:\n" |
| " {<cmd>}[N=<n>][T=<t>]\n" |
| " <n> is the number of repeats (default: n=1)\n" |
| " <t> is the delay in ms between commands (default: t=1000)\n\n" |
| " Examples:\n ------\n" |
| " Joining the multicast group 224.0.0.120 on local-IP " |
| "192.168.1.99, bind to 224.0.0.120:2000,\n" |
| " and receive a two packets.\n" |
| " %s udp join4 224.0.0.120-192.168.1.99 bind 224.0.0.120:2000 " |
| "{recvfrom}N=2\n" |
| "\n" |
| " Send two packets, 50ms apart, to the multicast group 224.0.0.120:2000 " |
| "from the local interface 192.168.1.166\n" |
| " %s udp set-mcast-if4 192.168.1.166 {sendto}N=2T=50 224.0.0.120:2000\n" |
| "\n\n", |
| name, socket_types_str.str().c_str(), cmds_str.str().c_str(), name, name); |
| return 99; |
| } |
| |
| int SockScripter::Execute(int argc, char* const argv[]) { |
| optind = 0; |
| int opt; |
| while ((opt = getopt(argc, argv, "hsp:ca:")) != -1) { |
| switch (opt) { |
| case 's': |
| return print_socket_types(); |
| case 'p': |
| return check_socket_type_has_proto(optarg); |
| case 'c': |
| return print_commands_list(); |
| case 'a': |
| return check_command_has_args(optarg); |
| case 'h': |
| default: |
| return usage(argv[0]); |
| } |
| } |
| |
| if (optind >= argc) { |
| return usage(argv[0]); |
| } |
| |
| bool found = false; |
| for (const auto& socket_type : socket_types) { |
| if (strcmp(argv[optind], socket_type.name) == 0) { |
| found = true; |
| int proto = 0; |
| if (socket_type.arg) { |
| optind++; |
| if (optind >= argc) { |
| fprintf(stderr, "Error-Need protocol# for RAW socket!\n\n"); |
| return -1; |
| } |
| if (!str2int(argv[optind], &proto)) { |
| fprintf(stderr, "Error-Invalid protocol# (%s) for RAW socket!\n\n", argv[optind]); |
| return -1; |
| } |
| } |
| if (!Open(socket_type.domain, socket_type.type, proto)) { |
| return -1; |
| } |
| break; |
| } |
| } |
| if (!found) { |
| fprintf(stderr, "Error-first parameter needs to be socket type:"); |
| print_socket_types(); |
| } |
| |
| optind++; |
| while (optind < argc) { |
| TestRepeatCfg cfg; |
| const char* cmd_arg; |
| if (argv[optind][0] == '{') { |
| if (!cfg.Parse(argv[optind])) { |
| return -1; |
| } |
| cmd_arg = cfg.command.c_str(); |
| } else { |
| cmd_arg = argv[optind]; |
| } |
| |
| bool found = false; |
| for (const auto& cmd : kCommands) { |
| if (strcmp(cmd_arg, cmd.name) == 0) { |
| found = true; |
| optind++; |
| char* arg = nullptr; |
| if (cmd.opt_arg_descr) { |
| if (optind < argc) { |
| arg = argv[optind]; |
| optind++; |
| } else { |
| fprintf(stderr, "Missing argument %s for %s!\n\n", cmd.opt_arg_descr, cmd.name); |
| return -1; |
| } |
| } |
| for (int i = 0; i < cfg.repeat_count; i++) { |
| auto handler = cmd.handler; |
| if (!(this->*(handler))(arg)) { |
| return -1; |
| } |
| usleep(1000 * cfg.delay_ms); |
| } |
| break; |
| } |
| } |
| if (!found) { |
| fprintf(stderr, "Error: Cannot find command '%s'!\n\n", argv[optind]); |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| bool SockScripter::Open(int domain, int type, int proto) { |
| const char* domain_str = (domain == AF_INET) ? "IPv4-" : "IPv6-"; |
| sockfd_ = api_->socket(domain, type, proto); |
| if (sockfd_ < 0) { |
| LOG(ERROR) << "Error-Opening " << domain_str << GetTypeName(type) << " socket " |
| << "(proto:" << proto << ") " |
| << "failed-[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| LOG(INFO) << "Opened " << domain_str << GetTypeName(type) << " socket " |
| << "(proto:" << proto << ") fd:" << sockfd_; |
| return true; |
| } |
| |
| bool SockScripter::Close(char* arg) { |
| if (sockfd_ >= 0) { |
| api_->close(sockfd_); |
| LOG(INFO) << "Closed socket-fd:" << sockfd_; |
| sockfd_ = -1; |
| } |
| if (prev_sock_fd_ >= 0) { |
| sockfd_ = prev_sock_fd_; |
| prev_sock_fd_ = -1; |
| } |
| return true; |
| } |
| |
| bool SockScripter::SetBroadcast(char* arg) { |
| int flag; |
| if (!getFlagInt(arg, &flag)) { |
| LOG(ERROR) << "Error: Invalid broadcast flag='" << arg << "'!"; |
| return false; |
| } |
| SET_SOCK_OPT_VAL(SOL_SOCKET, SO_BROADCAST, flag); |
| } |
| |
| bool SockScripter::LogBroadcast(char* arg) { LOG_SOCK_OPT_VAL(SOL_SOCKET, SO_BROADCAST, int); } |
| |
| bool SockScripter::SetReuseaddr(char* arg) { |
| int flag; |
| if (!getFlagInt(arg, &flag)) { |
| LOG(ERROR) << "Error: Invalid reuseaddr flag='" << arg << "'!"; |
| return false; |
| } |
| SET_SOCK_OPT_VAL(SOL_SOCKET, SO_REUSEADDR, flag); |
| } |
| |
| bool SockScripter::SetReuseport(char* arg) { |
| int flag; |
| if (!getFlagInt(arg, &flag)) { |
| LOG(ERROR) << "Error: Invalid reuseport flag='" << arg << "'!"; |
| return false; |
| } |
| SET_SOCK_OPT_VAL(SOL_SOCKET, SO_REUSEPORT, flag); |
| } |
| |
| bool SockScripter::LogReuseaddr(char* arg) { LOG_SOCK_OPT_VAL(SOL_SOCKET, SO_REUSEADDR, int); } |
| |
| bool SockScripter::LogReuseport(char* arg) { LOG_SOCK_OPT_VAL(SOL_SOCKET, SO_REUSEPORT, int); } |
| |
| bool SockScripter::SetIpUnicastTTL(char* arg) { |
| int ttl; |
| if (!str2int(arg, &ttl) || ttl < 0) { |
| LOG(ERROR) << "Error: Invalid unicast TTL='" << arg << "'!"; |
| return false; |
| } |
| SET_SOCK_OPT_VAL(IPPROTO_IP, IP_TTL, ttl); |
| } |
| |
| bool SockScripter::SetIpUnicastHops(char* arg) { |
| int hops; |
| if (!str2int(arg, &hops) || hops < 0) { |
| LOG(ERROR) << "Error: Invalid unicast hops='" << arg << "'!"; |
| return false; |
| } |
| SET_SOCK_OPT_VAL(IPPROTO_IPV6, IPV6_UNICAST_HOPS, hops); |
| } |
| |
| bool SockScripter::LogIpUnicastHops(char* arg) { |
| LOG_SOCK_OPT_VAL(IPPROTO_IPV6, IPV6_UNICAST_HOPS, int); |
| } |
| |
| bool SockScripter::SetIpMcastTTL(char* arg) { |
| int ttl; |
| if (!str2int(arg, &ttl) || ttl < 0) { |
| LOG(ERROR) << "Error: Invalid mcast TTL='" << arg << "'!"; |
| return false; |
| } |
| SET_SOCK_OPT_VAL(IPPROTO_IP, IP_MULTICAST_TTL, ttl); |
| } |
| |
| bool SockScripter::LogIpMcastTTL(char* arg) { LOG_SOCK_OPT_VAL(IPPROTO_IP, IP_MULTICAST_TTL, int); } |
| |
| bool SockScripter::LogIpUnicastTTL(char* arg) { LOG_SOCK_OPT_VAL(IPPROTO_IP, IP_TTL, int); } |
| |
| bool SockScripter::SetIpMcastLoop4(char* arg) { |
| int flag; |
| if (!getFlagInt(arg, &flag)) { |
| LOG(ERROR) << "Error: Invalid loop4 flag='" << arg << "'!"; |
| return false; |
| } |
| SET_SOCK_OPT_VAL(IPPROTO_IP, IP_MULTICAST_LOOP, flag); |
| } |
| |
| bool SockScripter::LogIpMcastLoop4(char* arg) { |
| LOG_SOCK_OPT_VAL(IPPROTO_IP, IP_MULTICAST_LOOP, int); |
| } |
| |
| bool SockScripter::SetIpMcastHops(char* arg) { |
| int hops; |
| if (!str2int(arg, &hops) || hops < 0) { |
| LOG(ERROR) << "Error: Invalid mcast hops='" << arg << "'!"; |
| return false; |
| } |
| SET_SOCK_OPT_VAL(IPPROTO_IPV6, IPV6_MULTICAST_HOPS, hops); |
| } |
| |
| bool SockScripter::LogIpMcastHops(char* arg) { |
| LOG_SOCK_OPT_VAL(IPPROTO_IPV6, IPV6_MULTICAST_HOPS, int); |
| } |
| |
| bool SockScripter::SetIpV6Only(char* arg) { |
| int flag; |
| if (!getFlagInt(arg, &flag)) { |
| LOG(ERROR) << "Error: Invalid ipv6-only flag='" << arg << "'!"; |
| return false; |
| } |
| SET_SOCK_OPT_VAL(IPPROTO_IPV6, IPV6_V6ONLY, flag); |
| } |
| |
| bool SockScripter::LogIpV6Only(char* arg) { LOG_SOCK_OPT_VAL(IPPROTO_IPV6, IPV6_V6ONLY, int); } |
| |
| bool SockScripter::SetIpMcastLoop6(char* arg) { |
| int flag; |
| if (!getFlagInt(arg, &flag)) { |
| LOG(ERROR) << "Error: Invalid loop6 flag='" << arg << "'!"; |
| return false; |
| } |
| SET_SOCK_OPT_VAL(IPPROTO_IPV6, IPV6_MULTICAST_LOOP, flag); |
| } |
| |
| bool SockScripter::LogIpMcastLoop6(char* arg) { |
| LOG_SOCK_OPT_VAL(IPPROTO_IPV6, IPV6_MULTICAST_LOOP, int); |
| } |
| |
| bool SockScripter::SetBindToDevice(char* arg) { |
| #ifdef SO_BINDTODEVICE |
| if (api_->setsockopt(sockfd_, SOL_SOCKET, SO_BINDTODEVICE, arg, |
| static_cast<socklen_t>(strlen(arg))) < 0) { |
| LOG(ERROR) << "Error-Setting SO_BINDTODEVICE failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| LOG(INFO) << "Set SO_BINDTODEVICE to " << arg; |
| return true; |
| #else |
| LOG(ERROR) << "SO_BINDTODEVICE not defined in this platform"; |
| return false; |
| #endif |
| } |
| |
| bool SockScripter::LogBindToDevice(char* arg) { |
| #ifdef SO_BINDTODEVICE |
| char name[IFNAMSIZ] = {}; |
| socklen_t name_len = sizeof(name); |
| if (api_->getsockopt(sockfd_, SOL_SOCKET, SO_BINDTODEVICE, name, &name_len) < 0) { |
| LOG(ERROR) << "Error-Getting SO_BINDTODEVICE failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| LOG(INFO) << "SO_BINDTODEVICE is set to " << name; |
| return true; |
| #else |
| LOG(ERROR) << "SO_BINDTODEVICE not defined in this platform"; |
| return false; |
| #endif |
| } |
| |
| bool SockScripter::SetIpMcastIf4(char* arg) { |
| LocalIfAddr if_addr; |
| if (!if_addr.Set(arg)) { |
| return false; |
| } |
| if (!if_addr.IsSet()) { |
| LOG(ERROR) << "Error-No IPv4 address or local interface id given for " |
| << "IP_MULTICAST_IF"; |
| return false; |
| } |
| |
| struct ip_mreqn mreq; |
| memset(&mreq, 0, sizeof(mreq)); |
| // mreq.imr_multiaddr is not set |
| if (if_addr.HasAddr4()) { |
| mreq.imr_address = if_addr.GetAddr4(); |
| } |
| if (if_addr.HasId()) { |
| mreq.imr_ifindex = if_addr.GetId(); |
| } |
| if (api_->setsockopt(sockfd_, IPPROTO_IP, IP_MULTICAST_IF, &mreq, sizeof(mreq)) < 0) { |
| LOG(ERROR) << "Error-Setting IP_MULTICAST_IF failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| LOG(INFO) << "Set IP_MULTICAST_IF to " << if_addr.Name(); |
| return true; |
| } |
| |
| bool SockScripter::LogIpMcastIf4(char* arg) { |
| struct in_addr addr; |
| memset(&addr, 0, sizeof(addr)); |
| socklen_t addr_len = sizeof(addr); |
| if (api_->getsockopt(sockfd_, IPPROTO_IP, IP_MULTICAST_IF, &addr, &addr_len) < 0) { |
| LOG(ERROR) << "Error-Getting IP_MULTICAST_IF failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| LocalIfAddr if_addr; |
| if (!if_addr.Set(&addr, addr_len)) { |
| return false; |
| } |
| LOG(INFO) << "IP_MULTICAST_IF is set to " << if_addr.Name(); |
| return true; |
| } |
| |
| bool SockScripter::SetIpMcastIf6(char* arg) { |
| LocalIfAddr if_addr; |
| if (!if_addr.Set(arg)) { |
| return false; |
| } |
| if (!if_addr.HasId()) { |
| LOG(ERROR) << "Error-No local interface ID given for IPV6_MULTICAST_IF"; |
| return false; |
| } |
| |
| int id = if_addr.GetId(); |
| if (api_->setsockopt(sockfd_, IPPROTO_IPV6, IPV6_MULTICAST_IF, &id, sizeof(id)) < 0) { |
| LOG(ERROR) << "Error-Setting IPV6_MULTICAST_IF to " << id << " failed-[" << errno << "]" |
| << strerror(errno); |
| return false; |
| } |
| LOG(INFO) << "Set IPV6_MULTICAST_IF to " << id; |
| return true; |
| } |
| |
| bool SockScripter::LogIpMcastIf6(char* arg) { |
| int id = -1; |
| socklen_t id_len = sizeof(id); |
| if (api_->getsockopt(sockfd_, IPPROTO_IPV6, IPV6_MULTICAST_IF, &id, &id_len) < 0) { |
| LOG(ERROR) << "Error-Getting IPV6_MULTICAST_IF failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| LOG(INFO) << "IPV6_MULTICAST_IF is set to " << id; |
| return true; |
| } |
| |
| bool SockScripter::LogBoundToAddress(char* arg) { |
| struct sockaddr* addr = GetSockAddrStorage(); |
| socklen_t addr_len = sizeof(sockaddr_store_); |
| |
| if (api_->getsockname(sockfd_, addr, &addr_len) < 0) { |
| LOG(ERROR) << "Error-Calling getsockname failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| SockAddrIn bound_addr; |
| bound_addr.Set(addr, addr_len); |
| LOG(INFO) << "Bound to " << bound_addr.Name(); |
| return true; |
| } |
| |
| bool SockScripter::LogPeerAddress(char* arg) { |
| struct sockaddr* addr = GetSockAddrStorage(); |
| socklen_t addr_len = sizeof(sockaddr_store_); |
| |
| if (api_->getpeername(sockfd_, addr, &addr_len) < 0) { |
| LOG(ERROR) << "Error-Calling getpeername failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| SockAddrIn bound_addr; |
| bound_addr.Set(addr, addr_len); |
| LOG(INFO) << "Connected to " << bound_addr.Name(); |
| return true; |
| } |
| |
| bool SockScripter::Bind(char* arg) { |
| SockAddrIn bind_addr; |
| if (!bind_addr.Set(arg)) { |
| return false; |
| } |
| |
| LOG(INFO) << "Bind(fd:" << sockfd_ << ") to " << bind_addr.Name(); |
| |
| struct sockaddr* addr = GetSockAddrStorage(); |
| int addr_len = sizeof(sockaddr_store_); |
| if (!bind_addr.Fill(addr, &addr_len)) { |
| return false; |
| } |
| |
| if (api_->bind(sockfd_, addr, addr_len) < 0) { |
| LOG(ERROR) << "Error-Bind(fd:" << sockfd_ << ") failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| return LogBoundToAddress(nullptr); |
| } |
| |
| bool SockScripter::Connect(char* arg) { |
| SockAddrIn connect_addr; |
| if (!connect_addr.Set(arg)) { |
| return false; |
| } |
| |
| LOG(INFO) << "Connect(fd:" << sockfd_ << ") to " << connect_addr.Name(); |
| |
| struct sockaddr* addr = GetSockAddrStorage(); |
| int addr_len = sizeof(sockaddr_store_); |
| if (!connect_addr.Fill(addr, &addr_len, true)) { |
| return false; |
| } |
| |
| if (api_->connect(sockfd_, addr, addr_len) < 0) { |
| LOG(ERROR) << "Error-Connect(fd:" << sockfd_ << ") failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| return LogBoundToAddress(nullptr); |
| } |
| |
| bool SockScripter::Disconnect(char* arg) { |
| LOG(INFO) << "Disconnect(fd:" << sockfd_ << ")"; |
| |
| struct sockaddr_storage addr = {}; |
| addr.ss_family = AF_UNSPEC; |
| if (api_->connect(sockfd_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr.ss_family)) < 0) { |
| LOG(ERROR) << "Error-Disconnect(fd:" << sockfd_ << ") failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| return LogBoundToAddress(nullptr); |
| } |
| |
| bool SockScripter::JoinOrDrop4(const char* func, char* arg, int optname, const char* optname_str) { |
| if (arg == nullptr) { |
| LOG(ERROR) << "Called " << func << " with arg == nullptr!"; |
| return false; |
| } |
| |
| std::string saved_arg(arg); |
| char* mcast_ip_str = strtok(arg, "-"); |
| char* ip_id_str = strtok(nullptr, "-"); |
| |
| if (mcast_ip_str == nullptr || ip_id_str == nullptr) { |
| LOG(ERROR) << "Error-" << func << " got arg='" << saved_arg << "', " |
| << "needs to be <mcast-ip>-{<local-intf-ip>|<local-intf-id>}"; |
| return false; |
| } |
| |
| InAddr mcast_addr; |
| if (!mcast_addr.Set(mcast_ip_str) || !mcast_addr.IsAddr4()) { |
| LOG(ERROR) << "Error-" << func << " got invalid mcast address='" << mcast_ip_str << "'!"; |
| return false; |
| } |
| |
| LocalIfAddr if_addr; |
| if (!if_addr.Set(ip_id_str)) { |
| LOG(ERROR) << "Error-" << func << " got invalid interface='" << ip_id_str << "'!"; |
| return false; |
| } |
| |
| struct ip_mreqn mreq; |
| memset(&mreq, 0, sizeof(mreq)); |
| mreq.imr_multiaddr = mcast_addr.GetAddr4(); |
| if (if_addr.HasAddr4()) { |
| mreq.imr_address = if_addr.GetAddr4(); |
| } |
| if (if_addr.HasId()) { |
| mreq.imr_ifindex = if_addr.GetId(); |
| } |
| |
| if (api_->setsockopt(sockfd_, IPPROTO_IP, optname, &mreq, sizeof(mreq)) < 0) { |
| LOG(ERROR) << "Error-Setting " << optname_str << " failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| LOG(INFO) << "Set " << optname_str << " for " << mcast_addr.Name() << " on " << if_addr.Name() |
| << "."; |
| |
| return true; |
| } |
| |
| bool SockScripter::Join4(char* arg) { |
| return JoinOrDrop4(__FUNCTION__, arg, IP_ADD_MEMBERSHIP, "IP_ADD_MEMBERSHIP"); |
| } |
| |
| bool SockScripter::Drop4(char* arg) { |
| return JoinOrDrop4(__FUNCTION__, arg, IP_DROP_MEMBERSHIP, "IP_DROP_MEMBERSHIP"); |
| } |
| |
| bool SockScripter::JoinOrDrop6(const char* func, char* arg, int optname, const char* optname_str) { |
| if (arg == nullptr) { |
| LOG(ERROR) << "Called " << func << " with arg == nullptr!"; |
| return false; |
| } |
| |
| std::string saved_arg(arg); |
| char* mcast_ip_str = strtok(arg, "-"); |
| char* id_str = strtok(nullptr, "-"); |
| if (mcast_ip_str == nullptr || id_str == nullptr) { |
| LOG(ERROR) << "Error-" << func << " got arg='" << saved_arg << "'', " |
| << "needs to be '<mcast-ip>-<local-intf-id>'"; |
| return false; |
| } |
| |
| InAddr mcast_addr; |
| if (!mcast_addr.Set(mcast_ip_str) || !mcast_addr.IsAddr6()) { |
| LOG(ERROR) << "Error-" << func << " got invalid mcast address='" << mcast_ip_str << "'!"; |
| return false; |
| } |
| |
| LocalIfAddr if_addr; |
| if (!if_addr.Set(id_str)) { // || !if_addr.HasId()) { |
| LOG(ERROR) << "Error-" << func << " got invalid interface='" << id_str << "'!"; |
| return false; |
| } |
| |
| struct ipv6_mreq mreq; |
| memset(&mreq, 0, sizeof(mreq)); |
| mreq.ipv6mr_multiaddr = mcast_addr.GetAddr6(); |
| mreq.ipv6mr_interface = if_addr.GetId(); |
| |
| if (api_->setsockopt(sockfd_, IPPROTO_IPV6, optname, &mreq, sizeof(mreq)) < 0) { |
| LOG(ERROR) << "Error-Setting " << optname_str << " failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| LOG(INFO) << "Set " << optname_str << " for " << mcast_addr.Name() << " on " << if_addr.Name() |
| << "."; |
| |
| return true; |
| } |
| |
| bool SockScripter::Join6(char* arg) { |
| // Note that IPV6_JOIN_GROUP and IPV6_ADD_MEMBERSHIP map to the same optname integer code, so we |
| // can pick either one. We go with IPV6_JOIN_GROUP due to wider support. |
| return JoinOrDrop6(__FUNCTION__, arg, IPV6_JOIN_GROUP, "IPV6_JOIN_GROUP"); |
| } |
| |
| bool SockScripter::Drop6(char* arg) { |
| return JoinOrDrop6(__FUNCTION__, arg, IPV6_LEAVE_GROUP, "IPV6_LEAVE_GROUP"); |
| } |
| |
| bool SockScripter::Listen(char* arg) { |
| if (api_->listen(sockfd_, 1) < 0) { |
| LOG(ERROR) << "Error-listen(fd:" << sockfd_ << ") failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| return true; |
| } |
| |
| bool SockScripter::Accept(char* arg) { |
| struct sockaddr* addr = GetSockAddrStorage(); |
| socklen_t addr_len = sizeof(sockaddr_store_); |
| |
| LOG(INFO) << "Accepting(fd:" << sockfd_ << ")..."; |
| |
| int fd = api_->accept(sockfd_, addr, &addr_len); |
| if (fd < 0) { |
| LOG(ERROR) << "Error-Accept(fd:" << sockfd_ << ") failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| |
| SockAddrIn remote_addr; |
| if (!remote_addr.Set(addr, addr_len)) { |
| return false; |
| } |
| |
| LOG(INFO) << " accepted(fd:" << sockfd_ << ") new connection " |
| << "(fd:" << fd << ") from " << remote_addr.Name(); |
| |
| // Switch to the newly created connection, remember the previous one. |
| prev_sock_fd_ = sockfd_; |
| sockfd_ = fd; |
| return true; |
| } |
| |
| bool SockScripter::SendTo(char* arg) { |
| SockAddrIn dst; |
| if (!dst.Set(arg)) { |
| return false; |
| } |
| |
| auto snd_buf = snd_buf_gen_.GetSndStr(); |
| |
| LOG(INFO) << "Sending [" << snd_buf.length() << "]='" << snd_buf << "' on fd:" << sockfd_ |
| << " to " << dst.Name(); |
| |
| struct sockaddr* addr = GetSockAddrStorage(); |
| int addr_len = sizeof(sockaddr_store_); |
| if (!dst.Fill(addr, &addr_len)) { |
| return false; |
| } |
| |
| ssize_t sent = |
| api_->sendto(sockfd_, snd_buf.c_str(), snd_buf.length(), snd_flags_, addr, addr_len); |
| if (sent < 0) { |
| LOG(ERROR) << "Error-sendto(fd:" << sockfd_ << ") failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| LOG(INFO) << "Sent [" << sent << "] on fd:" << sockfd_; |
| return true; |
| } |
| |
| bool SockScripter::Send(char* arg) { |
| auto snd_buf = snd_buf_gen_.GetSndStr(); |
| |
| LOG(INFO) << "Sending [" << snd_buf.length() << "]='" << snd_buf << "' on fd:" << sockfd_; |
| |
| ssize_t sent = api_->send(sockfd_, snd_buf.c_str(), snd_buf.length(), snd_flags_); |
| if (sent < 0) { |
| LOG(ERROR) << "Error-send(fd:" << sockfd_ << ") failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| LOG(INFO) << "Sent [" << sent << "] on fd:" << sockfd_; |
| return true; |
| } |
| |
| bool SockScripter::RecvFromInternal(bool ping) { |
| struct sockaddr* addr = GetSockAddrStorage(); |
| socklen_t addr_len = sizeof(sockaddr_store_); |
| |
| LOG(INFO) << "RecvFrom(fd:" << sockfd_ << ")..."; |
| |
| memset(recv_buf_, 0, sizeof(recv_buf_)); |
| ssize_t recvd = |
| api_->recvfrom(sockfd_, recv_buf_, sizeof(recv_buf_) - 1, recv_flags_, addr, &addr_len); |
| if (recvd < 0) { |
| if (errno == EAGAIN || errno == EWOULDBLOCK) { |
| LOG(INFO) << " returned EAGAIN or EWOULDBLOCK!"; |
| } else { |
| LOG(ERROR) << " Error-recvfrom(fd:" << sockfd_ << ") failed-" |
| << "[" << errno << "]" << strerror(errno); |
| } |
| return false; |
| } |
| |
| SockAddrIn src_addr; |
| if (!src_addr.Set(addr, addr_len)) { |
| return false; |
| } |
| |
| LOG(INFO) << " received(fd:" << sockfd_ << ") [" << recvd << "]'" << recv_buf_ << "' " |
| << "from " << src_addr.Name(); |
| if (ping) { |
| if (api_->sendto(sockfd_, recv_buf_, recvd, snd_flags_, addr, addr_len) < 0) { |
| LOG(ERROR) << "Error-sendto(fd:" << sockfd_ << ") failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool SockScripter::RecvFrom(char* arg) { return RecvFromInternal(false /* ping */); } |
| |
| bool SockScripter::RecvFromPing(char* arg) { return RecvFromInternal(true /* ping */); } |
| |
| int SockScripter::RecvInternal(bool ping) { |
| LOG(INFO) << "Recv(fd:" << sockfd_ << ") ..."; |
| LogBoundToAddress(nullptr); |
| LogPeerAddress(nullptr); |
| |
| memset(recv_buf_, 0, sizeof(recv_buf_)); |
| ssize_t recvd = api_->recv(sockfd_, recv_buf_, sizeof(recv_buf_) - 1, recv_flags_); |
| if (recvd < 0) { |
| if (errno == EAGAIN || errno == EWOULDBLOCK) { |
| LOG(INFO) << " returned EAGAIN or EWOULDBLOCK!"; |
| } else { |
| LOG(ERROR) << " Error-recv(fd:" << sockfd_ << ") failed-" |
| << "[" << errno << "]" << strerror(errno); |
| } |
| return false; |
| } |
| LOG(INFO) << " received(fd:" << sockfd_ << ") [" << recvd << "]'" << recv_buf_ << "' "; |
| if (ping) { |
| if (api_->send(sockfd_, recv_buf_, recvd, recv_flags_) < 0) { |
| LOG(ERROR) << "Error-send(fd:" << sockfd_ << ") failed-" |
| << "[" << errno << "]" << strerror(errno); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool SockScripter::Recv(char* arg) { return RecvInternal(false /* ping */); } |
| |
| bool SockScripter::RecvPing(char* arg) { return RecvInternal(true /* ping */); } |
| |
| bool SockScripter::SetSendBufHex(char* arg) { return snd_buf_gen_.SetSendBufHex(arg); } |
| |
| bool SockScripter::SetSendBufText(char* arg) { return snd_buf_gen_.SetSendBufText(arg); } |
| |
| bool SockScripter::Sleep(char* arg) { |
| int sleeptime; |
| if (!str2int(arg, &sleeptime) || sleeptime < 0) { |
| LOG(ERROR) << "Error: Invalid sleeptime='" << arg << "'!"; |
| return false; |
| } |
| LOG(INFO) << "Sleep for " << sleeptime << " secs..."; |
| if (sleeptime > 0) { |
| sleep(sleeptime); |
| } |
| LOG(INFO) << "Wake up!"; |
| return true; |
| } |
| |
| struct sockaddr* SockScripter::GetSockAddrStorage() { |
| memset(&sockaddr_store_, 0, sizeof(sockaddr_store_)); |
| return reinterpret_cast<struct sockaddr*>(&sockaddr_store_); |
| } |
| |
| std::string SendBufferGenerator::GetSndStr() { |
| switch (mode_) { |
| case COUNTER_TEXT: { |
| std::string ret_str = snd_str_; |
| auto cnt_specifier = ret_str.find("%c"); |
| if (cnt_specifier != std::string::npos) { |
| ret_str.replace(cnt_specifier, 2, std::to_string(counter_)); |
| } |
| counter_++; |
| return ret_str; |
| } |
| case STATIC_TEXT: |
| default: |
| return snd_str_; |
| } |
| } |
| |
| bool SendBufferGenerator::SetSendBufHex(const char* arg) { |
| std::string_view str(arg, strlen(arg)); |
| std::stringstream ss; |
| while (str.length()) { |
| auto f = str.front(); |
| uint8_t v; |
| // always allow any number of spaces or commas to happen |
| if (f == ' ' || f == ',') { |
| str = str.substr(1); |
| continue; |
| } else if (str.length() < 2 || |
| !fxl::StringToNumberWithError(str.substr(0, 2), &v, fxl::Base::k16)) { |
| // trailing character at the end that we don't recognize or failed to parse number |
| return false; |
| } |
| str = str.substr(2); |
| ss.put(v); |
| } |
| snd_str_ = ss.str(); |
| return true; |
| } |
| |
| bool SendBufferGenerator::SetSendBufText(const char* arg) { |
| snd_str_ = arg; |
| return true; |
| } |