| // 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 "src/developer/debug/zxdb/client/socket_connect.h" | |
| #include "src/developer/debug/shared/logging/logging.h" | |
| #include "src/developer/debug/zxdb/common/err.h" | |
| #if defined(__APPLE__) | |
| // NOTE): In the MacOS case, getaddrinfo (the approach used for Linux) would result almost always in | |
| // an "Address family not supported by protocol family" error when trying to connect. A lot of | |
| // debugging got nowhere and finally decided to go the inet_pton way. | |
| // | |
| // Ironically, we cannot (easily) use this approach for linux too because turns out that MacOS's | |
| // inet_pton has extended functionality that enables it to support link-local IPv6 addresses that | |
| // specify the interface (which is needed to correctly connect to link-local addresses). In linux is | |
| // a more contrived dance that requires to iterate over all the interfaces, so it's simpler to go | |
| // the normal getaddrinfo route. | |
| // | |
| // Some background info that lead me to discard getaddrinfo altogether: | |
| // | |
| // https://blog.powerdns.com/2014/05/21/a-surprising-discovery-on-converting-ipv6-addresses-we-no-longer-prefer-getaddrinfo/ | |
| #include <arpa/inet.h> | |
| #include <fcntl.h> | |
| #include <variant> | |
| namespace zxdb { | |
| namespace { | |
| using SockAddrVariant = std::variant<sockaddr_in, sockaddr_in6>; | |
| Err ResolveTargetAddress(const std::string& host, uint16_t port, SockAddrVariant* out) { | |
| int res; | |
| // First try IPv6. Result of 0 means that the string is not IPv6. | |
| struct sockaddr_in6 addr6 = {}; // zero-out. | |
| res = inet_pton(AF_INET6, host.c_str(), &addr6.sin6_addr); | |
| if (res == 1) { | |
| // Successfully found IPv6 address. | |
| addr6.sin6_family = AF_INET6; | |
| addr6.sin6_port = htons(port); | |
| *out = std::move(addr6); | |
| return Err(); | |
| } | |
| DEBUG_LOG(RemoteAPI) << "Could not resolve IPv6: " << strerror(errno) << " (res: " << res << ")."; | |
| // We now try IPv4. | |
| struct sockaddr_in addr4 = {}; // zero-out. | |
| res = inet_pton(AF_INET, host.c_str(), &addr4.sin_addr); | |
| if (res == 1) { | |
| // Successfully found IPv4 address. | |
| addr4.sin_family = AF_INET; | |
| addr4.sin_port = htons(port); | |
| *out = std::move(addr4); | |
| return Err(); | |
| } | |
| return Err("Address %s is not a valid IPv4 or IPv6 address.", host.c_str()); | |
| } | |
| } // namespace | |
| Err ConnectToUnixSocket(const std::string& path, fbl::unique_fd* socket_out) { | |
| return Err("Unix sockets are currently not implemented on Mac"); | |
| } | |
| Err ConnectToHost(const std::string& host, uint16_t port, fbl::unique_fd* socket_out) { | |
| SockAddrVariant addr_variant; | |
| Err err = ResolveTargetAddress(host, port, &addr_variant); | |
| if (err.has_error()) | |
| return err; | |
| fbl::unique_fd res_socket; | |
| // Is it IPv6? | |
| if (std::holds_alternative<sockaddr_in6>(addr_variant)) { | |
| res_socket.reset(socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)); | |
| if (!res_socket.is_valid()) | |
| return Err("Could not create socket: %s.", strerror(errno)); | |
| sockaddr_in6& addr6 = std::get<sockaddr_in6>(addr_variant); | |
| if (connect(res_socket.get(), reinterpret_cast<sockaddr*>(&addr6), sizeof(sockaddr_in6))) { | |
| return Err("Could not connect to socket: %s.", strerror(errno)); | |
| } | |
| } | |
| // Fallback to IPv4. | |
| if (std::holds_alternative<sockaddr_in>(addr_variant)) { | |
| res_socket.reset(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); | |
| if (!res_socket.is_valid()) | |
| return Err("Could not create socket: %s.", strerror(errno)); | |
| sockaddr_in& addr4 = std::get<sockaddr_in>(addr_variant); | |
| if (connect(res_socket.get(), reinterpret_cast<sockaddr*>(&addr4), sizeof(sockaddr_in))) { | |
| return Err("Could not connect to socket: %s.", strerror(errno)); | |
| } | |
| } | |
| // By default sockets are blocking which we don't want. | |
| if (fcntl(res_socket.get(), F_SETFL, O_NONBLOCK) < 0) | |
| return Err("Could not make nonblocking socket."); | |
| *socket_out = std::move(res_socket); | |
| return Err(); | |
| } | |
| } // namespace zxdb | |
| #elif defined(__linux__) | |
| #include <fcntl.h> | |
| #include <netdb.h> | |
| #include "src/lib/fxl/strings/string_printf.h" | |
| namespace zxdb { | |
| namespace { | |
| // 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 = fxl::StringPrintf("%u", port); | |
| struct addrinfo hints; | |
| memset(&hints, 0, sizeof(hints)); | |
| hints.ai_family = AF_UNSPEC; | |
| hints.ai_socktype = SOCK_STREAM; | |
| hints.ai_protocol = IPPROTO_TCP; | |
| struct addrinfo* addrs = nullptr; | |
| int addr_err = getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrs); | |
| if (addr_err != 0) { | |
| return Err("Failed to resolve %s: %s.", host.c_str(), gai_strerror(addr_err)); | |
| } | |
| struct addrinfo* p; | |
| for (p = addrs; p != nullptr; p = p->ai_next) { | |
| char buf[1024]; | |
| getnameinfo(p->ai_addr, p->ai_addrlen, buf, sizeof(buf), nullptr, 0, NI_NUMERICHOST); | |
| } | |
| *addr = *addrs; | |
| freeaddrinfo(addrs); | |
| return Err(); | |
| } | |
| } // namespace | |
| Err ConnectToUnixSocket(const std::string& path, fbl::unique_fd* socket_out) { | |
| int fd; | |
| if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { | |
| return Err("Socket error"); | |
| } | |
| fcntl(fd, F_SETFL, O_NONBLOCK); | |
| std::vector<uint8_t> sockaddr_data(path.size() + sizeof(sockaddr)); | |
| auto addr = reinterpret_cast<struct sockaddr*>(sockaddr_data.data()); | |
| addr->sa_family = AF_UNIX; | |
| strcpy(addr->sa_data, path.c_str()); | |
| if (connect(fd, addr, sockaddr_data.size()) == -1) { | |
| return Err("Connect error"); | |
| } | |
| *socket_out = fbl::unique_fd(fd); | |
| return Err(); | |
| } | |
| Err ConnectToHost(const std::string& host, uint16_t port, fbl::unique_fd* socket_out) { | |
| addrinfo addr; | |
| Err err = ResolveAddress(host, port, &addr); | |
| if (err.has_error()) | |
| return err; | |
| fbl::unique_fd res_socket; | |
| res_socket.reset(socket(addr.ai_family, SOCK_STREAM, IPPROTO_TCP)); | |
| if (!res_socket.is_valid()) | |
| return Err("Could not create socket: %s.", strerror(errno)); | |
| if (connect(res_socket.get(), addr.ai_addr, addr.ai_addrlen)) | |
| return Err("Failed to connect socket: %s.", strerror(errno)); | |
| // By default sockets are blocking which we don't want. | |
| if (fcntl(res_socket.get(), F_SETFL, O_NONBLOCK) < 0) | |
| return Err("Could not make nonblocking socket: %s.", strerror(errno)); | |
| *socket_out = std::move(res_socket); | |
| return Err(); | |
| } | |
| } // namespace zxdb | |
| #else | |
| #error Unsupported OS | |
| #endif |