1
0
mirror of https://git.ngram.ca/OpenJam/rc-servers synced 2026-08-23 23:08:52 +00:00

Create RC primary service (load balancer)

This commit is contained in:
NGnius (Graham)
2025-02-05 21:19:27 -05:00
parent 49357b2759
commit e7ceefceb3
14 changed files with 1049 additions and 11 deletions

271
Cargo.lock generated
View File

@@ -106,6 +106,12 @@ dependencies = [
"num-traits 0.2.19",
]
[[package]]
name = "array-init"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
[[package]]
name = "async-stream"
version = "0.3.6"
@@ -207,6 +213,30 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
[[package]]
name = "binrw"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d4bca59c20d6f40c2cc0802afbe1e788b89096f61bdf7aeea6bf00f10c2909b"
dependencies = [
"array-init",
"binrw_derive",
"bytemuck",
]
[[package]]
name = "binrw_derive"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8ba42866ce5bced2645bfa15e97eef2c62d2bdb530510538de8dd3d04efff3c"
dependencies = [
"either",
"owo-colors",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -300,6 +330,46 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "clap"
version = "4.5.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.96",
]
[[package]]
name = "clap_lex"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "colorchoice"
version = "1.0.3"
@@ -593,10 +663,22 @@ dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
"cfg-if",
"libc",
"wasi 0.13.3+wasi-0.2.2",
"windows-targets 0.52.6",
]
[[package]]
name = "gimli"
version = "0.31.1"
@@ -644,6 +726,12 @@ version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.3.9"
@@ -1184,7 +1272,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.52.0",
]
@@ -1217,6 +1305,20 @@ dependencies = [
"winapi",
]
[[package]]
name = "num"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits 0.2.19",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
@@ -1227,6 +1329,15 @@ dependencies = [
"num-traits 0.2.19",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits 0.2.19",
]
[[package]]
name = "num-conv"
version = "0.1.0"
@@ -1242,6 +1353,28 @@ dependencies = [
"num-traits 0.2.19",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits 0.2.19",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits 0.2.19",
]
[[package]]
name = "num-traits"
version = "0.1.43"
@@ -1318,6 +1451,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "owo-colors"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "parking_lot"
version = "0.12.3"
@@ -1392,6 +1531,25 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "polariton"
version = "0.1.0"
dependencies = [
"binrw",
]
[[package]]
name = "polariton_auth"
version = "0.1.0"
dependencies = [
"log",
"num",
"polariton",
"rand 0.9.0",
"ring",
"simple-rijndael",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
@@ -1404,7 +1562,7 @@ version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
"zerocopy 0.7.35",
]
[[package]]
@@ -1464,7 +1622,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d"
dependencies = [
"bytes",
"getrandom",
"getrandom 0.2.15",
"rand 0.8.5",
"ring",
"rustc-hash",
@@ -1520,10 +1678,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.0",
"zerocopy 0.8.14",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
@@ -1534,6 +1703,16 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.0",
]
[[package]]
name = "rand_core"
version = "0.3.1"
@@ -1555,7 +1734,29 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
"getrandom 0.2.15",
]
[[package]]
name = "rand_core"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff"
dependencies = [
"getrandom 0.3.1",
"zerocopy 0.8.14",
]
[[package]]
name = "rc_services"
version = "0.1.0"
dependencies = [
"clap",
"env_logger",
"log",
"polariton",
"polariton_auth",
"tokio",
]
[[package]]
@@ -1701,7 +1902,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"getrandom 0.2.15",
"libc",
"spin",
"untrusted",
@@ -1964,6 +2165,12 @@ dependencies = [
"libc",
]
[[package]]
name = "simple-rijndael"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fc9945b08a629a18fb7eaf7d76d9e2ef2fa628c5ea1b7782627fc21bb2285"
[[package]]
name = "simple_asn1"
version = "0.6.3"
@@ -2049,6 +2256,12 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef6b00f8fe8eaaaff22cb9b70822a48c1a5d772bc682c202a57c0b438175845"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
@@ -2105,7 +2318,7 @@ checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
dependencies = [
"cfg-if",
"fastrand",
"getrandom",
"getrandom 0.2.15",
"once_cell",
"rustix",
"windows-sys 0.59.0",
@@ -2508,7 +2721,7 @@ version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
dependencies = [
"getrandom",
"getrandom 0.2.15",
"rand 0.8.5",
]
@@ -2539,6 +2752,15 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
@@ -2866,6 +3088,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags 2.8.0",
]
[[package]]
name = "write16"
version = "1.0.0"
@@ -2918,7 +3149,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
"zerocopy-derive 0.7.35",
]
[[package]]
name = "zerocopy"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468"
dependencies = [
"zerocopy-derive 0.8.14",
]
[[package]]
@@ -2932,6 +3172,17 @@ dependencies = [
"syn 2.0.96",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.96",
]
[[package]]
name = "zerofrom"
version = "0.1.5"

