[cdc_rsync] Fix issue with IPV6 localhosts (#93)

Fixes an issue with port forwarding when localhost on the remote
system maps to the IPV6 localhost. In that case the server would time
out on accept() since it creates an IPV4 socket, so the connection
is never established.

Also allows passing in port 0, so that it will auto-detect an
available port. This will be used in a future CL to remove the
necessity of running netstat/ss.
This commit is contained in:
Lutz Justen
2023-02-14 11:09:03 +01:00
committed by GitHub
parent fcc4cbc3f3
commit 5fd86e4625
4 changed files with 109 additions and 19 deletions

View File

@@ -140,6 +140,7 @@ cc_library(
"//common:status",
"//common:util",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
],
)

View File

@@ -24,6 +24,7 @@
#include "common/log.h"
#include "common/path.h"
#include "common/status.h"
#include "common/status_macros.h"
#include "common/stopwatch.h"
#include "common/threadpool.h"
#include "common/util.h"
@@ -295,10 +296,11 @@ absl::Status CdcRsyncServer::Run(int port) {
socket_finalizer_ = std::make_unique<SocketFinalizer>();
socket_ = std::make_unique<ServerSocket>();
status = socket_->StartListening(port);
if (!status.ok()) {
return WrapStatus(status, "Failed to start listening on port %i", port);
}
int new_port;
ASSIGN_OR_RETURN(new_port, socket_->StartListening(port),
"Failed to start listening on port %i", port);
assert(port != 0);
assert(port == new_port);
LOG_INFO("cdc_rsync_server listening on port %i", port);
// This is the marker for the client, so it knows it can connect.

View File

@@ -22,11 +22,14 @@
#if PLATFORM_WINDOWS
#include <winsock2.h>
#include <ws2tcpip.h>
#elif PLATFORM_LINUX
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <cerrno>
@@ -95,6 +98,15 @@ void Close(SocketType* socket) {
std::string GetLastErrorStr() { return GetErrorStr(GetLastError()); }
class AddrInfoReleaser {
public:
AddrInfoReleaser(addrinfo* addr_infos) : addr_infos_(addr_infos) {}
~AddrInfoReleaser() { freeaddrinfo(addr_infos_); }
private:
addrinfo* addr_infos_;
};
} // namespace
struct ServerSocketInfo {
@@ -113,15 +125,57 @@ ServerSocket::~ServerSocket() {
StopListening();
}
absl::Status ServerSocket::StartListening(int port) {
absl::StatusOr<int> ServerSocket::StartListening(int port) {
if (socket_info_->listen_sock != kInvalidSocket) {
return MakeStatus("Already listening");
}
LOG_DEBUG("Open socket");
socket_info_->listen_sock = socket(AF_INET, SOCK_STREAM, 0);
// Find addrinfos suitable for listening via IPV4 and IPV6.
addrinfo hints;
addrinfo* addr_infos = nullptr;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// AI_PASSIVE indicates that the addresses are used with bind(). The returned
// addresses will be the unspecified addresses for each family.
hints.ai_flags = AI_NUMERICHOST | AI_PASSIVE;
int result = getaddrinfo(/*address=*/nullptr, std::to_string(port).c_str(),
&hints, &addr_infos);
if (result != 0) {
return MakeStatus("Getting address infos failed: %s", GetLastErrorStr());
}
AddrInfoReleaser releaser(addr_infos);
// Prefer IPV6 sockets. They can also accept IPV4 connections.
for (addrinfo* curr = addr_infos; curr; curr = curr->ai_next) {
if (curr->ai_family == PF_INET6) {
return StartListeningInternal(port, curr);
}
}
// Fall back to IPV4 sockets.
for (addrinfo* curr = addr_infos; curr; curr = curr->ai_next) {
if (curr->ai_family == PF_INET) {
return StartListeningInternal(port, curr);
}
}
return MakeStatus("No IPV4 and IPV6 network addresses available");
}
absl::StatusOr<int> ServerSocket::StartListeningInternal(int port,
addrinfo* addr) {
assert(addr->ai_family == PF_INET || addr->ai_family == PF_INET6);
const char* family = addr->ai_family == PF_INET ? "IPV4" : "IPV6";
// Open a socket with the correct address family for this address.
LOG_DEBUG("Open %s listen socket", family);
socket_info_->listen_sock =
socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (socket_info_->listen_sock == kInvalidSocket) {
return MakeStatus("Creating listen socket failed: %s", GetLastErrorStr());
return MakeStatus("Creating %s listen socket failed: %s", family,
GetLastErrorStr());
}
// If the program terminates abnormally, the socket might remain in a
@@ -136,16 +190,21 @@ absl::Status ServerSocket::StartListening(int port) {
LOG_DEBUG("Enabling address reusal failed");
}
LOG_DEBUG("Bind socket");
sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(port);
// Allow ipv4 connections on the ipv6 socket. By default, ipv6 sockets only
// allow ipv4 connections on Windows.
if (addr->ai_family == PF_INET6) {
const int disable = 0;
result =
setsockopt(socket_info_->listen_sock, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<const char*>(&disable), sizeof(disable));
if (result == kSocketError) {
LOG_DEBUG("Disabling IPV6-only failed");
}
}
result = bind(socket_info_->listen_sock,
reinterpret_cast<const SockAddrType*>(&serv_addr),
sizeof(serv_addr));
LOG_DEBUG("Bind socket");
result = bind(socket_info_->listen_sock, addr->ai_addr,
static_cast<int>(addr->ai_addrlen));
if (result == kSocketError) {
int err = GetLastError();
absl::Status status =
@@ -159,6 +218,21 @@ absl::Status ServerSocket::StartListening(int port) {
return status;
}
if (port == 0) {
// Find out which port was auto-selected.
socklen_t len = addr->ai_addrlen;
result = getsockname(socket_info_->listen_sock, addr->ai_addr, &len);
if (result == kSocketError) {
Close(&socket_info_->listen_sock);
return MakeStatus("Getting port failed: %s", GetLastErrorStr());
}
if (addr->ai_family == PF_INET) {
port = ntohs(reinterpret_cast<sockaddr_in*>(addr->ai_addr)->sin_port);
} else if (addr->ai_family == PF_INET6) {
port = ntohs(reinterpret_cast<sockaddr_in6*>(addr->ai_addr)->sin6_port);
}
}
LOG_DEBUG("Listen");
result = listen(socket_info_->listen_sock, 1);
if (result == kSocketError) {
@@ -167,7 +241,7 @@ absl::Status ServerSocket::StartListening(int port) {
return MakeStatus("Listening to socket failed: %s", GetErrorStr(err));
}
return absl::OkStatus();
return port;
}
void ServerSocket::StopListening() {
@@ -179,6 +253,9 @@ absl::Status ServerSocket::WaitForConnection() {
if (socket_info_->conn_sock != kInvalidSocket) {
return MakeStatus("Already connected");
}
if (socket_info_->listen_sock == kInvalidSocket) {
return MakeStatus("Not listening");
}
socket_info_->conn_sock = accept(socket_info_->listen_sock, nullptr, nullptr);
if (socket_info_->conn_sock == kInvalidSocket) {

View File

@@ -18,8 +18,11 @@
#define CDC_RSYNC_SERVER_SERVER_SOCKET_H_
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "cdc_rsync/base/socket.h"
struct addrinfo;
namespace cdc_ft {
class ServerSocket : public Socket {
@@ -28,7 +31,9 @@ class ServerSocket : public Socket {
~ServerSocket();
// Starts listening for connections on |port|.
absl::Status StartListening(int port);
// Passing 0 as port will bind to any available port.
// Returns the port that was bound to.
absl::StatusOr<int> StartListening(int port);
// Stops listening for connections. No-op if already stopped/never started.
void StopListening();
@@ -50,6 +55,11 @@ class ServerSocket : public Socket {
size_t* bytes_received) override;
private:
// Called by StartListening() for a specific IPV4 or IPV6 |addr_info|.
// Passing 0 as port will bind to any available port.
// Returns the port that was bound to.
absl::StatusOr<int> StartListeningInternal(int port, addrinfo* addr);
std::unique_ptr<struct ServerSocketInfo> socket_info_;
};