1
0

Merge branch 'master' into Inventory

This commit is contained in:
Howaner 2015-02-06 21:52:14 +01:00
commit 2c7925f0ad
54 changed files with 1409 additions and 2121 deletions

View File

@ -0,0 +1,99 @@
Libevent is available for use under the following license, commonly known
as the 3-clause (or "modified") BSD license:
==============================
Copyright (c) 2000-2007 Niels Provos <provos@citi.umich.edu>
Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
==============================
Portions of Libevent are based on works by others, also made available by
them under the three-clause BSD license above. The copyright notices are
available in the corresponding source files; the license is as above. Here's
a list:
log.c:
Copyright (c) 2000 Dug Song <dugsong@monkey.org>
Copyright (c) 1993 The Regents of the University of California.
strlcpy.c:
Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
win32select.c:
Copyright (c) 2003 Michael A. Davis <mike@datanerds.net>
evport.c:
Copyright (c) 2007 Sun Microsystems
ht-internal.h:
Copyright (c) 2002 Christopher Clark
minheap-internal.h:
Copyright (c) 2006 Maxim Yegorushkin <maxim.yegorushkin@gmail.com>
==============================
The arc4module is available under the following, sometimes called the
"OpenBSD" license:
Copyright (c) 1996, David Mazieres <dm@uun.org>
Copyright (c) 2008, Damien Miller <djm@openbsd.org>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
==============================
The Windows timer code is based on code from libutp, which is
distributed under this license, sometimes called the "MIT" license.
Copyright (c) 2010 BitTorrent, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1164,7 +1164,7 @@ function HandleSched(a_Split, a_Player)
end
-- Schedule a broadcast of the final message and a note to the originating player
-- Note that we CANNOT us the a_Player in the callback - what if the player disconnected?
-- Note that we CANNOT use the a_Player in the callback - what if the player disconnected?
-- Therefore we store the player's EntityID
local PlayerID = a_Player:GetUniqueID()
World:ScheduleTask(220,
@ -1607,10 +1607,13 @@ end
function HandleConsoleSchedule(a_Split)
LOG("Scheduling a task for 2 seconds in the future")
local prev = os.clock()
LOG("Scheduling a task for 2 seconds in the future (current os.clock is " .. prev .. ")")
cRoot:Get():GetDefaultWorld():ScheduleTask(40,
function ()
LOG("Scheduled function is called.")
local current = os.clock()
local diff = current - prev
LOG("Scheduled function is called. Current os.clock is " .. current .. ", difference is " .. diff .. ")")
end
)
return true, "Task scheduled"

View File

@ -1,3 +1,4 @@
@echo off
echo This script generates the certificate and private key for the https webadmin
echo Note that the generated certificate is self-signed, and therefore not trusted by browsers
echo Note that this script requires openssl to be installed and in PATH

View File

@ -22,6 +22,18 @@
#define ALIGN_8
#define ALIGN_16
#define FORMATSTRING(formatIndex, va_argsIndex)
// MSVC has its own custom version of zu format
#define SIZE_T_FMT "%Iu"
#define SIZE_T_FMT_PRECISION(x) "%" #x "Iu"
#define SIZE_T_FMT_HEX "%Ix"
#define NORETURN __declspec(noreturn)
// Use non-standard defines in <cmath>
#define _USE_MATH_DEFINES
#elif defined(__GNUC__)
// TODO: Can GCC explicitly mark classes as abstract (no instances can be created)?
@ -38,6 +50,29 @@
// Some portability macros :)
#define stricmp strcasecmp
#define FORMATSTRING(formatIndex, va_argsIndex) __attribute__((format (printf, formatIndex, va_argsIndex)))
#if defined(_WIN32)
// We're compiling on MinGW, which uses an old MSVCRT library that has no support for size_t printfing.
// We need direct size formats:
#if defined(_WIN64)
#define SIZE_T_FMT "%I64u"
#define SIZE_T_FMT_PRECISION(x) "%" #x "I64u"
#define SIZE_T_FMT_HEX "%I64x"
#else
#define SIZE_T_FMT "%u"
#define SIZE_T_FMT_PRECISION(x) "%" #x "u"
#define SIZE_T_FMT_HEX "%x"
#endif
#else
// We're compiling on Linux, so we can use libc's size_t printf format:
#define SIZE_T_FMT "%zu"
#define SIZE_T_FMT_PRECISION(x) "%" #x "zu"
#define SIZE_T_FMT_HEX "%zx"
#endif
#define NORETURN __attribute((__noreturn__))
#else
#error "You are using an unsupported compiler, you might need to #define some stuff here for your compiler"
@ -74,6 +109,8 @@ typedef unsigned long long UInt64;
typedef unsigned int UInt32;
typedef unsigned short UInt16;
typedef unsigned char Byte;
@ -94,7 +131,7 @@ typedef unsigned short UInt16;
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x501 // We want to target WinXP and higher
#define _WIN32_WINNT 0x502 // We want to target WinXP SP2 and higher
#include <Windows.h>
#include <winsock2.h>
@ -175,7 +212,8 @@ typedef unsigned short UInt16;
#include "StringUtils.h"
#include "OSSupport/CriticalSection.h"
#include "OSSupport/File.h"
#include "MCLogger.h"
#include "OSSupport/Event.h"
#include "Logger.h"

View File

@ -80,14 +80,14 @@ bool cRCONPacketizer::SendPacket(int a_PacketType, const AString & a_PacketPaylo
size_t Length = Packet.size();
if (!m_Socket.Send((const char *)&Length, 4))
{
fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.",
fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.\n",
cSocket::GetLastError(), cSocket::GetLastErrorString().c_str()
);
return false;
}
if (!m_Socket.Send(Packet.data(), Packet.size()))
{
fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.",
fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.\n",
cSocket::GetLastError(), cSocket::GetLastErrorString().c_str()
);
return false;
@ -110,12 +110,12 @@ bool cRCONPacketizer::ReceiveResponse(void)
int NumReceived = m_Socket.Receive(buf, sizeof(buf), 0);
if (NumReceived == 0)
{
fprintf(stderr, "The remote end closed the connection. Aborting.");
fprintf(stderr, "The remote end closed the connection. Aborting.\n");
return false;
}
if (NumReceived < 0)
{
fprintf(stderr, "Network error while receiving response: %d, %d (%s). Aborting.",
fprintf(stderr, "Network error while receiving response: %d, %d (%s). Aborting.\n",
NumReceived, cSocket::GetLastError(), cSocket::GetLastErrorString().c_str()
);
return false;
@ -156,13 +156,13 @@ bool cRCONPacketizer::ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength)
{
if ((RequestID == -1) && (m_RequestID == 0))
{
fprintf(stderr, "Login failed. Aborting.");
fprintf(stderr, "Login failed. Aborting.\n");
IsValid = false;
// Continue, so that the payload is printed before the program aborts.
}
else
{
fprintf(stderr, "The server returned an invalid request ID, got %d, exp. %d. Aborting.", RequestID, m_RequestID);
fprintf(stderr, "The server returned an invalid request ID, got %d, exp. %d. Aborting.\n", RequestID, m_RequestID);
return false;
}
}
@ -172,7 +172,7 @@ bool cRCONPacketizer::ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength)
VERIFY(a_Buffer.ReadLEInt(PacketType));
if (PacketType != ptCommand)
{
fprintf(stderr, "The server returned an unknown packet type: %d. Aborting.", PacketType);
fprintf(stderr, "The server returned an unknown packet type: %d. Aborting.\n", PacketType);
IsValid = false;
// Continue, so that the payload is printed before the program aborts.
}
@ -200,8 +200,8 @@ bool cRCONPacketizer::ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength)
int RealMain(int argc, char * argv[])
{
new cMCLogger; // Create a new logger
cLogger::InitiateMultithreading();
// Parse the cmdline params for server IP, port, password and the commands to send:
AString ServerAddress, Password;
int ServerPort = -1;
@ -301,6 +301,7 @@ int RealMain(int argc, char * argv[])
}
}
// Send each command:
for (AStringVector::const_iterator itr = Commands.begin(), end = Commands.end(); itr != end; ++itr)
{
if (g_IsVerbose)

View File

@ -1,7 +1,9 @@

Microsoft Visual Studio Solution File, Format Version 10.00
# Visual C++ Express 2008
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RCONClient", "RCONClient.vcproj", "{1A48B032-07D0-4DDD-8362-66C0FC7F7849}"
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Express 2013 for Windows Desktop
VisualStudioVersion = 12.0.31101.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RCONClient", "RCONClient.vcxproj", "{1A48B032-07D0-4DDD-8362-66C0FC7F7849}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

@ -1 +1 @@
Subproject commit 0b49ae34594533daa82c06a506078de9e336a013
Subproject commit 62eaa889cc1996a7c58a389bf2dfa5d8ce784bdb

@ -1 +1 @@
Subproject commit d6a15321ae51762098e49a976d26efa2493c94f6
Subproject commit 38f47a8546b55e2b593bba27f03070e1e82d3c84

View File

@ -346,20 +346,6 @@ protected:
/** Number of arguments currently pushed (for the Push / Call chain) */
int m_NumCurrentFunctionArgs;
/** Variadic template terminator: Counting zero args returns zero. */
int CountArgs(void)
{
return 0;
}
/** Variadic template: Counting args means add one to the count of the rest. */
template <typename T, typename... Args>
int CountArgs(T, Args... args)
{
return 1 + CountArgs(args...);
}
/** Variadic template terminator: If there's nothing more to push / pop, just call the function.
Note that there are no return values either, because those are prefixed by a cRet value, so the arg list is never empty. */
bool PushCallPop(void)
@ -380,7 +366,7 @@ protected:
bool PushCallPop(cLuaState::cRet, Args &&... args)
{
// Calculate the number of return values (number of args left):
int NumReturns = CountArgs(args...);
int NumReturns = sizeof...(args);
// Call the function:
if (!CallFunction(NumReturns))

View File

@ -167,6 +167,7 @@ local function ProcessFile(a_FileName)
os.exit(1)
end
local all = f:read("*all")
f:close()
-- Check that the last line is empty - otherwise processing won't work properly:
local lastChar = string.byte(all, string.len(all))

View File

@ -1570,12 +1570,18 @@ void cChunk::FastSetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockT
if (
a_SendToClients && // ... we are told to do so AND ...
(
(OldBlockMeta != a_BlockMeta) || // ... the meta value is different OR ...
!( // ... the old and new blocktypes AREN'T liquids (because client doesn't need to distinguish betwixt them):
((OldBlockType == E_BLOCK_STATIONARY_WATER) && (a_BlockType == E_BLOCK_WATER)) || // Replacing stationary water with water
((OldBlockType == E_BLOCK_WATER) && (a_BlockType == E_BLOCK_STATIONARY_WATER)) || // Replacing water with stationary water
((OldBlockType == E_BLOCK_STATIONARY_LAVA) && (a_BlockType == E_BLOCK_LAVA)) || // Replacing stationary water with water
((OldBlockType == E_BLOCK_LAVA) && (a_BlockType == E_BLOCK_STATIONARY_LAVA)) // Replacing water with stationary water
!( // ... the old and new blocktypes AREN'T leaves (because the client doesn't need meta updates)
((OldBlockType == E_BLOCK_LEAVES) && (a_BlockType == E_BLOCK_LEAVES)) ||
((OldBlockType == E_BLOCK_NEW_LEAVES) && (a_BlockType == E_BLOCK_NEW_LEAVES))
) && // ... AND ...
(
(OldBlockMeta != a_BlockMeta) || // ... the meta value is different OR ...
!( // ... the old and new blocktypes AREN'T liquids (because client doesn't need to distinguish betwixt them):
((OldBlockType == E_BLOCK_STATIONARY_WATER) && (a_BlockType == E_BLOCK_WATER)) || // Replacing stationary water with water
((OldBlockType == E_BLOCK_WATER) && (a_BlockType == E_BLOCK_STATIONARY_WATER)) || // Replacing water with stationary water
((OldBlockType == E_BLOCK_STATIONARY_LAVA) && (a_BlockType == E_BLOCK_LAVA)) || // Replacing stationary water with water
((OldBlockType == E_BLOCK_LAVA) && (a_BlockType == E_BLOCK_STATIONARY_LAVA)) // Replacing water with stationary water
)
)
)
)

View File

@ -18,7 +18,6 @@
#include "Item.h"
#include "Mobs/Monster.h"
#include "ChatColor.h"
#include "OSSupport/Socket.h"
#include "Items/ItemHandler.h"
#include "Blocks/BlockHandler.h"
#include "Blocks/BlockSlab.h"
@ -59,16 +58,15 @@ int cClientHandle::s_ClientCount = 0;
////////////////////////////////////////////////////////////////////////////////
// cClientHandle:
cClientHandle::cClientHandle(const cSocket * a_Socket, int a_ViewDistance) :
cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) :
m_CurrentViewDistance(a_ViewDistance),
m_RequestedViewDistance(a_ViewDistance),
m_IPString(a_Socket->GetIPString()),
m_OutgoingData(64 KiB),
m_IPString(a_IPString),
m_Player(nullptr),
m_HasSentDC(false),
m_LastStreamedChunkX(0x7fffffff), // bogus chunk coords to force streaming upon login
m_LastStreamedChunkZ(0x7fffffff),
m_TimeSinceLastPacket(0),
m_TicksSinceLastPacket(0),
m_Ping(1000),
m_PingID(1),
m_BlockDigAnimStage(-1),
@ -138,9 +136,6 @@ cClientHandle::~cClientHandle()
SendDisconnect("Server shut down? Kthnxbai");
}
// Close the socket as soon as it sends all outgoing data:
cRoot::Get()->GetServer()->RemoveClient(this);
delete m_Protocol;
m_Protocol = nullptr;
@ -153,6 +148,10 @@ cClientHandle::~cClientHandle()
void cClientHandle::Destroy(void)
{
{
cCSLock Lock(m_CSOutgoingData);
m_Link.reset();
}
{
cCSLock Lock(m_CSDestroyingState);
if (m_State >= csDestroying)
@ -171,6 +170,10 @@ void cClientHandle::Destroy(void)
RemoveFromAllChunks();
m_Player->GetWorld()->RemoveClientFromChunkSender(this);
}
if (m_Player != nullptr)
{
m_Player->RemoveClientHandle();
}
m_State = csDestroyed;
}
@ -329,7 +332,8 @@ void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID,
m_Protocol->SendLoginSuccess();
// Spawn player (only serversided, so data is loaded)
m_Player = new cPlayer(this, GetUsername());
m_Player = new cPlayer(m_Self, GetUsername());
m_Self.reset();
cWorld * World = cRoot::Get()->GetWorld(m_Player->GetLoadedWorldName());
if (World == nullptr)
@ -692,6 +696,47 @@ void cClientHandle::HandleCreativeInventory(short a_SlotNum, const cItem & a_Hel
void cClientHandle::HandleEnchantItem(Byte a_WindowID, Byte a_Enchantment)
{
if (a_Enchantment > 2)
{
LOGWARNING("%s attempt to crash the server with invalid enchanting selection!", GetUsername().c_str());
Kick("Invalid enchanting!");
return;
}
if (
(m_Player->GetWindow() == nullptr) ||
(m_Player->GetWindow()->GetWindowID() != a_WindowID) ||
(m_Player->GetWindow()->GetWindowType() != cWindow::wtEnchantment)
)
{
return;
}
cEnchantingWindow * Window = reinterpret_cast<cEnchantingWindow *>(m_Player->GetWindow());
cItem Item = *Window->m_SlotArea->GetSlot(0, *m_Player); // Make a copy of the item
short BaseEnchantmentLevel = Window->GetPropertyValue(a_Enchantment);
if (Item.EnchantByXPLevels(BaseEnchantmentLevel))
{
if (m_Player->IsGameModeCreative() || m_Player->DeltaExperience(-m_Player->XpForLevel(BaseEnchantmentLevel)) >= 0)
{
Window->m_SlotArea->SetSlot(0, *m_Player, Item);
Window->SendSlot(*m_Player, Window->m_SlotArea, 0);
Window->BroadcastWholeWindow();
Window->SetProperty(0, 0, *m_Player);
Window->SetProperty(1, 0, *m_Player);
Window->SetProperty(2, 0, *m_Player);
}
}
}
void cClientHandle::HandlePlayerAbilities(bool a_CanFly, bool a_IsFlying, float FlyingSpeed, float WalkingSpeed)
{
UNUSED(FlyingSpeed); // Ignore the client values for these
@ -1780,44 +1825,9 @@ void cClientHandle::SendData(const char * a_Data, size_t a_Size)
// This could crash the client, because they've already unloaded the world etc., and suddenly a wild packet appears (#31)
return;
}
{
cCSLock Lock(m_CSOutgoingData);
// _X 2012_09_06: We need an overflow buffer, usually when streaming the initial chunks
if (m_OutgoingDataOverflow.empty())
{
// No queued overflow data; if this packet fits into the ringbuffer, put it in, otherwise put it in the overflow buffer:
size_t CanFit = m_OutgoingData.GetFreeSpace();
if (CanFit > a_Size)
{
CanFit = a_Size;
}
if (CanFit > 0)
{
m_OutgoingData.Write(a_Data, CanFit);
}
if (a_Size > CanFit)
{
m_OutgoingDataOverflow.append(a_Data + CanFit, a_Size - CanFit);
}
}
else
{
// There is a queued overflow. Append to it, then send as much from its front as possible
m_OutgoingDataOverflow.append(a_Data, a_Size);
size_t CanFit = m_OutgoingData.GetFreeSpace();
if (CanFit > 128)
{
// No point in moving the data over if it's not large enough - too much effort for too little an effect
m_OutgoingData.Write(m_OutgoingDataOverflow.data(), CanFit);
m_OutgoingDataOverflow.erase(0, CanFit);
}
}
} // Lock(m_CSOutgoingData)
// Notify SocketThreads that we have something to write:
cRoot::Get()->GetServer()->NotifyClientWrite(this);
cCSLock Lock(m_CSOutgoingData);
m_OutgoingData.append(a_Data, a_Size);
}
@ -1874,10 +1884,24 @@ void cClientHandle::Tick(float a_Dt)
cCSLock Lock(m_CSIncomingData);
std::swap(IncomingData, m_IncomingData);
}
m_Protocol->DataReceived(IncomingData.data(), IncomingData.size());
if (!IncomingData.empty())
{
m_Protocol->DataReceived(IncomingData.data(), IncomingData.size());
}
// Send any queued outgoing data:
AString OutgoingData;
{
cCSLock Lock(m_CSOutgoingData);
std::swap(OutgoingData, m_OutgoingData);
}
if ((m_Link != nullptr) && !OutgoingData.empty())
{
m_Link->Send(OutgoingData.data(), OutgoingData.size());
}
m_TimeSinceLastPacket += a_Dt;
if (m_TimeSinceLastPacket > 30000.f) // 30 seconds time-out
m_TicksSinceLastPacket += 1;
if (m_TicksSinceLastPacket > 600) // 30 seconds time-out
{
SendDisconnect("Nooooo!! You timed out! D: Come back!");
Destroy();
@ -1958,7 +1982,21 @@ void cClientHandle::ServerTick(float a_Dt)
cCSLock Lock(m_CSIncomingData);
std::swap(IncomingData, m_IncomingData);
}
m_Protocol->DataReceived(IncomingData.data(), IncomingData.size());
if (!IncomingData.empty())
{
m_Protocol->DataReceived(IncomingData.data(), IncomingData.size());
}
// Send any queued outgoing data:
AString OutgoingData;
{
cCSLock Lock(m_CSOutgoingData);
std::swap(OutgoingData, m_OutgoingData);
}
if ((m_Link != nullptr) && !OutgoingData.empty())
{
m_Link->Send(OutgoingData.data(), OutgoingData.size());
}
if (m_State == csAuthenticated)
{
@ -1973,8 +2011,8 @@ void cClientHandle::ServerTick(float a_Dt)
return;
}
m_TimeSinceLastPacket += a_Dt;
if (m_TimeSinceLastPacket > 30000.f) // 30 seconds time-out
m_TicksSinceLastPacket += 1;
if (m_TicksSinceLastPacket > 600) // 30 seconds
{
SendDisconnect("Nooooo!! You timed out! D: Come back!");
Destroy();
@ -2846,49 +2884,13 @@ void cClientHandle::PacketError(UInt32 a_PacketType)
bool cClientHandle::DataReceived(const char * a_Data, size_t a_Size)
{
// Data is received from the client, store it in the buffer to be processed by the Tick thread:
m_TimeSinceLastPacket = 0;
cCSLock Lock(m_CSIncomingData);
m_IncomingData.append(a_Data, a_Size);
return false;
}
void cClientHandle::GetOutgoingData(AString & a_Data)
{
// Data can be sent to client
{
cCSLock Lock(m_CSOutgoingData);
m_OutgoingData.ReadAll(a_Data);
m_OutgoingData.CommitRead();
a_Data.append(m_OutgoingDataOverflow);
m_OutgoingDataOverflow.clear();
}
// Disconnect player after all packets have been sent
if (m_HasSentDC && a_Data.empty())
{
Destroy();
}
}
void cClientHandle::SocketClosed(void)
{
// The socket has been closed for any reason
LOGD("Player %s @ %s disconnected", m_Username.c_str(), m_IPString.c_str());
if (!m_Username.empty()) // Ignore client pings
{
LOGD("Client %s @ %s disconnected", m_Username.c_str(), m_IPString.c_str());
cRoot::Get()->GetPluginManager()->CallHookDisconnect(*this, "Player disconnected");
}
@ -2899,41 +2901,62 @@ void cClientHandle::SocketClosed(void)
void cClientHandle::HandleEnchantItem(Byte & a_WindowID, Byte & a_Enchantment)
void cClientHandle::SetSelf(cClientHandlePtr a_Self)
{
if (a_Enchantment > 2)
{
LOGWARNING("%s attempt to crash the server with invalid enchanting selection!", GetUsername().c_str());
Kick("Invalid enchanting!");
return;
}
if (
(m_Player->GetWindow() == nullptr) ||
(m_Player->GetWindow()->GetWindowID() != a_WindowID) ||
(m_Player->GetWindow()->GetWindowType() != cWindow::wtEnchantment)
)
{
return;
}
cEnchantingWindow * Window = (cEnchantingWindow*) m_Player->GetWindow();
cItem Item = *Window->m_SlotArea->GetSlot(0, *m_Player);
int BaseEnchantmentLevel = Window->GetPropertyValue(a_Enchantment);
if (Item.EnchantByXPLevels(BaseEnchantmentLevel))
{
if (m_Player->IsGameModeCreative() || m_Player->DeltaExperience(-m_Player->XpForLevel(BaseEnchantmentLevel)) >= 0)
{
Window->m_SlotArea->SetSlot(0, *m_Player, Item);
Window->SendSlot(*m_Player, Window->m_SlotArea, 0);
Window->BroadcastWholeWindow();
Window->SetProperty(0, 0, *m_Player);
Window->SetProperty(1, 0, *m_Player);
Window->SetProperty(2, 0, *m_Player);
}
}
ASSERT(m_Self == nullptr);
m_Self = a_Self;
}
void cClientHandle::OnLinkCreated(cTCPLinkPtr a_Link)
{
m_Link = a_Link;
}
void cClientHandle::OnReceivedData(const char * a_Data, size_t a_Length)
{
// Reset the timeout:
m_TicksSinceLastPacket = 0;
// Queue the incoming data to be processed in the tick thread:
cCSLock Lock(m_CSIncomingData);
m_IncomingData.append(a_Data, a_Length);
}
void cClientHandle::OnRemoteClosed(void)
{
{
cCSLock Lock(m_CSOutgoingData);
m_Link.reset();
}
SocketClosed();
}
void cClientHandle::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
{
LOGD("An error has occurred on client link for %s @ %s: %d (%s). Client disconnected.",
m_Username.c_str(), m_IPString.c_str(), a_ErrorCode, a_ErrorMsg.c_str()
);
{
cCSLock Lock(m_CSOutgoingData);
m_Link.reset();
}
SocketClosed();
}

View File

@ -8,12 +8,10 @@
#pragma once
#ifndef CCLIENTHANDLE_H_INCLUDED
#define CCLIENTHANDLE_H_INCLUDED
#include "OSSupport/Network.h"
#include "Defines.h"
#include "Vector3.h"
#include "OSSupport/SocketThreads.h"
#include "ChunkDef.h"
#include "ByteBuffer.h"
#include "Scoreboard.h"
@ -27,6 +25,7 @@
// fwd:
class cChunkDataSerializer;
class cInventory;
class cMonster;
@ -42,25 +41,29 @@ class cItemHandler;
class cWorld;
class cCompositeChat;
class cStatManager;
class cClientHandle;
typedef SharedPtr<cClientHandle> cClientHandlePtr;
class cClientHandle : // tolua_export
public cSocketThreads::cCallback
class cClientHandle // tolua_export
: public cTCPLink::cCallbacks
{ // tolua_export
public:
#if defined(ANDROID_NDK)
static const int DEFAULT_VIEW_DISTANCE = 4; // The default ViewDistance (used when no value is set in Settings.ini)
#else
static const int DEFAULT_VIEW_DISTANCE = 10;
#endif
public: // tolua_export
#if defined(ANDROID_NDK)
static const int DEFAULT_VIEW_DISTANCE = 4; // The default ViewDistance (used when no value is set in Settings.ini)
#else
static const int DEFAULT_VIEW_DISTANCE = 10;
#endif
static const int MAX_VIEW_DISTANCE = 32;
static const int MIN_VIEW_DISTANCE = 1;
cClientHandle(const cSocket * a_Socket, int a_ViewDistance);
/** Creates a new client with the specified IP address in its description and the specified initial view distance. */
cClientHandle(const AString & a_IPString, int a_ViewDistance);
virtual ~cClientHandle();
const AString & GetIPString(void) const { return m_IPString; } // tolua_export
@ -276,6 +279,10 @@ public:
void HandleCommandBlockEntityChange(int a_EntityID, const AString & a_NewCommand);
void HandleCreativeInventory (short a_SlotNum, const cItem & a_HeldItem);
/** Called when the player enchants an Item in the Enchanting table UI. */
void HandleEnchantItem(Byte a_WindowID, Byte a_Enchantment);
void HandleEntityCrouch (int a_EntityID, bool a_IsCrouching);
void HandleEntityLeaveBed (int a_EntityID);
void HandleEntitySprinting (int a_EntityID, bool a_IsSprinting);
@ -329,9 +336,6 @@ public:
Sends an UnloadChunk packet for each loaded chunk and resets the streamed chunks. */
void RemoveFromWorld(void);
/** Called when the player will enchant a Item */
void HandleEnchantItem(Byte & a_WindowID, Byte & a_Enchantment);
/** Called by the protocol recognizer when the protocol version is known. */
void SetProtocolVersion(UInt32 a_ProtocolVersion) { m_ProtocolVersion = a_ProtocolVersion; }
@ -340,6 +344,9 @@ public:
private:
friend class cServer; // Needs access to SetSelf()
/** The type used for storing the names of registered plugin channels. */
typedef std::set<AString> cChannels;
@ -361,13 +368,20 @@ private:
cChunkCoordsList m_SentChunks; // Chunks that are currently sent to the client
cProtocol * m_Protocol;
/** Protects m_IncomingData against multithreaded access. */
cCriticalSection m_CSIncomingData;
AString m_IncomingData;
/** Queue for the incoming data received on the link until it is processed in Tick().
Protected by m_CSIncomingData. */
AString m_IncomingData;
/** Protects m_OutgoingData against multithreaded access. */
cCriticalSection m_CSOutgoingData;
cByteBuffer m_OutgoingData;
AString m_OutgoingDataOverflow; ///< For data that didn't fit into the m_OutgoingData ringbuffer temporarily
/** Buffer for storing outgoing data from any thread; will get sent in Tick() (to prevent deadlocks).
Protected by m_CSOutgoingData. */
AString m_OutgoingData;
Vector3d m_ConfirmPosition;
@ -379,8 +393,8 @@ private:
int m_LastStreamedChunkX;
int m_LastStreamedChunkZ;
/** Seconds since the last packet data was received (updated in Tick(), reset in DataReceived()) */
float m_TimeSinceLastPacket;
/** Number of ticks since the last network packet was received (increased in Tick(), reset in OnReceivedData()) */
int m_TicksSinceLastPacket;
/** Duration of the last completed client ping. */
std::chrono::steady_clock::duration m_Ping;
@ -458,6 +472,13 @@ private:
/** The version of the protocol that the client is talking, or 0 if unknown. */
UInt32 m_ProtocolVersion;
/** The link that is used for network communication.
m_CSOutgoingData is used to synchronize access for sending data. */
cTCPLinkPtr m_Link;
/** Shared pointer to self, so that this instance can keep itself alive when needed. */
cClientHandlePtr m_Self;
/** Returns true if the rate block interactions is within a reasonable limit (bot protection) */
bool CheckBlockInteractionsRate(void);
@ -483,17 +504,20 @@ private:
/** Removes all of the channels from the list of current plugin channels. Ignores channels that are not found. */
void UnregisterPluginChannels(const AStringVector & a_ChannelList);
// cSocketThreads::cCallback overrides:
virtual bool DataReceived (const char * a_Data, size_t a_Size) override; // Data is received from the client
virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client
virtual void SocketClosed (void) override; // The socket has been closed for any reason
/** Called when the network socket has been closed. */
void SocketClosed(void);
/** Called right after the instance is created to store its SharedPtr inside. */
void SetSelf(cClientHandlePtr a_Self);
// cTCPLink::cCallbacks overrides:
virtual void OnLinkCreated(cTCPLinkPtr a_Link) override;
virtual void OnReceivedData(const char * a_Data, size_t a_Length) override;
virtual void OnRemoteClosed(void) override;
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
}; // tolua_export
#endif // CCLIENTHANDLE_H_INCLUDED

View File

@ -47,7 +47,7 @@ const int cPlayer::EATING_TICKS = 30;
cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) :
cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) :
super(etPlayer, 0.6, 1.8),
m_bVisible(true),
m_FoodLevel(MAX_FOOD_LEVEL),
@ -174,7 +174,7 @@ void cPlayer::Destroyed()
void cPlayer::SpawnOn(cClientHandle & a_Client)
{
if (!m_bVisible || (m_ClientHandle == (&a_Client)))
if (!m_bVisible || (m_ClientHandle.get() == (&a_Client)))
{
return;
}
@ -246,7 +246,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
if (CanMove)
{
BroadcastMovementUpdate(m_ClientHandle);
BroadcastMovementUpdate(m_ClientHandle.get());
}
if (m_Health > 0) // make sure player is alive
@ -419,7 +419,7 @@ void cPlayer::StartChargingBow(void)
LOGD("Player \"%s\" started charging their bow", GetName().c_str());
m_IsChargingBow = true;
m_BowCharge = 0;
m_World->BroadcastEntityMetadata(*this, m_ClientHandle);
m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
}
@ -432,7 +432,7 @@ int cPlayer::FinishChargingBow(void)
int res = m_BowCharge;
m_IsChargingBow = false;
m_BowCharge = 0;
m_World->BroadcastEntityMetadata(*this, m_ClientHandle);
m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
return res;
}
@ -446,7 +446,7 @@ void cPlayer::CancelChargingBow(void)
LOGD("Player \"%s\" cancelled charging their bow at a charge of %d", GetName().c_str(), m_BowCharge);
m_IsChargingBow = false;
m_BowCharge = 0;
m_World->BroadcastEntityMetadata(*this, m_ClientHandle);
m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
}
@ -1391,7 +1391,7 @@ void cPlayer::SetVisible(bool a_bVisible)
if (!a_bVisible && m_bVisible)
{
m_bVisible = false;
m_World->BroadcastDestroyEntity(*this, m_ClientHandle); // Destroy on all clients
m_World->BroadcastDestroyEntity(*this, m_ClientHandle.get()); // Destroy on all clients
}
}
@ -2294,6 +2294,16 @@ void cPlayer::Detach()
void cPlayer::RemoveClientHandle(void)
{
ASSERT(m_ClientHandle != nullptr);
m_ClientHandle.reset();
}
AString cPlayer::GetUUIDFileName(const AString & a_UUID)
{
AString UUID = cMojangAPI::MakeUUIDDashed(a_UUID);

View File

@ -40,7 +40,7 @@ public:
CLASS_PROTODEF(cPlayer)
cPlayer(cClientHandle * a_Client, const AString & a_PlayerName);
cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName);
virtual ~cPlayer();
@ -222,7 +222,15 @@ public:
/** Closes the current window if it matches the specified ID, resets current window to m_InventoryWindow */
void CloseWindowIfID(char a_WindowID, bool a_CanRefuse = true);
cClientHandle * GetClientHandle(void) const { return m_ClientHandle; }
/** Returns the raw client handle associated with the player. */
cClientHandle * GetClientHandle(void) const { return m_ClientHandle.get(); }
// tolua_end
/** Returns the SharedPtr to client handle associated with the player. */
cClientHandlePtr GetClientHandlePtr(void) const { return m_ClientHandle; }
// tolua_begin
void SendMessage (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtCustom); }
void SendMessageInfo (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtInformation); }
@ -467,6 +475,10 @@ public:
virtual bool IsRclking (void) const { return IsEating() || IsChargingBow(); }
virtual void Detach(void);
/** Called by cClientHandle when the client is being destroyed.
The player removes its m_ClientHandle ownership so that the ClientHandle gets deleted. */
void RemoveClientHandle(void);
protected:
@ -537,7 +549,7 @@ protected:
std::chrono::steady_clock::time_point m_LastPlayerListTime;
cClientHandle * m_ClientHandle;
cClientHandlePtr m_ClientHandle;
cSlotNums m_InventoryPaintSlots;