View File

@@ -5,7 +5,7 @@ edition = "2021"
[workspace]
members = [
"auth", "rc_static_data",
"auth", "polariton_auth", "rc_services", "rc_static_data",
]
[workspace.dependencies]
@@ -13,3 +13,5 @@ rocket = { version = "0.5.1", features = [ "json" ] }
libfj = { version = "0.7.5", path = "../libfj" }
log = "0.4"
env_logger = "0.11"
clap = { version = "4.5", features = [ "derive" ] }
polariton = { version = "*", path = "../polariton" }

View File

@@ -0,0 +1,65 @@
# Connect to Photon
## TCP packet
Header 7 bytes
Connect Data 41 bytes
### Header
0..1 =251 (mode?)
1..5 packet length (big endian)
5..6 =0 channel ID
6..7 =1 reliable? (bool; 1->reliable, 0->unreliable)
### Connect Data
0..1 =243 1 byte (mode?)
1..2 =0
2..4 Version 2 bytes (always 1 6)
4..5 =30 1 byte (client ID shifted by 1 -- 15 is ID)
5..7 client version 3 bytes (4 bytes merged into 3 -- 0 is upper 4 bits of byte 0, 1 is OR-ed with 0, byte 1 is 2, byte 2 is 3)
8..9 =0
9..41 app ID string 32 bytes (if shorter than 32 bytes/chars the remaining bytes are 0; if longer than 32 bytes/chars the remaining app ID is ignored i.e. truncated)
### Ping Response Data
0..1 =240 1 byte
1..5 tick count at time of server's receipt (big endian) (unit of game ticks?)
5..9 tick count from client's request (big endian) (unit of game ticks?)
### Ping Request Data
0..1 =240
1..5 current tick count
### Modes
220 Region info (in)
222 Friends list info (in)
225 Join lobby info (in)
226 Join lobby info (in)
227 Join lobby info (in)
228 Master server connect success (in)
229 Lobby joined success (in)
230 Join/connect info (in)
231 Join/connect info (in)
240 Ping (both)
243 Connect (out)
244 Connect? response? (in)
254 Disconnect to Reconnect
### Parameters
#### Op code 230
192 Reset/redo Encryption? (Map<u8, Typed>)
202 Nickname (string)
225 User ID (string)
230 Master server address (string)
#### Op code always???
221 Auth token (string)

12
polariton_auth/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "polariton_auth"
version = "0.1.0"
edition = "2021"
[dependencies]
log.workspace = true
polariton.workspace = true
num = "0.4"
rand = "0.9"
ring = "0.17"
simple-rijndael = "0.3"

View File

