From 19c334a97456612821047837e5c1b487adea74e4 Mon Sep 17 00:00:00 2001 From: soruh Date: Fri, 31 May 2024 18:27:22 +0200 Subject: [PATCH] free up allocated ports if they fail to authenticate --- src/client.rs | 4 +- src/main.rs | 10 +- src/ports.rs | 12 +- web/main.js | 437 ++++++++++++++++++++++++++------------------------ 4 files changed, 240 insertions(+), 223 deletions(-) diff --git a/src/client.rs b/src/client.rs index 24378ff..cc37f3e 100644 --- a/src/client.rs +++ b/src/client.rs @@ -61,6 +61,8 @@ async fn authenticate( return Ok(AuthResult::OutOfPorts); }; + handler_metadata.port = Some(port); + // make sure the client is authenticated before opening any ports if !authenticated { let _ip = dyn_ip_update(&config.dyn_ip_server, number, pin, port).await?; @@ -98,8 +100,6 @@ async fn authenticate( .or_default() .new_state(PortStatus::Idle); - handler_metadata.port = Some(port); - break Ok(AuthResult::Success { port }); } diff --git a/src/main.rs b/src/main.rs index b246420..ded7101 100644 --- a/src/main.rs +++ b/src/main.rs @@ -284,10 +284,18 @@ async fn connection_handler( let mut port_handler = port_handler.lock().await; if let Some(port_state) = port_handler.port_state.get_mut(&port) { + // the client is known. Mark is as disconnected port_state.new_state(PortStatus::Disconnected); - port_handler.register_update(); + } else { + // the client is not known. Free its port for realloction + assert!( + port_handler.free_ports.insert(port), + "tried to free up a port that was not allocted" + ); } + port_handler.register_update(); + if let Some(listener) = handler_metadata.listener.take() { port_handler.start_rejector( port, diff --git a/src/ports.rs b/src/ports.rs index 23411a3..36d4bed 100644 --- a/src/ports.rs +++ b/src/ports.rs @@ -1,6 +1,6 @@ use std::{ collections::{BTreeSet, HashMap, HashSet}, - fmt::{Debug, Display}, + fmt::Debug, fs::File, io::{BufReader, BufWriter}, ops::RangeInclusive, @@ -41,7 +41,7 @@ pub struct PortHandler { allowed_ports: AllowedList, #[serde(skip)] - free_ports: HashSet, + pub free_ports: HashSet, errored_ports: BTreeSet<(UnixTimestamp, Port)>, allocated_ports: HashMap, @@ -103,14 +103,6 @@ pub async fn cache_daemon( } } -#[derive(Hash, PartialEq, Eq)] -struct DisplayAsDebug(T); -impl Debug for DisplayAsDebug { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - #[derive(Default, Serialize, Deserialize)] pub struct PortState { #[serde(deserialize_with = "deserialize_last_change")] diff --git a/web/main.js b/web/main.js index ab2ac6b..5ac6167 100644 --- a/web/main.js +++ b/web/main.js @@ -1,235 +1,252 @@ window.onload = () => { - const table_elem = document.getElementById("table"); - const last_change = document.getElementById("last_change"); - const last_ping = document.getElementById("last_ping"); - const connected = document.getElementById("connected"); - const free_ports = document.getElementById("free_ports"); + const table_elem = document.getElementById("table"); + const last_change = document.getElementById("last_change"); + const last_ping = document.getElementById("last_ping"); + const connected = document.getElementById("connected"); + const free_ports = document.getElementById("free_ports"); - const timeout_duration = 10*1000; - const retry_timeout = 5*1000; + const timeout_duration = 10 * 1000; + const retry_timeout = 5 * 1000; - let reconnect_timeout; - let ping_timeout; - let evtSource; - let table = []; + let reconnect_timeout; + let ping_timeout; + let evtSource; + let table = []; - let last_update = new Date(); + let last_update = new Date(); - let direction = -1; - let oldkey = "last_change"; + let direction = -1; + let oldkey = "last_change"; - table_elem.firstChild.firstChild.lastChild.className = "sort sort-down"; + table_elem.firstChild.firstChild.lastChild.className = "sort sort-down"; + let time_ago = (ms) => { + let value = ms / 1000; + // let prev = 0; + let unit = 0; - let time_ago = ms => { + let factors = [ + [1, "Sekunde", "n"], + [60, "Minute", "n"], + [60, "Stunde", "n"], + [24, "Tag", "en"], + [7, "Woche", "n"], + [4.348214, "Monat", "en"], + [12, "Jahr", "en"], + ]; - let value = ms / 1000; - // let prev = 0; - let unit = 0; + for (let i in factors) { + let factor = factors[i][0]; + let new_value = Math.floor(value / factor); + if (new_value == 0) break; - let factors = [ - [1, 'Sekunde', 'n'], - [60, 'Minute', 'n'], - [60, 'Stunde', 'n'], - [24, 'Tag', 'en'], - [7, 'Woche', 'n'], - [4.348214, 'Monat', 'en'], - [12, 'Jahr', 'en'], - ]; + // prev = Math.floor(value % factor); + value = new_value; + unit = i; + } - for (let i in factors) { - let factor = factors[i][0]; - let new_value = Math.floor(value / factor); - if (new_value == 0) break; + let factor = factors[unit]; - // prev = Math.floor(value % factor); - value = new_value; - unit = i; + return [value, factors[unit][1] + (value == 1 ? "" : factor[2])]; + }; + + sort = (element, key) => { + if (key == oldkey) { + direction *= -1; + } else { + oldkey = key; + direction = -1; + } + + for (let child of table_elem.firstChild.firstChild.children) { + child.className = ""; + } + + element.className = `sort ${direction > 0 ? "sort-up" : "sort-down"}`; + + do_sort(oldkey, direction); + }; + + let do_sort = (key, direction) => { + let is_number = !!~(["port", "number", "last_change"].indexOf(key)); + + table = table.sort((a, b) => (direction * ( + is_number ? a[key] - b[key] : ("" + a[key]).localeCompare(b[key], "de-DE") + ))); + + populate_table(); + }; + + let fmt = Intl.DateTimeFormat("de-DE", { + dateStyle: "medium", + timeStyle: "medium", + }); + + let format_date = (date) => fmt.format(date).replace(", ", " "); + let format_time = (date) => fmt.format(date).split(", ", 2)[1]; + + let populate_table = () => { + // clear everything except for the header row + while (table_elem.rows.length > 1) { + table_elem.deleteRow(-1); + } + + for (let row of table) { + let tr = table_elem.insertRow(-1); + + let values = [ + row.name === null ? "?" : row.name, + row.number, + row.port, + row.status, + time_ago(last_update - row.last_change), + ]; + + let names = [ + "name", + "number", + "port", + "status", + "last_change", + ]; + + for (let i in values) { + let value = values[i]; + let name = names[i]; + + let td = tr.insertCell(-1); + td.className = name; + + if (name == "last_change") { + let [number, unit] = value; + + let span = document.createElement("span"); + // span.className = "value"; + span.innerText = number; + td.appendChild(span); + + span = document.createElement("span"); + span.className = "unit"; + span.innerText = unit; + td.appendChild(span); + } else { + td.innerText = value; } + } + } + }; - let factor = factors[unit]; - - return [value, factors[unit][1] + (value == 1 ? "" : factor[2])]; + let update_table = (data) => { + const allowed_ports = data.allowed_ports.map((x) => x.end - x.start + 1) + .reduce((a, b) => a + b, 0); + free_ports.innerText = `Freie Ports: ${ + allowed_ports - Object.keys(data.allocated_ports).length - + data.errored_ports.length + }`; + + // last_change.innerHTML = `Letzte Änderung: ${format_date(new Date(+data.last_update * 1000))}`; + + table = []; + + for (let number in data.allocated_ports) { + let port = data.allocated_ports[number]; + number = +number; + + // allocated port has no state. This means that it is unknown and should not be displayed + if (data.port_state[port] == undefined) { + continue; } - sort = (element, key) => { - if (key == oldkey) { - direction *= -1; - } else { - oldkey = key; - direction = -1; - } + let { status, last_change } = data.port_state[port]; - for (let child of table_elem.firstChild.firstChild.children) { - child.className = "" - }; + let rejector = data.rejectors[port] || null; - element.className = `sort ${direction > 0 ? "sort-up" : "sort-down"}`; + if (rejector && rejector instanceof Array) { + rejector = rejector.map((x) => "0x" + x.toString(16).padStart(2, 0)) + .join(" "); + } - do_sort(oldkey, direction); - }; + last_change = new Date(last_change * 1000); - let do_sort = (key, direction) => { + let name = data.names[number] || null; - let is_number = !!~(["port", "number", "last_change"].indexOf(key)); + switch (status) { + case "disconnected": + status = rejector ? `getrennt: ${rejector}` : "getrennt"; + break; + case "idle": + status = "bereit"; + break; + case "in_call": + status = "anruf"; + break; + } - table = table.sort((a, b) => (direction * ( - is_number ? a[key] - b[key] : ('' +a[key]).localeCompare(b[key], "de-DE") - ))); - - populate_table(); - }; - - let fmt = Intl.DateTimeFormat('de-DE', { dateStyle: 'medium', timeStyle: 'medium' }); - - let format_date = date => fmt.format(date).replace(', ', ' '); - let format_time = date => fmt.format(date).split(', ', 2)[1]; - - let populate_table = () => { - // clear everything except for the header row - while(table_elem.rows.length > 1) { - table_elem.deleteRow(-1); - } - - for (let row of table) { - let tr = table_elem.insertRow(-1); - - let values = [ - row.name === null ? "?" : row.name, - row.number, - row.port, - row.status, - time_ago(last_update - row.last_change) - ]; - - let names = [ - "name", - "number", - "port", - "status", - "last_change" - ]; - - for(let i in values) { - let value = values[i]; - let name = names[i]; - - let td = tr.insertCell(-1); - td.className = name; - - if (name == "last_change") { - let [number, unit] = value; - - let span = document.createElement("span"); - // span.className = "value"; - span.innerText = number; - td.appendChild(span); - - span = document.createElement("span"); - span.className = "unit"; - span.innerText = unit; - td.appendChild(span); - } else { - td.innerText = value; - } - } - } - }; - - let update_table = data => { - const allowed_ports = data.allowed_ports.map(x => x.end - x.start + 1).reduce((a,b) => a + b, 0); - free_ports.innerText = `Freie Ports: ${allowed_ports - Object.keys(data.allocated_ports).length - data.errored_ports.length}`; - - // last_change.innerHTML = `Letzte Änderung: ${format_date(new Date(+data.last_update * 1000))}`; - - table = []; - - for(let number in data.allocated_ports) { - let port = data.allocated_ports[number]; - number = +number; - let {status, last_change} = data.port_state[port]; - - let rejector = data.rejectors[port] || null; - - if (rejector && rejector instanceof Array) { - rejector = rejector.map(x => "0x"+x.toString(16).padStart(2, 0)).join(" ") - } - - last_change = new Date(last_change * 1000); - - let name = data.names[number] || null; - - switch(status) { - case "disconnected": - status = rejector ? `getrennt: ${rejector}` : "getrennt"; - break; - case "idle": - status = "bereit"; - break; - case "in_call": - status = "anruf"; - break; - } - - table.push({port, number, status, last_change, rejector, name}) - } - - for (let [timestamp, port] of data.errored_ports) { - table.push({port, number: null, status: "Fehler", last_change: new Date(timestamp * 1000), rejector: null, name: ""}) - } - - do_sort(oldkey, direction); - - populate_table(); - }; - - - let format_event = (event, method) => (method || format_date)(new Date(+event.data * 1000)); - - let display_disconnected; - - let connect_event_source = () => { - - clearTimeout(reconnect_timeout); - clearTimeout(ping_timeout); - ping_timeout = setTimeout(connect_event_source, timeout_duration); - - evtSource && evtSource.close && evtSource.close(); - - evtSource = new EventSource("/events"); - evtSource.addEventListener("change", event => { - last_ping.innerText = `Stand: ${format_event(event, format_time)}`; - - last_update = new Date(+event.data * 1000); - - fetch("/data") - .then(res => res.json()) - .then(update_table); - }); - - evtSource.addEventListener("ping", event => { - clearTimeout(ping_timeout); - ping_timeout = setTimeout(connect_event_source, timeout_duration); - - last_update = new Date(+event.data * 1000); - - last_ping.innerText = `Stand: ${format_event(event, format_time)}`; - connected.className = "visible"; - - populate_table(); - - clearTimeout(display_disconnected); - display_disconnected = setTimeout(() => connected.className = "hidden", 5000); - }); - - - evtSource.onerror = () => { - clearTimeout(reconnect_timeout); - reconnect_timeout = setTimeout(connect_event_source, retry_timeout); - }; + table.push({ port, number, status, last_change, rejector, name }); } - - connect_event_source(); + for (let [timestamp, port] of data.errored_ports) { + table.push({ + port, + number: null, + status: "Fehler", + last_change: new Date(timestamp * 1000), + rejector: null, + name: "", + }); + } -}; \ No newline at end of file + do_sort(oldkey, direction); + + populate_table(); + }; + + let format_event = (event, method) => + (method || format_date)(new Date(+event.data * 1000)); + + let display_disconnected; + + let connect_event_source = () => { + clearTimeout(reconnect_timeout); + clearTimeout(ping_timeout); + ping_timeout = setTimeout(connect_event_source, timeout_duration); + + evtSource && evtSource.close && evtSource.close(); + + evtSource = new EventSource("/events"); + evtSource.addEventListener("change", (event) => { + last_ping.innerText = `Stand: ${format_event(event, format_time)}`; + + last_update = new Date(+event.data * 1000); + + fetch("/data") + .then((res) => res.json()) + .then(update_table); + }); + + evtSource.addEventListener("ping", (event) => { + clearTimeout(ping_timeout); + ping_timeout = setTimeout(connect_event_source, timeout_duration); + + last_update = new Date(+event.data * 1000); + + last_ping.innerText = `Stand: ${format_event(event, format_time)}`; + connected.className = "visible"; + + populate_table(); + + clearTimeout(display_disconnected); + display_disconnected = setTimeout( + () => connected.className = "hidden", + 5000, + ); + }); + + evtSource.onerror = () => { + clearTimeout(reconnect_timeout); + reconnect_timeout = setTimeout(connect_event_source, retry_timeout); + }; + }; + + connect_event_source(); +};