Compare commits

...

5 Commits

Author SHA1 Message Date
a9af8713bc Reject network messages too small for the packet size field 2023-03-12 01:45:18 +01:00
4825267537 Use memcpy() instead of casting to load keys
UBSAN complains about the casting approach because it loads a 64-bit
integer from the defaultKeys string which isn't guaranteed to be 64-bit
aligned, which is undefined behavior.
2023-03-11 23:16:09 +01:00
a92cfaff25 Differentiate new connection messages on the login and shard ports 2023-03-11 21:54:56 +01:00
abcfa3445b Move dead socket cleanup out of the poll() loop
This fixes a potential desync between the fds vector and the connections
map that could happen due to file descriptor number reuse.
2023-03-11 03:24:48 +01:00
2bf14200f7 Make CNSocket::kill() idempotent
CNSocket::kill() will now no longer call close() on already closed sockets.

close() should never be called on already closed file descriptors, yet
CNSocket::kill() was lacking any protection against that, despite its
use as both a high-level way of killing player connections and as a
means of ensuring that closing connections have been properly terminated
in the poll() loop.

This was causing close() to be erroneously called on each socket at least
one extra time. It was also introducing a race condition where the login
and shard threads could close each other's newly opened sockets due to
file descriptor reuse when a connection was accept()ed after the first
call to close(), but before the second one. See the close(2) manpage for
details.
2023-03-11 02:59:05 +01:00
4 changed files with 40 additions and 20 deletions

View File

