initial commit

This commit is contained in:
soruh 2023-01-25 19:58:27 +01:00
commit 3163644c62
5 changed files with 1178 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
db.json

506
Cargo.lock generated Normal file
View File

@ -0,0 +1,506 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "anyhow"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
dependencies = [
"backtrace",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytemuck"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393"
dependencies = [
"bytemuck_derive",
]
[[package]]
name = "bytemuck_derive"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aca418a974d83d40a0c1f0c5cba6ff4bc28d8df099109ca459a2118d40b6322"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "bytes"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]]
name = "cc"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]]
name = "centralex"
version = "0.1.0"
dependencies = [
"anyhow",
"bytemuck",
"rand",
"serde",
"serde_json",
"tokio",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793"
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "itoa"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "miniz_oxide"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [
"adler",
]
[[package]]
name = "mio"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "object"
version = "0.30.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b8c786513eb403643f2a88c244c2aaa270ef2153f55094587d0c48a3cf22a83"
dependencies = [
"memchr",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "ryu"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "socket2"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "syn"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
dependencies = [
"autocfg",
"bytes",
"libc",
"memchr",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"

14
Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "centralex"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = { version = "1.0.68", features = ["backtrace"] }
bytemuck = { version = "1.13.0", features = ["derive"] }
rand = "0.8.5"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.91"
tokio = { version = "1.24.2", features = ["full"] }

446
src/main.rs Normal file
View File

@ -0,0 +1,446 @@
#![feature(generic_const_exprs)]
#![allow(unused)]
use std::{
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
fmt::Debug,
fs::File,
future::Future,
io::{BufReader, BufWriter},
net::{IpAddr, Ipv4Addr, SocketAddr},
ops::Range,
path::{Path, PathBuf},
sync::{Arc, Mutex},
time::{Duration, SystemTime, UNIX_EPOCH},
};
use anyhow::bail;
use packets::{reject_static, Header, Packet, RemConnect};
use serde::{Deserialize, Serialize};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::{TcpListener, TcpSocket, TcpStream},
select,
task::JoinHandle,
time::Instant,
};
use crate::packets::dyn_ip_update;
const AUTH_TIMEOUT: Duration = Duration::from_secs(30);
const CALL_ACK_TIMEOUT: Duration = Duration::from_secs(30);
const PING_INTERVAL: Duration = Duration::from_secs(15);
const TIMEOUT_DELAY: Duration = Duration::from_secs(35);
const PORT_TIMEOUT: Duration = Duration::from_secs(60);
const PORT_RETRY_TIME: Duration = Duration::from_secs(60); // 10 *
const BIND_IP: &str = "0.0.0.0";
mod packets;
type Port = u16;
type Number = u32;
type UnixTimestamp = u64;
#[derive(Default, Debug, Serialize, Deserialize)]
struct Config {
allowed_ports: AllowedPorts,
}
impl Config {
fn load(db: &Path) -> std::io::Result<Self> {
println!("loading config");
Ok(serde_json::from_reader(BufReader::new(File::open(db)?))?)
}
fn load_or_default(db: &Path) -> std::io::Result<Self> {
match Self::load(db) {
Ok(db) => Ok(db),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(Self::default()),
Err(err) => Err(err),
}
}
}
#[derive(Default, Debug, Serialize, Deserialize)]
struct PortHandler {
#[serde(skip)]
last_update: Option<Instant>,
#[serde(skip)]
port_guards: HashMap<Port, PortGuard>,
allowed_ports: AllowedPorts,
free_ports: HashSet<Port>,
errored_ports: BTreeSet<(UnixTimestamp, Port)>,
allocated_ports: HashMap<Number, Port>,
port_status: HashMap<Port, PortStatus>,
}
#[derive(Debug, Serialize, Deserialize)]
struct PortStatus {}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
struct AllowedPorts(Vec<Range<u16>>);
impl AllowedPorts {
fn is_allowed(&self, port: Port) -> bool {
self.0.iter().any(|range| range.contains(&port))
}
}
impl PortHandler {
fn register_update(&mut self) {
self.last_update = Some(Instant::now());
}
fn store(&self, db: &Path) -> anyhow::Result<()> {
println!("storing database");
serde_json::to_writer(BufWriter::new(File::create(db)?), self)?;
Ok(())
}
fn load(db: &Path) -> std::io::Result<Self> {
println!("loading database");
Ok(serde_json::from_reader(BufReader::new(File::open(db)?))?)
}
fn load_or_default(db: &Path) -> std::io::Result<Self> {
match Self::load(db) {
Ok(db) => Ok(db),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(Self::default()),
Err(err) => Err(err),
}
}
fn update_allowed_ports(&mut self, allowed_ports: &AllowedPorts) {
self.register_update();
self.allowed_ports = allowed_ports.clone();
self.free_ports.clear();
self.free_ports
.extend(self.allowed_ports.0.iter().cloned().flatten());
self.free_ports.shrink_to_fit(); // we are at the maximum number of ports we'll ever reach
self.errored_ports
.retain(|(_, port)| self.allowed_ports.is_allowed(*port));
self.allocated_ports
.retain(|_, port| self.allowed_ports.is_allowed(*port));
self.free_ports.retain(|port| {
self.allocated_ports
.iter()
.find(|(_, allocated_port)| *allocated_port == port)
.is_none()
&& self
.errored_ports
.iter()
.find(|(_, errored_port)| errored_port == port)
.is_none()
});
}
fn start_port_guard<'fut, Fut, Func>(&mut self, port: Port, listener: TcpListener, f: Func)
where
Fut: Future<Output = ()> + Send + 'fut,
Func: FnOnce(&'_ mut TcpListener) -> Fut + Send + 'static,
{
assert!(self
.port_guards
.insert(port, PortGuard::start(listener, f))
.is_none());
}
fn start_rejector(&mut self, port: Port, listener: TcpListener, packet: Packet) {
assert!(self
.port_guards
.insert(
port,
PortGuard::start(listener, move |listener: &mut TcpListener| async move {
loop {
if let Ok((mut socket, _)) = listener.accept().await {
let (_, mut writer) = socket.split();
let _ = packet.send(&mut writer).await;
}
}
})
)
.is_none());
}
}
struct PortGuard {
listener: Arc<tokio::sync::Mutex<TcpListener>>,
handle: JoinHandle<()>,
}
impl Debug for PortGuard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PortGuard").finish()
}
}
impl PortGuard {
fn start<'fut, Fut>(
listener: TcpListener,
f: impl FnOnce(&mut TcpListener) -> Fut + Send + 'static,
) -> Self
where
Fut: Future<Output = ()> + Send + 'fut,
{
let mut listener = Arc::new(tokio::sync::Mutex::new(listener));
let handle = {
let listener = listener.clone();
tokio::spawn(async move {
let mut lock = listener.lock().await;
f(&mut *lock).await;
})
};
Self { listener, handle }
}
async fn stop(mut self) -> TcpListener {
self.handle.abort();
let _ = self.handle.await;
Arc::try_unwrap(self.listener).unwrap().into_inner()
}
}
impl PortHandler {
fn allocate_port_for_number(&mut self, number: Number) -> Option<Port> {
if let Some(port) = self.allocated_ports.get(&number) {
return Some(*port);
}
let port = if let Some(&port) = self.free_ports.iter().next() {
self.register_update();
self.free_ports.remove(&port);
port
} else {
self.try_recover_port()?
};
assert!(self.allocated_ports.insert(number, port).is_none());
Some(port)
}
fn try_recover_port(&mut self) -> Option<Port> {
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let mut recovered_port = None;
self.errored_ports = std::mem::take(&mut self.errored_ports)
.into_iter()
.filter_map(|(mut timestamp, mut port)| {
if recovered_port.is_none()
&& now.saturating_sub(Duration::from_secs(timestamp)) >= PORT_RETRY_TIME
{
println!(
" trying port: {port} at -{:?}",
Duration::from_secs(now.as_secs())
.saturating_sub(Duration::from_secs(timestamp))
);
match std::net::TcpListener::bind((BIND_IP, port)) {
Ok(_) => {
recovered_port = Some((timestamp, port));
return None;
}
Err(_) => timestamp = now.as_secs(),
}
} else {
println!(
"skipped port: {port} at -{:?}",
Duration::from_secs(now.as_secs())
.saturating_sub(Duration::from_secs(timestamp))
);
}
Some((timestamp, port))
})
.collect();
if let Some((_, port)) = recovered_port {
println!("recovered_port: {port}");
return Some(port);
}
None // TODO
}
fn mark_port_error(&mut self, number: Number, port: Port) {
self.register_update();
self.errored_ports.insert((
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
port,
));
self.allocated_ports.remove(&number);
self.free_ports.remove(&port);
}
fn open_port(&mut self, port: Port) -> Option<TcpListener> {
todo!()
}
fn close_port_for(&mut self, number: Number, listener: TcpListener) -> anyhow::Result<()> {
todo!()
}
}
async fn connection_handler(
port_handler: Arc<Mutex<PortHandler>>,
stream: &mut TcpStream,
) -> anyhow::Result<()> {
let (mut reader, mut writer) = stream.split();
let mut packet = Packet::recv(&mut reader).await?;
let RemConnect { number, pin } = packet.as_rem_connect()?;
let (port, listener) = loop {
let port = port_handler
.lock()
.unwrap()
.allocate_port_for_number(number);
println!("allocated port: {:?}", port);
let Some(port) = port else {
writer.write_all(&reject_static(b"oop")).await?;
return Ok(());
};
let ip = dyn_ip_update(number, pin, port).await?;
let listener = TcpListener::bind((BIND_IP, port)).await;
let listener = match listener {
Ok(listener) => break (port, listener),
Err(err) => {
port_handler.lock().unwrap().mark_port_error(number, port);
// tokio::time::sleep(Duration::from_millis(300)).await;
continue;
}
};
};
#[derive(Debug)]
enum Foo {
Caller { stream: TcpStream, addr: SocketAddr },
Packet { packet: Packet },
}
let result = select! {
kind = Packet::peek_packet_kind(&mut reader) => {
packet.recv_into(&mut reader).await?;
Foo::Packet { packet }
},
caller = listener.accept() => {
let (stream, addr) = caller?;
Foo::Caller { stream, addr }
},
};
dbg!(&result);
match result {
Foo::Caller { stream, addr } => todo!(),
Foo::Packet { mut packet } => {
match packet.kind() {
packets::PacketKind::End => {
packet.header = Header { kind: 3, length: 0 };
packet.data.clear();
}
packets::PacketKind::Reject => {}
kind => bail!("unexpected packet: {kind:?}"),
}
port_handler
.lock()
.unwrap()
.start_rejector(port, listener, packet);
}
}
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = Config::load_or_default("config.json".as_ref())?;
if config.allowed_ports.0.is_empty() {
panic!("no allowed ports");
}
let db_path = PathBuf::from("db.json");
let mut port_handler = PortHandler::load_or_default(&db_path)?;
port_handler.update_allowed_ports(&config.allowed_ports);
let port_handler = Arc::new(Mutex::new(port_handler));
{
let port_handler = port_handler.clone();
tokio::spawn(async move {
let mut last_store = None;
loop {
tokio::time::sleep(Duration::from_secs(1)).await;
let port_handler = port_handler.lock().unwrap();
if let Some(last_update) = port_handler.last_update {
let should_store = last_store
.map(|last_store| last_update > last_store)
.unwrap_or(true);
if should_store {
last_store = Some(last_update);
port_handler.store(&db_path).unwrap();
}
}
}
});
}
let listener = TcpListener::bind(("127.0.0.1", 11812)).await?;
while let Ok((mut stream, addr)) = listener.accept().await {
println!("connection from {addr}");
let port_handler = port_handler.clone();
tokio::spawn(async move {
if let Err(err) = connection_handler(port_handler, &mut stream).await {
println!("client at {addr} had an error: {err}");
let mut packet = Packet::default();
packet.data.extend_from_slice(err.to_string().as_bytes());
packet.data.truncate(0xfe);
packet.data.push(0);
packet.header = Header {
kind: 0xff,
length: packet.data.len() as u8,
};
let (_, mut writer) = stream.split();
let _ = packet.send(&mut writer).await;
}
});
}
Ok(())
}