@@ -0,0 +1,86 @@
use num::BigInt;
use rand::Rng;
use simple_rijndael::impls::RijndaelCbc;
use simple_rijndael::paddings::Pkcs7Padding;
pub struct Keys {
pub pub_key: Vec<u8>,
pub enc: CryptoImpl,
}
const SECRET_LEN: usize = 160 / 8;
const PRIME_768: &[u8] = &[255, 255, 255, 255, 255, 255, 255, 255, 201, 15,
218, 162, 33, 104, 194, 52, 196, 198, 98, 139,
128, 220, 28, 209, 41, 2, 78, 8, 138, 103,
204, 116, 2, 11, 190, 166, 59, 19, 155, 34,
81, 74, 8, 121, 142, 52, 4, 221, 239, 149,
25, 179, 205, 58, 67, 27, 48, 43, 10, 109,
242, 95, 20, 55, 79, 225, 53, 109, 109, 81,
194, 69, 228, 133, 181, 118, 98, 94, 126, 198,
244, 76, 66, 233, 166, 58, 54, 32, 255, 255,
255, 255, 255, 255, 255, 255];
const PRIME_ROOT: u8 = 22;
pub fn generate_encryption_details(client_pub_key: &[u8]) -> Keys {
let client_num = BigInt::from_bytes_be(num::bigint::Sign::Plus, client_pub_key);
let big_0 = BigInt::from(0);
let prime_root = BigInt::from(PRIME_ROOT);
let my_prime = BigInt::from_bytes_be(num::bigint::Sign::Plus, PRIME_768);
log::debug!("Generating keys for client pub key {}", client_num.to_string());
let mut rng = rand::rng();
let mut bytes = rng.random::<[u8; SECRET_LEN]>();
let mut my_secret = BigInt::from_bytes_be(num::bigint::Sign::Plus, &bytes);
while my_secret >= &my_prime - 1 || my_secret == big_0 {
bytes = rng.random::<[u8; SECRET_LEN]>();
my_secret = BigInt::from_bytes_be(num::bigint::Sign::Plus, &bytes);
log::debug!("Generated secret {} (prime to beat: {})", my_secret.to_string(), my_prime.to_string());
}
let my_pub_key = prime_root.modpow(&my_secret, &my_prime);
let shared_key = client_num.modpow(&my_secret, &my_prime);
log::debug!("Generated shared key {} and pub key {}", shared_key.to_string(), my_pub_key.to_string());
let shared_key = shared_key.to_bytes_be().1;
let enc_key: Vec<u8> = ring::digest::digest(&ring::digest::SHA256, &shared_key).as_ref().into();
log::debug!("Encryption key is {:?}", enc_key.as_slice());
Keys {
pub_key: my_pub_key.to_signed_bytes_be(),
enc: CryptoImpl::new(enc_key),
}
}
pub struct CryptoImpl {
crypto: RijndaelCbc<Pkcs7Padding>,
iv: [u8; 16],
key: Vec<u8>
}
impl CryptoImpl {
fn new(key: Vec<u8>) -> Self {
Self {
crypto: RijndaelCbc::<Pkcs7Padding>::new(&key, 16).unwrap(),
iv: [0u8; 16],
key,
}
}
fn decrypt(&self, data: Vec<u8>) -> Vec<u8> {
self.crypto.decrypt(&self.iv, data).unwrap_or_default()
}
fn encrypt(&self, data: Vec<u8>) -> Vec<u8> {
self.crypto.encrypt(&self.iv, data).unwrap_or_default()
}
}
impl polariton::packet::Cryptographer for CryptoImpl {
fn decrypt(&self, data: Vec<u8>) -> Vec<u8> {
self.decrypt(data)
}
fn encrypt(&self, data: Vec<u8>) -> Vec<u8> {
self.encrypt(data)
}
fn secret(&self) -> &'_ [u8] {
self.key.as_slice()
}
}

View File

