From 3dc133a74a947add5d6d1ab638e45376dc4e9c73 Mon Sep 17 00:00:00 2001 From: Brendon <43822289+12brendon34@users.noreply.github.com> Date: Sat, 7 Mar 2026 01:56:11 -0800 Subject: [PATCH 1/4] Add IPv6/Dual Stack Also Fix SetupHeadlessServerConsole() to use an existing console if possible. --- .../Windows64/Network/WinsockNetLayer.cpp | 54 ++++++++++++++----- .../Windows64/Network/WinsockNetLayer.h | 3 +- .../Windows64/Windows64_Minecraft.cpp | 16 +++--- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp index c76bc2fe8..b1cd8cbbb 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp @@ -153,6 +153,20 @@ void WinsockNetLayer::Shutdown() } } +bool WinsockNetLayer::IsNumericAddress(const char* addr) +{ + if (addr == NULL) + return false; + + if (strchr(addr, ':') != NULL) + return true;// IPv6 + + if (inet_addr(addr) != INADDR_NONE) + return true;// IPv4 + + return false;// hostname +} + bool WinsockNetLayer::HostGame(int port, const char* bindIp) { if (!s_initialized && !Initialize()) return false; @@ -171,18 +185,23 @@ bool WinsockNetLayer::HostGame(int port, const char* bindIp) s_smallIdToSocket[i] = INVALID_SOCKET; LeaveCriticalSection(&s_smallIdToSocketLock); - struct addrinfo hints = {}; - struct addrinfo* result = NULL; - - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - hints.ai_flags = (bindIp == NULL || bindIp[0] == 0) ? AI_PASSIVE : 0; - char portStr[16]; sprintf_s(portStr, "%d", port); - const char* resolvedBindIp = (bindIp != NULL && bindIp[0] != 0) ? bindIp : NULL; + + struct addrinfo hints = {}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + if (resolvedBindIp == NULL) + hints.ai_flags = AI_PASSIVE; + else if (IsNumericAddress(resolvedBindIp)) + hints.ai_flags = AI_NUMERICHOST; + else + hints.ai_flags = 0; + + struct addrinfo* result = NULL; int iResult = getaddrinfo(resolvedBindIp, portStr, &hints, &result); if (iResult != 0) { @@ -201,8 +220,8 @@ bool WinsockNetLayer::HostGame(int port, const char* bindIp) return false; } - int opt = 1; - setsockopt(s_listenSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt)); + DWORD ipv6only = 0; + setsockopt(s_listenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&ipv6only, sizeof(ipv6only)); iResult = ::bind(s_listenSocket, result->ai_addr, (int)result->ai_addrlen); freeaddrinfo(result); @@ -226,7 +245,7 @@ bool WinsockNetLayer::HostGame(int port, const char* bindIp) s_active = true; s_connected = true; - s_acceptThread = CreateThread(NULL, 0, AcceptThreadProc, NULL, 0, NULL); + s_acceptThread = CreateThread(NULL, 0, AcceptThreadProc, (LPVOID)s_listenSocket, 0, NULL); app.DebugPrintf("Win64 LAN: Hosting on %s:%d\n", resolvedBindIp != NULL ? resolvedBindIp : "*", @@ -252,7 +271,12 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port) struct addrinfo hints = {}; struct addrinfo* result = NULL; - hints.ai_family = AF_INET; + if (IsNumericAddress(ip)) + hints.ai_flags = AI_NUMERICHOST; + else + hints.ai_flags = 0; + + hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; @@ -468,9 +492,11 @@ void WinsockNetLayer::HandleDataReceived(BYTE fromSmallId, BYTE toSmallId, unsig DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) { + SOCKET listenSock = (SOCKET)(uintptr_t)param; + while (s_active) { - SOCKET clientSocket = accept(s_listenSocket, NULL, NULL); + SOCKET clientSocket = accept(listenSock, NULL, NULL); if (clientSocket == INVALID_SOCKET) { if (s_active) diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h index f30240d32..bdac3e01a 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h @@ -65,7 +65,8 @@ class WinsockNetLayer public: static bool Initialize(); static void Shutdown(); - + + static bool IsNumericAddress(const char* addr); static bool HostGame(int port, const char* bindIp = NULL); static bool JoinGame(const char* ip, int port); diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index 35afc6947..ba19851b6 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -288,15 +288,19 @@ static BOOL WINAPI HeadlessServerCtrlHandler(DWORD ctrlType) static void SetupHeadlessServerConsole() { - if (AllocConsole()) + //use existing console if possible + if (!AttachConsole(ATTACH_PARENT_PROCESS)) { - FILE* stream = NULL; - freopen_s(&stream, "CONIN$", "r", stdin); - freopen_s(&stream, "CONOUT$", "w", stdout); - freopen_s(&stream, "CONOUT$", "w", stderr); - SetConsoleTitleA("Minecraft Server"); + //https://giphy.com/gifs/17tvv5lv6KqX51mbfA + AllocConsole(); } + FILE* stream = NULL; + freopen_s(&stream, "CONIN$", "r", stdin); + freopen_s(&stream, "CONOUT$", "w", stdout); + freopen_s(&stream, "CONOUT$", "w", stderr); + SetConsoleTitleA("Minecraft Server"); + SetConsoleCtrlHandler(HeadlessServerCtrlHandler, TRUE); } From 203abca1e93d03e1706ae07d7611b7b74297f619 Mon Sep 17 00:00:00 2001 From: Brendon <43822289+12brendon34@users.noreply.github.com> Date: Sun, 8 Mar 2026 04:11:47 -0700 Subject: [PATCH 2/4] Fix ipv6 in new servers.db I do not like the new servers.db personally --- .../Common/Network/PlatformNetworkManagerStub.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp index 44ca3c2f9..a3c536065 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp @@ -821,7 +821,11 @@ void CPlatformNetworkManagerStub::SearchForGames() info->data.isJoinable = true; strncpy_s(info->data.hostIP, sizeof(info->data.hostIP), ipBuf, _TRUNCATE); info->data.hostPort = port; - info->sessionId = (SessionID)(static_cast(inet_addr(ipBuf)) | (static_cast(port) << 32)); + + //Hash IP so IPv4 and 6 both are a valid sess id + uint64_t ipHash = (uint64_t)std::hash{}(ipBuf); + info->sessionId = (SessionID)(ipHash ^ (static_cast(port << 32))); + friendsSessions[0].push_back(info); } } From d8537f6fa61099ebba2b712fa7f00de2ed33cafc Mon Sep 17 00:00:00 2001 From: Brendon <43822289+12brendon34@users.noreply.github.com> Date: Sun, 15 Mar 2026 19:58:41 -0700 Subject: [PATCH 3/4] Fix ipv6 for new dedicated server Change bind IP to ipv6 unspecified address "::" from "0.0.0.0" use sockaddr_storage instead of sockaddr_in changed TryGetNumericRemoteIp to use getnameinfo instead of inet_ntop --- .../Windows64/Network/WinsockNetLayer.cpp | 38 ++++++++++++------- Minecraft.Server/ServerProperties.cpp | 4 +- Minecraft.Server/Windows64/ServerMain.cpp | 12 ++++-- docker/dedicated-server/entrypoint.sh | 10 ++++- 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp index c7de39f95..ca5ad6fc4 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp @@ -248,16 +248,19 @@ bool WinsockNetLayer::HostGame(int port, const char* bindIp) const char* resolvedBindIp = (bindIp != NULL && bindIp[0] != 0) ? bindIp : NULL; struct addrinfo hints = {}; - hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; if (resolvedBindIp == NULL) + { + hints.ai_family = AF_INET6; hints.ai_flags = AI_PASSIVE; - else if (IsNumericAddress(resolvedBindIp)) - hints.ai_flags = AI_NUMERICHOST; + } else - hints.ai_flags = 0; + { + hints.ai_family = AF_UNSPEC; + hints.ai_flags = IsNumericAddress(resolvedBindIp) ? AI_NUMERICHOST : 0; + } struct addrinfo* result = NULL; int iResult = getaddrinfo(resolvedBindIp, portStr, &hints, &result); @@ -278,8 +281,14 @@ bool WinsockNetLayer::HostGame(int port, const char* bindIp) return false; } - DWORD ipv6only = 0; - setsockopt(s_listenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&ipv6only, sizeof(ipv6only)); + if (result->ai_family == AF_INET6) + { + DWORD ipv6only = 0; + if (setsockopt(s_listenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&ipv6only, sizeof(ipv6only)) == SOCKET_ERROR) + { + app.DebugPrintf("setsockopt() failed: %d\n", WSAGetLastError()); + } + } iResult = ::bind(s_listenSocket, result->ai_addr, static_cast(result->ai_addrlen)); freeaddrinfo(result); @@ -551,7 +560,7 @@ static bool RecvExact(SOCKET sock, BYTE* buf, int len) } #if defined(MINECRAFT_SERVER_BUILD) -static bool TryGetNumericRemoteIp(const sockaddr_in &remoteAddress, std::string *outIp) +static bool TryGetNumericRemoteIp(const sockaddr_storage& remoteAddress, int remoteAddressLength, std::string* outIp) { if (outIp == nullptr) { @@ -559,14 +568,17 @@ static bool TryGetNumericRemoteIp(const sockaddr_in &remoteAddress, std::string } outIp->clear(); - char ipBuffer[64] = {}; - const char *ip = inet_ntop(AF_INET, (void *)&remoteAddress.sin_addr, ipBuffer, sizeof(ipBuffer)); - if (ip == nullptr || ip[0] == 0) + char host[NI_MAXHOST] = {}; + if (getnameinfo((const sockaddr*)&remoteAddress, remoteAddressLength, host, sizeof(host), nullptr, 0, NI_NUMERICHOST) != 0) + { + return false; + } + if (host[0] == 0) { return false; } - *outIp = ip; + *outIp = host; return true; } #endif @@ -605,7 +617,7 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) { while (s_active) { - sockaddr_in remoteAddress; + sockaddr_storage remoteAddress; ZeroMemory(&remoteAddress, sizeof(remoteAddress)); int remoteAddressLength = sizeof(remoteAddress); SOCKET clientSocket = accept(s_listenSocket, (sockaddr*)&remoteAddress, &remoteAddressLength); @@ -621,7 +633,7 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) #if defined(MINECRAFT_SERVER_BUILD) std::string remoteIp; - const bool hasRemoteIp = TryGetNumericRemoteIp(remoteAddress, &remoteIp); + const bool hasRemoteIp = TryGetNumericRemoteIp(remoteAddress, remoteAddressLength, &remoteIp); const char *remoteIpForLog = hasRemoteIp ? remoteIp.c_str() : "unknown"; if (g_Win64DedicatedServer) { diff --git a/Minecraft.Server/ServerProperties.cpp b/Minecraft.Server/ServerProperties.cpp index d6ba64e7e..561255fda 100644 --- a/Minecraft.Server/ServerProperties.cpp +++ b/Minecraft.Server/ServerProperties.cpp @@ -71,7 +71,7 @@ static const ServerPropertyDefault kServerPropertyDefaults[] = { "motd", "A Minecraft Server" }, { "natural-regeneration", "true" }, { "pvp", "true" }, - { "server-ip", "0.0.0.0" }, + { "server-ip", "::" }, { "server-name", "DedicatedServer" }, { "server-port", "25565" }, { "white-list", "false" }, @@ -815,7 +815,7 @@ ServerPropertiesConfig LoadServerPropertiesConfig() config.worldSaveId = worldSaveId; config.serverPort = ReadNormalizedIntProperty(&merged, "server-port", kDefaultServerPort, 1, 65535, &shouldWrite); - config.serverIp = ReadNormalizedStringProperty(&merged, "server-ip", "0.0.0.0", 255, &shouldWrite); + config.serverIp = ReadNormalizedStringProperty(&merged, "server-ip", "::", 255, &shouldWrite); config.lanAdvertise = ReadNormalizedBoolProperty(&merged, kLanAdvertisePropertyKey, false, &shouldWrite); config.whiteListEnabled = ReadNormalizedBoolProperty(&merged, "white-list", false, &shouldWrite); config.serverName = ReadNormalizedStringProperty(&merged, "server-name", "DedicatedServer", 16, &shouldWrite); diff --git a/Minecraft.Server/Windows64/ServerMain.cpp b/Minecraft.Server/Windows64/ServerMain.cpp index a8d5fc66b..f9e397228 100644 --- a/Minecraft.Server/Windows64/ServerMain.cpp +++ b/Minecraft.Server/Windows64/ServerMain.cpp @@ -297,7 +297,7 @@ static void ApplyServerPropertiesToDedicatedConfig(const ServerPropertiesConfig strncpy_s( config->bindIP, sizeof(config->bindIP), - serverProperties.serverIp.empty() ? "0.0.0.0" : serverProperties.serverIp.c_str(), + serverProperties.serverIp.empty() ? "::" : serverProperties.serverIp.c_str(), _TRUNCATE); strncpy_s( config->name, @@ -351,7 +351,7 @@ int main(int argc, char **argv) { DedicatedServerConfig config; config.port = WIN64_NET_DEFAULT_PORT; - strncpy_s(config.bindIP, sizeof(config.bindIP), "0.0.0.0", _TRUNCATE); + strncpy_s(config.bindIP, sizeof(config.bindIP), "::", _TRUNCATE); strncpy_s(config.name, sizeof(config.name), "DedicatedServer", _TRUNCATE); config.maxPlayers = MINECRAFT_NET_MAX_PLAYERS; config.worldSize = e_worldSize_Classic; @@ -616,7 +616,13 @@ int main(int argc, char **argv) } LogStartupStep("server startup complete"); - LogInfof("startup", "Dedicated server listening on %s:%d", g_Win64MultiplayerIP, g_Win64MultiplayerPort); + + const std::string displayIp = strchr(g_Win64MultiplayerIP, ':') + ? "[" + std::string(g_Win64MultiplayerIP) + "]" //ipv6 address will always have a : + : g_Win64MultiplayerIP; + + LogInfof("startup", "Dedicated server listening on %s:%d", displayIp.c_str(), g_Win64MultiplayerPort); + if (worldBootstrap.status == eWorldBootstrap_CreatedNew && !IsShutdownRequested() && !app.m_bShutdown) { // Windows64 suppresses saveToDisc right after new world creation diff --git a/docker/dedicated-server/entrypoint.sh b/docker/dedicated-server/entrypoint.sh index a8f8907a7..c29b62bbf 100644 --- a/docker/dedicated-server/entrypoint.sh +++ b/docker/dedicated-server/entrypoint.sh @@ -5,7 +5,7 @@ SERVER_DIR="/srv/mc" SERVER_EXE="Minecraft.Server.exe" # ip & port are fixed since they run inside the container SERVER_PORT="25565" -SERVER_BIND_IP="0.0.0.0" +SERVER_BIND_IP="::" PERSIST_DIR="/srv/persist" WINE_CMD="" @@ -87,5 +87,11 @@ args=( -bind "${SERVER_BIND_IP}" ) -echo "[info] Starting ${SERVER_EXE} on ${SERVER_BIND_IP}:${SERVER_PORT}" +if [[ "$SERVER_BIND_IP" == *:* ]]; then + display_ip="[${SERVER_BIND_IP}]" +else + display_ip="$SERVER_BIND_IP" +fi + +echo "[info] Starting ${SERVER_EXE} on ${display_ip}:${SERVER_PORT}" exec "${WINE_CMD}" "${SERVER_EXE}" "${args[@]}" From 0b9f6763992cf3d2e89001b6ab226f81221bf908 Mon Sep 17 00:00:00 2001 From: Brendon <43822289+12brendon34@users.noreply.github.com> Date: Sun, 15 Mar 2026 20:16:41 -0700 Subject: [PATCH 4/4] Update IsNumericAddress Better then heuristic check --- .../Windows64/Network/WinsockNetLayer.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp index ca5ad6fc4..c71030c0e 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp @@ -213,16 +213,17 @@ void WinsockNetLayer::Shutdown() bool WinsockNetLayer::IsNumericAddress(const char* addr) { - if (addr == NULL) + if (addr == nullptr) return false; - if (strchr(addr, ':') != NULL) - return true;// IPv6 + in6_addr buf{}; + if (inet_pton(AF_INET6, addr, &buf) == 1) + return true; - if (inet_addr(addr) != INADDR_NONE) - return true;// IPv4 + if (inet_pton(AF_INET, addr, &buf) == 1) + return true; - return false;// hostname + return false; } bool WinsockNetLayer::HostGame(int port, const char* bindIp)