blob: df676a82cf57774de14f4fb61d4510ecc0545e0e [file] [log] [blame]
// Copyright 2011 The Goma Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Note for 64-bit SOCKET usage:
// It's okay to cast between int and SOCKET since Windows Handle value will not
// exceed 2^24 according to Windows Internals. See
// http://blogs.technet.com/b/markrussinovich/archive/2009/09/29/3283844.aspx.
#ifdef _WIN32
#include "socket_helper_win.h"
#include "glog/logging.h"
#include "lockhelper.h"
#include "platform_thread.h"
#define WSA_VERSION MAKEWORD(2, 2) // using winsock 2.2
int inet_aton(const char* input, struct in_addr* output) {
return inet_pton(AF_INET, input, &output->s_addr);
}
/* socketpair.c
* Copyright 2007, 2010 by Nathan C. Myers <ncm@cantrip.org>
* This code is Free Software. It may be copied freely, in original or
* modified form, subject only to the restrictions that (1) the author is
* relieved from all responsibilities for any use for any purpose, and (2)
* this copyright notice must be retained, unchanged, in its entirety. If
* for any reason the author might be held responsible for any consequences
* of copying or use, license is withheld.
*/
// Original version:
// https://github.com/ncm/selectable-socketpair/blob/master/socketpair.c
// This implementation can only be blocking and is not select-able.
// TODO: Use type SOCKET for socks[2] instead of type int.
int socketpair(sa_family_t domain, int type, int protocol, int socks[2]) {
union {
struct sockaddr_in inaddr;
struct sockaddr addr;
} addr;
SOCKET listener;
socklen_t addr_len = sizeof(addr.inaddr);
if (socks == nullptr) {
WSASetLastError(WSA_INVALID_PARAMETER);
return SOCKET_ERROR;
}
listener = socket(domain, type, protocol);
if (listener == INVALID_SOCKET) {
return SOCKET_ERROR;
}
memset(&addr, 0, sizeof(addr));
addr.inaddr.sin_family = domain;
addr.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.inaddr.sin_port = 0;
socks[0] = static_cast<int>(INVALID_SOCKET);
socks[1] = static_cast<int>(INVALID_SOCKET);
int reuse = 1;
for (;;) {
if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse,
(socklen_t)sizeof(reuse)) == -1) {
break;
}
if (bind(listener, &addr.addr, sizeof(addr.inaddr)) == SOCKET_ERROR) {
break;
}
memset(&addr, 0, sizeof(addr));
if (getsockname(listener, &addr.addr, &addr_len) == SOCKET_ERROR) {
break;
}
addr.inaddr.sin_family = AF_INET;
addr.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (listen(listener, 1) == SOCKET_ERROR) {
break;
}
socks[0] = static_cast<int>(WSASocket(
domain, type, protocol, nullptr, 0, 0));
if (socks[0] == static_cast<int>(INVALID_SOCKET)) {
break;
}
if (connect(socks[0], &addr.addr, sizeof(addr.inaddr)) == SOCKET_ERROR) {
break;
}
socks[1] = static_cast<int>(accept(listener, nullptr, nullptr));
if (socks[1] == static_cast<int>(INVALID_SOCKET)) {
break;
}
closesocket(listener);
return 0;
}
int last_error = WSAGetLastError();
closesocket(listener);
closesocket(socks[1]);
closesocket(socks[0]);
WSASetLastError(last_error);
return SOCKET_ERROR;
}
namespace {
class ServerThread : public devtools_goma::PlatformThread::Delegate {
public:
ServerThread(const ServerThread&) = delete;
ServerThread& operator=(const ServerThread&) = delete;
// Creates |*listener| socket and starts listen at |*listener| on |*port|.
// Returns WSA error code. If success, returns 0.
// |*port| is allocated from available port by system.
// It is stored in host byte order.
static DWORD StartListen(SOCKET* listener, int* port) {
DCHECK(listener != nullptr);
DCHECK(port != nullptr);
*listener = INVALID_SOCKET;
*port = 0;
sockaddr_in inaddr = {};
while (*port == 0) {
*listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (*listener == INVALID_SOCKET) {
DWORD err = WSAGetLastError();
LOG(ERROR) << "listen failed:" << err;
return err;
}
memset(&inaddr, 0, sizeof(inaddr));
inaddr.sin_family = AF_INET;
inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
inaddr.sin_port = htons(0);
socklen_t addr_len = sizeof(inaddr);
unsigned long non_blocking = 1;
if (ioctlsocket(*listener, FIONBIO, &non_blocking) == SOCKET_ERROR) {
DWORD err = WSAGetLastError();
LOG(ERROR) << "socket non blocking failed:" << err;
closesocket(*listener);
*listener = INVALID_SOCKET;
return err;
}
if (bind(*listener, (sockaddr*)&inaddr, addr_len) == SOCKET_ERROR) {
// bind may fail if other process/thread uses the port.
LOG(WARNING) << "bind failed:" << WSAGetLastError();
closesocket(*listener);
continue;
}
memset(&inaddr, 0, sizeof(inaddr));
addr_len = sizeof(inaddr);
if (getsockname(*listener, (sockaddr*)&inaddr, &addr_len)
== SOCKET_ERROR) {
DWORD err = WSAGetLastError();
LOG(ERROR) << "getsockname failed:" << err;
closesocket(*listener);
*listener = INVALID_SOCKET;
return err;
}
*port = ntohs(inaddr.sin_port);
}
if (listen(*listener, 1) == SOCKET_ERROR) {
DWORD err = WSAGetLastError();
LOG(ERROR) << "listen failed:" << err;
closesocket(*listener);
*listener = INVALID_SOCKET;
return err;
}
return 0;
}
// ServerThread listen at |listener| and set accepted socket in |*accept|.
// ServerThread will close |listener|.
ServerThread(SOCKET listener, SOCKET* accept)
: listener_(listener), accept_(accept), result_(WSAETIMEDOUT) {
DCHECK_NE(listener_, INVALID_SOCKET);
DCHECK_EQ(*accept_, INVALID_SOCKET);
}
~ServerThread() {
closesocket(listener_);
}
void ThreadMain() override {
VLOG(1) << "socketpair ServerThread: start";
fd_set r_set;
SOCKET s = INVALID_SOCKET;
for (;;) {
timeval tv;
tv.tv_sec = 2;
tv.tv_usec = 0; // timeout is two seconds
FD_ZERO(&r_set);
MSVC_PUSH_DISABLE_WARNING_FOR_FD_SET();
FD_SET(listener_, &r_set);
MSVC_POP_WARNING();
int r = select(static_cast<int>(listener_ + 1),
&r_set, nullptr, nullptr, &tv);
if (r < 0) {
LOG(WARNING) << "select error:" << r
<< " result=" << WSAGetLastError();
continue;
} else if (r == 0) {
LOG(WARNING) << "select timed-out";
continue;
}
if (FD_ISSET(listener_, &r_set)) {
sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
s = accept(listener_, (sockaddr*)&client_addr, &addr_len);
if (s == INVALID_SOCKET) {
result_ = WSAGetLastError();
if (result_ == WSAEWOULDBLOCK) {
continue;
}
DCHECK_NE(result_, 0);
LOG(ERROR) << "accpet failed:" << result_;
closesocket(s);
return;
}
// accepted.
*accept_ = s;
result_ = 0;
VLOG(1) << "socketpair ServerThread: ready";
return;
}
}
}
int result() { return result_; }
private:
SOCKET listener_;
SOCKET* accept_;
int result_;
};
class ClientThread : public devtools_goma::PlatformThread::Delegate {
public:
explicit ClientThread(SOCKET* client, int port)
: client_(client), port_(port), result_(WSAETIMEDOUT) {}
ClientThread(const ClientThread&) = delete;
ClientThread& operator=(const ClientThread&) = delete;
void ThreadMain() override {
*client_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (*client_ == INVALID_SOCKET) {
result_ = WSAGetLastError();
return;
}
sockaddr_in inaddr;
memset(&inaddr, 0, sizeof(inaddr));
inaddr.sin_family = AF_INET;
inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
inaddr.sin_port = htons((unsigned short)port_);
socklen_t addr_len = sizeof(inaddr);
timeval tv;
tv.tv_sec = 2;
tv.tv_usec = 0; // timeout is two seconds
fd_set w_set, e_set;
FD_ZERO(&w_set);
FD_ZERO(&e_set);
for (;;) {
int r = connect(*client_, (sockaddr*)&inaddr, addr_len);
if (r != SOCKET_ERROR) { // Connected immediately
result_ = 0;
return;
}
result_ = WSAGetLastError();
if (result_ != WSAEWOULDBLOCK) {
break;
} else {
// need select
MSVC_PUSH_DISABLE_WARNING_FOR_FD_SET();
FD_SET(*client_, &w_set);
MSVC_POP_WARNING();
e_set = w_set;
r = select(static_cast<int>(*client_ + 1), nullptr,
&w_set, &e_set, &tv);
if (r == 0) { // Connection timeout
result_ = WSAETIMEDOUT;
break;
}
if (FD_ISSET(*client_, &w_set) || FD_ISSET(*client_, &e_set)) {
int len = sizeof(result_);
if (getsockopt(*client_, SOL_SOCKET, SO_ERROR, (char*)&result_,
&len) >= 0) { // Connection established
return;
}
result_ = WSAGetLastError();
} else { // Unknown error in connect
result_ = WSAGetLastError();
}
}
break;
}
closesocket(*client_);
}
int result() { return result_; }
private:
SOCKET* client_;
int port_;
int result_;
};
} // namespace
// TODO: Use type SOCKET for socks[2].
int async_socketpair(int socks[2]) {
if (socks == nullptr) {
WSASetLastError(WSA_INVALID_PARAMETER);
return SOCKET_ERROR;
}
SOCKET listener = INVALID_SOCKET;
int port = 0;
DWORD err = ServerThread::StartListen(&listener, &port);
if (err != 0) {
LOG(ERROR) << "StartListen failed:" << err;
WSASetLastError(err);
return SOCKET_ERROR;
}
DCHECK_NE(listener, INVALID_SOCKET);
DCHECK_NE(port, 0);
SOCKET server_socket = INVALID_SOCKET;
SOCKET client_socket = INVALID_SOCKET;
ServerThread server(listener, &server_socket);
devtools_goma::PlatformThreadHandle server_thread_handle =
devtools_goma::kNullThreadHandle;
devtools_goma::PlatformThread::Create(&server, &server_thread_handle);
// This will be blocked until server started listening.
ClientThread client(&client_socket, port);
devtools_goma::PlatformThreadHandle client_thread_handle =
devtools_goma::kNullThreadHandle;
devtools_goma::PlatformThread::Create(&client, &client_thread_handle);
DWORD result = WaitForSingleObject(client_thread_handle, INFINITE);
if (result == WAIT_OBJECT_0) {
socks[1] = static_cast<int>(client_socket);
if (client_socket == INVALID_SOCKET) {
LOG(ERROR) << "client thread result=" << client.result();
}
} else {
socks[1] = static_cast<int>(INVALID_SOCKET);
LOG(ERROR) << "client wait error: result=" << result;
}
result = WaitForSingleObject(server_thread_handle, INFINITE);
if (result == WAIT_OBJECT_0) {
socks[0] = static_cast<int>(server_socket);
if (server_socket == INVALID_SOCKET) {
LOG(ERROR) << "server thread result=" << server.result();
}
} else {
socks[0] = static_cast<int>(INVALID_SOCKET);
LOG(ERROR) << "server wait error: result=" << result;
}
if (socks[0] != static_cast<int>(INVALID_SOCKET) &&
socks[1] != static_cast<int>(INVALID_SOCKET)) {
return 0;
}
return SOCKET_ERROR;
}
WinsockHelper::WinsockHelper() : initialized_(false) {
WSADATA WSAData = {};
if (WSAStartup(WSA_VERSION, &WSAData) != 0) {
// Tell the user that we could not find a usable WinSock DLL.
if (LOBYTE(WSAData.wVersion) != LOBYTE(WSA_VERSION) ||
HIBYTE(WSAData.wVersion) != HIBYTE(WSA_VERSION)) {
PLOG(ERROR) << "GOMA: Incorrect winsock version, required 2.2 and up";
}
WSACleanup();
} else {
initialized_ = true;
}
}
WinsockHelper::~WinsockHelper() {
if (initialized_) {
WSACleanup();
}
}
#endif // _WIN32