|
|
@ -2,30 +2,22 @@
|
|
|
|
#![allow(clippy::cast_possible_truncation)]
|
|
|
|
#![allow(clippy::cast_possible_truncation)]
|
|
|
|
#![allow(clippy::cast_possible_wrap)]
|
|
|
|
#![allow(clippy::cast_possible_wrap)]
|
|
|
|
|
|
|
|
|
|
|
|
use std::{
|
|
|
|
use std::{convert::Infallible, net::SocketAddr, sync::Arc, time::Duration};
|
|
|
|
convert::Infallible,
|
|
|
|
|
|
|
|
net::SocketAddr,
|
|
|
|
|
|
|
|
sync::{atomic::AtomicUsize, Arc},
|
|
|
|
|
|
|
|
time::Duration,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
use eyre::{anyhow, Context, self as anyhow};
|
|
|
|
use axum::{
|
|
|
|
use axum::{
|
|
|
|
http::StatusCode,
|
|
|
|
http::StatusCode,
|
|
|
|
routing::{get, post},
|
|
|
|
routing::{get, post},
|
|
|
|
};
|
|
|
|
};
|
|
|
|
use eyre::{eyre, OptionExt, WrapErr};
|
|
|
|
use log::{error, info};
|
|
|
|
use netty::{Handshake, ReadError};
|
|
|
|
use netty::{Handshake, ReadError};
|
|
|
|
use quinn::{
|
|
|
|
use quinn::{Connecting, ConnectionError, Endpoint, ServerConfig, TransportConfig};
|
|
|
|
crypto::rustls::QuicServerConfig, ConnectionError, Endpoint, Incoming, ServerConfig,
|
|
|
|
|
|
|
|
TransportConfig,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
use routing::RoutingTable;
|
|
|
|
use routing::RoutingTable;
|
|
|
|
|
|
|
|
use rustls::{Certificate, PrivateKey};
|
|
|
|
use tokio::{
|
|
|
|
use tokio::{
|
|
|
|
io::{AsyncReadExt, AsyncWriteExt},
|
|
|
|
io::{AsyncReadExt, AsyncWriteExt},
|
|
|
|
net::{TcpListener, TcpStream},
|
|
|
|
net::TcpStream,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
use tracing::{error, info};
|
|
|
|
|
|
|
|
use tracing_subscriber::prelude::*;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
use crate::{
|
|
|
|
netty::{ReadExt, WriteExt},
|
|
|
|
netty::{ReadExt, WriteExt},
|
|
|
@ -38,24 +30,50 @@ mod routing;
|
|
|
|
mod unicode_madness;
|
|
|
|
mod unicode_madness;
|
|
|
|
mod wordlist;
|
|
|
|
mod wordlist;
|
|
|
|
|
|
|
|
|
|
|
|
async fn create_server_config() -> eyre::Result<ServerConfig> {
|
|
|
|
fn any_private_keys(rd: &mut dyn std::io::BufRead) -> Result<Vec<Vec<u8>>, std::io::Error> {
|
|
|
|
let cert_file =
|
|
|
|
let mut keys = Vec::<Vec<u8>>::new();
|
|
|
|
tokio::fs::read(std::env::var("QUICLIME_CERT_PATH").context("Reading QUICLIME_CERT_PATH")?)
|
|
|
|
|
|
|
|
.await?;
|
|
|
|
loop {
|
|
|
|
let cert_chain = rustls_pemfile::certs(&mut cert_file.as_slice())
|
|
|
|
match rustls_pemfile::read_one(rd)? {
|
|
|
|
.filter_map(Result::ok)
|
|
|
|
None => return Ok(keys),
|
|
|
|
|
|
|
|
Some(
|
|
|
|
|
|
|
|
rustls_pemfile::Item::RSAKey(key)
|
|
|
|
|
|
|
|
| rustls_pemfile::Item::PKCS8Key(key)
|
|
|
|
|
|
|
|
| rustls_pemfile::Item::ECKey(key),
|
|
|
|
|
|
|
|
) => keys.push(key),
|
|
|
|
|
|
|
|
_ => {}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn get_certs() -> anyhow::Result<(Vec<Certificate>, PrivateKey)> {
|
|
|
|
|
|
|
|
let mut cert_file = std::io::BufReader::new(std::fs::File::open(
|
|
|
|
|
|
|
|
std::env::var("QUICLIME_CERT_PATH").context("Reading QUICLIME_CERT_PATH")?,
|
|
|
|
|
|
|
|
)?);
|
|
|
|
|
|
|
|
let certs = rustls_pemfile::certs(&mut cert_file)?
|
|
|
|
|
|
|
|
.into_iter()
|
|
|
|
|
|
|
|
.map(Certificate)
|
|
|
|
.collect();
|
|
|
|
.collect();
|
|
|
|
let key_file =
|
|
|
|
let mut key_file = std::io::BufReader::new(std::fs::File::open(
|
|
|
|
tokio::fs::read(std::env::var("QUICLIME_KEY_PATH").context("Reading QUICLIME_KEY_PATH")?)
|
|
|
|
std::env::var("QUICLIME_KEY_PATH").context("Reading QUICLIME_KEY_PATH")?,
|
|
|
|
.await?;
|
|
|
|
)?);
|
|
|
|
let key_der = rustls_pemfile::private_key(&mut key_file.as_slice())?
|
|
|
|
let key = PrivateKey(
|
|
|
|
.ok_or_eyre("No private key in QUICLIME_KEY_PATH!")?;
|
|
|
|
any_private_keys(&mut key_file)?
|
|
|
|
|
|
|
|
.into_iter()
|
|
|
|
|
|
|
|
.next()
|
|
|
|
|
|
|
|
.ok_or(anyhow::anyhow!("No private key?"))?,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok((certs, key))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async fn create_server_config() -> anyhow::Result<ServerConfig> {
|
|
|
|
|
|
|
|
let (cert_chain, key_der) = tokio::task::spawn_blocking(get_certs).await??;
|
|
|
|
let mut rustls_config = rustls::ServerConfig::builder()
|
|
|
|
let mut rustls_config = rustls::ServerConfig::builder()
|
|
|
|
|
|
|
|
.with_safe_defaults()
|
|
|
|
.with_no_client_auth()
|
|
|
|
.with_no_client_auth()
|
|
|
|
.with_single_cert(cert_chain, key_der)?;
|
|
|
|
.with_single_cert(cert_chain, key_der)?;
|
|
|
|
rustls_config.alpn_protocols = vec![b"quiclime".to_vec()];
|
|
|
|
rustls_config.alpn_protocols = vec![b"quiclime".to_vec()];
|
|
|
|
let config: QuicServerConfig = rustls_config.try_into()?;
|
|
|
|
let mut config = ServerConfig::with_crypto(Arc::new(rustls_config));
|
|
|
|
let mut config = ServerConfig::with_crypto(Arc::new(config));
|
|
|
|
|
|
|
|
let mut transport = TransportConfig::default();
|
|
|
|
let mut transport = TransportConfig::default();
|
|
|
|
transport
|
|
|
|
transport
|
|
|
|
.max_concurrent_bidi_streams(1u32.into())
|
|
|
|
.max_concurrent_bidi_streams(1u32.into())
|
|
|
@ -65,30 +83,10 @@ async fn create_server_config() -> eyre::Result<ServerConfig> {
|
|
|
|
Ok(config)
|
|
|
|
Ok(config)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static CLIENT_COUNT: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct ClientCounterGuard;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl ClientCounterGuard {
|
|
|
|
|
|
|
|
fn new() -> Self {
|
|
|
|
|
|
|
|
CLIENT_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
|
|
|
|
|
|
|
Self
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl Drop for ClientCounterGuard {
|
|
|
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
|
|
|
CLIENT_COUNT.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
#[tokio::main]
|
|
|
|
async fn main() -> eyre::Result<()> {
|
|
|
|
async fn main() -> anyhow::Result<()> {
|
|
|
|
let _guard = sentry::init(std::env::var("SENTRY_DSN").ok());
|
|
|
|
let _guard = sentry::init(std::env::var("SENTRY_DSN").ok());
|
|
|
|
tracing_subscriber::registry()
|
|
|
|
env_logger::init();
|
|
|
|
.with(tracing_subscriber::fmt::layer())
|
|
|
|
|
|
|
|
.with(sentry_tracing::layer())
|
|
|
|
|
|
|
|
.init();
|
|
|
|
|
|
|
|
// JUSTIFICATION: this lives until the end of the entire program
|
|
|
|
// JUSTIFICATION: this lives until the end of the entire program
|
|
|
|
let endpoint = Box::leak(Box::new(Endpoint::server(
|
|
|
|
let endpoint = Box::leak(Box::new(Endpoint::server(
|
|
|
|
create_server_config().await?,
|
|
|
|
create_server_config().await?,
|
|
|
@ -106,10 +104,14 @@ async fn main() -> eyre::Result<()> {
|
|
|
|
listen_control(endpoint, routing_table),
|
|
|
|
listen_control(endpoint, routing_table),
|
|
|
|
listen_minecraft(routing_table)
|
|
|
|
listen_minecraft(routing_table)
|
|
|
|
)?;
|
|
|
|
)?;
|
|
|
|
|
|
|
|
drop(_guard);
|
|
|
|
Ok(())
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fn try_handle_quic(connection: Incoming, routing_table: &RoutingTable) -> eyre::Result<()> {
|
|
|
|
async fn try_handle_quic(
|
|
|
|
|
|
|
|
connection: Connecting,
|
|
|
|
|
|
|
|
routing_table: &RoutingTable,
|
|
|
|
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
let connection = connection.await?;
|
|
|
|
let connection = connection.await?;
|
|
|
|
info!(
|
|
|
|
info!(
|
|
|
|
"QUIClime connection established to: {}",
|
|
|
|
"QUIClime connection established to: {}",
|
|
|
@ -163,7 +165,7 @@ async fn try_handle_quic(connection: Incoming, routing_table: &RoutingTable) ->
|
|
|
|
} else if let Err(ConnectionError::ConnectionClosed(_)) = pair {
|
|
|
|
} else if let Err(ConnectionError::ConnectionClosed(_)) = pair {
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
remote.send(pair?).map_err(|e| eyre!("{:?}", e))?;
|
|
|
|
remote.send(pair?).map_err(|e| anyhow::anyhow!("{:?}", e))?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
routing::RouterRequest::BroadcastRequest(message) => {
|
|
|
|
routing::RouterRequest::BroadcastRequest(message) => {
|
|
|
|
let response =
|
|
|
|
let response =
|
|
|
@ -180,8 +182,7 @@ async fn try_handle_quic(connection: Incoming, routing_table: &RoutingTable) ->
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[tracing::instrument(skip(routing_table))]
|
|
|
|
async fn handle_quic(connection: Connecting, routing_table: &RoutingTable) {
|
|
|
|
async fn handle_quic(connection: Incoming, routing_table: &RoutingTable) {
|
|
|
|
|
|
|
|
if let Err(e) = try_handle_quic(connection, routing_table).await {
|
|
|
|
if let Err(e) = try_handle_quic(connection, routing_table).await {
|
|
|
|
sentry::capture_error::<dyn std::error::Error>(e.as_ref());
|
|
|
|
sentry::capture_error::<dyn std::error::Error>(e.as_ref());
|
|
|
|
error!("Error handling QUIClime connection: {}", e);
|
|
|
|
error!("Error handling QUIClime connection: {}", e);
|
|
|
@ -192,27 +193,21 @@ async fn handle_quic(connection: Incoming, routing_table: &RoutingTable) {
|
|
|
|
async fn listen_quic(
|
|
|
|
async fn listen_quic(
|
|
|
|
endpoint: &'static Endpoint,
|
|
|
|
endpoint: &'static Endpoint,
|
|
|
|
routing_table: &'static RoutingTable,
|
|
|
|
routing_table: &'static RoutingTable,
|
|
|
|
) -> eyre::Result<Infallible> {
|
|
|
|
) -> anyhow::Result<Infallible> {
|
|
|
|
while let Some(connection) = endpoint.accept().await {
|
|
|
|
while let Some(connection) = endpoint.accept().await {
|
|
|
|
tokio::spawn(handle_quic(connection, routing_table));
|
|
|
|
tokio::spawn(handle_quic(connection, routing_table));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(eyre!("quiclime endpoint closed"))
|
|
|
|
Err(anyhow!("quiclime endpoint closed"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fn listen_control(
|
|
|
|
async fn listen_control(
|
|
|
|
endpoint: &'static Endpoint,
|
|
|
|
endpoint: &'static Endpoint,
|
|
|
|
routing_table: &'static RoutingTable,
|
|
|
|
routing_table: &'static RoutingTable,
|
|
|
|
) -> eyre::Result<Infallible> {
|
|
|
|
) -> anyhow::Result<Infallible> {
|
|
|
|
let app = axum::Router::new()
|
|
|
|
let app = axum::Router::new()
|
|
|
|
.route(
|
|
|
|
.route(
|
|
|
|
"/metrics",
|
|
|
|
"/metrics",
|
|
|
|
get(|| async {
|
|
|
|
get(|| async { format!("host_count {}", routing_table.size()) }),
|
|
|
|
format!(
|
|
|
|
|
|
|
|
"host_count {}\nguest_count {}\n",
|
|
|
|
|
|
|
|
routing_table.size(),
|
|
|
|
|
|
|
|
CLIENT_COUNT.load(std::sync::atomic::Ordering::Relaxed)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.route(
|
|
|
|
.route(
|
|
|
|
"/reload-certs",
|
|
|
|
"/reload-certs",
|
|
|
@ -233,24 +228,26 @@ async fn listen_control(
|
|
|
|
.route(
|
|
|
|
.route(
|
|
|
|
"/stop",
|
|
|
|
"/stop",
|
|
|
|
post(|| async {
|
|
|
|
post(|| async {
|
|
|
|
|
|
|
|
endpoint.reject_new_connections();
|
|
|
|
routing_table.broadcast("e4mc relay server stopping!");
|
|
|
|
routing_table.broadcast("e4mc relay server stopping!");
|
|
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
|
|
endpoint.close(0u32.into(), b"e4mc closing");
|
|
|
|
endpoint.close(0u32.into(), b"e4mc closing");
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
let listener = TcpListener::bind(
|
|
|
|
axum::Server::bind(
|
|
|
|
std::env::var("QUICLIME_BIND_ADDR_WEB").context("Reading QUICLIME_BIND_ADDR_WEB")?,
|
|
|
|
&std::env::var("QUICLIME_BIND_ADDR_WEB")
|
|
|
|
|
|
|
|
.context("Reading QUICLIME_BIND_ADDR_WEB")?
|
|
|
|
|
|
|
|
.parse()?,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
.serve(app.into_make_service())
|
|
|
|
.await?;
|
|
|
|
.await?;
|
|
|
|
axum::serve(listener, app).await?;
|
|
|
|
Err(anyhow!("control endpoint closed"))
|
|
|
|
Err(eyre!("control endpoint closed"))
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fn try_handle_minecraft(
|
|
|
|
async fn try_handle_minecraft(
|
|
|
|
mut connection: TcpStream,
|
|
|
|
mut connection: TcpStream,
|
|
|
|
routing_table: &'static RoutingTable,
|
|
|
|
routing_table: &'static RoutingTable,
|
|
|
|
) -> eyre::Result<()> {
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
let guard = ClientCounterGuard::new();
|
|
|
|
|
|
|
|
let peer = connection.peer_addr()?;
|
|
|
|
let peer = connection.peer_addr()?;
|
|
|
|
info!("Minecraft client connected from: {}", peer);
|
|
|
|
info!("Minecraft client connected from: {}", peer);
|
|
|
|
let handshake = netty::read_packet(&mut connection).await;
|
|
|
|
let handshake = netty::read_packet(&mut connection).await;
|
|
|
@ -271,21 +268,23 @@ async fn try_handle_minecraft(
|
|
|
|
let mut conn_host = tokio::io::join(&mut recv_host, &mut send_host);
|
|
|
|
let mut conn_host = tokio::io::join(&mut recv_host, &mut send_host);
|
|
|
|
_ = tokio::io::copy_bidirectional(&mut connection, &mut conn_host);
|
|
|
|
_ = tokio::io::copy_bidirectional(&mut connection, &mut conn_host);
|
|
|
|
_ = connection.shutdown().await;
|
|
|
|
_ = connection.shutdown().await;
|
|
|
|
_ = send_host.finish();
|
|
|
|
_ = send_host.finish().await;
|
|
|
|
_ = recv_host.stop(0u32.into());
|
|
|
|
_ = recv_host.stop(0u32.into());
|
|
|
|
info!("Minecraft client disconnected from: {}", peer);
|
|
|
|
info!("Minecraft client disconnected from: {}", peer);
|
|
|
|
drop(guard);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fn politely_disconnect(mut connection: TcpStream, handshake: Handshake) -> eyre::Result<()> {
|
|
|
|
async fn politely_disconnect(
|
|
|
|
|
|
|
|
mut connection: TcpStream,
|
|
|
|
|
|
|
|
handshake: Handshake,
|
|
|
|
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
match handshake.next_state {
|
|
|
|
match handshake.next_state {
|
|
|
|
netty::HandshakeType::Status => {
|
|
|
|
netty::HandshakeType::Status => {
|
|
|
|
let packet = netty::read_packet(&mut connection).await?;
|
|
|
|
let packet = netty::read_packet(&mut connection).await?;
|
|
|
|
let mut packet = packet.as_slice();
|
|
|
|
let mut packet = packet.as_slice();
|
|
|
|
let id = packet.read_varint()?;
|
|
|
|
let id = packet.read_varint()?;
|
|
|
|
if id != 0 {
|
|
|
|
if id != 0 {
|
|
|
|
return Err(eyre!(
|
|
|
|
return Err(anyhow!(
|
|
|
|
"Packet isn't a Status Request(0x00), but {:#04x}",
|
|
|
|
"Packet isn't a Status Request(0x00), but {:#04x}",
|
|
|
|
id
|
|
|
|
id
|
|
|
|
));
|
|
|
|
));
|
|
|
@ -300,7 +299,10 @@ async fn politely_disconnect(mut connection: TcpStream, handshake: Handshake) ->
|
|
|
|
let mut packet = packet.as_slice();
|
|
|
|
let mut packet = packet.as_slice();
|
|
|
|
let id = packet.read_varint()?;
|
|
|
|
let id = packet.read_varint()?;
|
|
|
|
if id != 1 {
|
|
|
|
if id != 1 {
|
|
|
|
return Err(eyre!("Packet isn't a Ping Request(0x01), but {:#04x}", id));
|
|
|
|
return Err(anyhow!(
|
|
|
|
|
|
|
|
"Packet isn't a Ping Request(0x01), but {:#04x}",
|
|
|
|
|
|
|
|
id
|
|
|
|
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let payload = packet.read_long()?;
|
|
|
|
let payload = packet.read_long()?;
|
|
|
|
let mut buf = Vec::with_capacity(1 + 8);
|
|
|
|
let mut buf = Vec::with_capacity(1 + 8);
|
|
|
@ -322,7 +324,6 @@ async fn politely_disconnect(mut connection: TcpStream, handshake: Handshake) ->
|
|
|
|
Ok(())
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[tracing::instrument(skip(routing_table))]
|
|
|
|
|
|
|
|
async fn handle_minecraft(connection: TcpStream, routing_table: &'static RoutingTable) {
|
|
|
|
async fn handle_minecraft(connection: TcpStream, routing_table: &'static RoutingTable) {
|
|
|
|
if let Err(e) = try_handle_minecraft(connection, routing_table).await {
|
|
|
|
if let Err(e) = try_handle_minecraft(connection, routing_table).await {
|
|
|
|
sentry::capture_error::<dyn std::error::Error>(e.as_ref());
|
|
|
|
sentry::capture_error::<dyn std::error::Error>(e.as_ref());
|
|
|
@ -330,7 +331,7 @@ async fn handle_minecraft(connection: TcpStream, routing_table: &'static Routing
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fn listen_minecraft(routing_table: &'static RoutingTable) -> eyre::Result<Infallible> {
|
|
|
|
async fn listen_minecraft(routing_table: &'static RoutingTable) -> anyhow::Result<Infallible> {
|
|
|
|
let server = tokio::net::TcpListener::bind(
|
|
|
|
let server = tokio::net::TcpListener::bind(
|
|
|
|
std::env::var("QUICLIME_BIND_ADDR_MC")
|
|
|
|
std::env::var("QUICLIME_BIND_ADDR_MC")
|
|
|
|
.context("Reading QUICLIME_BIND_ADDR_MC")?
|
|
|
|
.context("Reading QUICLIME_BIND_ADDR_MC")?
|
|
|
|