View File

@ -205,8 +205,7 @@ void cBiomeGenList::InitializeBiomes(const AString & a_Biomes)
int Count = 1;
if (Split2.size() >= 2)
{
Count = atol(Split2[1].c_str());
if (Count <= 0)
if (!StringToInteger(Split2[1], Count))
{
LOGWARNING("Cannot decode biome count: \"%s\"; using 1.", Split2[1].c_str());
Count = 1;

View File

@ -38,8 +38,7 @@ cHTTPConnection::~cHTTPConnection()
void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response)
{
AppendPrintf(m_OutgoingData, "%d %s\r\nContent-Length: 0\r\n\r\n", a_StatusCode, a_Response.c_str());
m_HTTPServer.NotifyConnectionWrite(*this);
SendData(Printf("%d %s\r\nContent-Length: 0\r\n\r\n", a_StatusCode, a_Response.c_str()));
m_State = wcsRecvHeaders;
}
@ -49,8 +48,7 @@ void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Re
void cHTTPConnection::SendNeedAuth(const AString & a_Realm)
{
AppendPrintf(m_OutgoingData, "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str());
m_HTTPServer.NotifyConnectionWrite(*this);
SendData(Printf("HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str()));
m_State = wcsRecvHeaders;
}
@ -61,9 +59,10 @@ void cHTTPConnection::SendNeedAuth(const AString & a_Realm)
void cHTTPConnection::Send(const cHTTPResponse & a_Response)
{
ASSERT(m_State == wcsRecvIdle);
a_Response.AppendToData(m_OutgoingData);
AString toSend;
a_Response.AppendToData(toSend);
m_State = wcsSendingResp;
m_HTTPServer.NotifyConnectionWrite(*this);
SendData(toSend);
}
@ -73,10 +72,10 @@ void cHTTPConnection::Send(const cHTTPResponse & a_Response)
void cHTTPConnection::Send(const void * a_Data, size_t a_Size)
{
ASSERT(m_State == wcsSendingResp);
AppendPrintf(m_OutgoingData, SIZE_T_FMT_HEX "\r\n", a_Size);
m_OutgoingData.append((const char *)a_Data, a_Size);
m_OutgoingData.append("\r\n");
m_HTTPServer.NotifyConnectionWrite(*this);
// We're sending in Chunked transfer encoding
SendData(Printf(SIZE_T_FMT_HEX "\r\n", a_Size));
SendData(a_Data, a_Size);
SendData("\r\n");
}
@ -86,9 +85,8 @@ void cHTTPConnection::Send(const void * a_Data, size_t a_Size)
void cHTTPConnection::FinishResponse(void)
{
ASSERT(m_State == wcsSendingResp);
m_OutgoingData.append("0\r\n\r\n");
SendData("0\r\n\r\n");
m_State = wcsRecvHeaders;
m_HTTPServer.NotifyConnectionWrite(*this);
}
@ -108,8 +106,7 @@ void cHTTPConnection::AwaitNextRequest(void)
case wcsRecvIdle:
{
// The client is waiting for a response, send an "Internal server error":
m_OutgoingData.append("HTTP/1.1 500 Internal Server Error\r\n\r\n");
m_HTTPServer.NotifyConnectionWrite(*this);
SendData("HTTP/1.1 500 Internal Server Error\r\n\r\n");
m_State = wcsRecvHeaders;
break;
}
@ -117,7 +114,7 @@ void cHTTPConnection::AwaitNextRequest(void)
case wcsSendingResp:
{
// The response headers have been sent, we need to terminate the response body:
m_OutgoingData.append("0\r\n\r\n");
SendData("0\r\n\r\n");
m_State = wcsRecvHeaders;
break;
}
@ -140,15 +137,27 @@ void cHTTPConnection::Terminate(void)
{
m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
}
m_HTTPServer.CloseConnection(*this);
m_Link.reset();
}
bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
void cHTTPConnection::OnLinkCreated(cTCPLinkPtr a_Link)
{
ASSERT(m_Link == nullptr);
m_Link = a_Link;
}
void cHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size)
{
ASSERT(m_Link != nullptr);
switch (m_State)
{
case wcsRecvHeaders:
@ -164,13 +173,14 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
delete m_CurrentRequest;
m_CurrentRequest = nullptr;
m_State = wcsInvalid;
m_HTTPServer.CloseConnection(*this);
return true;
m_Link->Close();
m_Link.reset();
return;
}
if (m_CurrentRequest->IsInHeaders())
{
// The request headers are not yet complete
return false;
return;
}
// The request has finished parsing its headers successfully, notify of it:
@ -186,11 +196,13 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
// Process the rest of the incoming data into the request body:
if (a_Size > BytesConsumed)
{
return cHTTPConnection::DataReceived(a_Data + BytesConsumed, a_Size - BytesConsumed);
cHTTPConnection::OnReceivedData(a_Data + BytesConsumed, a_Size - BytesConsumed);
return;
}
else
{
return cHTTPConnection::DataReceived("", 0); // If the request has zero body length, let it be processed right-away
cHTTPConnection::OnReceivedData("", 0); // If the request has zero body length, let it be processed right-away
return;
}
}
@ -210,8 +222,9 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
if (!m_CurrentRequest->DoesAllowKeepAlive())
{
m_State = wcsInvalid;
m_HTTPServer.CloseConnection(*this);
return true;
m_Link->Close();
m_Link.reset();
return;
}
delete m_CurrentRequest;
m_CurrentRequest = nullptr;
@ -225,32 +238,39 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
break;
}
}
return false;
}
void cHTTPConnection::GetOutgoingData(AString & a_Data)
{
std::swap(a_Data, m_OutgoingData);
}
void cHTTPConnection::SocketClosed(void)
void cHTTPConnection::OnRemoteClosed(void)
{
if (m_CurrentRequest != nullptr)
{
m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
}
m_HTTPServer.CloseConnection(*this);
m_Link.reset();
}
void cHTTPConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
{
OnRemoteClosed();
}
void cHTTPConnection::SendData(const void * a_Data, size_t a_Size)
{
m_Link->Send(a_Data, a_Size);
}

