mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2025-02-25 18:55:32 -06:00
Update the LTS system to periodically ask if we have a license yet, allowing for automatic configuration of LTS if and when they sign up. This is a WIP - not recommended to merge yet.
This commit is contained in:
parent
5d5bfcb26d
commit
916645ab93
@ -211,6 +211,53 @@ impl EtcLqos {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_long_term_stats(license_key: String) {
|
||||
if let Ok(raw) = std::fs::read_to_string("/etc/lqos.conf") {
|
||||
let document = raw.parse::<Document>();
|
||||
match document {
|
||||
Err(e) => {
|
||||
error!("Unable to parse TOML from /etc/lqos.conf");
|
||||
error!("Full error: {:?}", e);
|
||||
return;
|
||||
}
|
||||
Ok(mut config_doc) => {
|
||||
let cfg = toml_edit::de::from_document::<EtcLqos>(config_doc.clone());
|
||||
match cfg {
|
||||
Ok(mut cfg) => {
|
||||
// Now we enable LTS if its not present
|
||||
if let Ok(isp_config) = crate::LibreQoSConfig::load() {
|
||||
if cfg.long_term_stats.is_none() {
|
||||
cfg.long_term_stats = Some(LongTermStats {
|
||||
gather_stats: true,
|
||||
collation_period_seconds: 60,
|
||||
license_key: Some(license_key),
|
||||
uisp_reporting_interval_seconds: if isp_config.automatic_import_uisp {
|
||||
Some(300)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
config_doc["long_term_stats"] = value(toml_edit::ser::to_string(&cfg.long_term_stats.unwrap()).unwrap());
|
||||
let new_cfg = config_doc.to_string();
|
||||
if let Err(e) = fs::write(Path::new("/etc/lqos.conf"), new_cfg) {
|
||||
log::error!("Unable to write to /etc/lqos.conf");
|
||||
log::error!("{e:?}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Unable to parse TOML from /etc/lqos.conf");
|
||||
error!("Full error: {:?}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_config(cfg_doc: &mut Document, cfg: &mut EtcLqos) {
|
||||
use sha2::digest::Update;
|
||||
use sha2::Digest;
|
||||
|
@ -14,7 +14,7 @@ mod program_control;
|
||||
mod shaped_devices;
|
||||
|
||||
pub use authentication::{UserRole, WebUsers};
|
||||
pub use etc::{BridgeConfig, BridgeInterface, BridgeVlan, EtcLqos, Tunables};
|
||||
pub use etc::{BridgeConfig, BridgeInterface, BridgeVlan, EtcLqos, Tunables, enable_long_term_stats};
|
||||
pub use libre_qos_config::LibreQoSConfig;
|
||||
pub use network_json::{NetworkJson, NetworkJsonNode, NetworkJsonTransport};
|
||||
pub use program_control::load_libreqos;
|
||||
|
@ -35,10 +35,16 @@ pub async fn start_long_term_stats() -> Sender<StatsUpdateMessage> {
|
||||
}
|
||||
|
||||
async fn collation_scheduler(tx: Sender<StatsUpdateMessage>) {
|
||||
log::info!("Starting collation scheduler");
|
||||
loop {
|
||||
let collation_period = get_collation_period();
|
||||
tx.send(StatsUpdateMessage::CollationTime).await.unwrap();
|
||||
log::info!("Collation period: {}s", collation_period.as_secs());
|
||||
if tx.send(StatsUpdateMessage::CollationTime).await.is_err() {
|
||||
log::warn!("Unable to send collation time message");
|
||||
}
|
||||
log::info!("Sent collation time message. Sleeping.");
|
||||
tokio::time::sleep(collation_period).await;
|
||||
log::info!("Collation scheduler woke up.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,11 +113,20 @@ fn get_uisp_collation_period() -> Option<Duration> {
|
||||
}
|
||||
|
||||
async fn uisp_collection_manager(control_tx: Sender<StatsUpdateMessage>) {
|
||||
if let Some(period) = get_uisp_collation_period() {
|
||||
log::info!("Starting UISP poller with period {:?}", period);
|
||||
loop {
|
||||
control_tx.send(StatsUpdateMessage::UispCollationTime).await.unwrap();
|
||||
tokio::time::sleep(period).await;
|
||||
// Outer loop: If UISP is disabled, check hourly to see if it
|
||||
// was enabled. If it is enabled, start the inner loop.
|
||||
loop {
|
||||
// Inner loop - if there's a collation period set for UISP,
|
||||
// poll it.
|
||||
if let Some(period) = get_uisp_collation_period() {
|
||||
log::info!("Starting UISP poller with period {:?}", period);
|
||||
loop {
|
||||
control_tx.send(StatsUpdateMessage::UispCollationTime).await.unwrap();
|
||||
tokio::time::sleep(period).await;
|
||||
}
|
||||
} else {
|
||||
// Sleep for one hour - then we'll check again
|
||||
tokio::time::sleep(Duration::from_secs(3600)).await;
|
||||
}
|
||||
}
|
||||
}
|
@ -62,6 +62,7 @@ async fn connect_if_permitted() -> Result<TcpStream, QueueError> {
|
||||
QueueError::NoLocalLicenseKey
|
||||
})?;
|
||||
if !usage_cfg.gather_stats {
|
||||
log::warn!("Gathering long-term stats is disabled.");
|
||||
return Err(QueueError::StatsDisabled);
|
||||
}
|
||||
let license_key = usage_cfg.license_key.ok_or_else(|| {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::transport_data::{ask_license_server, LicenseReply};
|
||||
use crate::transport_data::{ask_license_server, LicenseReply, ask_license_server_for_new_account};
|
||||
use lqos_config::EtcLqos;
|
||||
use lqos_utils::unix_time::unix_now;
|
||||
use once_cell::sync::Lazy;
|
||||
@ -44,7 +44,11 @@ pub(crate) async fn get_license_status() -> LicenseState {
|
||||
const MISERLY_NO_KEY: &str = "IDontSupportDevelopersAndShouldFeelBad";
|
||||
|
||||
async fn check_license(unix_time: u64) -> LicenseState {
|
||||
log::info!("Checking LTS stats license");
|
||||
if let Ok(cfg) = EtcLqos::load() {
|
||||
// The config file is good. Is LTS enabled?
|
||||
// If it isn't, we need to try very gently to see if a pending
|
||||
// request has been submitted.
|
||||
if let Some(cfg) = cfg.long_term_stats {
|
||||
if let Some(key) = cfg.license_key {
|
||||
if key == MISERLY_NO_KEY {
|
||||
@ -81,7 +85,29 @@ async fn check_license(unix_time: u64) -> LicenseState {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LTS is unconfigured - but not explicitly disabled.
|
||||
// So we need to check if we have a pending request.
|
||||
// If a license key has been assigned, then we'll setup
|
||||
// LTS. If it hasn't, we'll just return Unknown.
|
||||
if let Some(node_id) = &cfg.node_id {
|
||||
if let Ok(result) = ask_license_server_for_new_account(node_id.to_string()).await {
|
||||
if let LicenseReply::NewActivation { license_key } = result {
|
||||
// We have a new license!
|
||||
let _ = lqos_config::enable_long_term_stats(license_key);
|
||||
// Note that we're not doing anything beyond this - the next cycle
|
||||
// will pick up on there actually being a license
|
||||
} else {
|
||||
log::info!("No pending LTS license found");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// There's no node ID either - we can't talk to this
|
||||
log::warn!("No NodeID is configured. No online services are possible.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("Unable to load lqosd configuration. Not going to try.");
|
||||
}
|
||||
LicenseState::Unknown
|
||||
}
|
||||
|
@ -24,6 +24,11 @@ pub enum LicenseRequest {
|
||||
/// The sodium-style public key of the requesting shaper node
|
||||
public_key: PublicKey,
|
||||
},
|
||||
/// Check to see if this node has been newly approved
|
||||
PendingLicenseRequest {
|
||||
/// The local node id
|
||||
node_id: String,
|
||||
}
|
||||
}
|
||||
|
||||
/// License server responses for a key
|
||||
@ -43,6 +48,11 @@ pub enum LicenseReply {
|
||||
/// The server's public key
|
||||
public_key: PublicKey,
|
||||
},
|
||||
/// New Activation
|
||||
NewActivation {
|
||||
/// The license key to apply
|
||||
license_key: String,
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur when checking licenses
|
||||
|
@ -17,7 +17,27 @@ fn build_license_request(key: String) -> Result<Vec<u8>, LicenseCheckError> {
|
||||
let mut result = Vec::new();
|
||||
let payload = serde_cbor::to_vec(&LicenseRequest::LicenseCheck { key });
|
||||
if let Err(e) = payload {
|
||||
log::warn!("Unable to serialize statistics. Not sending them.");
|
||||
log::warn!("Unable to serialize license request. Not sending them.");
|
||||
log::warn!("{e:?}");
|
||||
return Err(LicenseCheckError::SerializeFail);
|
||||
}
|
||||
let payload = payload.unwrap();
|
||||
|
||||
// Store the version as network order
|
||||
result.extend(1u16.to_be_bytes());
|
||||
// Store the payload size as network order
|
||||
result.extend((payload.len() as u64).to_be_bytes());
|
||||
// Store the payload itself
|
||||
result.extend(payload);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn build_activation_query(node_id: String) -> Result<Vec<u8>, LicenseCheckError> {
|
||||
let mut result = Vec::new();
|
||||
let payload = serde_cbor::to_vec(&LicenseRequest::PendingLicenseRequest { node_id } );
|
||||
if let Err(e) = payload {
|
||||
log::warn!("Unable to serialize license request. Not sending them.");
|
||||
log::warn!("{e:?}");
|
||||
return Err(LicenseCheckError::SerializeFail);
|
||||
}
|
||||
@ -107,6 +127,47 @@ pub async fn ask_license_server(key: String) -> Result<LicenseReply, LicenseChec
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ask_license_server_for_new_account(
|
||||
node_id: String,
|
||||
) -> Result<LicenseReply, LicenseCheckError>
|
||||
{
|
||||
if let Ok(buffer) = build_activation_query(node_id) {
|
||||
let stream = TcpStream::connect(LICENSE_SERVER).await;
|
||||
if let Err(e) = &stream {
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
log::error!("Unable to access {LICENSE_SERVER}. Check that lqosd is running and you have appropriate permissions.");
|
||||
return Err(LicenseCheckError::SendFail);
|
||||
}
|
||||
}
|
||||
let stream = stream;
|
||||
match stream {
|
||||
Ok(mut stream) => {
|
||||
let ret = stream.write(&buffer).await;
|
||||
if ret.is_err() {
|
||||
log::error!("Unable to write to {LICENSE_SERVER} stream.");
|
||||
log::error!("{:?}", ret);
|
||||
return Err(LicenseCheckError::SendFail);
|
||||
}
|
||||
let mut buf = Vec::with_capacity(10240);
|
||||
let ret = stream.read_to_end(&mut buf).await;
|
||||
if ret.is_err() {
|
||||
log::error!("Unable to read from {LICENSE_SERVER} stream.");
|
||||
log::error!("{:?}", ret);
|
||||
return Err(LicenseCheckError::SendFail);
|
||||
}
|
||||
|
||||
decode_response(&buf)
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("TCP stream failed to connect: {:?}", e);
|
||||
Err(LicenseCheckError::ReceiveFail)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(LicenseCheckError::SerializeFail)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ask the license server for the public key
|
||||
pub async fn exchange_keys_with_license_server(
|
||||
node_id: String,
|
||||
|
Loading…
Reference in New Issue
Block a user