@@ -0,0 +1,198 @@
use std::sync::Arc;
use polariton::packet::{Packet, Message, StandardMessage, Data, Cryptographer};
use polariton::operation::{Typed, ParameterTable, OperationResponse};
#[derive(Debug)]
pub struct Handshake<T> {
state: T,
}
pub struct HandshakeAnd<T, X> {
pub handshake: Handshake<T>,
pub extra: X,
}
pub struct Start<'a> {
app_id: &'a str,
}
#[derive(Debug)]
pub enum ConnectError<'b, 'a> {
UnexpectedPacket,
WrongAppId { got: &'b str, expected: &'a str },
}
// TODO impl core::fmt::Display for ConnectError
// TODO impl std::error::Error for ConnectError
impl <'a> Handshake<Start<'a>> {
pub fn new(app_id: &'a str) -> Self {
Self {
state: Start { app_id },
}
}
pub fn connect<'b>(self, packet: &'b Packet) -> Result<HandshakeAnd<Connected, Packet>, HandshakeAnd<Start<'a>, ConnectError<'b, 'a>>> {
if let Packet::Packet(packet) = &packet {
if let Message::Standard(conn) = &packet.message {
if let Data::InitStart(info) = &conn.data {
if info.app_id != self.state.app_id {
let err = ConnectError::WrongAppId { got: &info.app_id, expected: &self.state.app_id };
return Err(HandshakeAnd {
handshake: self,
extra: err,
});
}
let new_self = Handshake::<Connected> {
state: Connected,
};
return Ok(HandshakeAnd {
handshake: new_self,
extra: Packet::from_message(
Message::Standard(StandardMessage {
flags: 0,
data: Data::InitAck ,
}),
packet.header.channel, true, None).unwrap()
});
}
}
}
Err(HandshakeAnd {
handshake: self,
extra: ConnectError::UnexpectedPacket,
})
}
}
pub struct Connected;
#[derive(Debug)]
pub enum EncryptError {
UnexpectedPacket,
MissingParameter(u8),
}
impl Handshake<Connected> {
const PUBLIC_KEY_PARAM_KEY: u8 = 1;
pub fn encrypt<'a>(self, packet: &'a Packet) -> Result<HandshakeAnd<Encrypted, (Packet, Box<Arc<dyn Cryptographer>>)>, HandshakeAnd<Connected, EncryptError>> {
if let Packet::Packet(packet) = &packet {
if let Message::Standard(conn) = &packet.message {
if let Data::InternalOpReq(req) = &conn.data {
if req.code == 0 {
let params = req.params.to_owned().to_dict();
if let Some(Typed::Bytes(pub_key)) = params.get(&Self::PUBLIC_KEY_PARAM_KEY) {
let keys = crate::encryption::generate_encryption_details(&pub_key.vec);
let mut response_params = std::collections::HashMap::with_capacity(1);
response_params.insert(Self::PUBLIC_KEY_PARAM_KEY, Typed::Bytes(keys.pub_key.into()));
let resp_packet = Packet::from_message(
Message::Standard(
StandardMessage {
flags: 0,
data: Data::InternalOpResp(OperationResponse {
code: req.code,
return_code: 0,
message: Typed::Null,
params: ParameterTable::from_dict(response_params) })
}), 0, true, None).unwrap();
let new_self = Handshake::<Encrypted> {
state: Encrypted,
};
return Ok(HandshakeAnd {
handshake: new_self,
extra: (resp_packet, Box::new(Arc::new(keys.enc))),
});
} else {
return Err(HandshakeAnd {
handshake: self,
extra: EncryptError::MissingParameter(Self::PUBLIC_KEY_PARAM_KEY),
});
}
}
}
}
}
Err(HandshakeAnd {
handshake: self,
extra: EncryptError::UnexpectedPacket,
})
}
}
pub struct Encrypted;
impl Handshake<Encrypted> {
pub fn with_auth<T: AuthProvider<E>, E>(self, auth: T) -> Handshake<Auth<T, E>> {
Handshake {
state: Auth {
authenticator: auth,
_e: Default::default(),
}
}
}
}
pub trait AuthProvider<E> {
fn validate(&mut self, params: &std::collections::HashMap<u8, Typed>) -> Result<std::collections::HashMap<u8, Typed>, E>;
}
pub struct Auth<T: AuthProvider<E>, E> {
authenticator: T,
_e: std::marker::PhantomData<E>,
}
#[derive(Debug)]
pub enum AuthError<E> {
Validation(E),
UnexpectedPacket
}
impl <T: AuthProvider<E>, E> Handshake<Auth<T, E>> {
//const SERVER_ADDRESS_KEY: u8 = 230;
const AUTH_REQUEST_CODE: u8 = 230;
const USER_ID_KEY: u8 = 225;
const NICKNAME_KEY: u8 = 225;
pub fn authenticate<'a>(mut self, packet: &'a Packet, crypto: Box<Arc<dyn Cryptographer>>) -> Result<Packet, HandshakeAnd<Auth<T, E>, AuthError<E>>> {
if let Packet::Packet(packet) = &packet {
if let Message::Standard(conn) = &packet.message {
if let Data::OpReq(req) = &conn.data {
if req.code == Self::AUTH_REQUEST_CODE /* or 231 ???*/ {
let req_dict = req.params.to_owned().to_dict();
let mut params_resp = match self.state.authenticator.validate(&req_dict) {
Ok(x) => x,
Err(e) => {
return Err(HandshakeAnd {
handshake: self,
extra: AuthError::Validation(e),
});
}
};
if let Some(user_id) = req_dict.get(&Self::USER_ID_KEY) {
params_resp.insert(Self::USER_ID_KEY, user_id.to_owned());
params_resp.insert(Self::NICKNAME_KEY, user_id.to_owned());
}
return Ok(Packet::from_message(
Message::Standard(
StandardMessage {
flags: 0,
data: Data::OpResp(OperationResponse {
code: Self::AUTH_REQUEST_CODE,
return_code: 0,
message: Typed::Null,
params: params_resp.into(),
})
}.encrypt(conn.is_encrypted())
), packet.header.channel, true, Some(crypto)).unwrap());
}
}
}
}
Err(HandshakeAnd {
handshake: self,
extra: AuthError::UnexpectedPacket,
})
}
}