View File

@ -9,7 +9,7 @@
#pragma once
#include "../OSSupport/SocketThreads.h"
#include "../OSSupport/Network.h"
@ -25,7 +25,7 @@ class cHTTPRequest;
class cHTTPConnection :
public cSocketThreads::cCallback
public cTCPLink::cCallbacks
{
public:
@ -78,9 +78,6 @@ protected:
/** Status in which the request currently is */
eState m_State;
/** Data that is queued for sending, once the socket becomes writable */
AString m_OutgoingData;
/** The request being currently received
Valid only between having parsed the headers and finishing receiving the body. */
cHTTPRequest * m_CurrentRequest;
@ -88,18 +85,34 @@ protected:
/** Number of bytes that remain to read for the complete body of the message to be received.
Valid only in wcsRecvBody */
size_t m_CurrentRequestBodyRemaining;
/** The network link attached to this connection. */
cTCPLinkPtr m_Link;
// cSocketThreads::cCallback overrides:
/** Data is received from the client.
Returns true if the connection has been closed as the result of parsing the data. */
virtual bool DataReceived(const char * a_Data, size_t a_Size) override;
// cTCPLink::cCallbacks overrides:
/** The link instance has been created, remember it. */
virtual void OnLinkCreated(cTCPLinkPtr a_Link) override;
/** Data is received from the client. */
virtual void OnReceivedData(const char * a_Data, size_t a_Size) override;
/** Data can be sent to client */
virtual void GetOutgoingData(AString & a_Data) override;
/** The socket has been closed for any reason. */
virtual void OnRemoteClosed(void) override;
/** The socket has been closed for any reason */
virtual void SocketClosed(void) override;
/** An error has occurred on the socket. */
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
// Overridable:
/** Called to send raw data over the link. Descendants may provide data transformations (SSL etc.) */
virtual void SendData(const void * a_Data, size_t a_Size);
/** Sends the raw data over the link.
Descendants may provide data transformations (SSL etc.) via the overridable SendData() function. */
void SendData(const AString & a_Data)
{
SendData(a_Data.data(), a_Data.size());
}
} ;
typedef std::vector<cHTTPConnection *> cHTTPConnections;

View File

@ -55,7 +55,10 @@ void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value)
}
else if (Key == "content-length")
{
m_ContentLength = static_cast<size_t>(atol(m_Headers[Key].c_str()));
if (!StringToInteger(m_Headers[Key], m_ContentLength))
{
m_ContentLength = 0;
}
}
}

View File

@ -118,12 +118,46 @@ class cDebugCallbacks :
////////////////////////////////////////////////////////////////////////////////
// cHTTPServerListenCallbacks:
class cHTTPServerListenCallbacks:
public cNetwork::cListenCallbacks
{
public:
cHTTPServerListenCallbacks(cHTTPServer & a_HTTPServer, UInt16 a_Port):
m_HTTPServer(a_HTTPServer),
m_Port(a_Port)
{
}
protected:
/** The HTTP server instance that we're attached to. */
cHTTPServer & m_HTTPServer;
/** The port for which this instance is responsible. */
UInt16 m_Port;
// cNetwork::cListenCallbacks overrides:
virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override
{
return m_HTTPServer.OnIncomingConnection(a_RemoteIPAddress, a_RemotePort);
}
virtual void OnAccepted(cTCPLink & a_Link) override {}
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
{
LOGWARNING("HTTP server error on port %d: %d (%s)", m_Port, a_ErrorCode, a_ErrorMsg.c_str());
}
};
////////////////////////////////////////////////////////////////////////////////
// cHTTPServer:
cHTTPServer::cHTTPServer(void) :
m_ListenThreadIPv4(*this, cSocket::IPv4, "WebServer"),
m_ListenThreadIPv6(*this, cSocket::IPv6, "WebServer"),
m_Callbacks(nullptr)
{
}
@ -141,7 +175,7 @@ cHTTPServer::~cHTTPServer()
bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6)
bool cHTTPServer::Initialize(void)
{
// Read the HTTPS cert + key:
AString CertFile = cFile::ReadWholeFile("webadmin/httpscert.crt");
@ -177,18 +211,6 @@ bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_Port
{
LOGINFO("WebServer: The server is running in secure HTTPS mode.");
}
// Open up requested ports:
bool HasAnyPort;
m_ListenThreadIPv4.SetReuseAddr(true);
m_ListenThreadIPv6.SetReuseAddr(true);
HasAnyPort = m_ListenThreadIPv4.Initialize(a_PortsIPv4);
HasAnyPort = m_ListenThreadIPv6.Initialize(a_PortsIPv6) || HasAnyPort;
if (!HasAnyPort)
{
return false;
}
return true;
}
@ -196,19 +218,28 @@ bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_Port
bool cHTTPServer::Start(cCallbacks & a_Callbacks)
bool cHTTPServer::Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports)
{
m_Callbacks = &a_Callbacks;
if (!m_ListenThreadIPv4.Start())
// Open up requested ports:
for (auto port : a_Ports)
{
return false;
}
if (!m_ListenThreadIPv6.Start())
{
m_ListenThreadIPv4.Stop();
return false;
}
return true;
UInt16 PortNum;
if (!StringToInteger(port, PortNum))
{
LOGWARNING("WebServer: Invalid port value: \"%s\". Ignoring.", port.c_str());
continue;
}
auto Handle = cNetwork::Listen(PortNum, std::make_shared<cHTTPServerListenCallbacks>(*this, PortNum));
if (Handle->IsListening())
{
m_ServerHandles.push_back(Handle);
}
} // for port - a_Ports[]
// Report success if at least one port opened successfully:
return !m_ServerHandles.empty();
}
@ -217,63 +248,30 @@ bool cHTTPServer::Start(cCallbacks & a_Callbacks)
void cHTTPServer::Stop(void)
{
m_ListenThreadIPv4.Stop();
m_ListenThreadIPv6.Stop();
// Drop all current connections:
cCSLock Lock(m_CSConnections);
while (!m_Connections.empty())
for (auto handle : m_ServerHandles)
{
m_Connections.front()->Terminate();
} // for itr - m_Connections[]
handle->Close();
}
m_ServerHandles.clear();
}
void cHTTPServer::OnConnectionAccepted(cSocket & a_Socket)
cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort)
{
cHTTPConnection * Connection;
UNUSED(a_RemoteIPAddress);
UNUSED(a_RemotePort);
if (m_Cert.get() != nullptr)
{
Connection = new cSslHTTPConnection(*this, m_Cert, m_CertPrivKey);
return std::make_shared<cSslHTTPConnection>(*this, m_Cert, m_CertPrivKey);
}
else
{
Connection = new cHTTPConnection(*this);
return std::make_shared<cHTTPConnection>(*this);
}
m_SocketThreads.AddClient(a_Socket, Connection);
cCSLock Lock(m_CSConnections);
m_Connections.push_back(Connection);
}
void cHTTPServer::CloseConnection(cHTTPConnection & a_Connection)
{
m_SocketThreads.RemoveClient(&a_Connection);
cCSLock Lock(m_CSConnections);
for (cHTTPConnections::iterator itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr)
{
if (*itr == &a_Connection)
{
m_Connections.erase(itr);
break;
}
}
delete &a_Connection;
}
void cHTTPServer::NotifyConnectionWrite(cHTTPConnection & a_Connection)
{
m_SocketThreads.NotifyWrite(&a_Connection);
}

View File

@ -9,8 +9,7 @@
#pragma once
#include "../OSSupport/ListenThread.h"
#include "../OSSupport/SocketThreads.h"
#include "../OSSupport/Network.h"
#include "../IniFile.h"
#include "PolarSSL++/RsaPrivateKey.h"
#include "PolarSSL++/CryptoKey.h"
@ -33,8 +32,7 @@ typedef std::vector<cHTTPConnection *> cHTTPConnections;
class cHTTPServer :
public cListenThread::cCallback
class cHTTPServer
{
public:
class cCallbacks
@ -42,44 +40,39 @@ public:
public:
virtual ~cCallbacks() {}
/** Called when a new request arrives over a connection and its headers have been parsed.
The request body needn't have arrived yet.
*/
/** Called when a new request arrives over a connection and all its headers have been parsed.
The request body needn't have arrived yet. */
virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0;
/** Called when another part of request body has arrived.
May be called multiple times for a single request. */
virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) = 0;
/// Called when the request body has been fully received in previous calls to OnRequestBody()
/** Called when the request body has been fully received in previous calls to OnRequestBody() */
virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0;
} ;
cHTTPServer(void);
virtual ~cHTTPServer();
/// Initializes the server on the specified ports
bool Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6);
/** Initializes the server - reads the cert files etc. */
bool Initialize(void);
/// Starts the server and assigns the callbacks to use for incoming requests
bool Start(cCallbacks & a_Callbacks);
/** Starts the server and assigns the callbacks to use for incoming requests */
bool Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports);
/// Stops the server, drops all current connections
/** Stops the server, drops all current connections */
void Stop(void);
protected:
friend class cHTTPConnection;
friend class cSslHTTPConnection;
friend class cHTTPServerListenCallbacks;
cListenThread m_ListenThreadIPv4;
cListenThread m_ListenThreadIPv6;
/** The cNetwork API handle for the listening socket. */
cServerHandlePtrs m_ServerHandles;
cSocketThreads m_SocketThreads;
cCriticalSection m_CSConnections;
cHTTPConnections m_Connections; ///< All the connections that are currently being serviced
/// The callbacks to call for various events
/** The callbacks to call for various events */
cCallbacks * m_Callbacks;
/** The server certificate to use for the SSL connections */
@ -89,23 +82,18 @@ protected:
cCryptoKeyPtr m_CertPrivKey;
// cListenThread::cCallback overrides:
virtual void OnConnectionAccepted(cSocket & a_Socket) override;
/// Called by cHTTPConnection to close the connection (presumably due to an error)
void CloseConnection(cHTTPConnection & a_Connection);
/// Called by cHTTPConnection to notify SocketThreads that there's data to be sent for the connection
void NotifyConnectionWrite(cHTTPConnection & a_Connection);
/// Called by cHTTPConnection when it finishes parsing the request header
/** Called by cHTTPServerListenCallbacks when there's a new incoming connection.
Returns the connection instance to be used as the cTCPLink callbacks. */
cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort);
/** Called by cHTTPConnection when it finishes parsing the request header */
void NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
/** Called by cHTTPConenction when it receives more data for the request body.
May be called multiple times for a single request. */
void RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size);
/// Called by cHTTPConnection when it detects that the request has finished (all of its body has been received)
/** Called by cHTTPConnection when it detects that the request has finished (all of its body has been received) */
void RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
} ;

View File

