Middleware (#662)

* removed suspicious shadowing and clarified failure message; customized skipped bundling

* smp history is now a no-less-efficient circular array

* adjustment to bundle dispatch timing; adjustment to inbound sequence reorder queue

* adjustments to handling inbound packets with missing subslots

* unused PacketCoding features

* comments; delayed start of the queue processor task; turned sequence reorder task and subslot missing task into function literals

* optimizations to the inbound re-order by sequence routines by controlling execution flow

* the subslot request timer has been separated from the standard bundling processor; config values for bundling, sequence resolution, and subslot requests in the middleware actor have been included

* replacing func-array with conditional logic
This commit is contained in:
Fate-JH 2021-01-12 14:33:44 -05:00 committed by GitHub
parent 563afcdb19
commit b5fc2ecf70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 649 additions and 248 deletions

View file

@ -99,6 +99,23 @@ anti-cheat {
} }
network { network {
middleware {
# How often between executions of the outbound bundling process
packet-bundling-delay = 25 milliseconds
# Pause inbound packet transmission towards the network if the sequence number is out of order
# Packets are put aside until the sequence is restored, or this timeout passes
in-reorder-timeout = 50 milliseconds
# Wait on inbound packets if that packet is a SlottedMetaPacket and the next subslot number is greater than expected
# Does not stop the transmission of packets to the server
# but dictates how long between requests to the network (client) for missing packets with anticipated subslot numbers
in-subslot-missing-delay = 50 milliseconds
# How many attempts at resolving missing packets with anticipated subslot numbers
in-subslot-missing-attempts = 10
}
session { session {
# The maximum amount of time since the last inbound packet from a UDP session # The maximum amount of time since the last inbound packet from a UDP session
# before it is dropped. # before it is dropped.

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.packet package net.psforever.packet
import java.security.{Key, SecureRandom, Security} import java.security.{Key, Security}
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.spec.RC5ParameterSpec import javax.crypto.spec.RC5ParameterSpec
@ -16,8 +16,6 @@ import net.psforever.util.Md5Mac
object PacketCoding { object PacketCoding {
Security.addProvider(new BouncyCastleProvider) Security.addProvider(new BouncyCastleProvider)
private val random = new SecureRandom()
val RC5_BLOCK_SIZE = 8 val RC5_BLOCK_SIZE = 8
/** A lower bound on the packet size */ /** A lower bound on the packet size */
@ -40,9 +38,9 @@ object PacketCoding {
case _: PlanetSideControlPacket if crypto.isEmpty => BitVector.empty case _: PlanetSideControlPacket if crypto.isEmpty => BitVector.empty
case _ => case _ =>
sequence match { sequence match {
case Some(sequence) => case Some(_sequence) =>
uint16L.encode(sequence) match { uint16L.encode(_sequence) match {
case Successful(seq) => seq case Successful(_seq) => _seq
case f @ Failure(_) => return f case f @ Failure(_) => return f
} }
case None => case None =>
@ -53,8 +51,8 @@ object PacketCoding {
val (flags, payload) = packet match { val (flags, payload) = packet match {
case _: PlanetSideGamePacket | _: PlanetSideControlPacket if crypto.isDefined => case _: PlanetSideGamePacket | _: PlanetSideControlPacket if crypto.isDefined =>
encodePacket(packet) match { encodePacket(packet) match {
case Successful(payload) => case Successful(_payload) =>
val encryptedPayload = crypto.get.encrypt(payload.bytes) match { val encryptedPayload = crypto.get.encrypt(_payload.bytes) match {
case Successful(p) => p case Successful(p) => p
case f: Failure => return f case f: Failure => return f
} }
@ -68,29 +66,29 @@ object PacketCoding {
} }
case packet: PlanetSideGamePacket => case packet: PlanetSideGamePacket =>
encodePacket(packet) match { encodePacket(packet) match {
case Successful(payload) => case Successful(_payload) =>
( (
PlanetSidePacketFlags.codec.encode(PlanetSidePacketFlags(PacketType.Normal, secured = false)).require, PlanetSidePacketFlags.codec.encode(PlanetSidePacketFlags(PacketType.Normal, secured = false)).require,
payload _payload
) )
case f @ Failure(_) => return f case f @ Failure(_) => return f
} }
case packet: PlanetSideControlPacket => case packet: PlanetSideControlPacket =>
encodePacket(packet) match { encodePacket(packet) match {
case Successful(payload) => case Successful(_payload) =>
( (
// control packets don't have flags // control packets don't have flags
BitVector.empty, BitVector.empty,
payload _payload
) )
case f @ Failure(_) => return f case f @ Failure(_) => return f
} }
case packet: PlanetSideCryptoPacket => case packet: PlanetSideCryptoPacket =>
encodePacket(packet) match { encodePacket(packet) match {
case Successful(payload) => case Successful(_payload) =>
( (
PlanetSidePacketFlags.codec.encode(PlanetSidePacketFlags(PacketType.Crypto, secured = false)).require, PlanetSidePacketFlags.codec.encode(PlanetSidePacketFlags(PacketType.Crypto, secured = false)).require,
payload _payload
) )
case f @ Failure(_) => return f case f @ Failure(_) => return f
} }
@ -164,7 +162,7 @@ object PacketCoding {
crypto: Option[CryptoCoding] = None crypto: Option[CryptoCoding] = None
): Attempt[(PlanetSidePacket, Int)] = { ): Attempt[(PlanetSidePacket, Int)] = {
val (flags, remainder) = Codec.decode[PlanetSidePacketFlags](BitVector(msg)) match { val (flags, remainder) = Codec.decode[PlanetSidePacketFlags](BitVector(msg)) match {
case Successful(DecodeResult(value, remainder)) => (value, remainder) case Successful(DecodeResult(value, _remainder)) => (value, _remainder)
case Failure(e) => return Failure(Err(s"Failed to parse packet flags: ${e.message}")) case Failure(e) => return Failure(Err(s"Failed to parse packet flags: ${e.message}"))
} }
@ -184,8 +182,8 @@ object PacketCoding {
// all packets have a two byte sequence ID // all packets have a two byte sequence ID
val (sequence, payload) = uint16L.decode(remainder) match { val (sequence, payload) = uint16L.decode(remainder) match {
case Successful(DecodeResult(value, remainder)) => case Successful(DecodeResult(value, _remainder)) =>
(value, remainder.toByteVector) (value, _remainder.toByteVector)
case Failure(e) => case Failure(e) =>
return Failure(Err(s"Failed to parse packet sequence number: ${e.message}")) return Failure(Err(s"Failed to parse packet sequence number: ${e.message}"))
} }
@ -195,9 +193,9 @@ object PacketCoding {
CryptoPacketOpcode CryptoPacketOpcode
.getPacketDecoder(cryptoState)(payload.bits) .getPacketDecoder(cryptoState)(payload.bits)
.map(p => (p.value.asInstanceOf[PlanetSidePacket], sequence)) .map(p => (p.value.asInstanceOf[PlanetSidePacket], sequence))
case (PacketType.Normal, Some(crypto)) if flags.secured => case (PacketType.Normal, Some(_crypto)) if flags.secured =>
// encrypted payload is 4-byte aligned: 1b flags, 2b sequence, 1b padding // encrypted payload is 4-byte aligned: 1b flags, 2b sequence, 1b padding
crypto.decrypt(payload.drop(1)).map(p => decodePacket(p)).flatten.map(p => (p, sequence)) _crypto.decrypt(payload.drop(1)).map(p => decodePacket(p)).flatten.map(p => (p, sequence))
case (PacketType.Normal, None) if !flags.secured => case (PacketType.Normal, None) if !flags.secured =>
decodePacket(payload).map(p => (p, sequence)) decodePacket(payload).map(p => (p, sequence))
case (PacketType.Normal, None) => case (PacketType.Normal, None) =>
@ -279,7 +277,9 @@ object PacketCoding {
Successful(ByteVector.view(rc5Encrypt.doFinal(packetWithPadding.toArray))) Successful(ByteVector.view(rc5Encrypt.doFinal(packetWithPadding.toArray)))
} }
} catch { } catch {
case e: Throwable => Failure(Err(s"encrypt error: '${e.getMessage}' data: ${packetWithPadding.toHex}")) case e: Throwable =>
val msg = if(e.getMessage == null) e.getClass.getSimpleName else e.getMessage
Failure(Err(s"encrypt error: '$msg' data: ${packetWithPadding.toHex}"))
} }
} }
@ -295,7 +295,7 @@ object PacketCoding {
// last byte is the padding length // last byte is the padding length
val padding = uint8L.decode(payloadDecrypted.takeRight(1).bits) match { val padding = uint8L.decode(payloadDecrypted.takeRight(1).bits) match {
case Successful(padding) => padding.value case Successful(_padding) => _padding.value
case Failure(e) => return Failure(Err(s"Failed to decode the encrypted padding length: ${e.message}")) case Failure(e) => return Failure(Err(s"Failed to decode the encrypted padding length: ${e.message}"))
} }
@ -304,7 +304,7 @@ object PacketCoding {
val mac = bytes(Md5Mac.MACLENGTH).decode(payloadMac.bits) match { val mac = bytes(Md5Mac.MACLENGTH).decode(payloadMac.bits) match {
case Failure(e) => return Failure(Err("Failed to extract the encrypted MAC: " + e.message)) case Failure(e) => return Failure(Err("Failed to extract the encrypted MAC: " + e.message))
case Successful(mac) => mac.value case Successful(_mac) => _mac.value
} }
val payloadNoMac = payloadNoPadding.dropRight(Md5Mac.MACLENGTH) val payloadNoMac = payloadNoPadding.dropRight(Md5Mac.MACLENGTH)

View file

@ -114,7 +114,15 @@ case class AntiCheatConfig(
) )
case class NetworkConfig( case class NetworkConfig(
session: SessionConfig session: SessionConfig,
middleware: MiddlewareConfig
)
case class MiddlewareConfig(
packetBundlingDelay: FiniteDuration,
inReorderTimeout: FiniteDuration,
inSubslotMissingDelay: FiniteDuration,
inSubslotMissingAttempts: Int
) )
case class SessionConfig( case class SessionConfig(