210
src/packets.rs Normal file
View File

@ -0,0 +1,210 @@
use std::{ffi::CString, mem::discriminant};
use anyhow::bail;
use bytemuck::{Pod, Zeroable};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::tcp::{ReadHalf, WriteHalf},
};
pub const fn reject_static<const N: usize>(message: &[u8; N]) -> [u8; N + 2] {
let mut pkg = [0u8; N + 2];
pkg[0] = 4;
pkg[1] = message.len() as u8;
let mut i = 0;
while i < message.len() {
pkg[i + 2] = message[i];
i += 1;
}
pkg
}
pub const REJECT_OCC: &[u8; 6] = b"\x04\x04occ\x00";
pub const REJECT_NC: &[u8; 5] = b"\x04\x03nc\x00";
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PacketKind {
Unknown(u8),
DynIpUpdate = 0x01,
DynIpUpdateResponse = 0x02,
End = 0x03,
Reject = 0x04,
RemConnect = 0x81,
RemConfirm = 0x82,
RemCall = 0x83,
RemAck = 0x84,
Error = 0xff,
}
impl PacketKind {
fn from_u8(raw: u8) -> Self {
use PacketKind::*;
match raw {
0x01 => DynIpUpdate,
0x02 => DynIpUpdateResponse,
0x03 => End,
0x04 => Reject,
0x81 => RemConnect,
0x82 => RemConfirm,
0x83 => RemCall,
0x84 => RemAck,
0xff => Error,
kind => Unknown(kind),
}
}
fn kind(&self) -> u8 {
use PacketKind::*;
match self {
Unknown(value) => *value,
DynIpUpdate => 0x01,
DynIpUpdateResponse => 0x02,
End => 0x03,
Reject => 0x04,
RemConnect => 0x81,
RemConfirm => 0x82,
RemCall => 0x83,
RemAck => 0x84,
Error => 0xff,
}
}
}
#[derive(Default, Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Header {
pub kind: u8,
pub length: u8,
}
#[derive(Debug, Default, Clone)]
pub struct Packet {
pub header: Header,
pub data: Vec<u8>,
}
#[derive(Default, Debug, Clone, Copy)]
#[repr(C)]
pub struct RemConnect {
pub number: u32,
pub pin: u16,
}
impl Packet {
pub async fn peek_packet_kind(stream: &mut ReadHalf<'_>) -> std::io::Result<PacketKind> {
Self::peek_packet_kind_raw(stream)
.await
.map(PacketKind::from_u8)
}
pub async fn peek_packet_kind_raw(stream: &mut ReadHalf<'_>) -> std::io::Result<u8> {
let mut kind = 0;
let n = stream.peek(std::slice::from_mut(&mut kind)).await?;
if n == 1 {
Ok(kind)
} else {
Err(std::io::ErrorKind::UnexpectedEof.into())
}
}
pub async fn recv(stream: &mut ReadHalf<'_>) -> std::io::Result<Packet> {
let mut packet = Packet::default();
packet.recv_into(stream).await?;
Ok(packet)
}
pub async fn recv_into(&mut self, stream: &mut ReadHalf<'_>) -> std::io::Result<()> {
let header_bytes = bytemuck::bytes_of_mut(&mut self.header);
stream.read_exact(header_bytes).await?;
self.data.resize(self.header.length as usize, 0);
stream.read_exact(&mut self.data).await?;
Ok(())
}
pub async fn send(&self, stream: &mut WriteHalf<'_>) -> std::io::Result<()> {
stream.write_all(bytemuck::bytes_of(&self.header)).await?;
stream.write_all(&self.data).await?;
Ok(())
}
pub fn kind(&self) -> PacketKind {
PacketKind::from_u8(self.header.kind)
}
pub fn as_rem_connect(&self) -> anyhow::Result<RemConnect> {
if self.kind() != PacketKind::RemConnect {
bail!("Unexpected Packet: {:?} expected RemConnect", self.kind());
}
if self.data.len() < 6 {
bail!(
"Too little data for RemConnect. Need at least 6 Bytes got {}",
self.data.len()
);
}
Ok(RemConnect {
number: u32::from_le_bytes(self.data[..4].try_into()?),
pin: u16::from_le_bytes(self.data[4..6].try_into()?),
})
}
}
pub async fn dyn_ip_update(number: u32, pin: u16, port: u16) -> anyhow::Result<std::net::Ipv4Addr> {
let mut packet = Packet::default();
packet.header = Header {
kind: PacketKind::DynIpUpdate.kind(),
length: 8,
};
packet.data.clear();
packet.data.reserve(packet.header.length as usize);
packet.data.extend_from_slice(&number.to_le_bytes());
packet.data.extend_from_slice(&pin.to_le_bytes());
packet.data.extend_from_slice(&port.to_le_bytes());
let mut socket = tokio::net::TcpStream::connect(("127.0.0.1", 11811)).await?;
let (mut reader, mut writer) = socket.split();
packet.send(&mut writer).await?;
packet.recv_into(&mut reader).await?;
match packet.kind() {
PacketKind::DynIpUpdateResponse => Ok(<[u8; 4]>::try_from(packet.data)
.map_err(|err| {
anyhow::anyhow!(
"too little data for ip address. Need 4 bytes got {}",
err.len()
)
})?
.into()),
PacketKind::Error => {
let first_zero = packet
.data
.iter()
.enumerate()
.find_map(|(i, x)| (*x == 0).then_some(i));
bail!(
"{}",
std::str::from_utf8(
first_zero
.map(|i| &packet.data[..i])
.unwrap_or(&packet.data),
)?
)
}
_ => bail!("server returned unexpected packet"),
}
}