View File

@@ -0,0 +1,7 @@
mod encryption;
mod handshake;
pub use handshake::{Handshake, AuthProvider, AuthError};
mod ping_pong;
pub use ping_pong::ping_pong;

View File

@@ -0,0 +1,11 @@
use polariton::packet::Ping;
#[inline]
pub fn ping_pong(mut ping: Ping) -> Ping {
if ping.tick2.is_some() {
ping.tick2 = None;
} else {
ping.tick2 = Some(ping.tick1);
}
ping
}

12
rc_services/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "rc_services"
version = "0.1.0"
edition = "2021"
[dependencies]
log.workspace = true
env_logger.workspace = true
tokio = { version = "1.43", features = [ "net", "macros", "rt-multi-thread", "io-util" ] }
clap.workspace = true
polariton.workspace = true
polariton_auth = { version = "*", path = "../polariton_auth" }

3
rc_services/build_arm64.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
cargo build --release --target aarch64-unknown-linux-musl

3
rc_services/run_debug.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
RUST_BACKTRACE=1 RUST_LOG=debug cargo run

31
rc_services/src/cli.rs Normal file
View File

@@ -0,0 +1,31 @@
use clap::Parser;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct CliArgs {
/// TCP port on which to accept connections
#[arg(short, long, default_value_t = 4532)]
pub port: u16,
/// IP Address on which to accept connections
#[arg(long, default_value_t = {"127.0.0.1".to_string()})]
pub ip: String,
/// Socket read tries before giving up (0 to never give up)
#[arg(long, default_value_t = 5)]
pub retries: usize,
/// Domain and port of the game server to send new connections
#[arg(long, default_value_t = {"127.0.0.1:4533".to_string()})]
pub redirect: String,
/// Name of game server to send new connections
#[arg(long, default_value_t = {"ngram_is_ngnius".to_string()})]
pub room_name: String,
}
impl CliArgs {
pub fn get() -> Self {
Self::parse()
}
}

340
rc_services/src/main.rs Normal file
View File

