| // Copyright 2020 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 "src/developer/shell/mirror/client.h" |
| |
| #include <fcntl.h> |
| #include <netdb.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/select.h> |
| #include <sys/time.h> |
| |
| #include <fstream> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "src/developer/shell/mirror/wire_format.h" |
| |
| namespace shell::mirror::client { |
| |
| namespace { |
| |
| // Grabbed from zxdb/client code |
| |
| // Tries to resolve the host/port. On success populates *addr and returns Err(). |
| Err ResolveAddress(const std::string& host, uint16_t port, addrinfo** addr) { |
| std::string port_str = std::to_string(port); |
| |
| struct addrinfo hints; |
| memset(&hints, 0, sizeof(hints)); |
| hints.ai_family = AF_UNSPEC; |
| hints.ai_socktype = SOCK_STREAM; |
| hints.ai_protocol = IPPROTO_TCP; |
| |
| int addr_err = getaddrinfo(host.c_str(), port_str.c_str(), &hints, addr); |
| if (addr_err != 0) { |
| return Err(kConnection, "Failed to resolve " + host + ": " + gai_strerror(addr_err)); |
| } |
| |
| return Err(); |
| } |
| |
| Err ConnectToHost(const std::string& host, uint16_t port, fbl::unique_fd* socket_out) { |
| struct addrinfo* addr; |
| Err err = ResolveAddress(host, port, &addr); |
| if (err.code != 0) { |
| return err; |
| } |
| |
| for (struct addrinfo* a = addr; a != nullptr; a = a->ai_next) { |
| fbl::unique_fd res_socket; |
| |
| res_socket.reset(socket(a->ai_family, SOCK_STREAM, IPPROTO_TCP)); |
| if (!res_socket.is_valid()) { |
| err.msg = std::string("Could not create socket: ") + strerror(errno); |
| err.code = kConnection; |
| continue; |
| } |
| |
| if (connect(res_socket.get(), a->ai_addr, a->ai_addrlen)) { |
| err.msg = std::string("Could not connect socket: ") + strerror(errno); |
| err.code = kConnection; |
| continue; |
| } |
| |
| *socket_out = std::move(res_socket); |
| freeaddrinfo(addr); |
| return Err(); |
| } |
| |
| freeaddrinfo(addr); |
| return err; |
| } |
| |
| // grabbed from zxdb/common/inet_util |
| |
| Err ParseHostPort(const std::string& in_host, const std::string& in_port, std::string* out_host, |
| uint16_t* out_port) { |
| if (in_host.empty()) { |
| return Err(kConnection, "No host component specified."); |
| } |
| if (in_port.empty()) { |
| return Err(kConnection, "No port component specified."); |
| } |
| |
| // Trim brackets from the host name for IPv6 addresses. |
| if (in_host.front() == '[' && in_host.back() == ']') { |
| *out_host = in_host.substr(1, in_host.size() - 2); |
| } else { |
| *out_host = in_host; |
| } |
| |
| const char* port_p = in_port.c_str(); |
| char* port_ep = nullptr; |
| unsigned long int res = strtoul(port_p, &port_ep, 10); |
| if (*port_ep != '\0') { |
| return Err(kConnection, std::string("Invalid port number: ") + in_port); |
| } |
| if (res > 65535 || res < 0) { |
| return Err(kConnection, "Port value out of range."); |
| } |
| *out_port = static_cast<uint16_t>(res); |
| |
| return Err(); |
| } |
| |
| Err ParseHostPort(const std::string& input, std::string* out_host, uint16_t* out_port) { |
| // Separate based on the last colon. |
| size_t colon = input.rfind(':'); |
| if (colon == std::string::npos) { |
| return Err(kConnection, "Expected colon to separate host/port."); |
| } |
| |
| // If the host has a colon in it, it could be an IPv6 address. In this case, |
| // require brackets around it to differentiate the case where people |
| // supplied an IPv6 address and we just picked out the last component above. |
| std::string host = input.substr(0, colon); |
| if (host.empty()) { |
| return Err(kConnection, "No host component specified."); |
| } |
| if (host.find(':') != std::string::npos) { |
| if (host.front() != '[' || host.back() != ']') { |
| return Err(kConnection, "Missing brackets enclosing IPv6 address, e.g., \"[::1]:1234\"."); |
| } |
| } |
| |
| std::string port = input.substr(colon + 1); |
| |
| return ParseHostPort(host, port, out_host, out_port); |
| } |
| |
| bool Ipv6HostPortIsMissingBrackets(const std::string& input) { |
| size_t colon = input.rfind(':'); |
| if (colon == std::string::npos) { |
| return false; |
| } |
| std::string host = input.substr(0, colon); |
| if (host.empty()) { |
| return false; |
| } |
| if (host.find(':') == std::string::npos) { |
| return false; |
| } |
| return host.front() != '[' || host.back() != ']'; |
| } |
| |
| Err GetHostPort(const std::string& host_port, std::string* host, uint16_t* port) { |
| if (Ipv6HostPortIsMissingBrackets(host_port)) { |
| return Err(kConnection, |
| "For IPv6 addresses use either: \"[::1]:1234\"\n" |
| "or the two-parameter form: \"::1 1234."); |
| } |
| return ParseHostPort(host_port, host, port); |
| } |
| |
| } // namespace |
| |
| Err ClientConnection::Init(const std::string& host_and_port) { |
| std::string host; |
| uint16_t port = 0; |
| Err err = GetHostPort(host_and_port, &host, &port); |
| if (err.code != 0) { |
| return err; |
| } |
| |
| err = ConnectToHost(host, port, &socket_); |
| if (err.code != 0) { |
| return err; |
| } |
| |
| return Err(); |
| } |
| |
| Err ClientConnection::KillServer() { |
| if (write(socket_.get(), remote_commands::kQuitCommand, |
| strlen(remote_commands::kQuitCommand) + 1) == -1) { |
| std::string error = "Unable to write to server: " + std::string(strerror(errno)); |
| return Err(kWrite, error); |
| } |
| return Err(); |
| } |
| |
| Err ClientConnection::Load(Files* files, struct timeval* timeout) { |
| if (write(socket_.get(), remote_commands::kFilesCommand, |
| strlen(remote_commands::kFilesCommand) + 1) == -1) { |
| std::string error = "Unable to write to server: " + std::string(strerror(errno)); |
| return Err(kWrite, error); |
| } |
| |
| Err error; |
| *files = Files::FilesFromFD(socket_.get(), &error, timeout); |
| if (!error.ok()) { |
| return error; |
| } |
| |
| return Err(); |
| } |
| |
| } // namespace shell::mirror::client |