@ -41,7 +41,8 @@ int CNSocketEncryption::xorData(uint8_t* buffer, uint8_t* key, int size) {
uint64_t CNSocketEncryption::createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2) { uint64_t CNSocketEncryption::createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2) {
uint64_t num = (uint64_t)(iv1 + 1); uint64_t num = (uint64_t)(iv1 + 1);
uint64_t num2 = (uint64_t)(iv2 + 1); uint64_t num2 = (uint64_t)(iv2 + 1);
uint64_t dEKey = (uint64_t)(*(uint64_t*)&defaultKey[0]); uint64_t dEKey;
memcpy(&dEKey, defaultKey, sizeof(dEKey));
return dEKey * (uTime * num * num2); return dEKey * (uTime * num * num2);
} }
@ -65,7 +66,7 @@ CNPacketData::CNPacketData(void *b, uint32_t t, int l, int trnum, void *trs):
// ========================================================[[ CNSocket ]]======================================================== // ========================================================[[ CNSocket ]]========================================================
CNSocket::CNSocket(SOCKET s, struct sockaddr_in &addr, PacketHandler ph): sock(s), sockaddr(addr), pHandler(ph) { CNSocket::CNSocket(SOCKET s, struct sockaddr_in &addr, PacketHandler ph): sock(s), sockaddr(addr), pHandler(ph) {
EKey = (uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]); memcpy(&EKey, CNSocketEncryption::defaultKey, sizeof(EKey));
} }
bool CNSocket::sendData(uint8_t* data, int size) { bool CNSocket::sendData(uint8_t* data, int size) {
@ -109,7 +110,11 @@ bool CNSocket::isAlive() {
} }
void CNSocket::kill() { void CNSocket::kill() {
if (!alive)
return;
alive = false; alive = false;
#ifdef _WIN32 #ifdef _WIN32
shutdown(sock, SD_BOTH); shutdown(sock, SD_BOTH);
closesocket(sock); closesocket(sock);
@ -241,9 +246,10 @@ void CNSocket::step() {
if (readSize <= 0) { if (readSize <= 0) {
// we aren't reading a packet yet, try to start looking for one // we aren't reading a packet yet, try to start looking for one
int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0); int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0);
if (recved == 0) { if (recved >= 0 && recved < sizeof(int32_t)) {
// the socket was closed normally // too little data for readSize or the socket was closed normally (when 0 bytes were read)
kill(); kill();
return;
} else if (!SOCKETERROR(recved)) { } else if (!SOCKETERROR(recved)) {
// we got our packet size!!!! // we got our packet size!!!!
readSize = *((int32_t*)readBuffer); readSize = *((int32_t*)readBuffer);
@ -264,11 +270,12 @@ void CNSocket::step() {
} }
if (readSize > 0 && readBufferIndex < readSize) { if (readSize > 0 && readBufferIndex < readSize) {
// read until the end of the packet! (or at least try too) // read until the end of the packet (or at least try to)
int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0); int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0);
if (recved == 0) { if (recved == 0) {
// the socket was closed normally // the socket was closed normally
kill(); kill();
return;
} else if (!SOCKETERROR(recved)) } else if (!SOCKETERROR(recved))
readBufferIndex += recved; readBufferIndex += recved;
else if (OF_ERRNO != OF_EWOULD) { else if (OF_ERRNO != OF_EWOULD) {
@ -411,9 +418,9 @@ void CNServer::addPollFD(SOCKET s) {
fds.push_back({s, POLLIN}); fds.push_back({s, POLLIN});
} }
void CNServer::removePollFD(int i) { void CNServer::removePollFD(int fd) {
auto it = fds.begin(); auto it = fds.begin();
while (it != fds.end() && it->fd != fds[i].fd) while (it != fds.end() && it->fd != fd)
it++; it++;
assert(it != fds.end()); assert(it != fds.end());
@ -458,7 +465,7 @@ void CNServer::start() {
if (!setSockNonblocking(sock, newConnectionSocket)) if (!setSockNonblocking(sock, newConnectionSocket))
continue; continue;
std::cout << "New connection! " << inet_ntoa(address.sin_addr) << std::endl; std::cout << "New " << serverType << " connection! " << inet_ntoa(address.sin_addr) << std::endl;
addPollFD(newConnectionSocket); addPollFD(newConnectionSocket);
@ -490,22 +497,29 @@ void CNServer::start() {
if (fds[i].revents & ~POLLIN) if (fds[i].revents & ~POLLIN)
cSock->kill(); cSock->kill();
if (cSock->isAlive()) { if (cSock->isAlive())
cSock->step(); cSock->step();
} else {
killConnection(cSock);
connections.erase(fds[i].fd);
delete cSock;
removePollFD(i);
// a new entry was moved to this position, so we check it again
i--;
}
} }
} }
onStep(); onStep();
// clean up dead connection sockets
auto it = connections.begin();
while (it != connections.end()) {
CNSocket *cSock = it->second;
if (!cSock->isAlive()) {
killConnection(cSock);
it = connections.erase(it);
removePollFD(cSock->sock);
delete cSock;
} else {
it++;
}
}
} }
} }

View File

@ -230,6 +230,7 @@ protected:
const size_t STARTFDSCOUNT = 8; // number of initial PollFD slots const size_t STARTFDSCOUNT = 8; // number of initial PollFD slots
std::vector<PollFD> fds; std::vector<PollFD> fds;
std::string serverType = "invalid";
SOCKET sock; SOCKET sock;
uint16_t port; uint16_t port;
socklen_t addressSize; socklen_t addressSize;

View File

@ -11,6 +11,7 @@
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions; std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
CNLoginServer::CNLoginServer(uint16_t p) { CNLoginServer::CNLoginServer(uint16_t p) {
serverType = "login";
port = p; port = p;
pHandler = &CNLoginServer::handlePacket; pHandler = &CNLoginServer::handlePacket;
init(); init();
@ -208,9 +209,12 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
// send the resp in with original key // send the resp in with original key
sock->sendPacket(resp, P_LS2CL_REP_LOGIN_SUCC); sock->sendPacket(resp, P_LS2CL_REP_LOGIN_SUCC);
uint64_t defaultKey;
memcpy(&defaultKey, CNSocketEncryption::defaultKey, sizeof(defaultKey));
// update keys // update keys
sock->setEKey(CNSocketEncryption::createNewKey(resp.uiSvrTime, resp.iCharCount + 1, resp.iSlotNum + 1)); sock->setEKey(CNSocketEncryption::createNewKey(resp.uiSvrTime, resp.iCharCount + 1, resp.iSlotNum + 1));
sock->setFEKey(CNSocketEncryption::createNewKey((uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]), login->iClientVerC, 1)); sock->setFEKey(CNSocketEncryption::createNewKey(defaultKey, login->iClientVerC, 1));
DEBUGLOG( DEBUGLOG(
std::cout << "Login Server: Login success. Welcome " << userLogin << " [" << loginSessions[sock].userID << "]" << std::endl; std::cout << "Login Server: Login success. Welcome " << userLogin << " [" << loginSessions[sock].userID << "]" << std::endl;

View File

@ -16,6 +16,7 @@ std::map<uint32_t, PacketHandler> CNShardServer::ShardPackets;
std::list<TimerEvent> CNShardServer::Timers; std::list<TimerEvent> CNShardServer::Timers;
CNShardServer::CNShardServer(uint16_t p) { CNShardServer::CNShardServer(uint16_t p) {
serverType = "shard";
port = p; port = p;
pHandler = &CNShardServer::handlePacket; pHandler = &CNShardServer::handlePacket;
REGISTER_SHARD_TIMER(keepAliveTimer, 4000); REGISTER_SHARD_TIMER(keepAliveTimer, 4000);