diff --git a/Cargo.lock b/Cargo.lock index f726dc2..99d21d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -650,10 +650,12 @@ dependencies = [ "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "openssl 0.10.28 (registry+https://github.com/rust-lang/crates.io-index)", "rtp 0.1.0 (git+https://github.com/johni0702/rtp?rev=1444b3c)", + "serde 1.0.113 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-tls 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-tungstenite 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "tungstenite 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "webrtc-sdp 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1093,6 +1095,24 @@ dependencies = [ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "serde" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.113 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "sha-1" version = "0.8.2" @@ -1288,6 +1308,14 @@ dependencies = [ "tokio 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.113 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "trackable" version = "0.1.8" @@ -1573,6 +1601,8 @@ dependencies = [ "checksum schannel 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "039c25b130bd8c1321ee2d7de7fde2659fa9c2744e4bb29711cfc852ea53cd19" "checksum security-framework 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "572dfa3a0785509e7a44b5b4bebcf94d41ba34e9ed9eb9df722545c3b3c4144a" "checksum security-framework-sys 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8ddb15a5fec93b7021b8a9e96009c5d8d51c15673569f7c0f6b7204e5b7b404f" +"checksum serde 1.0.113 (registry+https://github.com/rust-lang/crates.io-index)" = "6135c78461981c79497158ef777264c51d9d0f4f3fc3a4d22b915900e42dac6a" +"checksum serde_derive 1.0.113 (registry+https://github.com/rust-lang/crates.io-index)" = "93c5eaa17d0954cb481cdcfffe9d84fcfa7a1a9f2349271e678677be4c26ae31" "checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" "checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" "checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" @@ -1593,6 +1623,7 @@ dependencies = [ "checksum tokio-tls 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7bde02a3a5291395f59b06ec6945a3077602fac2b07eeeaf0dee2122f3619828" "checksum tokio-tungstenite 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b8fe88007ebc363512449868d7da4389c9400072a3f666f212c7280082882a" "checksum tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +"checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" "checksum trackable 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "65defd0876240a5301307e55ada18dea77372391c65cee3a421fac482be3a25a" "checksum tungstenite 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cfea31758bf674f990918962e8e5f07071a3161bd7c4138ed23e416e1ac4264e" "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" diff --git a/Cargo.toml b/Cargo.toml index 0734152..222108f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,8 @@ argparse = "0.2.2" bytes = "0.5" byteorder = "1.2" futures = { version = "0.3", features = ["compat", "io-compat"] } +toml = "0.5" +serde = { version = "1.0", features = ["derive"] } tokio = { version = "0.2", features = ["full"] } tokio-util = { version = "0.3", features = ["codec"] } tokio-tls = "0.3" diff --git a/README.md b/README.md index f8edecb..4805e91 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,12 @@ E.g. if you want the proxy to listen on port `64737` and connect to your Mumble mumble-web-proxy --listen-ws 64737 --server mumbleserver:64738 ``` +Instead of specifying all the options directly in the arguments, you can also use `--config ` to point mumble-web-proxy at a toml file which contains them: +``` +listen-ws = 64737 +server = 'mumbleserver:64738' +``` + #### Firewalls or NAT If your mumble-web-proxy is running behind a firewall or NAT, you need to allocate a range of ports to it which it can use for ICE connection establishment. ``` diff --git a/src/connection.rs b/src/connection.rs index 12d1e62..1a25c49 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -204,8 +204,8 @@ impl Connection { // Setup ICE stream let mut stream = match { let mut builder = agent.stream_builder(1); - if self.config.min_port != 1 || self.config.max_port != u16::max_value() { - builder.set_port_range(self.config.min_port, self.config.max_port); + if self.config.ice_min_port != 1 || self.config.ice_max_port != u16::max_value() { + builder.set_port_range(self.config.ice_min_port, self.config.ice_max_port); } builder.build() } { @@ -259,7 +259,11 @@ impl Connection { // Map to public addresses (if configured) let config = &self.config; - match (&mut candidate.address, config.public_v4, config.public_v6) { + match ( + &mut candidate.address, + config.ice_public_v4, + config.ice_public_v6, + ) { (webrtc_sdp::address::Address::Ip(IpAddr::V4(addr)), Some(public), _) => { *addr = public; } diff --git a/src/error.rs b/src/error.rs index 44bbceb..ca3be83 100644 --- a/src/error.rs +++ b/src/error.rs @@ -49,6 +49,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: toml::de::Error) -> Self { + Error::Misc(Box::new(e)) + } +} + impl From<()> for Error { fn from(_: ()) -> Self { panic!(); diff --git a/src/main.rs b/src/main.rs index 752862c..804fe14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ use mumble_protocol::control::ClientControlCodec; use mumble_protocol::control::ControlPacket; use mumble_protocol::control::RawControlPacket; use mumble_protocol::Clientbound; +use serde::Deserialize; use std::convert::Into; use std::convert::TryInto; use std::io::ErrorKind; @@ -32,72 +33,128 @@ mod error; use connection::Connection; use error::Error; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize)] +#[serde(default, rename_all = "kebab-case")] pub struct Config { - pub min_port: u16, - pub max_port: u16, - pub public_v4: Option, - pub public_v6: Option, + pub file: Option, + #[serde(rename = "listen-ws")] + pub ws_port: u16, + #[serde(rename = "server")] + pub upstream: String, + #[serde(rename = "accept-invalid-certificate")] + pub accept_invalid_certs: bool, + pub ice_min_port: u16, + pub ice_max_port: u16, + pub ice_public_v4: Option, + pub ice_public_v6: Option, +} + +impl Default for Config { + fn default() -> Config { + Config { + file: None, + ws_port: 0_u16, + upstream: "".to_string(), + accept_invalid_certs: false, + ice_min_port: 1, + ice_max_port: u16::max_value(), + ice_public_v4: None, + ice_public_v6: None, + } + } +} + +fn create_argparser(config: &mut Config) -> ArgumentParser { + let mut ap = ArgumentParser::new(); + ap.set_description("Run the Mumble-WebRTC proxy"); + ap.refer(&mut config.file).add_option( + &["--config"], + StoreOption, + "Toml file to read options from", + ); + ap.refer(&mut config.ws_port).add_option( + &["--listen-ws"], + Store, + "Port to listen for WebSocket (non TLS) connections on", + ); + ap.refer(&mut config.upstream).add_option( + &["--server"], + Store, + "Hostname and (optionally) port of the upstream Mumble server", + ); + ap.refer(&mut config.accept_invalid_certs).add_option( + &["--accept-invalid-certificate"], + StoreTrue, + "Connect to upstream server even when its certificate is invalid. + Only ever use this if know that your server is using a self-signed certificate!", + ); + ap.refer(&mut config.ice_min_port).add_option( + &["--ice-port-min"], + Store, + "Minimum port number to use for ICE host candidates.", + ); + ap.refer(&mut config.ice_max_port).add_option( + &["--ice-port-max"], + Store, + "Maximum port number to use for ICE host candidates.", + ); + ap.refer(&mut config.ice_public_v4).add_option( + &["--ice-ipv4"], + StoreOption, + "Set a public IPv4 address to be used for ICE host candidates.", + ); + ap.refer(&mut config.ice_public_v6).add_option( + &["--ice-ipv6"], + StoreOption, + "Set a public IPv6 address to be used for ICE host candidates.", + ); + ap +} + +fn error_missing_arg(arg: &str) { + let args: Vec = std::env::args().collect(); + let name = if args.len() > 0 { + &args[0][..] + } else { + "unknown" + }; + let mut config = Config::default(); + let ap = create_argparser(&mut config); + ap.error( + name, + &format!("Option [\"--{}\"] is required", arg), + &mut std::io::stderr(), + ); + std::process::exit(2); } #[tokio::main] async fn main() -> Result<(), Error> { - let mut ws_port = 0_u16; - let mut upstream = "".to_string(); - let mut accept_invalid_certs = false; - let mut config = Config { - min_port: 1, - max_port: u16::max_value(), - public_v4: None, - public_v6: None, - }; + let mut config = Config::default(); - { - let mut ap = ArgumentParser::new(); - ap.set_description("Run the Mumble-WebRTC proxy"); - ap.refer(&mut ws_port) - .add_option( - &["--listen-ws"], - Store, - "Port to listen for WebSocket (non TLS) connections on", - ) - .required(); - ap.refer(&mut upstream) - .add_option( - &["--server"], - Store, - "Hostname and (optionally) port of the upstream Mumble server", - ) - .required(); - ap.refer(&mut accept_invalid_certs).add_option( - &["--accept-invalid-certificate"], - StoreTrue, - "Connect to upstream server even when its certificate is invalid. - Only ever use this if know that your server is using a self-signed certificate!", - ); - ap.refer(&mut config.min_port).add_option( - &["--ice-port-min"], - Store, - "Minimum port number to use for ICE host candidates.", - ); - ap.refer(&mut config.max_port).add_option( - &["--ice-port-max"], - Store, - "Maximum port number to use for ICE host candidates.", - ); - ap.refer(&mut config.public_v4).add_option( - &["--ice-ipv4"], - StoreOption, - "Set a public IPv4 address to be used for ICE host candidates.", - ); - ap.refer(&mut config.public_v6).add_option( - &["--ice-ipv6"], - StoreOption, - "Set a public IPv6 address to be used for ICE host candidates.", - ); - ap.parse_args_or_exit(); + // First pass to get the config file path + create_argparser(&mut config).parse_args_or_exit(); + if let Some(file) = config.file { + // Then read in the config defaults + config = toml::from_str(&std::fs::read_to_string(file)?)?; + // Second pass to allow for overwrites + create_argparser(&mut config).parse_args_or_exit(); } + if config.ws_port == 0 { + error_missing_arg("listen-ws"); + } + if config.upstream == "" { + error_missing_arg("server"); + } + + let Config { + ws_port, + upstream, + accept_invalid_certs, + .. + } = config.clone(); + // Try parsing as raw IPv6 address first let (upstream_host, upstream_port) = match upstream.parse::() { Ok(_) => (upstream.as_ref(), 64738),