diff --git a/CHANGELOG.md b/CHANGELOG.md index b8dcc324..f2ef1382 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ New features: - Added capture and playback devices Stdin & Stdout. - Improved error messages. - Improved validation of mixer config +- Added option to set which IP address to bind websocket server to Bugfixes: - Fix websocket `exit` command. diff --git a/README.md b/README.md index f5215982..f7e6afd1 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,7 @@ This starts the processing defined in the specified config file. The config is f Starting with the --help flag prints a short help message: ``` > camilladsp --help -CamillaDSP 0.1.0 +CamillaDSP 0.3.2 Henrik Enquist A flexible tool for processing audio @@ -247,7 +247,8 @@ FLAGS: -w, --wait Wait for config from websocket OPTIONS: - -p, --port Port for websocket server + -a, --address
IP address to bind websocket server to + -p, --port Port for websocket server ARGS: The configuration file to use @@ -256,6 +257,8 @@ If the "check" flag is given, the program will exit after checking the configura To enable the websocket server, provide a port number with the `-p` option. Leave it out, or give 0 to disable. +By default the websocket server binds to the address 127.0.0.1 which means it's only accessible locally. If it should be also available to remote machines, give the IP address of the interface where it should be available with the `-a` option. Giving 0.0.0.0 will bind to all interfaces. + If the "wait" flag, `-w` is given, CamillaDSP will start the websocket server and wait for a configuration to be uploaded. Then the config file argument must be left out. The default logging setting prints messages of levels "error", "warn" and "info". By passing the verbosity flag once, `-v` it also prints "debug". If and if's given twice, `-vv`, it also prints "trace" messages. @@ -267,8 +270,6 @@ The configuration can be reloaded without restarting by sending a SIGHUP to the ## Controlling via websocket See the [separate readme for the websocket server](./websocket.md) -If the websocket server is enabled with the -p option, CamillaDSP will listen to incoming websocket connections on the specified port. - # Capturing audio diff --git a/src/bin.rs b/src/bin.rs index beceb5ea..5cbbb318 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -41,6 +41,8 @@ use camillalib::config; use camillalib::processing; #[cfg(feature = "socketserver")] use camillalib::socketserver; +#[cfg(feature = "socketserver")] +use std::net::IpAddr; use camillalib::StatusMessage; @@ -336,6 +338,20 @@ fn main() { Err(String::from("Must be an integer between 0 and 65535")) }), ) + .arg( + Arg::with_name("address") + .help("IP address to bind websocket server to") + .short("a") + .long("address") + .takes_value(true) + .requires("port") + .validator(|val: String| -> Result<(), String> { + if val.parse::().is_ok() { + return Ok(()); + } + Err(String::from("Must be a valid IP address")) + }), + ) .arg( Arg::with_name("wait") .short("w") @@ -410,16 +426,20 @@ fn main() { #[cfg(feature = "socketserver")] { if let Some(port_str) = matches.value_of("port") { + let serveraddress = match matches.value_of("address") { + Some(addr) => addr, + None => "127.0.0.1", + }; let serverport = port_str.parse::().unwrap(); - socketserver::start_server( - serverport, - signal_reload.clone(), - signal_exit.clone(), - active_config.clone(), - active_config_path.clone(), - new_config.clone(), - capture_status.clone(), - ); + let shared_data = socketserver::SharedData { + signal_reload: signal_reload.clone(), + signal_exit: signal_exit.clone(), + active_config: active_config.clone(), + active_config_path: active_config_path.clone(), + new_config: new_config.clone(), + capture_status: capture_status.clone(), + }; + socketserver::start_server(serveraddress, serverport, shared_data); } } diff --git a/src/socketserver.rs b/src/socketserver.rs index f4be335f..dffd4cbc 100644 --- a/src/socketserver.rs +++ b/src/socketserver.rs @@ -6,6 +6,16 @@ use std::thread; use crate::CaptureStatus; use config; +#[derive(Debug, Clone)] +pub struct SharedData { + pub signal_reload: Arc, + pub signal_exit: Arc, + pub active_config: Arc>>, + pub active_config_path: Arc>>, + pub new_config: Arc>>, + pub capture_status: Arc>, +} + #[derive(Debug, PartialEq)] enum WSCommand { SetConfigName(String), @@ -110,57 +120,47 @@ fn parse_command(cmd: &ws::Message) -> WSCommand { } } -pub fn start_server( - port: usize, - signal_reload: Arc, - signal_exit: Arc, - active_config_shared: Arc>>, - active_config_path: Arc>>, - new_config_shared: Arc>>, - capture_status: Arc>, -) { +pub fn start_server(bind_address: &str, port: usize, shared_data: SharedData) { + let address = bind_address.to_owned(); debug!("Start websocket server on port {}", port); thread::spawn(move || { - ws::listen(format!("127.0.0.1:{}", port), |socket| { - let signal_reload_inst = signal_reload.clone(); - let signal_exit_inst = signal_exit.clone(); - let active_config_inst = active_config_shared.clone(); - let new_config_inst = new_config_shared.clone(); - let active_config_path_inst = active_config_path.clone(); - let capture_status_inst = capture_status.clone(); + let ws_result = ws::listen(format!("{}:{}", address, port), |socket| { + let shared_data_inst = shared_data.clone(); move |msg: ws::Message| { let command = parse_command(&msg); debug!("parsed command: {:?}", command); match command { WSCommand::Reload => { - signal_reload_inst.store(true, Ordering::Relaxed); + shared_data_inst + .signal_reload + .store(true, Ordering::Relaxed); socket.send("OK:RELOAD") } WSCommand::GetCaptureRate => { - let capstat = capture_status_inst.read().unwrap(); + let capstat = shared_data_inst.capture_status.read().unwrap(); socket.send(format!("OK:GETCAPTURERATE:{}", capstat.measured_samplerate)) } WSCommand::GetSignalRange => { - let capstat = capture_status_inst.read().unwrap(); + let capstat = shared_data_inst.capture_status.read().unwrap(); socket.send(format!("OK:GETSIGNALRANGE:{}", capstat.signal_range)) } WSCommand::GetVersion => { socket.send(format!("OK:GETVERSION:{}", crate_version!())) } WSCommand::GetState => { - let capstat = capture_status_inst.read().unwrap(); + let capstat = shared_data_inst.capture_status.read().unwrap(); socket.send(format!("OK:GETSTATE:{}", &capstat.state.to_string())) } WSCommand::GetRateAdjust => { - let capstat = capture_status_inst.read().unwrap(); + let capstat = shared_data_inst.capture_status.read().unwrap(); socket.send(format!("OK:GETRATEADJUST:{}", capstat.rate_adjust)) } WSCommand::GetUpdateInterval => { - let capstat = capture_status_inst.read().unwrap(); + let capstat = shared_data_inst.capture_status.read().unwrap(); socket.send(format!("OK:GETUPDATEINTERVAL:{}", capstat.update_interval)) } WSCommand::SetUpdateInterval(nbr) => { - let mut capstat = capture_status_inst.write().unwrap(); + let mut capstat = shared_data_inst.capture_status.write().unwrap(); capstat.update_interval = nbr; socket.send("OK:SETUPDATEINTERVAL".to_string()) } @@ -168,19 +168,22 @@ pub fn start_server( //let conf_yaml = serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(); socket.send(format!( "OK:GETCONFIG:{}", - serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(), + serde_yaml::to_string(&*shared_data_inst.active_config.lock().unwrap()) + .unwrap(), )) } WSCommand::GetConfigJson => { //let conf_yaml = serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(); socket.send(format!( "OK:GETCONFIGJSON:{}", - serde_json::to_string(&*active_config_inst.lock().unwrap()).unwrap(), + serde_json::to_string(&*shared_data_inst.active_config.lock().unwrap()) + .unwrap(), )) } WSCommand::GetConfigName => socket.send(format!( "OK:GETCONFIGNAME:{}", - active_config_path_inst + shared_data_inst + .active_config_path .lock() .unwrap() .as_ref() @@ -189,7 +192,8 @@ pub fn start_server( )), WSCommand::SetConfigName(path) => match config::load_validate_config(&path) { Ok(_) => { - *active_config_path_inst.lock().unwrap() = Some(path.clone()); + *shared_data_inst.active_config_path.lock().unwrap() = + Some(path.clone()); socket.send(format!("OK:SETCONFIGNAME:{}", path)) } _ => socket.send("ERROR:SETCONFIGNAME"), @@ -199,8 +203,10 @@ pub fn start_server( Ok(conf) => match config::validate_config(conf.clone()) { Ok(()) => { //*active_config_path_inst.lock().unwrap() = String::from("none"); - *new_config_inst.lock().unwrap() = Some(conf); - signal_reload_inst.store(true, Ordering::Relaxed); + *shared_data_inst.new_config.lock().unwrap() = Some(conf); + shared_data_inst + .signal_reload + .store(true, Ordering::Relaxed); socket.send("OK:SETCONFIG") } _ => socket.send("ERROR:SETCONFIG"), @@ -216,8 +222,10 @@ pub fn start_server( Ok(conf) => match config::validate_config(conf.clone()) { Ok(()) => { //*active_config_path_inst.lock().unwrap() = String::from("none"); - *new_config_inst.lock().unwrap() = Some(conf); - signal_reload_inst.store(true, Ordering::Relaxed); + *shared_data_inst.new_config.lock().unwrap() = Some(conf); + shared_data_inst + .signal_reload + .store(true, Ordering::Relaxed); socket.send("OK:SETCONFIGJSON") } _ => socket.send("ERROR:SETCONFIGJSON"), @@ -262,12 +270,12 @@ pub fn start_server( } } WSCommand::Stop => { - *new_config_inst.lock().unwrap() = None; - signal_exit_inst.store(2, Ordering::Relaxed); + *shared_data_inst.new_config.lock().unwrap() = None; + shared_data_inst.signal_exit.store(2, Ordering::Relaxed); socket.send("OK:STOP") } WSCommand::Exit => { - signal_exit_inst.store(1, Ordering::Relaxed); + shared_data_inst.signal_exit.store(1, Ordering::Relaxed); socket.send("OK:EXIT") } WSCommand::Invalid => { @@ -276,8 +284,11 @@ pub fn start_server( } } } - }) - .unwrap(); + }); + match ws_result { + Ok(_) => {} + Err(err) => error!("Failed to start websocket server: {}", err), + } }); } diff --git a/websocket.md b/websocket.md index dfa87f48..c1785f3a 100644 --- a/websocket.md +++ b/websocket.md @@ -4,6 +4,8 @@ If the websocket server is enabled with the `-p` option, CamillaDSP will listen If additionally the "wait" flag is given, it will wait for a config to be uploaded via the websocket server before starting the processing. +By default the websocket server binds to the address 127.0.0.1, which means it's only accessible locally (on the same machine). If it should be also available to remote machines, give the IP address of the interface where it should be available with the `-a` option. Giving 0.0.0.0 will bind to all interfaces. + The available commands are: ### General