@ -25,14 +25,8 @@ cSslHTTPConnection::cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509Ce
bool cSslHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
void cSslHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size)
{
// If there is outgoing data in the queue, notify the server that it should write it out:
if (!m_OutgoingData.empty())
{
m_HTTPServer.NotifyConnectionWrite(*this);
}
// Process the received data:
const char * Data = a_Data;
size_t Size = a_Size;
@ -52,17 +46,18 @@ bool cSslHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
int NumRead = m_Ssl.ReadPlain(Buffer, sizeof(Buffer));
if (NumRead > 0)
{
if (super::DataReceived(Buffer, (size_t)NumRead))
{
// The socket has been closed, and the object is already deleted. Bail out.
return true;
}
super::OnReceivedData(Buffer, (size_t)NumRead);
}
else if (NumRead == POLARSSL_ERR_NET_WANT_READ)
{
// SSL requires us to send data to peer first, do so by "sending" empty data:
SendData(nullptr, 0);
}
// If both failed, bail out:
if ((BytesWritten == 0) && (NumRead <= 0))
{
return false;
return;
}
}
}
@ -71,18 +66,20 @@ bool cSslHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
void cSslHTTPConnection::GetOutgoingData(AString & a_Data)
void cSslHTTPConnection::SendData(const void * a_Data, size_t a_Size)
{
const char * OutgoingData = reinterpret_cast<const char *>(a_Data);
size_t pos = 0;
for (;;)
{
// Write as many bytes from our buffer to SSL's encryption as possible:
int NumWritten = 0;
if (!m_OutgoingData.empty())
if (pos < a_Size)
{
NumWritten = m_Ssl.WritePlain(m_OutgoingData.data(), m_OutgoingData.size());
NumWritten = m_Ssl.WritePlain(OutgoingData + pos, a_Size - pos);
if (NumWritten > 0)
{
m_OutgoingData.erase(0, (size_t)NumWritten);
pos += static_cast<size_t>(NumWritten);
}
}
@ -91,7 +88,7 @@ void cSslHTTPConnection::GetOutgoingData(AString & a_Data)
size_t NumBytes = m_Ssl.ReadOutgoing(Buffer, sizeof(Buffer));
if (NumBytes > 0)
{
a_Data.append(Buffer, NumBytes);
m_Link->Send(Buffer, NumBytes);
}
// If both failed, bail out:

View File

@ -36,8 +36,8 @@ protected:
cCryptoKeyPtr m_PrivateKey;
// cHTTPConnection overrides:
virtual bool DataReceived (const char * a_Data, size_t a_Size) override; // Data is received from the client
virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client
virtual void OnReceivedData(const char * a_Data, size_t a_Size) override; // Data is received from the client
virtual void SendData(const void * a_Data, size_t a_Size) override; // Data is to be sent to client
} ;

View File

@ -888,3 +888,39 @@ void cIniFile::RemoveBom(AString & a_line) const
AStringVector ReadUpgradeIniPorts(
cIniFile & a_IniFile,
const AString & a_KeyName,
const AString & a_PortsValueName,
const AString & a_OldIPv4ValueName,
const AString & a_OldIPv6ValueName,
const AString & a_DefaultValue
)
{
// Read the regular value, but don't use the default (in order to detect missing value for upgrade):
AStringVector Ports = StringSplitAndTrim(a_IniFile.GetValue(a_KeyName, a_PortsValueName), ";,");
if (Ports.empty())
{
// Historically there were two separate entries for IPv4 and IPv6, merge them and migrate:
AString Ports4 = a_IniFile.GetValue(a_KeyName, a_OldIPv4ValueName, a_DefaultValue);
AString Ports6 = a_IniFile.GetValue(a_KeyName, a_OldIPv6ValueName);
Ports = MergeStringVectors(StringSplitAndTrim(Ports4, ";,"), StringSplitAndTrim(Ports6, ";,"));
a_IniFile.DeleteValue(a_KeyName, a_OldIPv4ValueName);
a_IniFile.DeleteValue(a_KeyName, a_OldIPv6ValueName);
// If those weren't present or were empty, use the default:"
if (Ports.empty())
{
Ports = StringSplitAndTrim(a_DefaultValue, ";,");
}
a_IniFile.SetValue(a_KeyName, a_PortsValueName, StringsConcat(Ports, ','));
}
return Ports;
}

View File

@ -15,9 +15,7 @@
!! MODIFIED BY FAKETRUTH and madmaxoft!!
*/
#ifndef CIniFile_H
#define CIniFile_H
#pragma once
@ -215,4 +213,22 @@ public:
// tolua_end
#endif
/** Reads the list of ports from the INI file, possibly upgrading from IPv4/IPv6-specific values into new version-agnostic value.
Reads the list of ports from a_PortsValueName. If that value doesn't exist or is empty, the list is combined from values
in a_OldIPv4ValueName and a_OldIPv6ValueName; in this case the old values are removed from the INI file.
If there is none of the three values or they are all empty, the default is used and stored in the Ports value. */
AStringVector ReadUpgradeIniPorts(
cIniFile & a_IniFile,
const AString & a_KeyName,
const AString & a_PortsValueName,
const AString & a_OldIPv4ValueName,
const AString & a_OldIPv6ValueName,
const AString & a_DefaultValue
);

View File

@ -13,12 +13,9 @@ SET (SRCS
HostnameLookup.cpp
IPLookup.cpp
IsThread.cpp
ListenThread.cpp
NetworkSingleton.cpp
Semaphore.cpp
ServerHandleImpl.cpp
Socket.cpp
SocketThreads.cpp
StackTrace.cpp
TCPLinkImpl.cpp
)
@ -32,14 +29,11 @@ SET (HDRS
HostnameLookup.h
IPLookup.h
IsThread.h
ListenThread.h
Network.h
NetworkSingleton.h
Queue.h
Semaphore.h
ServerHandleImpl.h
Socket.h
SocketThreads.h
StackTrace.h
TCPLinkImpl.h
)
@ -52,6 +46,6 @@ if(NOT MSVC)
target_link_libraries(OSSupport rt)
endif()
target_link_libraries(OSSupport pthread)
target_link_libraries(OSSupport pthread event_core event_extra)
endif()
endif()

View File

@ -126,7 +126,7 @@ public:
/** Returns the entire contents of the specified file as a string. Returns empty string on error. */
static AString ReadWholeFile(const AString & a_FileName);
// tolua_end
/** Returns the list of all items in the specified folder (files, folders, nix pipes, whatever's there). */

View File

@ -1,238 +0,0 @@
// ListenThread.cpp
// Implements the cListenThread class representing the thread that listens for client connections
#include "Globals.h"
#include "ListenThread.h"
cListenThread::cListenThread(cCallback & a_Callback, cSocket::eFamily a_Family, const AString & a_ServiceName) :
super(Printf("ListenThread %s", a_ServiceName.c_str())),
m_Callback(a_Callback),
m_Family(a_Family),
m_ShouldReuseAddr(false),
m_ServiceName(a_ServiceName)
{
}
cListenThread::~cListenThread()
{
Stop();
}
bool cListenThread::Initialize(const AString & a_PortsString)
{
ASSERT(m_Sockets.empty()); // Not yet started
if (!CreateSockets(a_PortsString))
{
return false;
}
return true;
}
bool cListenThread::Start(void)
{
if (m_Sockets.empty())
{
// There are no sockets listening, either forgotten to initialize or the user specified no listening ports
// Report as successful, though
return true;
}
return super::Start();
}
void cListenThread::Stop(void)
{
if (m_Sockets.empty())
{
// No sockets means no thread was running in the first place
return;
}
m_ShouldTerminate = true;
// Close one socket to wake the thread up from the select() call
m_Sockets[0].CloseSocket();
// Wait for the thread to finish
super::Wait();
// Close all the listening sockets:
for (cSockets::iterator itr = m_Sockets.begin() + 1, end = m_Sockets.end(); itr != end; ++itr)
{
itr->CloseSocket();
} // for itr - m_Sockets[]
m_Sockets.clear();
}
void cListenThread::SetReuseAddr(bool a_Reuse)
{
ASSERT(m_Sockets.empty()); // Must not have been Initialize()d yet
m_ShouldReuseAddr = a_Reuse;
}
bool cListenThread::CreateSockets(const AString & a_PortsString)
{
AStringVector Ports = StringSplitAndTrim(a_PortsString, ",");
if (Ports.empty())
{
return false;
}
AString FamilyStr = m_ServiceName;
switch (m_Family)
{
case cSocket::IPv4: FamilyStr.append(" IPv4"); break;
case cSocket::IPv6: FamilyStr.append(" IPv6"); break;
default:
{
ASSERT(!"Unknown address family");
break;
}
}
for (AStringVector::const_iterator itr = Ports.begin(), end = Ports.end(); itr != end; ++itr)
{
int Port = atoi(itr->c_str());
if ((Port <= 0) || (Port > 65535))
{
LOGWARNING("%s: Invalid port specified: \"%s\".", FamilyStr.c_str(), itr->c_str());
continue;
}
m_Sockets.push_back(cSocket::CreateSocket(m_Family));
if (!m_Sockets.back().IsValid())
{
LOGWARNING("%s: Cannot create listening socket for port %d: \"%s\"", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
m_Sockets.pop_back();
continue;
}
if (m_ShouldReuseAddr)
{
if (!m_Sockets.back().SetReuseAddress())
{
LOG("%s: Port %d cannot reuse addr, syscall failed: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
}
}
// Bind to port:
bool res = false;
switch (m_Family)
{
case cSocket::IPv4: res = m_Sockets.back().BindToAnyIPv4(Port); break;
case cSocket::IPv6: res = m_Sockets.back().BindToAnyIPv6(Port); break;
default:
{
ASSERT(!"Unknown address family");
res = false;
}
}
if (!res)
{
LOGWARNING("%s: Cannot bind port %d: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
m_Sockets.pop_back();
continue;
}
if (!m_Sockets.back().Listen())
{
LOGWARNING("%s: Cannot listen on port %d: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
m_Sockets.pop_back();
continue;
}
LOGINFO("%s: Port %d is open for connections", FamilyStr.c_str(), Port);
} // for itr - Ports[]
return !(m_Sockets.empty());
}
void cListenThread::Execute(void)
{
if (m_Sockets.empty())
{
LOGD("Empty cListenThread, ending thread now.");
return;
}
// Find the highest socket number:
cSocket::xSocket Highest = m_Sockets[0].GetSocket();
for (cSockets::iterator itr = m_Sockets.begin(), end = m_Sockets.end(); itr != end; ++itr)
{
if (itr->GetSocket() > Highest)
{
Highest = itr->GetSocket();
}
} // for itr - m_Sockets[]
while (!m_ShouldTerminate)
{
// Put all sockets into a FD set:
fd_set fdRead;
FD_ZERO(&fdRead);
for (cSockets::iterator itr = m_Sockets.begin(), end = m_Sockets.end(); itr != end; ++itr)
{
FD_SET(itr->GetSocket(), &fdRead);
} // for itr - m_Sockets[]
timeval tv; // On Linux select() doesn't seem to wake up when socket is closed, so let's kinda busy-wait:
tv.tv_sec = 1;
tv.tv_usec = 0;
if (select((int)Highest + 1, &fdRead, nullptr, nullptr, &tv) == -1)
{
LOG("select(R) call failed in cListenThread: \"%s\"", cSocket::GetLastErrorString().c_str());
continue;
}
for (cSockets::iterator itr = m_Sockets.begin(), end = m_Sockets.end(); itr != end; ++itr)
{
if (itr->IsValid() && FD_ISSET(itr->GetSocket(), &fdRead))
{
cSocket Client = (m_Family == cSocket::IPv4) ? itr->AcceptIPv4() : itr->AcceptIPv6();
if (Client.IsValid())
{
m_Callback.OnConnectionAccepted(Client);
}
}
} // for itr - m_Sockets[]
} // while (!m_ShouldTerminate)
}

View File

@ -1,85 +0,0 @@
// ListenThread.h
// Declares the cListenThread class representing the thread that listens for client connections
#pragma once
#include "IsThread.h"
#include "Socket.h"
// fwd:
class cServer;
class cListenThread :
public cIsThread
{
typedef cIsThread super;
public:
/** Used as the callback for connection events */
class cCallback
{
public:
virtual ~cCallback() {}
/** This callback is called whenever a socket connection is accepted */
virtual void OnConnectionAccepted(cSocket & a_Socket) = 0;
} ;
cListenThread(cCallback & a_Callback, cSocket::eFamily a_Family, const AString & a_ServiceName = "");
~cListenThread();
/** Creates all the sockets, returns trus if successful, false if not. */
bool Initialize(const AString & a_PortsString);
bool Start(void);
void Stop(void);
/** Call before Initialize() to set the "reuse" flag on the sockets */
void SetReuseAddr(bool a_Reuse = true);
protected:
typedef std::vector<cSocket> cSockets;
/** The callback which to notify of incoming connections */
cCallback & m_Callback;
/** Socket address family to use */
cSocket::eFamily m_Family;
/** Sockets that are being monitored */
cSockets m_Sockets;
/** If set to true, the SO_REUSEADDR socket option is set to true */
bool m_ShouldReuseAddr;
/** Name of the service that's listening on the ports; for logging purposes only */
AString m_ServiceName;
/** Fills in m_Sockets with individual sockets, each for one port specified in a_PortsString.
Returns true if successful and at least one socket has been created
*/
bool CreateSockets(const AString & a_PortsString);
// cIsThread override:
virtual void Execute(void) override;
} ;

View File

@ -18,7 +18,8 @@
cNetworkSingleton::cNetworkSingleton(void)
cNetworkSingleton::cNetworkSingleton(void):
m_HasTerminated(false)
{
// Windows: initialize networking:
#ifdef _WIN32
@ -72,6 +73,29 @@ cNetworkSingleton::cNetworkSingleton(void)
cNetworkSingleton::~cNetworkSingleton()
{
// Check that Terminate has been called already:
ASSERT(m_HasTerminated);
}
cNetworkSingleton & cNetworkSingleton::Get(void)
{
static cNetworkSingleton Instance;
return Instance;
}
void cNetworkSingleton::Terminate(void)
{
ASSERT(!m_HasTerminated);
m_HasTerminated = true;
// Wait for the LibEvent event loop to terminate:
event_base_loopbreak(m_EventBase);
m_EventLoopTerminated.Wait();
@ -96,16 +120,6 @@ cNetworkSingleton::~cNetworkSingleton()
cNetworkSingleton & cNetworkSingleton::Get(void)
{
static cNetworkSingleton Instance;
return Instance;
}
void cNetworkSingleton::LogCallback(int a_Severity, const char * a_Msg)
{
switch (a_Severity)
@ -138,6 +152,7 @@ void cNetworkSingleton::RunEventLoop(cNetworkSingleton * a_Self)
void cNetworkSingleton::AddHostnameLookup(cHostnameLookupPtr a_HostnameLookup)
{
ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
m_HostnameLookups.push_back(a_HostnameLookup);
}
@ -148,6 +163,7 @@ void cNetworkSingleton::AddHostnameLookup(cHostnameLookupPtr a_HostnameLookup)
void cNetworkSingleton::RemoveHostnameLookup(const cHostnameLookup * a_HostnameLookup)
{
ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
for (auto itr = m_HostnameLookups.begin(), end = m_HostnameLookups.end(); itr != end; ++itr)
{
@ -165,6 +181,7 @@ void cNetworkSingleton::RemoveHostnameLookup(const cHostnameLookup * a_HostnameL
void cNetworkSingleton::AddIPLookup(cIPLookupPtr a_IPLookup)
{
ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
m_IPLookups.push_back(a_IPLookup);
}
@ -175,6 +192,7 @@ void cNetworkSingleton::AddIPLookup(cIPLookupPtr a_IPLookup)
void cNetworkSingleton::RemoveIPLookup(const cIPLookup * a_IPLookup)
{
ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
for (auto itr = m_IPLookups.begin(), end = m_IPLookups.end(); itr != end; ++itr)
{
@ -192,6 +210,7 @@ void cNetworkSingleton::RemoveIPLookup(const cIPLookup * a_IPLookup)
void cNetworkSingleton::AddLink(cTCPLinkImplPtr a_Link)
{
ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
m_Connections.push_back(a_Link);
}
@ -202,6 +221,7 @@ void cNetworkSingleton::AddLink(cTCPLinkImplPtr a_Link)
void cNetworkSingleton::RemoveLink(const cTCPLinkImpl * a_Link)
{
ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
for (auto itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr)
{
@ -219,6 +239,7 @@ void cNetworkSingleton::RemoveLink(const cTCPLinkImpl * a_Link)
void cNetworkSingleton::AddServer(cServerHandleImplPtr a_Server)
{
ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
m_Servers.push_back(a_Server);
}
@ -229,6 +250,7 @@ void cNetworkSingleton::AddServer(cServerHandleImplPtr a_Server)
void cNetworkSingleton::RemoveServer(const cServerHandleImpl * a_Server)
{
ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
for (auto itr = m_Servers.begin(), end = m_Servers.end(); itr != end; ++itr)
{

View File

@ -4,7 +4,8 @@
// Declares the cNetworkSingleton class representing the storage for global data pertaining to network API
// such as a list of all connections, all listening sockets and the LibEvent dispatch thread.
// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead
// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead;
// the only exception being the main app entrypoint that needs to call Terminate before quitting.
@ -48,6 +49,11 @@ public:
/** Returns the singleton instance of this class */
static cNetworkSingleton & Get(void);
/** Terminates all network-related threads.
To be used only on app shutdown.
MSVC runtime requires that the LibEvent networking be shut down before the main() function is exitted; this is the way to do it. */
void Terminate(void);
/** Returns the main LibEvent handle for event registering. */
event_base * GetEventBase(void) { return m_EventBase; }
@ -113,6 +119,9 @@ protected:
/** Event that gets signalled when the event loop terminates. */
cEvent m_EventLoopTerminated;
/** Set to true if Terminate has been called. */
volatile bool m_HasTerminated;
/** Initializes the LibEvent internals. */
cNetworkSingleton(void);

View File

@ -83,6 +83,9 @@ void cServerHandleImpl::Close(void)
// Remove the ptr to self, so that the object may be freed:
m_SelfPtr.reset();
// Remove self from cNetworkSingleton:
cNetworkSingleton::Get().RemoveServer(this);
}
@ -157,10 +160,6 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
int res = setsockopt(MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero));
err = EVUTIL_SOCKET_ERROR();
NeedsTwoSockets = ((res == SOCKET_ERROR) && (err == WSAENOPROTOOPT));
LOGD("setsockopt(IPV6_V6ONLY) returned %d, err is %d (%s). %s",
res, err, evutil_socket_error_to_string(err),
NeedsTwoSockets ? "Second socket will be created" : "Second socket not needed"
);
#else
setsockopt(MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero));
#endif
@ -256,19 +255,20 @@ void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_
// Get the textual IP address and port number out of a_Addr:
char IPAddress[128];
evutil_inet_ntop(a_Addr->sa_family, a_Addr->sa_data, IPAddress, ARRAYCOUNT(IPAddress));
UInt16 Port = 0;
switch (a_Addr->sa_family)
{
case AF_INET:
{
sockaddr_in * sin = reinterpret_cast<sockaddr_in *>(a_Addr);
evutil_inet_ntop(AF_INET, sin, IPAddress, ARRAYCOUNT(IPAddress));
Port = ntohs(sin->sin_port);
break;
}
case AF_INET6:
{
sockaddr_in6 * sin6 = reinterpret_cast<sockaddr_in6 *>(a_Addr);
evutil_inet_ntop(AF_INET, sin6, IPAddress, ARRAYCOUNT(IPAddress));
Port = ntohs(sin6->sin6_port);
break;
}

View File

@ -1,702 +0,0 @@
// cSocketThreads.cpp
// Implements the cSocketThreads class representing the heart of MCS's client networking.
// This object takes care of network communication, groups sockets into threads and uses as little threads as possible for full read / write support
// For more detail, see http://forum.mc-server.org/showthread.php?tid=327
#include "Globals.h"
#include "SocketThreads.h"
#include "Errors.h"
////////////////////////////////////////////////////////////////////////////////
// cSocketThreads:
cSocketThreads::cSocketThreads(void)
{
}
cSocketThreads::~cSocketThreads()
{
for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
{
delete *itr;
} // for itr - m_Threads[]
m_Threads.clear();
}
bool cSocketThreads::AddClient(const cSocket & a_Socket, cCallback * a_Client)
{
// Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client
// Try to add to existing threads:
cCSLock Lock(m_CS);
for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
{
if ((*itr)->IsValid() && (*itr)->HasEmptySlot())
{
(*itr)->AddClient(a_Socket, a_Client);
return true;
}
}
// No thread has free space, create a new one:
LOGD("Creating a new cSocketThread (currently have " SIZE_T_FMT ")", m_Threads.size());
cSocketThread * Thread = new cSocketThread(this);
if (!Thread->Start())
{
// There was an error launching the thread (but it was already logged along with the reason)
LOGERROR("A new cSocketThread failed to start");
delete Thread;
Thread = nullptr;
return false;
}
Thread->AddClient(a_Socket, a_Client);
m_Threads.push_back(Thread);
return true;
}
void cSocketThreads::RemoveClient(const cCallback * a_Client)
{
// Remove the associated socket and the client from processing
cCSLock Lock(m_CS);
for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
{
if ((*itr)->RemoveClient(a_Client))
{
return;
}
} // for itr - m_Threads[]
// This client wasn't found.
// It's not an error, because it may have been removed by a different thread in the meantime.
}
void cSocketThreads::NotifyWrite(const cCallback * a_Client)
{
// Notifies the thread responsible for a_Client that the client has something to write
cCSLock Lock(m_CS);
for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
{
if ((*itr)->NotifyWrite(a_Client))
{
return;
}
} // for itr - m_Threads[]
// Cannot assert - this normally happens if a client disconnects and has pending packets, the cServer::cNotifyWriteThread will call this on invalid clients too
// ASSERT(!"Notifying write to an unknown client");
}
void cSocketThreads::Write(const cCallback * a_Client, const AString & a_Data)
{
// Puts a_Data into outgoing data queue for a_Client
cCSLock Lock(m_CS);
for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
{
if ((*itr)->Write(a_Client, a_Data))
{
return;
}
} // for itr - m_Threads[]
// This may be perfectly legal, if the socket has been destroyed and the client is finishing up
// ASSERT(!"Writing to an unknown socket");
}
////////////////////////////////////////////////////////////////////////////////
// cSocketThreads::cSocketThread:
cSocketThreads::cSocketThread::cSocketThread(cSocketThreads * a_Parent) :
cIsThread("cSocketThread"),
m_Parent(a_Parent),
m_NumSlots(0)
{
// Nothing needed yet
}
cSocketThreads::cSocketThread::~cSocketThread()
{
m_ShouldTerminate = true;
// Notify the thread:
ASSERT(m_ControlSocket2.IsValid());
m_ControlSocket2.Send("a", 1);
// Wait for the thread to finish:
Wait();
// Close the control sockets:
m_ControlSocket1.CloseSocket();
m_ControlSocket2.CloseSocket();
}
void cSocketThreads::cSocketThread::AddClient(const cSocket & a_Socket, cCallback * a_Client)
{
ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
ASSERT(m_NumSlots < MAX_SLOTS); // Use HasEmptySlot() to check before adding
m_Slots[m_NumSlots].m_Client = a_Client;
m_Slots[m_NumSlots].m_Socket = a_Socket;
m_Slots[m_NumSlots].m_Socket.SetNonBlocking();
m_Slots[m_NumSlots].m_Outgoing.clear();
m_Slots[m_NumSlots].m_State = sSlot::ssNormal;
m_NumSlots++;
// Notify the thread of the change:
ASSERT(m_ControlSocket2.IsValid());
m_ControlSocket2.Send("a", 1);
}
bool cSocketThreads::cSocketThread::RemoveClient(const cCallback * a_Client)
{
ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
if (m_NumSlots == 0)
{
return false;
}
for (int i = m_NumSlots - 1; i >= 0 ; --i)
{
if (m_Slots[i].m_Client != a_Client)
{
continue;
}
// Found the slot:
if (m_Slots[i].m_State == sSlot::ssRemoteClosed)
{
// The remote has already closed the socket, remove the slot altogether:
if (m_Slots[i].m_Socket.IsValid())
{
m_Slots[i].m_Socket.CloseSocket();
}
m_Slots[i] = m_Slots[--m_NumSlots];
}
else
{
// Query and queue the last batch of outgoing data:
AString Data;
m_Slots[i].m_Client->GetOutgoingData(Data);
m_Slots[i].m_Outgoing.append(Data);
if (m_Slots[i].m_Outgoing.empty())
{
// No more outgoing data, shut the socket down immediately:
m_Slots[i].m_Socket.ShutdownReadWrite();
m_Slots[i].m_State = sSlot::ssShuttingDown;
}
else
{
// More data to send, shut down reading and wait for the rest to get sent:
m_Slots[i].m_State = sSlot::ssWritingRestOut;
}
m_Slots[i].m_Client = nullptr;
}
// Notify the thread of the change:
ASSERT(m_ControlSocket2.IsValid());
m_ControlSocket2.Send("r", 1);
return true;
} // for i - m_Slots[]
// Not found
return false;
}
bool cSocketThreads::cSocketThread::HasClient(const cCallback * a_Client) const
{
ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
for (int i = m_NumSlots - 1; i >= 0; --i)
{
if (m_Slots[i].m_Client == a_Client)
{
return true;
}
} // for i - m_Slots[]
return false;
}
bool cSocketThreads::cSocketThread::HasSocket(const cSocket * a_Socket) const
{
for (int i = m_NumSlots - 1; i >= 0; --i)
{
if (m_Slots[i].m_Socket == *a_Socket)
{
return true;
}
} // for i - m_Slots[]
return false;
}
bool cSocketThreads::cSocketThread::NotifyWrite(const cCallback * a_Client)
{
ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
if (HasClient(a_Client))
{
// Notify the thread that there's another packet in the queue:
ASSERT(m_ControlSocket2.IsValid());
m_ControlSocket2.Send("q", 1);
return true;
}
return false;
}
bool cSocketThreads::cSocketThread::Write(const cCallback * a_Client, const AString & a_Data)
{
ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
for (int i = m_NumSlots - 1; i >= 0; --i)
{
if (m_Slots[i].m_Client == a_Client)
{
m_Slots[i].m_Outgoing.append(a_Data);
// Notify the thread that there's data in the queue:
ASSERT(m_ControlSocket2.IsValid());
m_ControlSocket2.Send("q", 1);
return true;
}
} // for i - m_Slots[]
return false;
}
bool cSocketThreads::cSocketThread::Start(void)
{
// Create the control socket listener
m_ControlSocket2 = cSocket::CreateSocket(cSocket::IPv4);
if (!m_ControlSocket2.IsValid())
{
LOGERROR("Cannot create a Control socket for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
return false;
}
if (!m_ControlSocket2.BindToLocalhostIPv4(cSocket::ANY_PORT))
{
LOGERROR("Cannot bind a Control socket for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
m_ControlSocket2.CloseSocket();
return false;
}
if (!m_ControlSocket2.Listen(1))
{
LOGERROR("Cannot listen on a Control socket for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
m_ControlSocket2.CloseSocket();
return false;
}
if (m_ControlSocket2.GetPort() == 0)
{
LOGERROR("Cannot determine Control socket port (\"%s\"); conitnuing, but the server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
m_ControlSocket2.CloseSocket();
return false;
}
// Start the thread
if (!super::Start())
{
LOGERROR("Cannot start new cSocketThread");
m_ControlSocket2.CloseSocket();
return false;
}
// Finish connecting the control socket by accepting connection from the thread's socket
cSocket tmp = m_ControlSocket2.AcceptIPv4();
if (!tmp.IsValid())
{
LOGERROR("Cannot link Control sockets for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
m_ControlSocket2.CloseSocket();
return false;
}
m_ControlSocket2.CloseSocket();
m_ControlSocket2 = tmp;
return true;
}
void cSocketThreads::cSocketThread::Execute(void)
{
// Connect the "client" part of the Control socket:
m_ControlSocket1 = cSocket::CreateSocket(cSocket::IPv4);
ASSERT(m_ControlSocket2.GetPort() != 0); // We checked in the Start() method, but let's be sure
if (!m_ControlSocket1.ConnectToLocalhostIPv4(m_ControlSocket2.GetPort()))
{
LOGERROR("Cannot connect Control sockets for a cSocketThread (\"%s\"); continuing, but the server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
m_ControlSocket2.CloseSocket();
return;
}
// The main thread loop:
while (!m_ShouldTerminate)
{
// Read outgoing data from the clients:
QueueOutgoingData();
// Put sockets into the sets
fd_set fdRead;
fd_set fdWrite;
cSocket::xSocket Highest = m_ControlSocket1.GetSocket();
PrepareSets(&fdRead, &fdWrite, Highest);
// Wait for the sockets:
timeval Timeout;
Timeout.tv_sec = 5;
Timeout.tv_usec = 0;
if (select((int)Highest + 1, &fdRead, &fdWrite, nullptr, &Timeout) == -1)
{
LOG("select() call failed in cSocketThread: \"%s\"", cSocket::GetLastErrorString().c_str());
continue;
}
// Perform the IO:
ReadFromSockets(&fdRead);
WriteToSockets(&fdWrite);
CleanUpShutSockets();
} // while (!mShouldTerminate)
}
void cSocketThreads::cSocketThread::PrepareSets(fd_set * a_Read, fd_set * a_Write, cSocket::xSocket & a_Highest)
{
FD_ZERO(a_Read);
FD_ZERO(a_Write);
FD_SET(m_ControlSocket1.GetSocket(), a_Read);
cCSLock Lock(m_Parent->m_CS);
for (int i = m_NumSlots - 1; i >= 0; --i)
{
if (!m_Slots[i].m_Socket.IsValid())
{
continue;
}
if (m_Slots[i].m_State == sSlot::ssRemoteClosed)
{
// This socket won't provide nor consume any data anymore, don't put it in the Set
continue;
}
cSocket::xSocket s = m_Slots[i].m_Socket.GetSocket();
FD_SET(s, a_Read);
if (s > a_Highest)
{
a_Highest = s;
}
if (!m_Slots[i].m_Outgoing.empty())
{
// There's outgoing data for the socket, put it in the Write set
FD_SET(s, a_Write);
}
} // for i - m_Slots[]
}
void cSocketThreads::cSocketThread::ReadFromSockets(fd_set * a_Read)
{
// Read on available sockets:
// Reset Control socket state:
if (FD_ISSET(m_ControlSocket1.GetSocket(), a_Read))
{
char Dummy[128];
m_ControlSocket1.Receive(Dummy, sizeof(Dummy), 0);
}
// Read from clients:
cCSLock Lock(m_Parent->m_CS);
for (int i = m_NumSlots - 1; i >= 0; --i)
{
cSocket::xSocket Socket = m_Slots[i].m_Socket.GetSocket();
if (!cSocket::IsValidSocket(Socket) || !FD_ISSET(Socket, a_Read))
{
continue;
}
char Buffer[1024];
int Received = m_Slots[i].m_Socket.Receive(Buffer, ARRAYCOUNT(Buffer), 0);
if (Received <= 0)
{
if (cSocket::GetLastError() != cSocket::ErrWouldBlock)
{
// The socket has been closed by the remote party
switch (m_Slots[i].m_State)
{
case sSlot::ssNormal:
{
// Close the socket on our side:
m_Slots[i].m_State = sSlot::ssRemoteClosed;
m_Slots[i].m_Socket.CloseSocket();
// Notify the callback that the remote has closed the socket, *after* removing the socket:
cCallback * client = m_Slots[i].m_Client;
m_Slots[i] = m_Slots[--m_NumSlots];
if (client != nullptr)
{
client->SocketClosed();
}
break;
}
case sSlot::ssWritingRestOut:
case sSlot::ssShuttingDown:
case sSlot::ssShuttingDown2:
{
// Force-close the socket and remove the slot:
m_Slots[i].m_Socket.CloseSocket();
m_Slots[i] = m_Slots[--m_NumSlots];
break;
}
default:
{
LOG("%s: Unexpected socket state: %d (%s)",
__FUNCTION__, m_Slots[i].m_Socket.GetSocket(), m_Slots[i].m_Socket.GetIPString().c_str()
);
ASSERT(!"Unexpected socket state");
break;
}
} // switch (m_Slots[i].m_State)
}
}
else
{
if (m_Slots[i].m_Client != nullptr)
{
m_Slots[i].m_Client->DataReceived(Buffer, Received);
}
}
} // for i - m_Slots[]
}
void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write)
{
// Write to available client sockets:
cCSLock Lock(m_Parent->m_CS);
for (int i = m_NumSlots - 1; i >= 0; --i)
{
cSocket::xSocket Socket = m_Slots[i].m_Socket.GetSocket();
if (!cSocket::IsValidSocket(Socket) || !FD_ISSET(Socket, a_Write))
{
continue;
}
if (m_Slots[i].m_Outgoing.empty())
{
// Request another chunk of outgoing data:
if (m_Slots[i].m_Client != nullptr)
{
AString Data;
m_Slots[i].m_Client->GetOutgoingData(Data);
m_Slots[i].m_Outgoing.append(Data);
}
if (m_Slots[i].m_Outgoing.empty())
{
// No outgoing data is ready
if (m_Slots[i].m_State == sSlot::ssWritingRestOut)
{
m_Slots[i].m_State = sSlot::ssShuttingDown;
m_Slots[i].m_Socket.ShutdownReadWrite();
}
continue;
}
} // if (outgoing data is empty)
if (m_Slots[i].m_State == sSlot::ssRemoteClosed)
{
continue;
}
if (!SendDataThroughSocket(m_Slots[i].m_Socket, m_Slots[i].m_Outgoing))
{
int Err = cSocket::GetLastError();
LOGWARNING("Error %d while writing to client \"%s\", disconnecting. \"%s\"", Err, m_Slots[i].m_Socket.GetIPString().c_str(), GetOSErrorString(Err).c_str());
m_Slots[i].m_Socket.CloseSocket();
if (m_Slots[i].m_Client != nullptr)
{
m_Slots[i].m_Client->SocketClosed();
}
continue;
}
if (m_Slots[i].m_Outgoing.empty() && (m_Slots[i].m_State == sSlot::ssWritingRestOut))
{
m_Slots[i].m_State = sSlot::ssShuttingDown;
m_Slots[i].m_Socket.ShutdownReadWrite();
}
// _X: If there's data left, it means the client is not reading fast enough, the server would unnecessarily spin in the main loop with zero actions taken; so signalling is disabled
// This means that if there's data left, it will be sent only when there's incoming data or someone queues another packet (for any socket handled by this thread)
/*
// If there's any data left, signalize the Control socket:
if (!m_Slots[i].m_Outgoing.empty())
{
ASSERT(m_ControlSocket2.IsValid());
m_ControlSocket2.Send("q", 1);
}
*/
} // for i - m_Slots[i]
}
bool cSocketThreads::cSocketThread::SendDataThroughSocket(cSocket & a_Socket, AString & a_Data)
{
// Send data in smaller chunks, so that the OS send buffers aren't overflown easily
while (!a_Data.empty())
{
size_t NumToSend = std::min(a_Data.size(), (size_t)1024);
int Sent = a_Socket.Send(a_Data.data(), NumToSend);
if (Sent < 0)
{
int Err = cSocket::GetLastError();
if (Err == cSocket::ErrWouldBlock)
{
// The OS send buffer is full, leave the outgoing data for the next time
return true;
}
// An error has occured
return false;
}
if (Sent == 0)
{
a_Socket.CloseSocket();
return true;
}
a_Data.erase(0, Sent);
}
return true;
}
void cSocketThreads::cSocketThread::CleanUpShutSockets(void)
{
cCSLock Lock(m_Parent->m_CS);
for (int i = m_NumSlots - 1; i >= 0; i--)
{
switch (m_Slots[i].m_State)
{
case sSlot::ssShuttingDown2:
{
// The socket has reached the shutdown timeout, close it and clear its slot:
m_Slots[i].m_Socket.CloseSocket();
m_Slots[i] = m_Slots[--m_NumSlots];
break;
}
case sSlot::ssShuttingDown:
{
// The socket has been shut down for a single thread loop, let it loop once more before closing:
m_Slots[i].m_State = sSlot::ssShuttingDown2;
break;
}
default: break;
}
} // for i - m_Slots[]
}
void cSocketThreads::cSocketThread::QueueOutgoingData(void)
{
cCSLock Lock(m_Parent->m_CS);
for (int i = 0; i < m_NumSlots; i++)
{
if (m_Slots[i].m_Client != nullptr)
{
AString Data;
m_Slots[i].m_Client->GetOutgoingData(Data);
m_Slots[i].m_Outgoing.append(Data);
}
if (m_Slots[i].m_Outgoing.empty())
{
// No outgoing data is ready
if (m_Slots[i].m_State == sSlot::ssWritingRestOut)
{
// The socket doesn't want to be kept alive anymore, and doesn't have any remaining data to send.
// Shut it down and then close it after a timeout, or when the other side agrees
m_Slots[i].m_State = sSlot::ssShuttingDown;
m_Slots[i].m_Socket.ShutdownReadWrite();
}
continue;
}
}
}

View File

@ -1,194 +0,0 @@
// SocketThreads.h
// Interfaces to the cSocketThreads class representing the heart of MCS's client networking.
// This object takes care of network communication, groups sockets into threads and uses as little threads as possible for full read / write support
// For more detail, see http://forum.mc-server.org/showthread.php?tid=327
/*
Additional details:
When a client wants to terminate the connection, they call the RemoveClient() function. This calls the
callback one last time to read all the available outgoing data, putting it in the slot's m_OutgoingData
buffer. Then it marks the slot as having no callback. The socket is kept alive until its outgoing data
queue is empty, then shutdown is called on it and finally the socket is closed after a timeout.
If at any time within this the remote end closes the socket, then the socket is closed directly.
As soon as the socket is closed, the slot is finally removed from the SocketThread.
The graph in $/docs/SocketThreads States.gv shows the state-machine transitions of the slot.
*/
/** How many clients should one thread handle? (must be less than FD_SETSIZE for your platform) */
#define MAX_SLOTS 63
#pragma once
#include "Socket.h"
#include "IsThread.h"
// Check MAX_SLOTS:
#if MAX_SLOTS >= FD_SETSIZE
#error "MAX_SLOTS must be less than FD_SETSIZE for your platform! (otherwise select() won't work)"
#endif
// fwd:
class cSocket;
class cClientHandle;
class cSocketThreads
{
public:
// Clients of cSocketThreads must implement this interface to be able to communicate
class cCallback
{
public:
// Force a virtual destructor in all subclasses:
virtual ~cCallback() {}
/** Called when data is received from the remote party.
SocketThreads does not care about the return value, others can use it for their specific purpose -
for example HTTPServer uses it to signal if the connection was terminated as a result of the data received. */
virtual bool DataReceived(const char * a_Data, size_t a_Size) = 0;
/** Called when data can be sent to remote party
The function is supposed to *set* outgoing data to a_Data (overwrite) */
virtual void GetOutgoingData(AString & a_Data) = 0;
/** Called when the socket has been closed for any reason */
virtual void SocketClosed(void) = 0;
} ;
cSocketThreads(void);
~cSocketThreads();
/** Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client; returns true if successful */
bool AddClient(const cSocket & a_Socket, cCallback * a_Client);
/** Remove the associated socket and the client from processing.
The socket is left to send its last outgoing data and is removed only after all its m_Outgoing is sent
and after the socket is properly shutdown (unless the remote disconnects before that)
*/
void RemoveClient(const cCallback * a_Client);
/** Notify the thread responsible for a_Client that the client has something to write */
void NotifyWrite(const cCallback * a_Client);
/** Puts a_Data into outgoing data queue for a_Client */
void Write(const cCallback * a_Client, const AString & a_Data);
private:
class cSocketThread :
public cIsThread
{
typedef cIsThread super;
public:
cSocketThread(cSocketThreads * a_Parent);
virtual ~cSocketThread();
// All these methods assume parent's m_CS is locked
bool HasEmptySlot(void) const {return m_NumSlots < MAX_SLOTS; }
bool IsEmpty (void) const {return m_NumSlots == 0; }
void AddClient (const cSocket & a_Socket, cCallback * a_Client); // Takes ownership of the socket
bool RemoveClient(const cCallback * a_Client); // Returns true if removed, false if not found
bool HasClient (const cCallback * a_Client) const;
bool HasSocket (const cSocket * a_Socket) const;
bool NotifyWrite (const cCallback * a_Client); // Returns true if client handled by this thread
bool Write (const cCallback * a_Client, const AString & a_Data); // Returns true if client handled by this thread
bool Start(void); // Hide the cIsThread's Start method, we need to provide our own startup to create the control socket
bool IsValid(void) const {return m_ControlSocket2.IsValid(); } // If the Control socket dies, the thread is not valid anymore
private:
cSocketThreads * m_Parent;
// Two ends of the control socket, the first is select()-ed, the second is written to for notifications
cSocket m_ControlSocket1;
cSocket m_ControlSocket2;
// Socket-client-dataqueues-state quadruplets.
// Manipulation with these assumes that the parent's m_CS is locked
struct sSlot
{
/** The socket is primarily owned by this object */
cSocket m_Socket;
/** The callback to call for events. May be nullptr */
cCallback * m_Client;
/** If sending writes only partial data, the rest is stored here for another send.
Also used when the slot is being removed to store the last batch of outgoing data. */
AString m_Outgoing;
enum eState
{
ssNormal, ///< Normal read / write operations
ssWritingRestOut, ///< The client callback was removed, continue to send outgoing data
ssShuttingDown, ///< The last outgoing data has been sent, the socket has called shutdown()
ssShuttingDown2, ///< The shutdown has been done at least 1 thread loop ago (timeout detection)
ssRemoteClosed, ///< The remote end has closed the connection (and we still have a client callback)
} m_State;
} ;
sSlot m_Slots[MAX_SLOTS];
int m_NumSlots; // Number of slots actually used
virtual void Execute(void) override;
/** Prepares the Read and Write socket sets for select()
Puts all sockets into the read set, along with m_ControlSocket1.
Only sockets that have outgoing data queued on them are put in the write set.*/
void PrepareSets(fd_set * a_ReadSet, fd_set * a_WriteSet, cSocket::xSocket & a_Highest);
/** Reads from sockets indicated in a_Read */
void ReadFromSockets(fd_set * a_Read);
/** Writes to sockets indicated in a_Write */
void WriteToSockets (fd_set * a_Write);
/** Sends data through the specified socket, trying to fill the OS send buffer in chunks.
Returns true if there was no error while sending, false if an error has occured.
Modifies a_Data to contain only the unsent data. */
bool SendDataThroughSocket(cSocket & a_Socket, AString & a_Data);
/** Removes those slots in ssShuttingDown2 state, sets those with ssShuttingDown state to ssShuttingDown2 */
void CleanUpShutSockets(void);
/** Calls each client's callback to retrieve outgoing data for that client. */
void QueueOutgoingData(void);
} ;
typedef std::list<cSocketThread *> cSocketThreadList;
cCriticalSection m_CS;
cSocketThreadList m_Threads;
} ;

View File

@ -17,8 +17,9 @@
cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks):
super(a_LinkCallbacks),
m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE))
m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE))
{
LOGD("Created new cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent);
}
@ -27,9 +28,11 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks):
cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImplPtr a_Server, const sockaddr * a_Address, socklen_t a_AddrLen):
super(a_LinkCallbacks),
m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), a_Socket, BEV_OPT_CLOSE_ON_FREE)),
m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), a_Socket, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE)),
m_Server(a_Server)
{
LOGD("Created new cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent);
// Update the endpoint addresses:
UpdateLocalAddress();
UpdateAddress(a_Address, a_AddrLen, m_RemoteIP, m_RemotePort);
@ -41,6 +44,7 @@ cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_L
cTCPLinkImpl::~cTCPLinkImpl()
{
LOGD("Deleting cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent);
bufferevent_free(m_BufferEvent);
}
@ -179,6 +183,8 @@ void cTCPLinkImpl::ReadCallback(bufferevent * a_BufferEvent, void * a_Self)
void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self)
{
LOGD("cTCPLink event callback for link %p, BEV %p; what = 0x%02x", a_Self, a_BufferEvent, a_What);
ASSERT(a_Self != nullptr);
cTCPLinkImplPtr Self = static_cast<cTCPLinkImpl *>(a_Self)->m_Self;

View File

@ -10,6 +10,80 @@
////////////////////////////////////////////////////////////////////////////////
// cBlockingSslClientSocketConnectCallbacks:
class cBlockingSslClientSocketConnectCallbacks:
public cNetwork::cConnectCallbacks
{
/** The socket object that is using this instance of the callbacks. */
cBlockingSslClientSocket & m_Socket;
virtual void OnConnected(cTCPLink & a_Link) override
{
m_Socket.OnConnected();
}
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
{
m_Socket.OnConnectError(a_ErrorMsg);
}
public:
cBlockingSslClientSocketConnectCallbacks(cBlockingSslClientSocket & a_Socket):
m_Socket(a_Socket)
{
}
};
////////////////////////////////////////////////////////////////////////////////
// cBlockingSslClientSocketLinkCallbacks:
class cBlockingSslClientSocketLinkCallbacks:
public cTCPLink::cCallbacks
{
cBlockingSslClientSocket & m_Socket;
virtual void OnLinkCreated(cTCPLinkPtr a_Link) override
{
m_Socket.SetLink(a_Link);
}
virtual void OnReceivedData(const char * a_Data, size_t a_Length)
{
m_Socket.OnReceivedData(a_Data, a_Length);
}
virtual void OnRemoteClosed(void)
{
m_Socket.OnDisconnected();
}
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg)
{
m_Socket.OnDisconnected();
}
public:
cBlockingSslClientSocketLinkCallbacks(cBlockingSslClientSocket & a_Socket):
m_Socket(a_Socket)
{
}
};
////////////////////////////////////////////////////////////////////////////////
// cBlockingSslClientSocket:
cBlockingSslClientSocket::cBlockingSslClientSocket(void) :
m_Ssl(*this),
m_IsConnected(false)
@ -32,10 +106,19 @@ bool cBlockingSslClientSocket::Connect(const AString & a_ServerName, UInt16 a_Po
}
// Connect the underlying socket:
m_Socket.CreateSocket(cSocket::IPv4);
if (!m_Socket.ConnectIPv4(a_ServerName.c_str(), a_Port))
m_ServerName = a_ServerName;
if (!cNetwork::Connect(a_ServerName, a_Port,
std::make_shared<cBlockingSslClientSocketConnectCallbacks>(*this),
std::make_shared<cBlockingSslClientSocketLinkCallbacks>(*this))
)
{
return false;
}
// Wait for the connection to succeed or fail:
m_Event.Wait();
if (!m_IsConnected)
{
Printf(m_LastErrorText, "Socket connect failed: %s", m_Socket.GetLastErrorString().c_str());
return false;
}
@ -102,7 +185,7 @@ bool cBlockingSslClientSocket::Send(const void * a_Data, size_t a_NumBytes)
ASSERT(m_IsConnected);
// Keep sending the data until all of it is sent:
const char * Data = (const char *)a_Data;
const char * Data = reinterpret_cast<const char *>(a_Data);
size_t NumBytes = a_NumBytes;
for (;;)
{
@ -156,7 +239,8 @@ void cBlockingSslClientSocket::Disconnect(void)
}
m_Ssl.NotifyClose();
m_Socket.CloseSocket();
m_Socket->Close();
m_Socket.reset();
m_IsConnected = false;
}
@ -166,13 +250,25 @@ void cBlockingSslClientSocket::Disconnect(void)
int cBlockingSslClientSocket::ReceiveEncrypted(unsigned char * a_Buffer, size_t a_NumBytes)
{
int res = m_Socket.Receive((char *)a_Buffer, a_NumBytes, 0);
if (res < 0)
// Wait for any incoming data, if there is none:
cCSLock Lock(m_CSIncomingData);
while (m_IsConnected && m_IncomingData.empty())
{
cCSUnlock Unlock(Lock);
m_Event.Wait();
}
// If we got disconnected, report an error after processing all data:
if (!m_IsConnected && m_IncomingData.empty())
{
// PolarSSL's net routines distinguish between connection reset and general failure, we don't need to
return POLARSSL_ERR_NET_RECV_FAILED;
}
return res;
// Copy the data from the incoming buffer into the specified space:
size_t NumToCopy = std::min(a_NumBytes, m_IncomingData.size());
memcpy(a_Buffer, m_IncomingData.data(), NumToCopy);
m_IncomingData.erase(0, NumToCopy);
return static_cast<int>(NumToCopy);
}
@ -181,13 +277,69 @@ int cBlockingSslClientSocket::ReceiveEncrypted(unsigned char * a_Buffer, size_t
int cBlockingSslClientSocket::SendEncrypted(const unsigned char * a_Buffer, size_t a_NumBytes)
{
int res = m_Socket.Send((const char *)a_Buffer, a_NumBytes);
if (res < 0)
cTCPLinkPtr Socket(m_Socket); // Make a copy so that multiple threads don't race on deleting the socket.
if (Socket == nullptr)
{
return POLARSSL_ERR_NET_SEND_FAILED;
}
if (!Socket->Send(a_Buffer, a_NumBytes))
{
// PolarSSL's net routines distinguish between connection reset and general failure, we don't need to
return POLARSSL_ERR_NET_SEND_FAILED;
}
return res;
return static_cast<int>(a_NumBytes);
}
void cBlockingSslClientSocket::OnConnected(void)
{
m_IsConnected = true;
m_Event.Set();
}
void cBlockingSslClientSocket::OnConnectError(const AString & a_ErrorMsg)
{
LOG("Cannot connect to %s: %s", m_ServerName.c_str(), a_ErrorMsg.c_str());
m_Event.Set();
}
void cBlockingSslClientSocket::OnReceivedData(const char * a_Data, size_t a_Size)
{
{
cCSLock Lock(m_CSIncomingData);
m_IncomingData.append(a_Data, a_Size);
}
m_Event.Set();
}
void cBlockingSslClientSocket::SetLink(cTCPLinkPtr a_Link)
{
m_Socket = a_Link;
}
void cBlockingSslClientSocket::OnDisconnected(void)
{
m_Socket.reset();
m_IsConnected = false;
m_Event.Set();
}

View File

@ -9,8 +9,8 @@
#pragma once
#include "OSSupport/Network.h"
#include "CallbackSslContext.h"
#include "../OSSupport/Socket.h"
@ -51,25 +51,56 @@ public:
const AString & GetLastErrorText(void) const { return m_LastErrorText; }
protected:
friend class cBlockingSslClientSocketConnectCallbacks;
friend class cBlockingSslClientSocketLinkCallbacks;
/** The SSL context used for the socket */
cCallbackSslContext m_Ssl;
/** The underlying socket to the SSL server */
cSocket m_Socket;
cTCPLinkPtr m_Socket;
/** The object used to signal state changes in the socket (the cause of the blocking). */
cEvent m_Event;
/** The trusted CA root cert store, if we are to verify the cert strictly. Set by SetTrustedRootCertsFromString(). */
cX509CertPtr m_CACerts;
/** The expected SSL peer's name, if we are to verify the cert strictly. Set by SetTrustedRootCertsFromString(). */
AString m_ExpectedPeerName;
/** The hostname to which the socket is connecting (stored for error reporting). */
AString m_ServerName;
/** Text of the last error that has occurred. */
AString m_LastErrorText;
/** Set to true if the connection established successfully. */
bool m_IsConnected;
/** Protects m_IncomingData against multithreaded access. */
cCriticalSection m_CSIncomingData;
/** Buffer for the data incoming on the network socket.
Protected by m_CSIncomingData. */
AString m_IncomingData;
/** Called when the connection is established successfully. */
void OnConnected(void);
/** Called when an error occurs while connecting the socket. */
void OnConnectError(const AString & a_ErrorMsg);
/** Called when there's incoming data from the socket. */
void OnReceivedData(const char * a_Data, size_t a_Size);
/** Called when the link for the connection is created. */
void SetLink(cTCPLinkPtr a_Link);
/** Called when the link is disconnected, either gracefully or by an error. */
void OnDisconnected(void);
// cCallbackSslContext::cDataCallbacks overrides:
virtual int ReceiveEncrypted(unsigned char * a_Buffer, size_t a_NumBytes) override;
virtual int SendEncrypted(const unsigned char * a_Buffer, size_t a_NumBytes) override;

View File

@ -70,7 +70,7 @@ int cSslContext::Initialize(bool a_IsClient, const SharedPtr<cCtrDrbgContext> &
// so they're disabled until someone needs them
ssl_set_dbg(&m_Ssl, &SSLDebugMessage, this);
ssl_set_verify(&m_Ssl, &SSLVerifyCert, this);
*/
//*/
/*
// Set ciphersuite to the easiest one to decode, so that the connection can be wireshark-decoded:

View File

@ -108,8 +108,17 @@ cProtocol180::cProtocol180(cClientHandle * a_Client, const AString & a_ServerAdd
{
static int sCounter = 0;
cFile::CreateFolder("CommLogs");
AString FileName = Printf("CommLogs/%x_%d__%s.log", (unsigned)time(nullptr), sCounter++, a_Client->GetIPString().c_str());
m_CommLogFile.Open(FileName, cFile::fmWrite);
AString IP(a_Client->GetIPString());
ReplaceString(IP, ":", "_");
AString FileName = Printf("CommLogs/%x_%d__%s.log",
static_cast<unsigned>(time(nullptr)),
sCounter++,
IP.c_str()
);
if (!m_CommLogFile.Open(FileName, cFile::fmWrite))
{
LOG("Cannot log communication to file, the log file \"%s\" cannot be opened for writing.", FileName.c_str());
}
}
}
@ -1659,7 +1668,7 @@ void cProtocol180::FixItemFramePositions(int a_ObjectData, double & a_PosX, doub
void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size)
{
// Write the incoming data into the comm log file:
if (g_ShouldLogCommIn)
if (g_ShouldLogCommIn && m_CommLogFile.IsOpen())
{
if (m_ReceivedData.GetReadableSpace() > 0)
{
@ -1764,7 +1773,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size)
bb.Write("\0", 1);
// Log the packet info into the comm log file:
if (g_ShouldLogCommIn)
if (g_ShouldLogCommIn && m_CommLogFile.IsOpen())
{
AString PacketData;
bb.ReadAll(PacketData);
@ -1796,7 +1805,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size)
#endif // _DEBUG
// Put a message in the comm log:
if (g_ShouldLogCommIn)
if (g_ShouldLogCommIn && m_CommLogFile.IsOpen())
{
m_CommLogFile.Printf("^^^^^^ Unhandled packet ^^^^^^\n\n\n");
}
@ -1813,7 +1822,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size)
);
// Put a message in the comm log:
if (g_ShouldLogCommIn)
if (g_ShouldLogCommIn && m_CommLogFile.IsOpen())
{
m_CommLogFile.Printf("^^^^^^ Wrong number of bytes read for this packet (exp %d left, got " SIZE_T_FMT " left) ^^^^^^\n\n\n",
1, bb.GetReadableSpace()
@ -1827,7 +1836,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size)
} // for (ever)
// Log any leftover bytes into the logfile:
if (g_ShouldLogCommIn && (m_ReceivedData.GetReadableSpace() > 0))
if (g_ShouldLogCommIn && (m_ReceivedData.GetReadableSpace() > 0) && m_CommLogFile.IsOpen())
{
AString AllData;
size_t OldReadableSpace = m_ReceivedData.GetReadableSpace();
@ -2798,7 +2807,7 @@ cProtocol180::cPacketizer::~cPacketizer()
}
// Log the comm into logfile:
if (g_ShouldLogCommOut)
if (g_ShouldLogCommOut && m_Protocol.m_CommLogFile.IsOpen())
{
AString Hex;
ASSERT(PacketData.size() > 0);

View File

@ -38,6 +38,43 @@ enum
////////////////////////////////////////////////////////////////////////////////
// cRCONListenCallbacks:
class cRCONListenCallbacks:
public cNetwork::cListenCallbacks
{
public:
cRCONListenCallbacks(cRCONServer & a_RCONServer, UInt16 a_Port):
m_RCONServer(a_RCONServer),
m_Port(a_Port)
{
}
protected:
/** The RCON server instance that we're attached to. */
cRCONServer & m_RCONServer;
/** The port for which this instance is responsible. */
UInt16 m_Port;
// cNetwork::cListenCallbacks overrides:
virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override
{
LOG("RCON Client \"%s\" connected!", a_RemoteIPAddress.c_str());
return std::make_shared<cRCONServer::cConnection>(m_RCONServer, a_RemoteIPAddress);
}
virtual void OnAccepted(cTCPLink & a_Link) override {}
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
{
LOGWARNING("RCON server error on port %d: %d (%s)", m_Port, a_ErrorCode, a_ErrorMsg.c_str());
}
};
////////////////////////////////////////////////////////////////////////////////
// cRCONCommandOutput:
@ -45,7 +82,7 @@ class cRCONCommandOutput :
public cCommandOutputCallback
{
public:
cRCONCommandOutput(cRCONServer::cConnection & a_Connection, int a_RequestID) :
cRCONCommandOutput(cRCONServer::cConnection & a_Connection, UInt32 a_RequestID) :
m_Connection(a_Connection),
m_RequestID(a_RequestID)
{
@ -59,13 +96,13 @@ public:
virtual void Finished(void) override
{
m_Connection.SendResponse(m_RequestID, RCON_PACKET_RESPONSE, (int)m_Buffer.size(), m_Buffer.c_str());
m_Connection.SendResponse(m_RequestID, RCON_PACKET_RESPONSE, static_cast<UInt32>(m_Buffer.size()), m_Buffer.c_str());
delete this;
}
protected:
cRCONServer::cConnection & m_Connection;
int m_RequestID;
UInt32 m_RequestID;
AString m_Buffer;
} ;
@ -77,9 +114,7 @@ protected:
// cRCONServer:
cRCONServer::cRCONServer(cServer & a_Server) :
m_Server(a_Server),
m_ListenThread4(*this, cSocket::IPv4, "RCON"),
m_ListenThread6(*this, cSocket::IPv6, "RCON")
m_Server(a_Server)
{
}
@ -89,8 +124,10 @@ cRCONServer::cRCONServer(cServer & a_Server) :
cRCONServer::~cRCONServer()
{
m_ListenThread4.Stop();
m_ListenThread6.Stop();
for (auto srv: m_ListenServers)
{
srv->Close();
}
}
@ -112,42 +149,29 @@ void cRCONServer::Initialize(cIniFile & a_IniFile)
return;
}
// Read and initialize both IPv4 and IPv6 ports for RCON
bool HasAnyPorts = false;
AString Ports4 = a_IniFile.GetValueSet("RCON", "PortsIPv4", "25575");
if (m_ListenThread4.Initialize(Ports4))
// Read the listening ports for RCON from config:
AStringVector Ports = ReadUpgradeIniPorts(a_IniFile, "RCON", "Ports", "PortsIPv4", "PortsIPv6", "25575");
// Start listening on each specified port:
for (auto port: Ports)
{
HasAnyPorts = true;
m_ListenThread4.Start();
}
AString Ports6 = a_IniFile.GetValueSet("RCON", "PortsIPv6", "25575");
if (m_ListenThread6.Initialize(Ports6))
{
HasAnyPorts = true;
m_ListenThread6.Start();
}
if (!HasAnyPorts)
{
LOGWARNING("RCON is requested, but no ports are specified. Specify at least one port in PortsIPv4 or PortsIPv6. RCON is now disabled.");
return;
}
}
void cRCONServer::OnConnectionAccepted(cSocket & a_Socket)
{
if (!a_Socket.IsValid())
{
return;
UInt16 PortNum;
if (!StringToInteger(port, PortNum))
{
LOGINFO("Invalid RCON port value: \"%s\". Ignoring.", port.c_str());
continue;
}
auto Handle = cNetwork::Listen(PortNum, std::make_shared<cRCONListenCallbacks>(*this, PortNum));
if (Handle->IsListening())
{
m_ListenServers.push_back(Handle);
}
}
LOG("RCON Client \"%s\" connected!", a_Socket.GetIPString().c_str());
// Create a new cConnection object, it will be deleted when the connection is closed
m_SocketThreads.AddClient(a_Socket, new cConnection(*this, a_Socket));
if (m_ListenServers.empty())
{
LOGWARNING("RCON is enabled but no valid ports were found. RCON is not accessible.");
}
}
@ -157,11 +181,10 @@ void cRCONServer::OnConnectionAccepted(cSocket & a_Socket)
////////////////////////////////////////////////////////////////////////////////
// cRCONServer::cConnection:
cRCONServer::cConnection::cConnection(cRCONServer & a_RCONServer, cSocket & a_Socket) :
cRCONServer::cConnection::cConnection(cRCONServer & a_RCONServer, const AString & a_IPAddress) :
m_IsAuthenticated(false),
m_RCONServer(a_RCONServer),
m_Socket(a_Socket),
m_IPAddress(a_Socket.GetIPString())
m_IPAddress(a_IPAddress)
{
}
@ -169,71 +192,78 @@ cRCONServer::cConnection::cConnection(cRCONServer & a_RCONServer, cSocket & a_So
bool cRCONServer::cConnection::DataReceived(const char * a_Data, size_t a_Size)
void cRCONServer::cConnection::OnLinkCreated(cTCPLinkPtr a_Link)
{
m_Link = a_Link;
}
void cRCONServer::cConnection::OnReceivedData(const char * a_Data, size_t a_Size)
{
ASSERT(m_Link != nullptr);
// Append data to the buffer:
m_Buffer.append(a_Data, a_Size);
// Process the packets in the buffer:
while (m_Buffer.size() >= 14)
{
int Length = IntFromBuffer(m_Buffer.data());
UInt32 Length = UIntFromBuffer(m_Buffer.data());
if (Length > 1500)
{
// Too long, drop the connection
LOGWARNING("Received an invalid RCON packet length (%d), dropping RCON connection to %s.",
Length, m_IPAddress.c_str()
);
m_RCONServer.m_SocketThreads.RemoveClient(this);
m_Socket.CloseSocket();
delete this;
return false;
m_Link->Close();
m_Link.reset();
return;
}
if (Length > (int)(m_Buffer.size() + 4))
if (Length > static_cast<UInt32>(m_Buffer.size() + 4))
{
// Incomplete packet yet, wait for more data to come
return false;
return;
}
int RequestID = IntFromBuffer(m_Buffer.data() + 4);
int PacketType = IntFromBuffer(m_Buffer.data() + 8);
UInt32 RequestID = UIntFromBuffer(m_Buffer.data() + 4);
UInt32 PacketType = UIntFromBuffer(m_Buffer.data() + 8);
if (!ProcessPacket(RequestID, PacketType, Length - 10, m_Buffer.data() + 12))
{
m_RCONServer.m_SocketThreads.RemoveClient(this);
m_Socket.CloseSocket();
delete this;
return false;
m_Link->Close();
m_Link.reset();
return;
}
m_Buffer.erase(0, Length + 4);
} // while (m_Buffer.size() >= 14)
return false;
}
void cRCONServer::cConnection::GetOutgoingData(AString & a_Data)
void cRCONServer::cConnection::OnRemoteClosed(void)
{
a_Data.assign(m_Outgoing);
m_Outgoing.clear();
m_Link.reset();
}
void cRCONServer::cConnection::SocketClosed(void)
void cRCONServer::cConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
{
m_RCONServer.m_SocketThreads.RemoveClient(this);
delete this;
LOGD("Error in RCON connection %s: %d (%s)", m_IPAddress.c_str(), a_ErrorCode, a_ErrorMsg.c_str());
m_Link.reset();
}
bool cRCONServer::cConnection::ProcessPacket(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload)
bool cRCONServer::cConnection::ProcessPacket(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload)
{
switch (a_PacketType)
{
@ -242,7 +272,7 @@ bool cRCONServer::cConnection::ProcessPacket(int a_RequestID, int a_PacketType,
if (strncmp(a_Payload, m_RCONServer.m_Password.c_str(), a_PayloadLength) != 0)
{
LOGINFO("RCON: Invalid password from client %s, dropping connection.", m_IPAddress.c_str());
SendResponse(-1, RCON_PACKET_RESPONSE, 0, nullptr);
SendResponse(0xffffffffU, RCON_PACKET_RESPONSE, 0, nullptr);
return false;
}
m_IsAuthenticated = true;
@ -284,23 +314,22 @@ bool cRCONServer::cConnection::ProcessPacket(int a_RequestID, int a_PacketType,
/// Reads 4 bytes from a_Buffer and returns the int they represent
int cRCONServer::cConnection::IntFromBuffer(const char * a_Buffer)
UInt32 cRCONServer::cConnection::UIntFromBuffer(const char * a_Buffer)
{
return ((unsigned char)a_Buffer[3] << 24) | ((unsigned char)a_Buffer[2] << 16) | ((unsigned char)a_Buffer[1] << 8) | (unsigned char)a_Buffer[0];
const Byte * Buffer = reinterpret_cast<const Byte *>(a_Buffer);
return (Buffer[3] << 24) | (Buffer[2] << 16) | (Buffer[1] << 8) | Buffer[0];
}
/// Puts 4 bytes representing the int into the buffer
void cRCONServer::cConnection::IntToBuffer(int a_Value, char * a_Buffer)
void cRCONServer::cConnection::UIntToBuffer(UInt32 a_Value, char * a_Buffer)
{
a_Buffer[0] = a_Value & 0xff;
a_Buffer[1] = (a_Value >> 8) & 0xff;
a_Buffer[2] = (a_Value >> 16) & 0xff;
a_Buffer[3] = (a_Value >> 24) & 0xff;
a_Buffer[0] = static_cast<char>(a_Value & 0xff);
a_Buffer[1] = static_cast<char>((a_Value >> 8) & 0xff);
a_Buffer[2] = static_cast<char>((a_Value >> 16) & 0xff);
a_Buffer[3] = static_cast<char>((a_Value >> 24) & 0xff);
}
@ -308,25 +337,22 @@ void cRCONServer::cConnection::IntToBuffer(int a_Value, char * a_Buffer)
/// Sends a RCON packet back to the client
void cRCONServer::cConnection::SendResponse(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload)
void cRCONServer::cConnection::SendResponse(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload)
{
ASSERT((a_PayloadLength == 0) || (a_Payload != nullptr)); // Either zero data to send, or a valid payload ptr
ASSERT(m_Link != nullptr);
char Buffer[4];
int Length = a_PayloadLength + 10;
IntToBuffer(Length, Buffer);
m_Outgoing.append(Buffer, 4);
IntToBuffer(a_RequestID, Buffer);
m_Outgoing.append(Buffer, 4);
IntToBuffer(a_PacketType, Buffer);
m_Outgoing.append(Buffer, 4);
char Buffer[12];
UInt32 Length = a_PayloadLength + 10;
UIntToBuffer(Length, Buffer);
UIntToBuffer(a_RequestID, Buffer + 4);
UIntToBuffer(a_PacketType, Buffer + 8);
m_Link->Send(Buffer, 12);
if (a_PayloadLength > 0)
{
m_Outgoing.append(a_Payload, a_PayloadLength);
m_Link->Send(a_Payload, a_PayloadLength);
}
m_Outgoing.push_back(0);
m_Outgoing.push_back(0);
m_RCONServer.m_SocketThreads.NotifyWrite(this);
m_Link->Send("\0", 2); // Send two zero chars as the padding
}

View File

@ -9,8 +9,7 @@
#pragma once
#include "OSSupport/SocketThreads.h"
#include "OSSupport/ListenThread.h"
#include "OSSupport/Network.h"
@ -24,8 +23,7 @@ class cIniFile;
class cRCONServer :
public cListenThread::cCallback
class cRCONServer
{
public:
cRCONServer(cServer & a_Server);
@ -35,72 +33,61 @@ public:
protected:
friend class cRCONCommandOutput;
friend class cRCONListenCallbacks;
class cConnection :
public cSocketThreads::cCallback
public cTCPLink::cCallbacks
{
public:
cConnection(cRCONServer & a_RCONServer, cSocket & a_Socket);
cConnection(cRCONServer & a_RCONServer, const AString & a_IPAddress);
protected:
friend class cRCONCommandOutput;
/// Set to true if the client has successfully authenticated
/** Set to true if the client has successfully authenticated */
bool m_IsAuthenticated;
/// Buffer for the incoming data
/** Buffer for the incoming data */
AString m_Buffer;
/// Buffer for the outgoing data
AString m_Outgoing;
/// Server that owns this connection and processes requests
/** Server that owns this connection and processes requests */
cRCONServer & m_RCONServer;
/// The socket belonging to the client
cSocket & m_Socket;
/** The TCP link to the client */
cTCPLinkPtr m_Link;
/// Address of the client
/** Address of the client */
AString m_IPAddress;
// cSocketThreads::cCallback overrides:
virtual bool DataReceived(const char * a_Data, size_t a_Size) override;
virtual void GetOutgoingData(AString & a_Data) override;
virtual void SocketClosed(void) override;
// cTCPLink::cCallbacks overrides:
virtual void OnLinkCreated(cTCPLinkPtr a_Link);
virtual void OnReceivedData(const char * a_Data, size_t a_Length) override;
virtual void OnRemoteClosed(void) override;
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
/// Processes the given packet and sends the response; returns true if successful, false if the connection is to be dropped
bool ProcessPacket(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload);
/** Processes the given packet and sends the response; returns true if successful, false if the connection is to be dropped */
bool ProcessPacket(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload);
/// Reads 4 bytes from a_Buffer and returns the int they represent
int IntFromBuffer(const char * a_Buffer);
/** Reads 4 bytes from a_Buffer and returns the LE UInt32 they represent */
UInt32 UIntFromBuffer(const char * a_Buffer);
/// Puts 4 bytes representing the int into the buffer
void IntToBuffer(int a_Value, char * a_Buffer);
/** Puts 4 bytes representing the int into the buffer */
void UIntToBuffer(UInt32 a_Value, char * a_Buffer);
/// Sends a RCON packet back to the client
void SendResponse(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload);
/** Sends a RCON packet back to the client */
void SendResponse(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload);
} ;
/// The server object that will process the commands received
/** The server object that will process the commands received */
cServer & m_Server;
/// The thread(s) that take care of all the traffic on the RCON ports
cSocketThreads m_SocketThreads;
/** The sockets for accepting RCON connections (one socket per port). */
cServerHandlePtrs m_ListenServers;
/// The thread for accepting IPv4 RCON connections
cListenThread m_ListenThread4;
/// The thread for accepting IPv6 RCON connections
cListenThread m_ListenThread6;
/// Password for authentication
/** Password for authentication */
AString m_Password;
// cListenThread::cCallback overrides:
virtual void OnConnectionAccepted(cSocket & a_Socket) override;
} ;

View File

@ -181,43 +181,49 @@ void cRoot::Start(void)
IniFile.WriteFile("settings.ini");
LOGD("Finalising startup...");
m_Server->Start();
m_WebAdmin->Start();
#if !defined(ANDROID_NDK)
LOGD("Starting InputThread...");
try
if (m_Server->Start())
{
m_InputThread = std::thread(InputThread, std::ref(*this));
m_InputThread.detach();
}
catch (std::system_error & a_Exception)
{
LOGERROR("cRoot::Start (std::thread) error %i: could not construct input thread; %s", a_Exception.code().value(), a_Exception.what());
}
#endif
m_WebAdmin->Start();
LOG("Startup complete, took %ldms!", static_cast<long int>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - BeginTime).count()));
#ifdef _WIN32
EnableMenuItem(hmenu, SC_CLOSE, MF_ENABLED); // Re-enable close button
#endif
#if !defined(ANDROID_NDK)
LOGD("Starting InputThread...");
try
{
m_InputThread = std::thread(InputThread, std::ref(*this));
m_InputThread.detach();
}
catch (std::system_error & a_Exception)
{
LOGERROR("cRoot::Start (std::thread) error %i: could not construct input thread; %s", a_Exception.code().value(), a_Exception.what());
}
#endif
while (!m_bStop && !m_bRestart && !m_TerminateEventRaised) // These are modified by external threads
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
LOG("Startup complete, took %ldms!", static_cast<long int>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - BeginTime).count()));
#ifdef _WIN32
EnableMenuItem(hmenu, SC_CLOSE, MF_ENABLED); // Re-enable close button
#endif
if (m_TerminateEventRaised)
while (!m_bStop && !m_bRestart && !m_TerminateEventRaised) // These are modified by external threads
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (m_TerminateEventRaised)
{
m_bStop = true;
}
// Stop the server:
m_WebAdmin->Stop();
LOG("Shutting down server...");
m_Server->Shutdown();
} // if (m_Server->Start())
else
{
m_bStop = true;
}
// Stop the server:
m_WebAdmin->Stop();
LOG("Shutting down server...");
m_Server->Shutdown();
delete m_MojangAPI; m_MojangAPI = nullptr;
LOGD("Shutting down deadlock detector...");

View File

@ -5,7 +5,6 @@
#include "Server.h"
#include "ClientHandle.h"
#include "Mobs/Monster.h"
#include "OSSupport/Socket.h"
#include "Root.h"
#include "World.h"
#include "ChunkDef.h"
@ -57,6 +56,39 @@ typedef std::list< cClientHandle* > ClientList;
////////////////////////////////////////////////////////////////////////////////
// cServerListenCallbacks:
class cServerListenCallbacks:
public cNetwork::cListenCallbacks
{
cServer & m_Server;
UInt16 m_Port;
virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override
{
return m_Server.OnConnectionAccepted(a_RemoteIPAddress);
}
virtual void OnAccepted(cTCPLink & a_Link) override {}
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg)
{
LOGWARNING("Cannot listen on port %d: %d (%s).", m_Port, a_ErrorCode, a_ErrorMsg.c_str());
}
public:
cServerListenCallbacks(cServer & a_Server, UInt16 a_Port):
m_Server(a_Server),
m_Port(a_Port)
{
}
};
////////////////////////////////////////////////////////////////////////////////
// cServer::cTickThread:
@ -100,8 +132,6 @@ void cServer::cTickThread::Execute(void)
// cServer:
cServer::cServer(void) :
m_ListenThreadIPv4(*this, cSocket::IPv4, "Client"),
m_ListenThreadIPv6(*this, cSocket::IPv6, "Client"),
m_PlayerCount(0),
m_PlayerCountDiff(0),
m_ClientViewDistance(0),
@ -121,42 +151,6 @@ cServer::cServer(void) :
void cServer::ClientDestroying(const cClientHandle * a_Client)
{
m_SocketThreads.RemoveClient(a_Client);
}
void cServer::NotifyClientWrite(const cClientHandle * a_Client)
{
m_NotifyWriteThread.NotifyClientWrite(a_Client);
}
void cServer::WriteToClient(const cClientHandle * a_Client, const AString & a_Data)
{
m_SocketThreads.Write(a_Client, a_Data);
}
void cServer::RemoveClient(const cClientHandle * a_Client)
{
m_SocketThreads.RemoveClient(a_Client);
}
void cServer::ClientMovedToWorld(const cClientHandle * a_Client)
{
cCSLock Lock(m_CSClients);
@ -211,33 +205,8 @@ bool cServer::InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth)
LOGINFO("Compatible clients: %s", MCS_CLIENT_VERSIONS);
LOGINFO("Compatible protocol versions %s", MCS_PROTOCOL_VERSIONS);
if (cSocket::WSAStartup() != 0) // Only does anything on Windows, but whatever
{
LOGERROR("WSAStartup() != 0");
return false;
}
bool HasAnyPorts = false;
AString Ports = a_SettingsIni.GetValueSet("Server", "Port", "25565");
m_ListenThreadIPv4.SetReuseAddr(true);
if (m_ListenThreadIPv4.Initialize(Ports))
{
HasAnyPorts = true;
}
Ports = a_SettingsIni.GetValueSet("Server", "PortsIPv6", "25565");
m_ListenThreadIPv6.SetReuseAddr(true);
if (m_ListenThreadIPv6.Initialize(Ports))
{
HasAnyPorts = true;
}
m_Ports = ReadUpgradeIniPorts(a_SettingsIni, "Server", "Ports", "Port", "PortsIPv6", "25565");
if (!HasAnyPorts)
{
LOGERROR("Couldn't open any ports. Aborting the server");
return false;
}
m_RCONServer.Initialize(a_SettingsIni);
m_bIsConnected = true;
@ -278,8 +247,6 @@ bool cServer::InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth)
LOGINFO("Setting default viewdistance to the maximum of %d", m_ClientViewDistance);
}
m_NotifyWriteThread.Start(this);
PrepareKeys();
return true;
@ -327,36 +294,14 @@ void cServer::PrepareKeys(void)
void cServer::OnConnectionAccepted(cSocket & a_Socket)
cTCPLink::cCallbacksPtr cServer::OnConnectionAccepted(const AString & a_RemoteIPAddress)
{
if (!a_Socket.IsValid())
{
return;
}
const AString & ClientIP = a_Socket.GetIPString();
if (ClientIP.empty())
{
LOGWARN("cServer: A client connected, but didn't present its IP, disconnecting.");
a_Socket.CloseSocket();
return;
}
LOGD("Client \"%s\" connected!", ClientIP.c_str());
cClientHandle * NewHandle = new cClientHandle(&a_Socket, m_ClientViewDistance);
if (!m_SocketThreads.AddClient(a_Socket, NewHandle))
{
// For some reason SocketThreads have rejected the handle, clean it up
LOGERROR("Client \"%s\" cannot be handled, server probably unstable", ClientIP.c_str());
a_Socket.CloseSocket();
delete NewHandle;
NewHandle = nullptr;
return;
}
LOGD("Client \"%s\" connected!", a_RemoteIPAddress.c_str());
cClientHandlePtr NewHandle = std::make_shared<cClientHandle>(a_RemoteIPAddress, m_ClientViewDistance);
NewHandle->SetSelf(NewHandle);
cCSLock Lock(m_CSClients);
m_Clients.push_back(NewHandle);
return NewHandle;
}
@ -403,23 +348,30 @@ bool cServer::Tick(float a_Dt)
void cServer::TickClients(float a_Dt)
{
cClientHandleList RemoveClients;
cClientHandlePtrs RemoveClients;
{
cCSLock Lock(m_CSClients);
// Remove clients that have moved to a world (the world will be ticking them from now on)
for (cClientHandleList::const_iterator itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
for (auto itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
{
m_Clients.remove(*itr);
for (auto itrC = m_Clients.begin(), endC = m_Clients.end(); itrC != endC; ++itrC)
{
if (itrC->get() == *itr)
{
m_Clients.erase(itrC);
break;
}
}
} // for itr - m_ClientsToRemove[]
m_ClientsToRemove.clear();
// Tick the remaining clients, take out those that have been destroyed into RemoveClients
for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();)
for (auto itr = m_Clients.begin(); itr != m_Clients.end();)
{
if ((*itr)->IsDestroyed())
{
// Remove the client later, when CS is not held, to avoid deadlock: http://forum.mc-server.org/showthread.php?tid=374
// Delete the client later, when CS is not held, to avoid deadlock: http://forum.mc-server.org/showthread.php?tid=374
RemoveClients.push_back(*itr);
itr = m_Clients.erase(itr);
continue;
@ -430,10 +382,7 @@ void cServer::TickClients(float a_Dt)
}
// Delete the clients that have been destroyed
for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr)
{
delete *itr;
} // for itr - RemoveClients[]
RemoveClients.clear();
}
@ -442,12 +391,23 @@ void cServer::TickClients(float a_Dt)
bool cServer::Start(void)
{
if (!m_ListenThreadIPv4.Start())
for (auto port: m_Ports)
{
return false;
}
if (!m_ListenThreadIPv6.Start())
UInt16 PortNum;
if (!StringToInteger(port, PortNum))
{
LOGWARNING("Invalid port specified for server: \"%s\". Ignoring.", port.c_str());
continue;
}
auto Handle = cNetwork::Listen(PortNum, std::make_shared<cServerListenCallbacks>(*this, PortNum));
if (Handle->IsListening())
{
m_ServerHandles.push_back(Handle);
}
} // for port - Ports[]
if (m_ServerHandles.empty())
{
LOGERROR("Couldn't open any ports. Aborting the server");
return false;
}
if (!m_TickThread.Start())
@ -640,7 +600,6 @@ void cServer::PrintHelp(const AStringVector & a_Split, cCommandOutputCallback &
const AStringPair & cmd = *itr;
a_Output.Out(Printf("%-*s%s\n", static_cast<int>(Callback.m_MaxLen), cmd.first.c_str(), cmd.second.c_str()));
} // for itr - Callback.m_Commands[]
a_Output.Finished();
}
@ -670,19 +629,24 @@ void cServer::BindBuiltInConsoleCommands(void)
void cServer::Shutdown(void)
{
m_ListenThreadIPv4.Stop();
m_ListenThreadIPv6.Stop();
// Stop listening on all sockets:
for (auto srv: m_ServerHandles)
{
srv->Close();
}
m_ServerHandles.clear();
// Notify the tick thread and wait for it to terminate:
m_bRestarting = true;
m_RestartEvent.Wait();
cRoot::Get()->SaveAllChunks();
// Remove all clients:
cCSLock Lock(m_CSClients);
for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
{
(*itr)->Destroy();
delete *itr;
}
m_Clients.clear();
}
@ -694,7 +658,7 @@ void cServer::Shutdown(void)
void cServer::KickUser(int a_ClientID, const AString & a_Reason)
{
cCSLock Lock(m_CSClients);
for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
{
if ((*itr)->GetUniqueID() == a_ClientID)
{
@ -710,7 +674,7 @@ void cServer::KickUser(int a_ClientID, const AString & a_Reason)
void cServer::AuthenticateUser(int a_ClientID, const AString & a_Name, const AString & a_UUID, const Json::Value & a_Properties)
{
cCSLock Lock(m_CSClients);
for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
{
if ((*itr)->GetUniqueID() == a_ClientID)
{
@ -724,82 +688,3 @@ void cServer::AuthenticateUser(int a_ClientID, const AString & a_Name, const ASt
////////////////////////////////////////////////////////////////////////////////
// cServer::cNotifyWriteThread:
cServer::cNotifyWriteThread::cNotifyWriteThread(void) :
super("ClientPacketThread"),
m_Server(nullptr)
{
}
cServer::cNotifyWriteThread::~cNotifyWriteThread()
{
m_ShouldTerminate = true;
m_Event.Set();
Wait();
}
bool cServer::cNotifyWriteThread::Start(cServer * a_Server)
{
m_Server = a_Server;
return super::Start();
}
void cServer::cNotifyWriteThread::Execute(void)
{
cClientHandleList Clients;
while (!m_ShouldTerminate)
{
cCSLock Lock(m_CS);
while (m_Clients.empty())
{
cCSUnlock Unlock(Lock);
m_Event.Wait();
if (m_ShouldTerminate)
{
return;
}
}
// Copy the clients to notify and unlock the CS:
Clients.splice(Clients.begin(), m_Clients);
Lock.Unlock();
for (cClientHandleList::iterator itr = Clients.begin(); itr != Clients.end(); ++itr)
{
m_Server->m_SocketThreads.NotifyWrite(*itr);
} // for itr - Clients[]
Clients.clear();
} // while (!mShouldTerminate)
}
void cServer::cNotifyWriteThread::NotifyClientWrite(const cClientHandle * a_Client)
{
{
cCSLock Lock(m_CS);
m_Clients.remove(const_cast<cClientHandle *>(a_Client)); // Put it there only once
m_Clients.push_back(const_cast<cClientHandle *>(a_Client));
}
m_Event.Set();
}

View File

@ -9,10 +9,9 @@
#pragma once
#include "OSSupport/SocketThreads.h"
#include "OSSupport/ListenThread.h"
#include "RCONServer.h"
#include "OSSupport/IsThread.h"
#include "OSSupport/Network.h"
#ifdef _MSC_VER
#pragma warning(push)
@ -36,10 +35,12 @@
// fwd:
class cPlayer;
class cClientHandle;
typedef SharedPtr<cClientHandle> cClientHandlePtr;
typedef std::list<cClientHandlePtr> cClientHandlePtrs;
typedef std::list<cClientHandle *> cClientHandles;
class cIniFile;
class cCommandOutputCallback;
typedef std::list<cClientHandle *> cClientHandleList;
namespace Json
{
@ -50,10 +51,11 @@ namespace Json
class cServer // tolua_export
: public cListenThread::cCallback
{ // tolua_export
public: // tolua_export
// tolua_begin
class cServer
{
public:
// tolua_end
virtual ~cServer() {}
bool InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth);
@ -105,13 +107,6 @@ public: // tolua_export
/** Called by cClientHandle's destructor; stop m_SocketThreads from calling back into a_Client */
void ClientDestroying(const cClientHandle * a_Client);
/** Notifies m_SocketThreads that client has something to be written */
void NotifyClientWrite(const cClientHandle * a_Client);
void WriteToClient(const cClientHandle * a_Client, const AString & a_Data); // Queues outgoing data for the client through m_SocketThreads
void RemoveClient(const cClientHandle * a_Client); // Removes the clienthandle from m_SocketThreads
/** Don't tick a_Client anymore, it will be ticked from its cPlayer instead */
void ClientMovedToWorld(const cClientHandle * a_Client);
@ -147,30 +142,7 @@ public: // tolua_export
private:
friend class cRoot; // so cRoot can create and destroy cServer
/** When NotifyClientWrite() is called, it is queued for this thread to process (to avoid deadlocks between cSocketThreads, cClientHandle and cChunkMap) */
class cNotifyWriteThread :
public cIsThread
{
typedef cIsThread super;
cEvent m_Event; // Set when m_Clients gets appended
cServer * m_Server;
cCriticalSection m_CS;
cClientHandleList m_Clients;
virtual void Execute(void);
public:
cNotifyWriteThread(void);
~cNotifyWriteThread();
bool Start(cServer * a_Server);
void NotifyClientWrite(const cClientHandle * a_Client);
} ;
friend class cServerListenCallbacks; // Accessing OnConnectionAccepted()
/** The server tick thread takes care of the players who aren't yet spawned in a world */
class cTickThread :
@ -189,21 +161,29 @@ private:
} ;
cNotifyWriteThread m_NotifyWriteThread;
/** The network sockets listening for client connections. */
cServerHandlePtrs m_ServerHandles;
/** Protects m_Clients and m_ClientsToRemove against multithreaded access. */
cCriticalSection m_CSClients;
/** Clients that are connected to the server and not yet assigned to a cWorld. */
cClientHandlePtrs m_Clients;
/** Clients that have just been moved into a world and are to be removed from m_Clients in the next Tick(). */
cClientHandles m_ClientsToRemove;
cListenThread m_ListenThreadIPv4;
cListenThread m_ListenThreadIPv6;
cCriticalSection m_CSClients; ///< Locks client lists
cClientHandleList m_Clients; ///< Clients that are connected to the server and not yet assigned to a cWorld
cClientHandleList m_ClientsToRemove; ///< Clients that have just been moved into a world and are to be removed from m_Clients in the next Tick()
mutable cCriticalSection m_CSPlayerCount; ///< Locks the m_PlayerCount
int m_PlayerCount; ///< Number of players currently playing in the server
cCriticalSection m_CSPlayerCountDiff; ///< Locks the m_PlayerCountDiff
int m_PlayerCountDiff; ///< Adjustment to m_PlayerCount to be applied in the Tick thread
cSocketThreads m_SocketThreads;
/** Protects m_PlayerCount against multithreaded access. */
mutable cCriticalSection m_CSPlayerCount;
/** Number of players currently playing in the server. */
int m_PlayerCount;
/** Protects m_PlayerCountDiff against multithreaded access. */
cCriticalSection m_CSPlayerCountDiff;
/** Adjustment to m_PlayerCount to be applied in the Tick thread. */
int m_PlayerCountDiff;
int m_ClientViewDistance; // The default view distance for clients; settable in Settings.ini
@ -250,19 +230,24 @@ private:
/** True if BungeeCord handshake packets (with player UUID) should be accepted. */
bool m_ShouldAllowBungeeCord;
/** The list of ports on which the server should listen for connections.
Initialized in InitServer(), used in Start(). */
AStringVector m_Ports;
cServer(void);
/** Loads, or generates, if missing, RSA keys for protocol encryption */
void PrepareKeys(void);
/** Creates a new cClientHandle instance and adds it to the list of clients.
Returns the cClientHandle reinterpreted as cTCPLink callbacks. */
cTCPLink::cCallbacksPtr OnConnectionAccepted(const AString & a_RemoteIPAddress);
bool Tick(float a_Dt);
/** Ticks the clients in m_Clients, manages the list in respect to removing clients */
void TickClients(float a_Dt);
// cListenThread::cCallback overrides:
virtual void OnConnectionAccepted(cSocket & a_Socket) override;
}; // tolua_export

View File

@ -905,3 +905,47 @@ bool SplitZeroTerminatedStrings(const AString & a_Strings, AStringVector & a_Out
AStringVector MergeStringVectors(const AStringVector & a_Strings1, const AStringVector & a_Strings2)
{
// Initialize the resulting vector by the first vector:
AStringVector res = a_Strings1;
// Add each item from strings2 that is not already present:
for (auto item : a_Strings2)
{
if (std::find(res.begin(), res.end(), item) == res.end())
{
res.push_back(item);
}
} // for item - a_Strings2[]
return res;
}
AString StringsConcat(const AStringVector & a_Strings, char a_Separator)
{
// If the vector is empty, return an empty string:
if (a_Strings.empty())
{
return "";
}
// Concatenate the strings in the vector:
AString res;
res.append(a_Strings[0]);
for (auto itr = a_Strings.cbegin() + 1, end = a_Strings.cend(); itr != end; ++itr)
{
res.push_back(a_Separator);
res.append(*itr);
}
return res;
}

View File

@ -115,7 +115,16 @@ a_Output is first cleared and then each separate string is pushed back into a_Ou
Returns true if there are at least two strings in a_Output (there was at least one \0 separator). */
extern bool SplitZeroTerminatedStrings(const AString & a_Strings, AStringVector & a_Output);
/// Parses any integer type. Checks bounds and returns errors out of band.
/** Merges the two vectors of strings, removing duplicate entries from the second vector.
The resulting vector contains items from a_Strings1 first, then from a_Strings2.
The order of items doesn't change, only the duplicates are removed.
If a_Strings1 contains duplicates, the result will still contain those duplicates. */
extern AStringVector MergeStringVectors(const AStringVector & a_Strings1, const AStringVector & a_Strings2);
/** Concatenates the specified strings into a single string, separated by the specified separator. */
extern AString StringsConcat(const AStringVector & a_Strings, char a_Separator);
/** Parses any integer type. Checks bounds and returns errors out of band. */
template <class T>
bool StringToInteger(const AString & a_str, T & a_Num)
{

View File

@ -19,7 +19,16 @@
/// Helper class - appends all player names together in a HTML list
static const char DEFAULT_WEBADMIN_PORTS[] = "8080";
////////////////////////////////////////////////////////////////////////////////
// cPlayerAccum:
/** Helper class - appends all player names together in an HTML list */
class cPlayerAccum :
public cPlayerListCallback
{
@ -40,11 +49,12 @@ public:
////////////////////////////////////////////////////////////////////////////////
// cWebAdmin:
cWebAdmin::cWebAdmin(void) :
m_IsInitialized(false),
m_IsRunning(false),
m_PortsIPv4("8080"),
m_PortsIPv6(""),
m_TemplateScript("<webadmin_template>")
{
}
@ -91,8 +101,7 @@ bool cWebAdmin::Init(void)
m_IniFile.AddHeaderComment(" Password format: Password=*password*; for example:");
m_IniFile.AddHeaderComment(" [User:admin]");
m_IniFile.AddHeaderComment(" Password=admin");
m_IniFile.SetValue("WebAdmin", "Port", m_PortsIPv4);
m_IniFile.SetValue("WebAdmin", "PortsIPv6", m_PortsIPv6);
m_IniFile.SetValue("WebAdmin", "Ports", DEFAULT_WEBADMIN_PORTS);
m_IniFile.WriteFile("webadmin.ini");
}
@ -104,10 +113,37 @@ bool cWebAdmin::Init(void)
LOGD("Initialising WebAdmin...");
m_PortsIPv4 = m_IniFile.GetValueSet("WebAdmin", "Port", m_PortsIPv4);
m_PortsIPv6 = m_IniFile.GetValueSet("WebAdmin", "PortsIPv6", m_PortsIPv6);
// Initialize the WebAdmin template script and load the file
m_TemplateScript.Create();
m_TemplateScript.RegisterAPILibs();
if (!m_TemplateScript.LoadFile(FILE_IO_PREFIX "webadmin/template.lua"))
{
LOGWARN("Could not load WebAdmin template \"%s\". WebAdmin disabled!", FILE_IO_PREFIX "webadmin/template.lua");
m_TemplateScript.Close();
m_HTTPServer.Stop();
return false;
}
if (!m_HTTPServer.Initialize(m_PortsIPv4, m_PortsIPv6))
// Load the login template, provide a fallback default if not found:
if (!LoadLoginTemplate())
{
LOGWARN("Could not load WebAdmin login template \"%s\", using fallback template.", FILE_IO_PREFIX "webadmin/login_template.html");
// Sets the fallback template:
m_LoginTemplate = \
"<h1>MCServer WebAdmin</h1>" \
"<center>" \
"<form method='get' action='webadmin/'>" \
"<input type='submit' value='Log in'>" \
"</form>" \
"</center>";
}
// Read the ports to be used:
// Note that historically the ports were stored in the "Port" and "PortsIPv6" values
m_Ports = ReadUpgradeIniPorts(m_IniFile, "WebAdmin", "Ports", "Port", "PortsIPv6", DEFAULT_WEBADMIN_PORTS);
if (!m_HTTPServer.Initialize())
{
return false;
}
@ -130,32 +166,7 @@ bool cWebAdmin::Start(void)
LOGD("Starting WebAdmin...");
// Initialize the WebAdmin template script and load the file
m_TemplateScript.Create();
m_TemplateScript.RegisterAPILibs();
if (!m_TemplateScript.LoadFile(FILE_IO_PREFIX "webadmin/template.lua"))
{
LOGWARN("Could not load WebAdmin template \"%s\". WebAdmin disabled!", FILE_IO_PREFIX "webadmin/template.lua");
m_TemplateScript.Close();
m_HTTPServer.Stop();
return false;
}
if (!LoadLoginTemplate())
{
LOGWARN("Could not load WebAdmin login template \"%s\", using fallback template.", FILE_IO_PREFIX "webadmin/login_template.html");
// Sets the fallback template:
m_LoginTemplate = \
"<h1>MCServer WebAdmin</h1>" \
"<center>" \
"<form method='get' action='webadmin/'>" \
"<input type='submit' value='Log in'>" \
"</form>" \
"</center>";
}
m_IsRunning = m_HTTPServer.Start(*this);
m_IsRunning = m_HTTPServer.Start(*this, m_Ports);
return m_IsRunning;
}

View File

@ -5,7 +5,6 @@
#pragma once
#include "OSSupport/Socket.h"
#include "Bindings/LuaState.h"
#include "IniFile.h"
#include "HTTPServer/HTTPServer.h"
@ -135,8 +134,16 @@ public:
/** Returns the prefix needed for making a link point to the webadmin root from the given URL ("../../../webadmin"-style) */
AString GetBaseURL(const AString & a_URL);
AString GetIPv4Ports(void) const { return m_PortsIPv4; }
AString GetIPv6Ports(void) const { return m_PortsIPv6; }
/** Returns the list of ports used for the webadmin. */
AString GetPorts(void) const { return StringsConcat(m_Ports, ','); }
/** OBSOLETE: Returns the list of IPv4 ports used for the webadmin.
Currently there is no distinction between IPv4 and IPv6; use GetPorts() instead. */
AString GetIPv4Ports(void) const { return GetPorts(); }
/** OBSOLETE: Returns the list of IPv6 ports used for the webadmin.
Currently there is no distinction between IPv4 and IPv6; use GetPorts() instead. */
AString GetIPv6Ports(void) const { return GetPorts(); }
// tolua_end
@ -205,8 +212,8 @@ protected:
PluginList m_Plugins;
AString m_PortsIPv4;
AString m_PortsIPv6;
/** The ports on which the webadmin is running. */
AStringVector m_Ports;
/** The Lua template script to provide templates: */
cLuaState m_TemplateScript;

View File

@ -815,10 +815,9 @@ void cWorld::Stop(void)
// Delete the clients that have been in this world:
{
cCSLock Lock(m_CSClients);
for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
{
(*itr)->Destroy();
delete *itr;
} // for itr - m_Clients[]
m_Clients.clear();
}
@ -1093,19 +1092,26 @@ void cWorld::TickScheduledTasks(void)
void cWorld::TickClients(float a_Dt)
{
cClientHandleList RemoveClients;
cClientHandlePtrs RemoveClients;
{
cCSLock Lock(m_CSClients);
// Remove clients scheduled for removal:
for (cClientHandleList::iterator itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
for (auto itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
{
m_Clients.remove(*itr);
for (auto itrC = m_Clients.begin(), endC = m_Clients.end(); itrC != endC; ++itrC)
{
if (itrC->get() == *itr)
{
m_Clients.erase(itrC);
break;
}
}
} // for itr - m_ClientsToRemove[]
m_ClientsToRemove.clear();
// Add clients scheduled for adding:
for (cClientHandleList::iterator itr = m_ClientsToAdd.begin(), end = m_ClientsToAdd.end(); itr != end; ++itr)
for (auto itr = m_ClientsToAdd.begin(), end = m_ClientsToAdd.end(); itr != end; ++itr)
{
ASSERT(std::find(m_Clients.begin(), m_Clients.end(), *itr) == m_Clients.end());
m_Clients.push_back(*itr);
@ -1113,7 +1119,7 @@ void cWorld::TickClients(float a_Dt)
m_ClientsToAdd.clear();
// Tick the clients, take out those that have been destroyed into RemoveClients
for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();)
for (auto itr = m_Clients.begin(); itr != m_Clients.end();)
{
if ((*itr)->IsDestroyed())
{
@ -1126,12 +1132,9 @@ void cWorld::TickClients(float a_Dt)
++itr;
} // for itr - m_Clients[]
}
// Delete the clients that have been destroyed
for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr)
{
delete *itr;
} // for itr - RemoveClients[]
// Delete the clients queued for removal:
RemoveClients.clear();
}
@ -3525,7 +3528,7 @@ void cWorld::AddQueuedPlayers(void)
cCSLock Lock(m_CSClients);
for (cPlayerList::iterator itr = PlayersToAdd.begin(), end = PlayersToAdd.end(); itr != end; ++itr)
{
cClientHandle * Client = (*itr)->GetClientHandle();
cClientHandlePtr Client = (*itr)->GetClientHandlePtr();
if (Client != nullptr)
{
m_Clients.push_back(Client);

View File

@ -38,6 +38,9 @@ class cRedstoneSimulator;
class cItem;
class cPlayer;
class cClientHandle;
typedef SharedPtr<cClientHandle> cClientHandlePtr;
typedef std::list<cClientHandlePtr> cClientHandlePtrs;
typedef std::list<cClientHandle *> cClientHandles;
class cEntity;
class cBlockEntity;
class cWorldGenerator; // The generator that actually generates the chunks for a single world
@ -1019,13 +1022,13 @@ private:
cCriticalSection m_CSClients;
/** List of clients in this world, these will be ticked by this world */
cClientHandleList m_Clients;
cClientHandlePtrs m_Clients;
/** Clients that are scheduled for removal (ticked in another world), waiting for TickClients() to remove them */
cClientHandleList m_ClientsToRemove;
cClientHandles m_ClientsToRemove;
/** Clients that are scheduled for adding, waiting for TickClients to add them */
cClientHandleList m_ClientsToAdd;
cClientHandlePtrs m_ClientsToAdd;
/** Guards m_EntitiesToAdd */
cCriticalSection m_CSEntitiesToAdd;

View File

@ -11,14 +11,18 @@
#include <dbghelp.h>
#endif // _MSC_VER
bool cRoot::m_TerminateEventRaised = false; // If something has told the server to stop; checked periodically in cRoot
static bool g_ServerTerminated = false; // Set to true when the server terminates, so our CTRL handler can then tell the OS to close the console
#include "OSSupport/NetworkSingleton.h"
/** If something has told the server to stop; checked periodically in cRoot */
bool cRoot::m_TerminateEventRaised = false;
/** Set to true when the server terminates, so our CTRL handler can then tell the OS to close the console. */
static bool g_ServerTerminated = false;
/** If set to true, the protocols will log each player's incoming (C->S) communication to a per-connection logfile */
bool g_ShouldLogCommIn;
@ -305,6 +309,9 @@ int main( int argc, char **argv)
g_ServerTerminated = true;
// Shutdown all of LibEvent:
cNetworkSingleton::Get().Terminate();
return EXIT_SUCCESS;
}

View File

@ -7,6 +7,7 @@
#include <iostream>
#include <string>
#include "OSSupport/Network.h"
#include "OSSupport/NetworkSingleton.h"
@ -98,7 +99,7 @@ class cEchoServerCallbacks:
int main()
void DoTest(void)
{
LOGD("EchoServer: starting up");
cServerHandlePtr Server = cNetwork::Listen(9876, std::make_shared<cEchoServerCallbacks>());
@ -119,9 +120,20 @@ int main()
Server->Close();
ASSERT(!Server->IsListening());
LOGD("Server has been closed.");
}
int main()
{
DoTest();
printf("Press enter to exit test.\n");
AString line;
std::getline(std::cin, line);
cNetworkSingleton::Get().Terminate();
LOG("Network test finished.");
return 0;

View File

@ -7,6 +7,7 @@
#include <thread>
#include "OSSupport/Event.h"
#include "OSSupport/Network.h"
#include "OSSupport/NetworkSingleton.h"
@ -96,7 +97,7 @@ public:
int main()
void DoTest(void)
{
cEvent evtFinish;
@ -109,7 +110,19 @@ int main()
LOGD("Connect request has been queued.");
evtFinish.Wait();
}
int main()
{
DoTest();
cNetworkSingleton::Get().Terminate();
LOGD("Network test finished");
return 0;
}

View File

@ -7,6 +7,7 @@
#include <thread>
#include "OSSupport/Event.h"
#include "OSSupport/Network.h"
#include "OSSupport/NetworkSingleton.h"
@ -45,7 +46,7 @@ public:
int main()
void DoTest(void)
{
cEvent evtFinish;
@ -70,7 +71,16 @@ int main()
LOGD("IP lookup has been successfully queued");
evtFinish.Wait();
LOGD("IP lookup finished.");
}
int main()
{
DoTest();
cNetworkSingleton::Get().Terminate();
LOGD("Network test finished");
return 0;
}