@@ -0,0 +1,340 @@
mod cli;
mod state;
use std::num::NonZero;
use std::sync::Arc;
use polariton_auth::Handshake;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net;
use polariton::packet::{Cryptographer, Data, Message, Packet, Ping, StandardMessage, StandardPacket};
use polariton::operation::{OperationResponse, Typed};
#[tokio::main]
async fn main() -> std::io::Result<()> {
env_logger::init();
let args = cli::CliArgs::get();
log::debug!("Got cli args {:?}", args);
let ip_addr: std::net::IpAddr = args.ip.parse().expect("Invalid IP address");
let listener = net::TcpListener::bind(std::net::SocketAddr::new(ip_addr, args.port)).await?;
loop {
let (socket, address) = listener.accept().await?;
process_socket(socket, address, NonZero::new(args.retries), &args.redirect, &args.room_name).await;
}
}
async fn process_socket(mut socket: net::TcpStream, address: std::net::SocketAddr, retries: Option<NonZero<usize>>, game_server_url: &str, game_server_name: &str) {
log::debug!("Accepting connection from address {}", address);
let mut buf = Vec::new();
let enc = match do_connect_handshake(&mut buf, &mut socket, retries, game_server_url, game_server_name).await {
Some(x) => x,
None => {
log::error!("Failed to do connect handshake with {}", address);
return;
}
};
buf.clear();
}
async fn handle_ping(ping: Ping, buf: &mut Vec<u8>, socket: &mut net::TcpStream) {
let resp = Packet::Ping(polariton_auth::ping_pong(ping));
resp.to_buf(buf, None).unwrap();
let write_count = socket.write(buf).await.unwrap();
log::debug!("(ping) Write {} bytes to socket: {:?}", write_count, buf);
}
fn buf_likely_valid(buf: &[u8]) -> bool {
buf.is_empty() || buf[0] == Packet::PING_MAGIC || buf[0] == Packet::FRAMED_MAGIC
}
async fn read_more(buf: &mut Vec<u8>, socket: &mut net::TcpStream) -> Result<usize, std::io::Error> {
let read_count = socket.read_buf(buf).await?;
log::debug!("Read {} bytes from socket: {:?}", read_count, buf);
Ok(read_count)
}
async fn receive_packet(buf: &mut Vec<u8>, socket: &mut net::TcpStream, max_retries: Option<NonZero<usize>>, args: Option<Box<Arc<dyn Cryptographer>>>) -> Result<Packet, std::io::Error> {
buf.clear();
let read_count = read_more(buf, socket).await?;
if read_count == 0 { return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "socket did not read any bytes")); } // bad packet
let mut last_err = None;
let mut must_succeed_next = false;
if let Some(max_retries) = max_retries {
for _ in 0..max_retries.get() {
match Packet::from_buf(&buf, args.clone()) {
Ok(packet) => {
log::debug!("(connect) Received packet {:?}", packet);
return Ok(packet);
},
Err(e) => last_err = Some(e),
}
if must_succeed_next {
break;
}
must_succeed_next = read_more(buf, socket).await? == 0;
}
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, last_err.unwrap()));
} else {
while buf_likely_valid(buf.as_slice()) {
match Packet::from_buf(&buf, args.clone()) {
Ok(packet) => {
log::debug!("(connect) Received packet {:?}", packet);
return Ok(packet);
},
Err(e) => last_err = Some(e),
}
if must_succeed_next {
break;
}
must_succeed_next = read_more(buf, socket).await? == 0;
}
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, last_err.unwrap()));
}
}
async fn send_packet(packet: Packet, buf: &mut Vec<u8>, socket: &mut net::TcpStream, args: Option<Box<Arc<dyn Cryptographer>>>) -> Result<(), std::io::Error> {
log::debug!("Sending packet {:?}", packet);
buf.clear();
packet.to_buf(buf, args).map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, e))?;
let write_count = socket.write(buf).await?;
log::debug!("Write {} bytes to socket: {:?}", write_count, buf);
#[cfg(debug_assertions)]
{
// print out unencrypted packet too
if let Packet::Packet(standard_p) = packet {
if let Message::Standard(standard_m) = standard_p.message {
if standard_m.is_encrypted() {
let standard_m = standard_m.encrypt(false);
let packet = Packet::Packet(StandardPacket { header: standard_p.header, message: Message::Standard(standard_m) });
packet.to_buf(buf, None).map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, e))?;
log::debug!("Unencrypted bytes of packet: {:?} (len: {})", buf, buf.len());
}
}
}
}
Ok(())
}
const APP_ID: &str = "WebServicesServer";
struct AuthImpl;
const TOKEN_KEY: u8 = 216; // token;refresh_token
//const UNKNOWN_BYTE_KEY: u8 = 217;
const SERVICE_KEY: u8 = 224;
const USERNAME_KEY: u8 = 225;
//const CCU_KEY: u8 = 245;
#[derive(Debug)]
enum AuthError {
WrongService { expected: String, actual: String },
MissingService,
MissingToken,
MissingUsername,
}
impl AuthError {
fn log_err(&self) {
match self {
Self::WrongService { expected, actual } => log::error!("(auth fail) Got unexpected service {}, expected {}", actual, expected),
Self::MissingService => log::error!("(auth fail) No service name param ({}) received", SERVICE_KEY),
Self::MissingToken => log::error!("(auth fail) No token param ({}) received", TOKEN_KEY),
Self::MissingUsername => log::error!("(auth fail) No username param ({}) received", USERNAME_KEY),
}
}
}
impl polariton_auth::AuthProvider<AuthError> for AuthImpl {
fn validate(&mut self, params: &std::collections::HashMap<u8, Typed>) -> Result<std::collections::HashMap<u8, Typed>, AuthError> {
if let Some(Typed::Str(token)) = params.get(&TOKEN_KEY) {
if let Some(Typed::Str(service)) = params.get(&SERVICE_KEY) {
if let Some(Typed::Str(user)) = params.get(&USERNAME_KEY) {
if service.string == APP_ID {
let params_resp = std::collections::HashMap::<u8, Typed>::new();
//params_resp.insert(CCU_KEY, Typed::Byte(0));
log::debug!("Auth success for {} (token: {})", user.string, token.string);
Ok(params_resp)
} else { Err(AuthError::WrongService { expected: APP_ID.to_owned(), actual: service.string.to_owned() }) }
} else { Err(AuthError::MissingUsername) }
} else { Err(AuthError::MissingService) }
} else { Err(AuthError::MissingToken) }
}
}
async fn do_connect_handshake(
buf: &mut Vec<u8>,
socket: &mut net::TcpStream,
max_retries: Option<NonZero<usize>>,
game_server_url: &str,
game_server_name: &str,
) -> Option<Box<std::sync::Arc<dyn Cryptographer>>> {
let handshake = Handshake::new(APP_ID);
// connect
log::debug!("(connect) Handling first packet");
let packet1 = match receive_packet(buf, socket, max_retries, None).await {
Ok(x) => x,
Err(e) => {
log::error!("Failed to read connect packet: {}", e);
return None;
}
};
let (handshake, to_send) = match handshake.connect(&packet1) {
Ok(x) => (x.handshake, x.extra),
Err(e) => {
log::error!("Failed to handle connect handshake: {:?}", e.extra);
return None;
}
};
match send_packet(to_send, buf, socket, None).await {
Ok(_) => {},
Err(e) => {
log::error!("Failed to send connect ack packet: {}", e);
return None;
}
}
// encrypt
log::debug!("(connect) Handling second packet");
let mut packet2 = match receive_packet(buf, socket, max_retries, None).await {
Ok(x) => x,
Err(e) => {
log::error!("Failed to read (maybe) public key packet: {}", e);
return None;
}
};
while let Packet::Ping(ping) = packet2 {
handle_ping(ping, buf, socket).await;
packet2 = match receive_packet(buf, socket, max_retries, None).await {
Ok(x) => x,
Err(e) => {
log::error!("Failed to read (maybe) public key packet: {}", e);
return None;
}
};
}
let (handshake, to_send, crypto) = match handshake.encrypt(&packet2) {
Ok(x) => (x.handshake, x.extra.0, x.extra.1),
Err(e) => {
log::error!("Failed to handle encryption handshake: {:?}", e.extra);
return None;
}
};
match send_packet(to_send, buf, socket, None).await {
Ok(_) => {},
Err(e) => {
log::error!("Failed to send encryption ack packet: {}", e);
return None;
}
}
// pre-auth
let handshake = handshake.with_auth(AuthImpl);
// authenticate
log::debug!("(connect) Handling third packet");
let mut packet3 = match receive_packet(buf, socket, max_retries, Some(crypto.clone())).await {
Ok(x) => x,
Err(e) => {
log::error!("Failed to read (maybe) auth packet: {}", e);
return None;
}
};
while let Packet::Ping(ping) = packet3 {
handle_ping(ping, buf, socket).await;
packet3 = match receive_packet(buf, socket, max_retries, Some(crypto.clone())).await {
Ok(x) => x,
Err(e) => {
log::error!("Failed to read (maybe) auth packet: {}", e);
return None;
}
};
}
let to_send = match handshake.authenticate(&packet3, crypto.clone()) {
Ok(x) => x,
Err(h) => match h.extra {
polariton_auth::AuthError::Validation(e) => {
e.log_err();
return None;
},
e => {
log::error!("Failed to handle auth handshake: {:?}", e);
return None;
},
},
};
match send_packet(to_send, buf, socket, Some(crypto.clone())).await {
Ok(_) => {},
Err(e) => {
log::error!("Failed to send auth ack packet: {}", e);
return None;
}
}
// send CCU passed event
let ccu_passed_event = Packet::from_message(
Message::Standard(
StandardMessage { flags: 0,
data: Data::Event(polariton::operation::Event {
code: 14, // CCU passed event code for Web service
params: std::collections::HashMap::new().into(),
}),
}.encrypt(true)), 0, true, Some(crypto.clone())).unwrap();
match send_packet(ccu_passed_event, buf, socket, Some(crypto.clone())).await {
Ok(_) => {},
Err(e) => {
log::error!("Failed to send CCU event packet: {}", e);
return None;
}
}
let mut packet_j = match receive_packet(buf, socket, max_retries, Some(crypto.clone())).await {
Ok(x) => x,
Err(e) => {
log::error!("Failed to read (maybe) join packet: {}", e);
return None;
}
};
while let Packet::Ping(ping) = packet_j {
handle_ping(ping, buf, socket).await;
packet_j = match receive_packet(buf, socket, max_retries, Some(crypto.clone())).await {
Ok(x) => x,
Err(e) => {
log::error!("Failed to read (maybe) join packet: {}", e);
return None;
}
};
}
if let Packet::Packet(msg) = &packet_j {
if let Message::Standard(st) = &msg.message {
if let Data::OpReq(req) = &st.data {
if req.code == 225 { // join lobby
log::debug!("Max players from lobby join request: {:?}", req.params.to_owned().to_dict().get(&255));
let mut params = std::collections::HashMap::<u8, Typed>::new();
params.insert(230 /* game server address */, Typed::Str(game_server_url.into()));
params.insert(255 /* room name */, Typed::Str(game_server_name.into()));
let resp = Packet::from_message(
Message::Standard(
StandardMessage { flags: 0,
data: Data::OpResp(OperationResponse {
code: req.code,
return_code: 0,
message: Typed::Null,
params: params.into(),
}),
}.encrypt(true)), 0, true, Some(crypto.clone())).unwrap();
match send_packet(resp, buf, socket, Some(crypto.clone())).await {
Ok(_) => {},
Err(e) => {
log::error!("Failed to send lobby ack packet: {}", e);
return None;
}
}
}
}
}
}
Some(crypto)
}

17
rc_services/src/state.rs Normal file
View File

@@ -0,0 +1,17 @@
use std::sync::Arc;
pub struct State {
pub crypto: Box<Arc<dyn polariton::packet::Cryptographer>>,
}
impl State {
pub fn new(c: Box<Arc<dyn polariton::packet::Cryptographer>>) -> Self {
Self {
crypto: c,
}
}
pub fn binrw_args(&self) -> polariton::packet::WriteArgs {
Some(self.crypto.clone())
}
}