mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-07-14 19:15:19 +02:00
206 lines
5.2 KiB
C++
206 lines
5.2 KiB
C++
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
|
|
#include <string>
|
|
|
|
#include "Common/File/Path.h"
|
|
|
|
#include "Common/TimeUtil.h"
|
|
#include "Common/Log.h"
|
|
#include "Common/Net/SocketCompat.h"
|
|
#include "Common/Buffer.h"
|
|
#include "Common/File/FileDescriptor.h"
|
|
#include "Common/SysError.h"
|
|
#include "Common/Net/Connection.h"
|
|
|
|
namespace net {
|
|
|
|
Connection::~Connection() {
|
|
Disconnect();
|
|
if (resolved_ != nullptr)
|
|
DNSResolveFree(resolved_);
|
|
}
|
|
|
|
// For whatever crazy reason, htons isn't available on android x86 on the build server. so here we go.
|
|
|
|
// TODO: Fix for big-endian
|
|
inline unsigned short myhtons(unsigned short x) {
|
|
return (x >> 8) | (x << 8);
|
|
}
|
|
|
|
const char *DNSTypeAsString(DNSType type) {
|
|
switch (type) {
|
|
case DNSType::IPV4:
|
|
return "IPV4";
|
|
case DNSType::IPV6:
|
|
return "IPV6";
|
|
case DNSType::ANY:
|
|
return "ANY";
|
|
default:
|
|
return "N/A";
|
|
}
|
|
}
|
|
|
|
bool Connection::Resolve(const char *host, int port, DNSType type) {
|
|
if ((intptr_t)sock_ != -1) {
|
|
ERROR_LOG(Log::IO, "Resolve: Already have a socket");
|
|
return false;
|
|
}
|
|
if (!host || port < 1 || port > 65535) {
|
|
ERROR_LOG(Log::IO, "Resolve: Invalid host or port (%d)", port);
|
|
return false;
|
|
}
|
|
|
|
host_ = host;
|
|
port_ = port;
|
|
|
|
char port_str[16];
|
|
snprintf(port_str, sizeof(port_str), "%d", port);
|
|
|
|
std::string processedHostname(host);
|
|
|
|
if (customResolve_) {
|
|
processedHostname = customResolve_(host);
|
|
}
|
|
|
|
std::string err;
|
|
if (!net::DNSResolve(processedHostname.c_str(), port_str, &resolved_, err, type)) {
|
|
WARN_LOG(Log::IO, "Failed to resolve host '%s': '%s' (%s)", host, err.c_str(), DNSTypeAsString(type));
|
|
// Zero port so that future calls fail.
|
|
port_ = 0;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void FormatAddr(char *addrbuf, size_t bufsize, const addrinfo *info) {
|
|
switch (info->ai_family) {
|
|
case AF_INET:
|
|
case AF_INET6:
|
|
inet_ntop(info->ai_family, &((sockaddr_in *)info->ai_addr)->sin_addr, addrbuf, bufsize);
|
|
break;
|
|
default:
|
|
snprintf(addrbuf, bufsize, "(Unknown AF %d)", info->ai_family);
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) {
|
|
if (port_ <= 0) {
|
|
ERROR_LOG(Log::IO, "Bad port");
|
|
return false;
|
|
}
|
|
sock_ = -1;
|
|
|
|
for (int tries = maxTries; tries > 0; --tries) {
|
|
std::vector<uintptr_t> sockets;
|
|
fd_set fds;
|
|
int maxfd = 1;
|
|
FD_ZERO(&fds);
|
|
for (addrinfo *possible = resolved_; possible != nullptr; possible = possible->ai_next) {
|
|
if (possible->ai_family != AF_INET && possible->ai_family != AF_INET6)
|
|
continue;
|
|
|
|
int sock = socket(possible->ai_family, SOCK_STREAM, IPPROTO_TCP);
|
|
if ((intptr_t)sock == -1) {
|
|
ERROR_LOG(Log::IO, "Bad socket");
|
|
continue;
|
|
}
|
|
// Windows sockets aren't limited by socket number, just by count, so checking FD_SETSIZE there is wrong.
|
|
#if !PPSSPP_PLATFORM(WINDOWS)
|
|
if (sock >= FD_SETSIZE) {
|
|
ERROR_LOG(Log::IO, "Socket doesn't fit in FD_SET: %d We probably have a leak.", sock);
|
|
closesocket(sock);
|
|
continue;
|
|
}
|
|
#endif
|
|
fd_util::SetNonBlocking(sock, true);
|
|
|
|
// Start trying to connect (async with timeout.)
|
|
errno = 0;
|
|
if (connect(sock, possible->ai_addr, (int)possible->ai_addrlen) < 0) {
|
|
int errorCode = socket_errno;
|
|
std::string errorString = GetStringErrorMsg(errorCode);
|
|
bool unreachable = errorCode == ENETUNREACH;
|
|
bool inProgress = errorCode == EINPROGRESS || errorCode == EWOULDBLOCK;
|
|
if (!inProgress) {
|
|
char addrStr[128]{};
|
|
FormatAddr(addrStr, sizeof(addrStr), possible);
|
|
if (!unreachable) {
|
|
ERROR_LOG(Log::HTTP, "connect(%d) call to %s failed (%d: %s)", sock, addrStr, errorCode, errorString.c_str());
|
|
} else {
|
|
INFO_LOG(Log::HTTP, "connect(%d): Ignoring unreachable resolved address %s", sock, addrStr);
|
|
}
|
|
closesocket(sock);
|
|
continue;
|
|
}
|
|
}
|
|
sockets.push_back(sock);
|
|
FD_SET(sock, &fds);
|
|
if (maxfd < sock + 1) {
|
|
maxfd = sock + 1;
|
|
}
|
|
}
|
|
|
|
int selectResult = 0;
|
|
long timeoutHalfSeconds = floor(2 * timeout);
|
|
while (timeoutHalfSeconds >= 0 && selectResult == 0) {
|
|
struct timeval tv {};
|
|
tv.tv_sec = 0;
|
|
if (timeoutHalfSeconds > 0) {
|
|
// Wait up to 0.5 seconds between cancel checks.
|
|
tv.tv_usec = 500000;
|
|
} else {
|
|
// Wait the remaining <= 0.5 seconds. Possibly 0, but that's okay.
|
|
tv.tv_usec = (timeout - floor(2 * timeout) / 2) * 1000000.0;
|
|
}
|
|
--timeoutHalfSeconds;
|
|
|
|
selectResult = select(maxfd, nullptr, &fds, nullptr, &tv);
|
|
if (cancelConnect && *cancelConnect) {
|
|
WARN_LOG(Log::HTTP, "connect: cancelled (1): %s:%d", host_.c_str(), port_);
|
|
break;
|
|
}
|
|
}
|
|
if (selectResult > 0) {
|
|
// Something connected. Pick the first one that did (if multiple.)
|
|
for (int sock : sockets) {
|
|
if ((intptr_t)sock_ == -1 && FD_ISSET(sock, &fds)) {
|
|
sock_ = sock;
|
|
} else {
|
|
closesocket(sock);
|
|
}
|
|
}
|
|
|
|
// Great, now we're good to go.
|
|
return true;
|
|
} else {
|
|
// Fail. Close all the sockets.
|
|
for (int sock : sockets) {
|
|
closesocket(sock);
|
|
}
|
|
}
|
|
|
|
if (cancelConnect && *cancelConnect) {
|
|
WARN_LOG(Log::HTTP, "connect: cancelled (2): %s:%d", host_.c_str(), port_);
|
|
break;
|
|
}
|
|
|
|
sleep_ms(1, "connect");
|
|
}
|
|
|
|
// Nothing connected, unfortunately.
|
|
return false;
|
|
}
|
|
|
|
void Connection::Disconnect() {
|
|
if ((intptr_t)sock_ != -1) {
|
|
closesocket(sock_);
|
|
sock_ = -1;
|
|
}
|
|
}
|
|
|
|
} // net
|