add /status endpoint to proxy
This commit is contained in:
Generated
+63
@@ -2217,6 +2217,18 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
"wasip2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ghash"
|
name = "ghash"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -3595,6 +3607,7 @@ dependencies = [
|
|||||||
"hmac-sha256",
|
"hmac-sha256",
|
||||||
"mumble-web2-common",
|
"mumble-web2-common",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"rand 0.9.2",
|
||||||
"rcgen",
|
"rcgen",
|
||||||
"rustls",
|
"rustls",
|
||||||
"salvo",
|
"salvo",
|
||||||
@@ -4596,6 +4609,12 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "5.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
@@ -4621,6 +4640,16 @@ dependencies = [
|
|||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha 0.9.0",
|
||||||
|
"rand_core 0.9.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_chacha"
|
name = "rand_chacha"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -4641,6 +4670,16 @@ dependencies = [
|
|||||||
"rand_core 0.6.4",
|
"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.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -4659,6 +4698,15 @@ dependencies = [
|
|||||||
"getrandom 0.2.15",
|
"getrandom 0.2.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_hc"
|
name = "rand_hc"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -6575,6 +6623,15 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip2"
|
||||||
|
version = "1.0.1+wasi-0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.100"
|
version = "0.2.100"
|
||||||
@@ -7343,6 +7400,12 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.46.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "write16"
|
name = "write16"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|||||||
@@ -5,3 +5,13 @@ pub struct ClientConfig {
|
|||||||
pub proxy_url: Option<String>,
|
pub proxy_url: Option<String>,
|
||||||
pub cert_hash: Option<Vec<u8>>,
|
pub cert_hash: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||||
|
pub struct ServerStatus {
|
||||||
|
#[serde(default)]
|
||||||
|
pub success: bool,
|
||||||
|
pub version: Option<(u32, u32, u32)>,
|
||||||
|
pub users: Option<u32>,
|
||||||
|
pub max_users: Option<u32>,
|
||||||
|
pub bandwidth: Option<u32>,
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ localhost:64444 {
|
|||||||
# Proxy /config path to mumble-web2-proxy
|
# Proxy /config path to mumble-web2-proxy
|
||||||
reverse_proxy /config http://127.0.0.1:4400
|
reverse_proxy /config http://127.0.0.1:4400
|
||||||
|
|
||||||
|
# Proxy /status path to mumble-web2-proxy
|
||||||
|
reverse_proxy /status http://127.0.0.1:4400
|
||||||
|
|
||||||
|
|
||||||
# Proxy root path to dx-serve
|
# Proxy root path to dx-serve
|
||||||
reverse_proxy http://127.0.0.1:8080
|
reverse_proxy http://127.0.0.1:8080
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -27,4 +27,5 @@ rustls = { version = "^0.23", features = ["aws_lc_rs"] }
|
|||||||
rcgen = "^0.13.2"
|
rcgen = "^0.13.2"
|
||||||
hmac-sha256 = "^1.1.8"
|
hmac-sha256 = "^1.1.8"
|
||||||
time = "0.3"
|
time = "0.3"
|
||||||
url = "2"
|
url = { version = "2", features = ["serde"] }
|
||||||
|
rand = "0.9.2"
|
||||||
|
|||||||
+89
-2
@@ -1,5 +1,10 @@
|
|||||||
use color_eyre::eyre::{anyhow, bail, Context, Result};
|
use color_eyre::eyre::{anyhow, bail, Context, Result};
|
||||||
use mumble_web2_common::ClientConfig;
|
use color_eyre::owo_colors::OwoColorize;
|
||||||
|
use mumble_web2_common::{ClientConfig, ServerStatus};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use rand::Rng;
|
||||||
|
use rcgen::date_time_ymd;
|
||||||
|
use rustls::server;
|
||||||
use salvo::conn::rustls::{Keycert, RustlsConfig};
|
use salvo::conn::rustls::{Keycert, RustlsConfig};
|
||||||
use salvo::cors::{AllowOrigin, Cors};
|
use salvo::cors::{AllowOrigin, Cors};
|
||||||
use salvo::logging::Logger;
|
use salvo::logging::Logger;
|
||||||
@@ -25,6 +30,8 @@ use tracing_subscriber::filter::LevelFilter;
|
|||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
mod ping;
|
||||||
|
|
||||||
fn default_cert_alt_names() -> Vec<String> {
|
fn default_cert_alt_names() -> Vec<String> {
|
||||||
vec!["localhost".into()]
|
vec!["localhost".into()]
|
||||||
}
|
}
|
||||||
@@ -122,10 +129,15 @@ async fn main() -> Result<()> {
|
|||||||
client_config,
|
client_config,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let status_craft = StatusCraft {
|
||||||
|
mumble_server_address: server_config.mumble_server_address.unwrap().clone(),
|
||||||
|
};
|
||||||
|
|
||||||
// Server routing
|
// Server routing
|
||||||
let mut router = Router::new()
|
let mut router = Router::new()
|
||||||
.push(Router::with_path("/proxy").goal(config_craft.connect_proxy()))
|
.push(Router::with_path("/proxy").goal(config_craft.connect_proxy()))
|
||||||
.push(Router::with_path("/config").get(config_craft.get_config()))
|
.push(Router::with_path("/config").get(config_craft.get_config()))
|
||||||
|
.push(Router::with_path("/status").get(status_craft.get_status()))
|
||||||
.hoop(Logger::new());
|
.hoop(Logger::new());
|
||||||
if let Some(gui_path) = server_config.gui_path.clone() {
|
if let Some(gui_path) = server_config.gui_path.clone() {
|
||||||
router =
|
router =
|
||||||
@@ -158,6 +170,82 @@ async fn main() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct StatusCraft {
|
||||||
|
mumble_server_address: SocketAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[craft]
|
||||||
|
impl StatusCraft {
|
||||||
|
#[craft(handler)]
|
||||||
|
async fn get_status(&self) -> Json<ServerStatus> {
|
||||||
|
let mut server_status = ServerStatus::default();
|
||||||
|
|
||||||
|
let ping_packet = ping::PingPacket {
|
||||||
|
id: rand::rng().random(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let sock = match tokio::net::UdpSocket::bind("0.0.0.0:0").await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Could not bind udp socket: {}", e);
|
||||||
|
return Json(server_status);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match sock.connect(self.mumble_server_address).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Could not send ping packet: {}", e);
|
||||||
|
return Json(server_status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match sock.send(&<[u8; 12]>::from(ping_packet)).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Could not send ping packet");
|
||||||
|
return Json(server_status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pong_buf: [u8; 24] = [0; 24];
|
||||||
|
|
||||||
|
match tokio::time::timeout(
|
||||||
|
tokio::time::Duration::from_secs(1),
|
||||||
|
sock.recv(&mut pong_buf),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Could not send ping packet");
|
||||||
|
return Json(server_status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pong_packet = match ping::PongPacket::try_from(pong_buf.as_slice()) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Could not parse pong packet: {:?}", e);
|
||||||
|
return Json(server_status);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
server_status.success = true;
|
||||||
|
server_status.version = Some((
|
||||||
|
pong_packet.version & 0xFF,
|
||||||
|
(pong_packet.version >> 8) & 0xFF,
|
||||||
|
(pong_packet.version >> 16) & 0xFF,
|
||||||
|
));
|
||||||
|
server_status.users = Some(pong_packet.users);
|
||||||
|
server_status.max_users = Some(pong_packet.max_users);
|
||||||
|
server_status.bandwidth = Some(pong_packet.bandwidth);
|
||||||
|
|
||||||
|
Json(server_status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ConfigCraft {
|
pub struct ConfigCraft {
|
||||||
server_config: Arc<Config>,
|
server_config: Arc<Config>,
|
||||||
@@ -175,7 +263,6 @@ impl ConfigCraft {
|
|||||||
async fn connect_proxy(&self, req: &mut Request, res: &mut Response) {
|
async fn connect_proxy(&self, req: &mut Request, res: &mut Response) {
|
||||||
info!("received proxy request");
|
info!("received proxy request");
|
||||||
let mumble_server_address = self.server_config.mumble_server_address.unwrap();
|
let mumble_server_address = self.server_config.mumble_server_address.unwrap();
|
||||||
|
|
||||||
let wt = match req.web_transport_mut().await {
|
let wt = match req.web_transport_mut().await {
|
||||||
Ok(wt) => wt,
|
Ok(wt) => wt,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
// This code was taken from mumble-protocol-2x (https://github.com/dblsaiko/rust-mumble-protocol)
|
||||||
|
// and originally from mumble-protocol (https://github.com/Johni0702/rust-mumble-protocol)
|
||||||
|
// These projects are licensed under MIT and Apache 2.0.
|
||||||
|
|
||||||
|
//! Ping messages and codec
|
||||||
|
//!
|
||||||
|
//! A Mumble client can send periodic UDP [PingPacket]s to servers
|
||||||
|
//! in order to query their current state and measure latency.
|
||||||
|
//! A server will usually respond with a corresponding [PongPacket] containing
|
||||||
|
//! the requested details.
|
||||||
|
//!
|
||||||
|
//! Both packets are of fixed size and can be converted to/from `u8` arrays/slices via
|
||||||
|
//! the respective `From`/`TryFrom` impls.
|
||||||
|
|
||||||
|
/// A ping packet sent to the server.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct PingPacket {
|
||||||
|
/// Opaque, client-generated id.
|
||||||
|
///
|
||||||
|
/// Will be returned by the server unmodified and can be used to correlate
|
||||||
|
/// pong replies to ping requests to e.g. calculate latency.
|
||||||
|
pub id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A pong packet sent to the client in reply to a previously received [PingPacket].
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct PongPacket {
|
||||||
|
/// Opaque, client-generated id.
|
||||||
|
///
|
||||||
|
/// Should match the value in the corresponding [PingPacket].
|
||||||
|
pub id: u64,
|
||||||
|
|
||||||
|
/// Server version. E.g. `0x010300` for `1.3.0`.
|
||||||
|
pub version: u32,
|
||||||
|
|
||||||
|
/// Current amount of users connected to the server.
|
||||||
|
pub users: u32,
|
||||||
|
|
||||||
|
/// Configured limit on the amount of users which can be connected to the server.
|
||||||
|
pub max_users: u32,
|
||||||
|
|
||||||
|
/// Maximum bandwidth for server-bound speech per client in bits per second
|
||||||
|
pub bandwidth: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error during parsing of a [PingPacket].
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum ParsePingError {
|
||||||
|
/// Ping packets must always be 12 bytes in size.
|
||||||
|
InvalidSize,
|
||||||
|
/// Ping packets must have an all zero header of 4 bytes.
|
||||||
|
InvalidHeader,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[u8]> for PingPacket {
|
||||||
|
type Error = ParsePingError;
|
||||||
|
fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
|
||||||
|
match <[u8; 12]>::try_from(buf) {
|
||||||
|
Ok(array) => {
|
||||||
|
if array[0..4] != [0, 0, 0, 0] {
|
||||||
|
Err(ParsePingError::InvalidHeader)
|
||||||
|
} else {
|
||||||
|
Ok(Self {
|
||||||
|
id: u64::from_be_bytes(array[4..12].try_into().unwrap()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => Err(ParsePingError::InvalidSize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PingPacket> for [u8; 12] {
|
||||||
|
fn from(packet: PingPacket) -> Self {
|
||||||
|
let id = packet.id.to_be_bytes();
|
||||||
|
// Is there no nicer way to do this?
|
||||||
|
[
|
||||||
|
0, 0, 0, 0, id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error during parsing of a [PongPacket].
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum ParsePongError {
|
||||||
|
/// Pong packets must always be 24 bytes in size.
|
||||||
|
InvalidSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[u8]> for PongPacket {
|
||||||
|
type Error = ParsePongError;
|
||||||
|
fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
|
||||||
|
match <[u8; 24]>::try_from(buf) {
|
||||||
|
Ok(array) => Ok(Self {
|
||||||
|
version: u32::from_be_bytes(array[0..4].try_into().unwrap()),
|
||||||
|
id: u64::from_be_bytes(array[4..12].try_into().unwrap()),
|
||||||
|
users: u32::from_be_bytes(array[12..16].try_into().unwrap()),
|
||||||
|
max_users: u32::from_be_bytes(array[16..20].try_into().unwrap()),
|
||||||
|
bandwidth: u32::from_be_bytes(array[20..24].try_into().unwrap()),
|
||||||
|
}),
|
||||||
|
Err(_) => Err(ParsePongError::InvalidSize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PongPacket> for [u8; 24] {
|
||||||
|
fn from(packet: PongPacket) -> Self {
|
||||||
|
let version = packet.version.to_be_bytes();
|
||||||
|
let id = packet.id.to_be_bytes();
|
||||||
|
let users = packet.users.to_be_bytes();
|
||||||
|
let max_users = packet.max_users.to_be_bytes();
|
||||||
|
let bandwidth = packet.bandwidth.to_be_bytes();
|
||||||
|
// Is there no nicer way to do this?
|
||||||
|
[
|
||||||
|
version[0],
|
||||||
|
version[1],
|
||||||
|
version[2],
|
||||||
|
version[3],
|
||||||
|
id[0],
|
||||||
|
id[1],
|
||||||
|
id[2],
|
||||||
|
id[3],
|
||||||
|
id[4],
|
||||||
|
id[5],
|
||||||
|
id[6],
|
||||||
|
id[7],
|
||||||
|
users[0],
|
||||||
|
users[1],
|
||||||
|
users[2],
|
||||||
|
users[3],
|
||||||
|
max_users[0],
|
||||||
|
max_users[1],
|
||||||
|
max_users[2],
|
||||||
|
max_users[3],
|
||||||
|
bandwidth[0],
|
||||||
|
bandwidth[1],
|
||||||
|
bandwidth[2],
|
||||||
|
bandwidth[3],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user