mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-05 04:33:51 +00:00
fix: partial send on non-blocking socket silently dropped data
A single send() that returned fewer bytes than requested was logged but not retried, leaving the server with a truncated packet. This causes an irreversible TCP framing desync (next header lands mid-payload) that manifests as a disconnect under network pressure. Added a retry loop that handles EWOULDBLOCK with a brief yield. Also rejects payloads > 64KB instead of silently truncating the 16-bit CMSG size field, which would have written a wrong header while still appending all bytes.
This commit is contained in:
parent
e5b4e86600
commit
9da97e5e88
1 changed files with 30 additions and 7 deletions
|
|
@ -314,6 +314,14 @@ void WorldSocket::send(const Packet& packet) {
|
|||
|
||||
const auto& data = packet.getData();
|
||||
uint16_t opcode = packet.getOpcode();
|
||||
// CMSG header uses a 16-bit size field, so payloads > 64KB are unsupported.
|
||||
// Guard here rather than silently truncating via cast, which would write a
|
||||
// wrong size to the header while still appending all bytes.
|
||||
if (data.size() > 0xFFFF) {
|
||||
LOG_ERROR("Packet payload too large for CMSG header: ", data.size(),
|
||||
" bytes (opcode=0x", std::hex, opcode, std::dec, "). Dropping.");
|
||||
return;
|
||||
}
|
||||
uint16_t payloadLen = static_cast<uint16_t>(data.size());
|
||||
|
||||
// Debug: parse and log character-create payload fields (helps diagnose appearance issues).
|
||||
|
|
@ -426,14 +434,29 @@ void WorldSocket::send(const Packet& packet) {
|
|||
" payload=", payloadLen, " enc=", encryptionEnabled ? "yes" : "no");
|
||||
}
|
||||
|
||||
// Send complete packet
|
||||
ssize_t sent = net::portableSend(sockfd, sendData.data(), sendData.size());
|
||||
if (sent < 0) {
|
||||
LOG_ERROR("Send failed: ", net::errorString(net::lastError()));
|
||||
} else {
|
||||
if (static_cast<size_t>(sent) != sendData.size()) {
|
||||
LOG_WARNING("Partial send: ", sent, " of ", sendData.size(), " bytes");
|
||||
// Send complete packet, retrying on partial sends. Non-blocking sockets
|
||||
// can return fewer bytes than requested when the kernel buffer is full.
|
||||
// Without a retry loop, the server receives a truncated packet and the
|
||||
// TCP stream permanently desyncs (next header lands mid-payload).
|
||||
size_t totalSent = 0;
|
||||
while (totalSent < sendData.size()) {
|
||||
ssize_t sent = net::portableSend(sockfd, sendData.data() + totalSent,
|
||||
sendData.size() - totalSent);
|
||||
if (sent < 0) {
|
||||
int err = net::lastError();
|
||||
if (net::isWouldBlock(err)) {
|
||||
// Kernel buffer full — yield briefly and retry.
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(100));
|
||||
continue;
|
||||
}
|
||||
LOG_ERROR("Send failed: ", net::errorString(err));
|
||||
break;
|
||||
}
|
||||
if (sent == 0) break; // connection closed
|
||||
totalSent += static_cast<size_t>(sent);
|
||||
}
|
||||
if (totalSent != sendData.size()) {
|
||||
LOG_WARNING("Incomplete send: ", totalSent, " of ", sendData.size(), " bytes");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue