fix(core): Tightens the network stack (#479)

- [X] Removes network pause/resume
- [X] Adds back packet profiler
- [X] Adds state machine to keep track and trace netstates
- [X] Changes NetState.Running back to using an interlock exchange
- [X] Adds preliminary packet throttling support


### Packet Throttling
- `[GetThrottle <packetId>` to get the delay in milliseconds for that packet
- `[SetThrottle <packetId> <delay>` to set the delay in milliseconds for that packet
The settings are saved to `Configuration/throttles.json`
This commit is contained in:
Kamron Batman 2021-02-07 23:30:56 -08:00 committed by GitHub
parent 1736469ac0
commit 8903028b5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 1018 additions and 405 deletions

View file

@ -140,7 +140,7 @@ namespace Server.Network
if (info == null || a == null || cityIndex < 0 || cityIndex >= info.Length)
{
state.Disconnect();
state.Disconnect("Invalid city selected during character creation.");
}
else
{
@ -197,7 +197,7 @@ namespace Server.Network
else
{
state.BlockAllPackets = false;
state.Disconnect();
state.Disconnect("Character creation blocked.");
}
}
}
@ -256,7 +256,7 @@ namespace Server.Network
if (a == null || charSlot < 0 || charSlot >= a.Length)
{
state.Disconnect();
state.Disconnect("Invalid character slot selected.");
}
else
{
@ -277,11 +277,11 @@ namespace Server.Network
if (m == null)
{
state.Disconnect();
state.Disconnect("Empty character slot selected.");
return;
}
m.NetState?.Disconnect();
m.NetState?.Disconnect("Character selected for a player already logged in.");
state.SendClientVersionRequest();
@ -380,9 +380,11 @@ namespace Server.Network
public static void GameLogin(NetState state, CircularBufferReader reader, ref int packetLength)
{
// TODO: Connection throttling
if (state.SentFirstPacket)
{
state.Disconnect();
state.Disconnect("Duplicate game login packet received.");
return;
}
@ -390,14 +392,16 @@ namespace Server.Network
var authID = reader.ReadInt32();
if (
!m_AuthIDWindow.TryGetValue(authID, out var ap) ||
state._authId != 0 && authID != state._authId ||
state._authId == 0 && authID != state._seed
)
if (!m_AuthIDWindow.TryGetValue(authID, out var ap))
{
state.WriteConsole("Invalid client detected, disconnecting");
state.Disconnect();
state.WriteConsole("Invalid client detected, disconnecting...");
state.Disconnect("Unable to find auth id.");
}
if (state._authId != 0 && authID != state._authId || state._authId == 0 && authID != state._seed)
{
state.WriteConsole("Invalid client detected, disconnecting...");
state.Disconnect("Invalid auth id in game login packet.");
return;
}
@ -424,7 +428,7 @@ namespace Server.Network
}
else
{
state.Disconnect();
state.Disconnect("Login rejected by GameLogin packet handler.");
}
}
@ -436,7 +440,7 @@ namespace Server.Network
if (info == null || a == null || index < 0 || index >= info.Length)
{
state.Disconnect();
state.Disconnect("Invalid server selected.");
}
else
{
@ -457,7 +461,7 @@ namespace Server.Network
if (state._seed == 0)
{
state.WriteConsole("Invalid client detected, disconnecting");
state.Disconnect();
state.Disconnect("Duplicate seed sent.");
return;
}
@ -471,9 +475,11 @@ namespace Server.Network
public static void AccountLogin(NetState state, CircularBufferReader reader, ref int packetLength)
{
// TODO: Throttle Connection
if (state.SentFirstPacket)
{
state.Disconnect();
state.Disconnect("Duplicate account login packet sent.");
return;
}
@ -512,7 +518,7 @@ namespace Server.Network
private static void AccountLogin_ReplyRej(this NetState state, ALRReason reason)
{
state.SendAccountLoginRejected(reason);
state.Disconnect();
state.Disconnect("Account login rejected by AccountLogin packet handler.");
}
private class LoginTimer : Timer

View file

@ -106,9 +106,9 @@ namespace Server.Network
public static void ExtendedCommand(NetState state, CircularBufferReader reader, ref int packetLength)
{
int packetID = reader.ReadUInt16();
int packetId = reader.ReadUInt16();
var ph = GetExtendedHandler(packetID);
var ph = GetExtendedHandler(packetId);
if (ph == null)
{
@ -122,11 +122,11 @@ namespace Server.Network
{
state.WriteConsole(
"Sent in-game packet (0xBFx{0:X2}) before having been attached to a mobile",
packetID
packetId
);
}
state.Disconnect();
state.Disconnect($"Sent in-game packet(0xBFx{packetId:X2}) but mobile is deleted.");
}
else
{

View file

@ -15,6 +15,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Server.Network
{
@ -82,87 +83,25 @@ namespace Server.Network
}
}
public static int ProcessPacket(this NetState ns, ArraySegment<byte>[] buffer)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInfoPacket(byte packetId)
{
var reader = new CircularBufferReader(buffer);
var packetId = reader.ReadByte();
if (!ns.Seeded)
// These packets can arrive at any time during the login process. They're just informational.
return packetId switch
{
if (packetId == 0xEF)
{
// new packet in client 6.0.5.0 replaces the traditional seed method with a seed packet
// 0xEF = 239 = multicast IP, so this should never appear in a normal seed. So this is backwards compatible with older clients.
ns.Seeded = true;
}
else
{
var seed = (packetId << 24) | (reader.ReadByte() << 16) | (reader.ReadByte() << 8) | reader.ReadByte();
if (seed == 0)
{
ns.WriteConsole("Invalid client detected, disconnecting");
return -1;
}
ns._seed = seed;
ns.Seeded = true;
return 4;
}
}
if (ns.CheckEncrypted(packetId))
{
return -1;
}
// Get Handlers
var handler = ns.GetHandler(packetId);
if (handler == null)
{
reader.Trace(ns);
return -1;
}
// We use this for failing fast where we already know the length, but may not read it entirely
var packetLength = handler.Length;
if (handler.Length <= 0 && reader.Length >= 3)
{
packetLength = reader.ReadUInt16();
if (packetLength < 3)
{
return -1;
}
}
// Not enough data, let's wait for more to come in
if (reader.Length < packetLength)
{
return 0;
}
if (handler.Ingame && ns.Mobile?.Deleted != false)
{
ns.WriteConsole("Sent ingame packet (0x{1:X2}) without being attached to a valid mobile.", ns, packetId);
return -1;
}
var throttled = handler.ThrottleCallback?.Invoke(ns) ?? TimeSpan.Zero;
if (throttled > TimeSpan.Zero)
{
ns.ThrottledUntil = DateTime.UtcNow + throttled;
}
// The packet length is sent in as a ref to support situations where a smaller/larger packet is read.
// Example is DropReq to support 6.0.1.7+ where the packet is 1 byte larger
handler.OnReceive(ns, reader, ref packetLength);
return packetLength;
0x01 => true, // Disconnect
0x73 => true, // Ping
0xA4 => true, // SystemInfo
0xB1 => true, // Gump Response
0xBB => true, // Account ID
0xBD => true, // Client Version
0xBE => true, // Assist Version
0xD9 => true, // Hardware Info
0xDD => true, // Gumps (Packed)
0xE1 => true, // Client Type
0xF4 => true, // CrashReport
_ => false
};
}
}
}

View file

@ -372,7 +372,7 @@ namespace Server.Network
if (!buttonExists)
{
state.WriteConsole("Invalid gump response, disconnecting...");
state.Disconnect();
state.Disconnect("Invalid gump response.");
return;
}
@ -381,7 +381,7 @@ namespace Server.Network
if (switchCount < 0 || switchCount > gump.m_Switches)
{
state.WriteConsole("Invalid gump response, disconnecting...");
state.Disconnect();
state.Disconnect("Invalid gump response.");
return;
}
@ -397,7 +397,7 @@ namespace Server.Network
if (textCount < 0 || textCount > gump.m_TextEntries)
{
state.WriteConsole("Invalid gump response, disconnecting...");
state.Disconnect();
state.Disconnect("Invalid gump response.");
return;
}
@ -411,7 +411,7 @@ namespace Server.Network
if (textLength > 239)
{
state.WriteConsole("Invalid gump response, disconnecting...");
state.Disconnect();
state.Disconnect("Invalid gump response.");
return;
}
@ -593,14 +593,14 @@ namespace Server.Network
if (ph.Ingame && state.Mobile == null)
{
state.WriteConsole(
"Sent ingame packet (0xD7x{0:X2}) before having been attached to a mobile",
"Sent in-game packet (0xD7x{0:X2}) before being attached to a mobile",
packetId
);
state.Disconnect();
state.Disconnect($"Sent in-game packet (0xD7x{packetId:X2}) before being attached to a mobile.");
}
else if (ph.Ingame && state.Mobile.Deleted)
{
state.Disconnect();
state.Disconnect($"Sent in-game packet(0xD7x{packetId:X2}) but mobile is deleted.");
}
else
{