diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index c9d71c6ab80a..9831701e17a9 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -71,13 +71,34 @@ static void RegisterServer(int port) { http::Client http; Buffer theVoid; - if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT)) { + char resource4[1024] = {}; + if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT, net::DNSType::IPV4)) { if (http.Connect(2, 20.0, &scanCancelled)) { - char resource[1024] = {}; std::string ip = fd_util::GetLocalIP(http.sock()); - snprintf(resource, sizeof(resource) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port); + snprintf(resource4, sizeof(resource4) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port); + + http.GET(resource4, &theVoid); + theVoid.Skip(theVoid.size()); + http.Disconnect(); + } + } + + if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT, net::DNSType::IPV6)) { + // We register both IPv4 and IPv6 in case the other client is using a different one. + if (resource4[0] != 0 && http.Connect()) { + http.GET(resource4, &theVoid); + theVoid.Skip(theVoid.size()); + http.Disconnect(); + } + + // Currently, we're not using keepalive, so gotta reconnect... + if (http.Connect()) { + char resource6[1024] = {}; + std::string ip = fd_util::GetLocalIP(http.sock()); + snprintf(resource6, sizeof(resource6) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port); - http.GET(resource, &theVoid); + http.GET(resource6, &theVoid); + theVoid.Skip(theVoid.size()); http.Disconnect(); } } diff --git a/ext/native/file/fd_util.cpp b/ext/native/file/fd_util.cpp index 8663d4476060..b61703c26310 100644 --- a/ext/native/file/fd_util.cpp +++ b/ext/native/file/fd_util.cpp @@ -133,11 +133,22 @@ void SetNonBlocking(int sock, bool non_blocking) { } std::string GetLocalIP(int sock) { - struct sockaddr_in server_addr; + union { + struct sockaddr sa; + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + } server_addr; memset(&server_addr, 0, sizeof(server_addr)); socklen_t len = sizeof(server_addr); if (getsockname(sock, (struct sockaddr *)&server_addr, &len) == 0) { - char *result = inet_ntoa(*(in_addr *)&server_addr.sin_addr); + char temp[64]; + void *addr; + if (server_addr.sa.sa_family == AF_INET6) { + addr = &server_addr.ipv6.sin6_addr; + } else { + addr = &server_addr.ipv4.sin_addr; + } + const char *result = inet_ntop(server_addr.sa.sa_family, addr, temp, sizeof(temp)); if (result) { return result; } diff --git a/ext/native/net/http_client.cpp b/ext/native/net/http_client.cpp index becb4e4473a1..3cb6967326dd 100644 --- a/ext/native/net/http_client.cpp +++ b/ext/native/net/http_client.cpp @@ -46,7 +46,7 @@ inline unsigned short myhtons(unsigned short x) { return (x >> 8) | (x << 8); } -bool Connection::Resolve(const char *host, int port) { +bool Connection::Resolve(const char *host, int port, DNSType type) { if ((intptr_t)sock_ != -1) { ELOG("Resolve: Already have a socket"); return false; @@ -63,7 +63,7 @@ bool Connection::Resolve(const char *host, int port) { snprintf(port_str, sizeof(port_str), "%d", port); std::string err; - if (!net::DNSResolve(host, port_str, &resolved_, err)) { + if (!net::DNSResolve(host, port_str, &resolved_, err, type)) { ELOG("Failed to resolve host %s: %s", host, err.c_str()); // So that future calls fail. port_ = 0; @@ -87,10 +87,10 @@ bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) { FD_ZERO(&fds); for (addrinfo *possible = resolved_; possible != nullptr; possible = possible->ai_next) { // TODO: Could support ipv6 without huge difficulty... - if (possible->ai_family != AF_INET) + if (possible->ai_family != AF_INET && possible->ai_family != AF_INET6) continue; - int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + int sock = socket(possible->ai_family, SOCK_STREAM, IPPROTO_TCP); if ((intptr_t)sock == -1) { ELOG("Bad socket"); continue; diff --git a/ext/native/net/http_client.h b/ext/native/net/http_client.h index 0f63c4bb9c21..ac3d26ed974f 100644 --- a/ext/native/net/http_client.h +++ b/ext/native/net/http_client.h @@ -6,6 +6,7 @@ #include "base/basictypes.h" #include "base/buffer.h" +#include "net/resolve.h" #ifdef _WIN32 #ifndef NOMINMAX @@ -28,7 +29,7 @@ class Connection { virtual ~Connection(); // Inits the sockaddr_in. - bool Resolve(const char *host, int port); + bool Resolve(const char *host, int port, DNSType type = DNSType::ANY); bool Connect(int maxTries = 2, double timeout = 20.0f, bool *cancelConnect = nullptr); void Disconnect(); diff --git a/ext/native/net/http_server.cpp b/ext/native/net/http_server.cpp index 9357d2fe6967..ddd893e5ac3a 100644 --- a/ext/native/net/http_server.cpp +++ b/ext/native/net/http_server.cpp @@ -130,9 +130,21 @@ void Server::SetFallbackHandler(UrlHandlerFunc handler) { fallback_ = handler; } -bool Server::Listen(int port) { +bool Server::Listen(int port, net::DNSType type) { + bool success = false; + if (type == net::DNSType::ANY || type == net::DNSType::IPV6) { + success = Listen6(port, type == net::DNSType::IPV6); + } + if (!success && (type == net::DNSType::ANY || type == net::DNSType::IPV4)) { + success = Listen4(port); + } + return success; +} + +bool Server::Listen4(int port) { listener_ = socket(AF_INET, SOCK_STREAM, 0); - CHECK_GE(listener_, 0); + if (listener_ < 0) + return false; struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); @@ -145,6 +157,7 @@ bool Server::Listen(int port) { setsockopt(listener_, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); if (bind(listener_, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { + closesocket(listener_); ELOG("Failed to bind to port %i. Bailing.", port); return false; } @@ -152,7 +165,10 @@ bool Server::Listen(int port) { fd_util::SetNonBlocking(listener_, true); // 1024 is the max number of queued requests. - CHECK_GE(listen(listener_, 1024), 0); + if (listen(listener_, 1024) < 0) { + closesocket(listener_); + return false; + } socklen_t len = sizeof(server_addr); if (getsockname(listener_, (struct sockaddr *)&server_addr, &len) == 0) { @@ -165,6 +181,50 @@ bool Server::Listen(int port) { return true; } +bool Server::Listen6(int port, bool ipv6_only) { + listener_ = socket(AF_INET6, SOCK_STREAM, 0); + if (listener_ < 0) + return false; + + struct sockaddr_in6 server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin6_family = AF_INET6; + server_addr.sin6_addr = in6addr_any; + server_addr.sin6_port = htons(port); + + int opt = 1; + // Enable re-binding to avoid the pain when restarting the server quickly. + setsockopt(listener_, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); + + // Enable listening on IPv6 and IPv4? + opt = ipv6_only ? 1 : 0; + setsockopt(listener_, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&opt, sizeof(opt)); + + if (bind(listener_, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { + closesocket(listener_); + ELOG("Failed to bind to port %i. Bailing.", port); + return false; + } + + fd_util::SetNonBlocking(listener_, true); + + // 1024 is the max number of queued requests. + if (listen(listener_, 1024) < 0) { + closesocket(listener_); + return false; + } + + socklen_t len = sizeof(server_addr); + if (getsockname(listener_, (struct sockaddr *)&server_addr, &len) == 0) { + port = ntohs(server_addr.sin6_port); + } + + ILOG("HTTP server started on port %i", port); + port_ = port; + + return true; +} + bool Server::RunSlice(double timeout) { if (listener_ < 0 || port_ == 0) { return false; @@ -177,9 +237,13 @@ bool Server::RunSlice(double timeout) { return false; } - sockaddr client_addr; + union { + struct sockaddr sa; + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + } client_addr; socklen_t client_addr_size = sizeof(client_addr); - int conn_fd = accept(listener_, &client_addr, &client_addr_size); + int conn_fd = accept(listener_, &client_addr.sa, &client_addr_size); if (conn_fd >= 0) { executor_->Run(std::bind(&Server::HandleConnection, this, conn_fd)); return true; diff --git a/ext/native/net/http_server.h b/ext/native/net/http_server.h index 974b45ee6dc8..5220bda7622a 100644 --- a/ext/native/net/http_server.h +++ b/ext/native/net/http_server.h @@ -6,6 +6,7 @@ #include "base/buffer.h" #include "net/http_headers.h" +#include "net/resolve.h" #include "thread/executor.h" namespace net { @@ -73,7 +74,7 @@ class Server { // May run for (significantly) longer than timeout, but won't wait longer than that // for a new connection to handle. bool RunSlice(double timeout); - bool Listen(int port); + bool Listen(int port, net::DNSType type = net::DNSType::ANY); void Stop(); void RegisterHandler(const char *url_path, UrlHandlerFunc handler); @@ -85,10 +86,13 @@ class Server { virtual void HandleRequest(const Request &request); int Port() { - return port_; + return port_; } private: + bool Listen6(int port, bool ipv6_only); + bool Listen4(int port); + void HandleConnection(int conn_fd); // Things like default 404, etc. diff --git a/ext/native/net/resolve.cpp b/ext/native/net/resolve.cpp index 1876427b146a..ec7d17f1d1e1 100644 --- a/ext/native/net/resolve.cpp +++ b/ext/native/net/resolve.cpp @@ -77,7 +77,7 @@ char *DNSResolve(const char *host) return ip; } -bool DNSResolve(const std::string &host, const std::string &service, addrinfo **res, std::string &error) +bool DNSResolve(const std::string &host, const std::string &service, addrinfo **res, std::string &error, DNSType type) { addrinfo hints = {0}; // TODO: Might be uses to lookup other values. @@ -89,7 +89,11 @@ bool DNSResolve(const std::string &host, const std::string &service, addrinfo ** // http://stackoverflow.com/questions/1408030/what-is-the-purpose-of-the-ai-v4mapped-flag-in-getaddrinfo hints.ai_flags = /*AI_V4MAPPED |*/ AI_ADDRCONFIG; #endif - hints.ai_protocol = IPPROTO_TCP; + hints.ai_protocol = 0; + if (type == DNSType::IPV4) + hints.ai_family = AF_INET; + else if (type == DNSType::IPV6) + hints.ai_family = AF_INET6; const char *servicep = service.length() == 0 ? NULL : service.c_str(); diff --git a/ext/native/net/resolve.h b/ext/native/net/resolve.h index 46a8b3e598cf..58ce0984d93d 100644 --- a/ext/native/net/resolve.h +++ b/ext/native/net/resolve.h @@ -14,7 +14,13 @@ void Shutdown(); char *DNSResolveTry(const char *host, const char **err); char *DNSResolve(const char *host); -bool DNSResolve(const std::string &host, const std::string &service, addrinfo **res, std::string &error); +enum class DNSType { + ANY = 0, + IPV4 = 1, + IPV6 = 2, +}; + +bool DNSResolve(const std::string &host, const std::string &service, addrinfo **res, std::string &error, DNSType type = DNSType::ANY); void DNSResolveFree(addrinfo *res); int inet_pton(int af, const char* src, void* dst);