diff options
author | Himbeer <himbeer@disroot.org> | 2024-08-06 16:52:24 +0200 |
---|---|---|
committer | Himbeer <himbeer@disroot.org> | 2024-08-06 16:52:24 +0200 |
commit | 7f87ea7bbbae04c9bde3f0ea5b0bb3d14900e993 (patch) | |
tree | 045d121b068892ca277258547fd27751fa81b5f5 | |
parent | 8a610131b32b44e9f4acc99e46a4f453a6c00e10 (diff) |
Add LAN information and settings
-rw-r--r-- | src-tauri/src/main.rs | 329 | ||||
-rw-r--r-- | src/lan.html | 400 | ||||
-rw-r--r-- | src/lan.js | 202 |
3 files changed, 927 insertions, 4 deletions
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index b555123..c10f666 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -162,7 +162,7 @@ impl From<Dhcpv6Lease> for Dhcpv6Status { { String::from("ff02::1:2 (Alle DHCPv6-Server, da der Server keine spezifische Adresse angegeben hat)") } else { - format!("{}", lease.server) + lease.server.to_string() }, srvid: hex::encode(lease.server_id), t1: if lease.t1 == 0 { @@ -231,8 +231,8 @@ impl From<Dhcpv6Lease> for Dhcpv6Status { lease.validlft, remaining_secs ) }, - dns1: format!("{}", lease.dns1), - dns2: format!("{}", lease.dns2), + dns1: lease.dns1.to_string(), + dns2: lease.dns2.to_string(), aftr: match lease.aftr { Some(aftr) => format!("đą Aktiviert | Tunnel-Endpunkt (AFTR): {}", aftr), None => String::from("âȘ Deaktiviert"), @@ -270,6 +270,70 @@ impl Dhcpv6Lease { } } +#[derive(Debug, Deserialize)] +struct Dhcpv4Lease { + address: Ipv4Addr, + expires: SystemTime, + client_id: Vec<u8>, + hostname: Option<String>, +} + +#[derive(Debug, Serialize)] +struct LeaseRow { + addr: String, + client_id: String, + hostname: String, + expires: String, +} + +#[derive(Debug, Serialize)] +struct Leases { + clients: Vec<LeaseRow>, + status_text: String, +} + +#[derive(Debug, Serialize)] +struct Domain { + domain: String, + status_text: String, +} + +impl FromIterator<Dhcpv4Lease> for Leases { + fn from_iter<T>(iter: T) -> Self + where + T: IntoIterator<Item = Dhcpv4Lease>, + { + Self { + clients: iter + .into_iter() + .map(|lease| { + let remaining_secs = std::cmp::max( + (lease + .expires + .duration_since(SystemTime::now()) + .unwrap_or(Duration::ZERO)) + .as_secs(), + 0, + ); + + LeaseRow { + addr: lease.address.to_string(), + client_id: hex::encode(lease.client_id), + hostname: lease.hostname.unwrap_or_default(), + expires: format!( + "{} ({} Sekunden verbleibend)", + DateTime::<Local>::from(lease.expires) + .format("%d.%m.%Y %H:%M:%S UTC%Z"), + remaining_secs + ), + } + }) + .collect(), + status_text: String::new(), + } + } +} + // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command #[tauri::command] async fn connect( @@ -772,6 +836,259 @@ fn handle_change_duid_response(response: Response) -> String { } } +#[tauri::command] +async fn leases(subnet: String, state: State<'_, Mutex<Session>>) -> Result<Leases, ()> { + let (client, instance) = { + let state = state.lock().unwrap(); + (state.client.clone(), state.instance.clone()) + }; + let instance = match instance { + Some(instance) => instance, + None => { + return Ok(Leases { + clients: Vec::new(), + status_text: String::from("Keine Instanz ausgewĂ€hlt, bitte melden Sie sich neu an"), + }) + } + }; + + let leases_path = format!( + "/data/dhcp4d.leases_eth0{}", + match subnet.as_str() { + "management" => "", + "trusted" => ".10", + "untrusted" => ".20", + "isolated" => ".30", + "exposed" => ".40", + subnet => + return Ok(Leases { + clients: Vec::new(), + status_text: format!( + r#"Anwendungsinterner Fehler: UngĂŒltiges Subnetz ("management", "trusted", "untrusted", "isolated" oder "exposed" erwartet, "{}" erhalten)"#, + subnet + ) + }), + } + ); + + let response = client + .get(instance.url.join("/data/read").unwrap()) + .query(&[("path", leases_path)]) + .basic_auth("rustkrazy", Some(&instance.password)) + .send(); + + Ok(match response.await { + Ok(response) => handle_leases_response(response).await, + Err(e) => Leases { + clients: Vec::new(), + status_text: format!("Abfrage fehlgeschlagen: {}", e), + }, + }) +} + +async fn handle_leases_response(response: Response) -> Leases { + let status = response.status(); + if status.is_success() { + match response.json::<Vec<Dhcpv4Lease>>().await { + Ok(leases) => Leases::from_iter(leases), + Err(e) => Leases { + clients: Vec::new(), + status_text: format!("Fehlerhafte Leasedatei. Fehler: {}", e), + }, + } + } else if status == StatusCode::UNAUTHORIZED { + Leases { + clients: Vec::new(), + status_text: String::from( + "UngĂŒltiges Verwaltungspasswort, bitte melden Sie sich neu an!", + ), + } + } else if status == StatusCode::NOT_FOUND { + Leases { + clients: Vec::new(), + status_text: String::from( + "Noch keine Leasedatei vorhanden (erster Systemstart oder Stromausfall?)", + ), + } + } else if status.is_client_error() { + Leases { + clients: Vec::new(), + status_text: format!("Clientseitiger Fehler: {}", status), + } + } else if status.is_server_error() { + Leases { + clients: Vec::new(), + status_text: format!("Serverseitiger Fehler: {}", status), + } + } else { + Leases { + clients: Vec::new(), + status_text: format!("Unerwarteter Statuscode: {}", status), + } + } +} + +#[tauri::command] +async fn load_domain(state: State<'_, Mutex<Session>>) -> Result<Domain, ()> { + let (client, instance) = { + let state = state.lock().unwrap(); + (state.client.clone(), state.instance.clone()) + }; + let instance = match instance { + Some(instance) => instance, + None => { + return Ok(Domain { + domain: String::new(), + status_text: String::from( + "Keine Instanz ausgewĂ€hlt, bitte melden Sie sich neu an!", + ), + }) + } + }; + + let response = client + .get(instance.url.join("/data/read").unwrap()) + .query(&[("path", "/data/dnsd.domain")]) + .basic_auth("rustkrazy", Some(&instance.password)) + .send(); + + Ok(match response.await { + Ok(response) => handle_load_domain_response(response).await, + Err(e) => Domain { + domain: String::new(), + status_text: format!("Abfrage fehlgeschlagen: {}", e), + }, + }) +} + +async fn handle_load_domain_response(response: Response) -> Domain { + let status = response.status(); + if status.is_success() { + match response.text().await { + Ok(domain) => Domain { + domain, + status_text: String::new(), + }, + Err(e) => Domain { + domain: String::new(), + status_text: format!("Keinen Text vom Server erhalten. Fehler: {}", e), + }, + } + } else if status == StatusCode::UNAUTHORIZED { + Domain { + domain: String::new(), + status_text: format!("UngĂŒltiges Verwaltungspasswort, bitte melden Sie sich neu an!"), + } + } else if status == StatusCode::NOT_FOUND { + Domain { + domain: String::new(), + status_text: format!("Keine lokale Domain eingestellt"), + } + } else if status.is_client_error() { + Domain { + domain: String::new(), + status_text: format!("Clientseitiger Fehler: {}", status), + } + } else if status.is_server_error() { + Domain { + domain: String::new(), + status_text: format!("Serverseitiger Fehler: {}", status), + } + } else { + Domain { + domain: String::new(), + status_text: format!("Unerwarteter Statuscode: {}", status), + } + } +} + +#[tauri::command] +async fn change_domain(domain: String, state: State<'_, Mutex<Session>>) -> Result<String, ()> { + let (client, instance) = { + let state = state.lock().unwrap(); + (state.client.clone(), state.instance.clone()) + }; + let instance = match instance { + Some(instance) => instance, + None => { + return Ok(String::from( + "Keine Instanz ausgewĂ€hlt, bitte melden Sie sich neu an!", + )) + } + }; + + let response = client + .post(instance.url.join("/data/write").unwrap()) + .query(&[("path", "/data/dnsd.domain")]) + .basic_auth("rustkrazy", Some(&instance.password)) + .body(domain) + .send(); + + Ok(match response.await { + Ok(response) => handle_change_domain_response(response), + Err(e) => format!("Ănderung fehlgeschlagen: {}", e), + }) +} + +fn handle_change_domain_response(response: Response) -> String { + let status = response.status(); + if status.is_success() { + String::from("Ănderung erfolgreich") + } else if status == StatusCode::UNAUTHORIZED { + String::from("UngĂŒltiges Verwaltungspasswort, bitte melden Sie sich neu an!") + } else if status.is_client_error() { + format!("Clientseitiger Fehler: {}", status) + } else if status.is_server_error() { + format!("Serverseitiger Fehler: {}", status) + } else { + format!("Unerwarteter Statuscode: {}", status) + } +} + +#[tauri::command] +async fn delete(file_path: String, state: State<'_, Mutex<Session>>) -> Result<String, ()> { + let (client, instance) = { + let state = state.lock().unwrap(); + (state.client.clone(), state.instance.clone()) + }; + let instance = match instance { + Some(instance) => instance, + None => { + return Ok(String::from( + "Keine Instanz ausgewĂ€hlt, bitte melden Sie sich neu an!", + )) + } + }; + + let response = client + .post(instance.url.join("/data/remove").unwrap()) + .query(&[("path", file_path)]) + .basic_auth("rustkrazy", Some(&instance.password)) + .send(); + + Ok(match response.await { + Ok(response) => handle_delete_response(response), + Err(e) => format!("Löschung fehlgeschlagen: {}", e), + }) +} + +fn handle_delete_response(response: Response) -> String { + let status = response.status(); + if status.is_success() { + String::from("Löschung erfolgreich") + } else if status == StatusCode::UNAUTHORIZED { + String::from("UngĂŒltiges Verwaltungspasswort, bitte melden Sie sich neu an!") + } else if status == StatusCode::NOT_FOUND { + String::from("Bereits gelöscht (keine unnötige Ănderung vorgenommen)") + } else if status.is_client_error() { + format!("Clientseitiger Fehler: {}", status) + } else if status.is_server_error() { + format!("Serverseitiger Fehler: {}", status) + } else { + format!("Unerwarteter Statuscode: {}", status) + } +} + fn main() { tauri::Builder::default() .manage(Mutex::new(Session { @@ -790,7 +1107,11 @@ fn main() { connection_status, dhcpv6_status, load_duid, - change_duid + change_duid, + leases, + load_domain, + change_domain, + delete ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src/lan.html b/src/lan.html new file mode 100644 index 0000000..c179718 --- /dev/null +++ b/src/lan.html @@ -0,0 +1,400 @@ +<!doctype html> +<html lang="de"> + <head> + <meta charset="UTF-8" /> + <link rel="stylesheet" href="styles.css" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>LAN - RSDSL Verwaltungswerkzeug</title> + <script type="module" src="/dashboard.js" defer></script> + <script type="module" src="/lan.js" defer></script> + </head> + + <body> + <div class="container"> + <h1>LAN</h1> + + <div class="row"> + <form id="dashboard-form"> + <button id="dashboard-submit" type="submit">â© ZurĂŒck zur Ăbersicht</button> + </form> + + <form id="wan-open-form"> + <button id="wan-open-submit" type="submit">Einwahl und Zugansdaten</button> + </form> + + <form id="lan-open-form"> + <button id="lan-open-submit" type="submit">LAN</button> + </form> + + <form id="ddns-open-form"> + <button id="ddns-open-submit" type="submit">Dynamisches DNS (INWX)</button> + </form> + + <form id="log-open-form"> + <button id="log-open-submit" type="submit">Diagnoseprotokolle</button> + </form> + + <form id="sys-open-form"> + <button id="sys-open-submit" type="submit">Systemeinstellungen</button> + </form> + + <form id="disconnect-form"> + <button id="disconnect-submit" type="submit">đȘ Abmelden</button> + </form> + </div> + + <br /> + + <form id="vlan-form"> + <fieldset> + <legend>Virtuelle Netzwerke</legend> + + <p>Zur Erhöhung der Sicherheit ist dieses Netzwerk in mehrere + virtuelle Subnetze unterteilt. Bei richtiger Verteilung neuer GerĂ€te + auf diese Netzwerke wird die Sicherheit der Kernnetze erheblich + gesteigert. Die folgende Tabelle listet alle Subnetze auf und + beschreibt ihre technischen Informationen und Zwecke.</p> + + <table> + <th> + <td>WLAN-Name (SSID)</td> + <td>VLAN ID</td> + <td>Subnetznummer (IPv4)</td> + <td>Subnetznummer (IPv6, hexadezimal)</td> + <td>Beschreibung</td> + </th> + + <tr> + <td>Management</td> + <td>- (nur kabelgebunden)</td> + <td>Kein VLAN</td> + <td>0</td> + <td>1</td> + <td>Verwaltungsnetz fĂŒr Access Points, Switch und Telefonadapter. + Voller Internet- und Verwaltungszugriff. Nur fĂŒr genannte GerĂ€te + oder vorĂŒbergehende Diagnosearbeiten bestimmt. Ăber dieses + Netzwerk tauschen sich Access Points und Switch mit dem + Konfigurationsserver im lokalen Netz aus.</td> + </tr> + + <tr> + <td>Trusted</td> + <td>WLAN-Kabel</td> + <td>10</td> + <td>10</td> + <td>2</td> + <td>Kernnetz fĂŒr vertrauenswĂŒrdige GerĂ€te. Voller Internet- und + Verwaltungszugriff. Nur fĂŒr Computer, Tablets und Smartphones von + Familienmitgliedern.</td> + </tr> + + <tr> + <td>Untrusted</td> + <td>Internet</td> + <td>20</td> + <td>20</td> + <td>3</td> + <td>Netz fĂŒr GĂ€ste und internetgebundene IoT-GerĂ€te. Nur fĂŒr + solche GerĂ€te bestimmt, die eine Internetverbindung fĂŒr ihre + KernfunktionalitĂ€t zwingend benötigen (ausgenommen Computer, + Tablets und Smartphones von GĂ€sten). Keine Kommunikation zwischen + diesem Netzwerk und dem Kernnetz möglich, aber voller + Internetzugriff. Kein Verwaltungszugriff auf wichtige + NetzwerkgerĂ€te, auch nicht mit korrekten Zugangsdaten.</td> + </tr> + + <tr> + <td>Isolated</td> + <td>Schwefelhexafluorid</td> + <td>30</td> + <td>30</td> + <td>4</td> + <td>Abgeschottetes Netz fĂŒr IoT-GerĂ€te ohne Internetzwang (z.B. + Drucker). Keine Kommunikation zur AuĂenwelt möglich (auch nicht + zu anderen lokalen Netzen). Kein Verwaltungszugriff auf wichtige + NetzwerkgerĂ€te. GerĂ€te in diesem Netz erhalten IP-Adressen nach + dem gleichen Muster wie in den anderen Netzwerken, können aber + selbst mit den öffentlichen IPv6-Adressen die Firewall nicht + passieren. Verbindungen der GerĂ€te untereinander sind erlaubt. Um + mit hier untergebrachten GerĂ€ten zu kommunizieren, muss der + eigene Rechner vorĂŒbergehend selbst mit diesem Netzwerk verbunden + werden.</td> + </tr> + + <tr> + <td>Exposed</td> + <td>Neuland</td> + <td>40</td> + <td>40</td> + <td>5</td> + <td>Servernetz. Keine Kommunikation zu GerĂ€tenetzen möglich, aber + GerĂ€tenetze dĂŒrfen Verbindungen zu Servern in diesem Netzwerk + aufbauen. Voller Internetzugriff, aber kein Verwaltungszugriff + auf wichtige NetzwerkgerĂ€te. EnthĂ€lt den Hauptserver und + Telefonadapter. Die Router-Firewall schĂŒtzt GerĂ€te in diesem + Netzwerk nicht und lĂ€sst Verbindungsanfragen aus dem öffentlichen + Internet zu. Aufgrund von NAT ist nur Telefonie ĂŒber IPv4 + freigegeben, alle anderen Dienste sind trotz Firewallausnahme nur + ĂŒber IPv6 erreichbar. Gleiches gilt fĂŒr temporĂ€re Server auf + gewöhnlichen Computern, die vorĂŒbergehend mit diesem Netzwerk + verbunden werden. Da die öffentliche IPv4-Adresse spĂ€testens bei + einem Anbieterwechsel mit groĂer Wahrscheinlichkeit verschwinden + wird, sind bereits jetzt nur IPv6-Freigaben möglich. Dieses + Netzwerk wird von Servern verwendet, um bei Kompromittierung die + anderen GerĂ€te nicht zu gefĂ€hrden. Es kann aber auch von + gewöhnlichen Rechnern genutzt werden, wenn die Freigabe einer + Software fĂŒr das öffentliche Internet nötig ist.</td> + </tr> + </table> + + <p>Information: Die privaten IPv4-Adressen folgen der Form + 10.128.<i>IPv4-Subnetznummer</i>.X, wobei X eine gerĂ€tespezifische + Zahl zwischen 1 und (einschlieĂlich) 254 ist. Die öffentlichen + IPv6-Adressen werden zugewiesen, indem die IPv6-Subnetznummer an das + vom Internetanbieter zugewiesene PrĂ€fix angehĂ€ngt wird (optimal ist + eine PrĂ€fixlĂ€nge von /56, die kleinste unterstĂŒtzte PrĂ€fixlĂ€nge ist + /61) und den GerĂ€ten die Wahl der zweiten AdresshĂ€lfte ĂŒberlassen + wird. Der Router wĂ€hlt stets ::1 als eigene zweite AdresshĂ€lfte. Die + verbindungslokalen IPv6-Adressen werden von den einzelnen GerĂ€ten + selbst verwaltet, der Router nutzt in jedem Netzwerk (auĂer der + Internetverbindung nach auĂen) fe80::1.</p> + </fieldset> + </form> + + <br /> + + <form id="dhcpv4-form"> + <fieldset> + <legend>DHCPv4-Server</legend> + + <span class="row" id="dhcpv4-starthostid">Startadresse der dynamischen Vergabe (nur letztes Oktett): 100</span> + <span class="row" id="dhcpv4-endhostid"></span>Endadresse (inkl.) der dynamischen Vergabe (nur letztes Oktett): 239</span> + <span class="row" id="dhcpv4-self">Routeradresse (nur letztes Oktett): 254</span> + <span class="row" id="dhcpv4-netmask">Subnetzmaske: 255.255.255.0 (CIDR: /24)</span> + <span class="row" id="dhcpv4-leasetime">GĂŒltigkeitsdauer dynamisch vergebener Adressen: 12 Stunden (43200 Sekunden)</span> + + <p>Information: Wichtige NetzwerkgerĂ€te sind statisch mit Adressen + mit letztem Oktett 240 und aufwĂ€rts konfiguriert. Normale GerĂ€te, die + eine statische bzw. manuelle Adressvergabe benötigen, sollten + Adressen mit letztem Oktett echt kleiner als 100 verwenden.</p> + + <p>Information: Dynamisch vergebene Adressen bleiben in der Regel + auch nach Ablauf der GĂŒltigkeitsdauer konstant, d.h. beim nĂ€chsten + Verbindungsaufbau des entsprechenden GerĂ€tes wird wieder die gleiche + Adresse zugewiesen. Hierzu wird ein Verfahren verwendet, welches die + IP-Adresse aus der Hardwareadresse berechnet. Ausnahmen können + entstehen, wenn der Zuweisungsalgorithmus eine Kollision erkennt, + also wenn mehrere GerĂ€te die gleiche IP-Adresse zugewiesen bekommen + wĂŒrden. In derartigen FĂ€llen wird eine alternative Adresse gesucht + und zugewiesen. Werden nun beide GerĂ€te fĂŒr die restliche + GĂŒltigkeitsdauer vom Netzwerk getrennt und danach in umgekehrter + Reihenfolge wieder verbunden, werden ihre IP-Adressen vertauscht. + Dies hat keine Auswirkung auf die Internetverbindung und bedeutet + lediglich, dass nicht garantiert ist, dass der DHCP-Server jedem + GerĂ€t stets die gleiche Adresse zuweist (Standardverhalten aller + gĂ€ngigen Implementationen). Werden feste Adressen benötigt, so mĂŒssen + diese direkt auf den jeweiligen GerĂ€ten eingetragen und DHCP in deren + Einstellungen deaktiviert werden.</p> + + <fieldset> + <legend>Wichtige, statisch konfigurierte NetzwerkgerĂ€te (nicht per DHCP verwaltet)</legend> + + <span class="row">192.168.1.1/24: DSL-Modem</span> + <span class="row">10.128.0.248/24: Switch</span> + <span class="row">10.128.0.249/24: Access Point im Dachgeschoss</span> + <span class="row">10.128.0.250/24: Access Point im Obergeschoss</span> + <span class="row">10.128.0.251/24: Access Point im Erdgeschoss</span> + <span class="row">10.128.0.253/24 und 10.128.40.253/24: Hauptserver</span> + <span class="row">10.128.40.252/24: Telefonadapter</span> + </fieldset> + + <fieldset> + <legend>Dynamisch vergebene Adressen</legend> + + <h4>Management</h4> + <table id="dhcpv4-clients-management"> + <th> + <td>Client-ID (hexadezimal, ohne Doppelpunkttrennung)</td> + <td>Rechnername</td> + <td>Ablaufzeitpunkt</td> + </th> + </table> + + <div class="row"> + <output id="dhcpv4-status-management">Warte auf Initialisierung...</output> + </div> + + <h4>Trusted (WLAN-Kabel)</h4> + <table id="dhcpv4-clients-trusted"> + <th> + <td>Client-ID (hexadezimal, ohne Doppelpunkttrennung)</td> + <td>Rechnername</td> + <td>Ablaufzeitpunkt</td> + </th> + </table> + + <div class="row"> + <output id="dhcpv4-status-trusted">Warte auf Initialisierung...</output> + </div> + + <h4>Untrusted (Internet)</h4> + <table id="dhcpv4-clients-untrusted"> + <th> + <td>Client-ID (hexadezimal, ohne Doppelpunkttrennung)</td> + <td>Rechnername</td> + <td>Ablaufzeitpunkt</td> + </th> + </table> + + <div class="row"> + <output id="dhcpv4-status-untrusted">Warte auf Initialisierung...</output> + </div> + + <h4>Isolated (Schwefelhexafluorid)</h4> + <table id="dhcpv4-clients-isolated"> + <th> + <td>Client-ID (hexadezimal, ohne Doppelpunkttrennung)</td> + <td>Rechnername</td> + <td>Ablaufzeitpunkt</td> + </th> + </table> + + <div class="row"> + <output id="dhcpv4-status-isolated">Warte auf Initialisierung...</output> + </div> + + <h4>Exposed (Neuland)</h4> + <table id="dhcpv4-clients-exposed"> + <th> + <td>Client-ID (hexadezimal, ohne Doppelpunkttrennung)</td> + <td>Rechnername</td> + <td>Ablaufzeitpunkt</td> + </th> + </table> + + <div class="row"> + <output id="dhcpv4-status-exposed">Warte auf Initialisierung...</output> + </div> + + <br /> + + <p>Information: Abgelaufene Leases werden erst bei der nĂ€chsten + Anfrage an den Server aus der Liste entfernt. Dies kann Minuten + oder Wochen dauern.</p> + + <p>Information: Die Client-ID wird nicht von allen GerĂ€ten + mitgesendet. Insbesondere IoT-GerĂ€te teilen dem Server oft keinen + eigenen Wert mit. In diesen FĂ€llen erzeugt der Server automatisch + eine Client-ID nach dem Muster 01:HARDWAREADRESSE.</p> + + <p>Information: Der Rechnername wird nicht von allen GerĂ€ten + mitgesendet. Insbesondere iOS-GerĂ€te verschleiern ihn oft. Das + Deaktivieren der Option "Private WLAN-Adresse verwenden" in den + Einstellungen der WLAN-Verbindung (Info-I-Symbol) kann Abhilfe + schaffen. Unbekannte Rechnernamen beeintrĂ€chtigen die + Internetverbindung nicht, erschweren aber die Identifikation der + GerĂ€te (Client-ID und Diagnoseprotokoll des DNS-Forwarders können + sie weiterhin deanonymisieren).</p> + </fieldset> + </fieldset> + </form> + + <br /> + + <form id="slaac-form"> + <fieldset> + <legend>SLAAC-Server</legend> + + <span class="row" id="slaac-flags">Flags: On-Link, Autonomous (A)</span> + <span class="row" id="slaac-ralft">Standardgateway-GĂŒltigkeitsdauer: 1800 Sekunden</span> + <span class="row" id="slaac-preflft">PrĂ€fix-Verwendungsdauer: 1500 Sekunden</span> + <span class="row" id="slaac-validlft">PrĂ€fix-GĂŒltigkeitsdauer: 1800 Sekunden</span> + <span class="row" id="slaac-dnslft">DNS-Server-GĂŒltigkeitsdauer: 1800 Sekunden</span> + + <p>Information: IPv6-Adressen werden per SLAAC zugewiesen, DHCPv6 im + Heimnetz wird nicht unterstĂŒtzt. Das bedeutet, dass der Router auf + jedem Netzwerk periodisch sowie auf Anfrage beitretender GerĂ€te das + jeweilige öffentliche PrĂ€fix und weitere Parameter verbreitet (ULA + bzw. eindeutige lokale IPv6-Adressen werden nicht unterstĂŒtzt). Die + GerĂ€te wĂ€hlen die zweite AdresshĂ€lfte selbst und stellen sicher, dass + diese nicht mit existierenden Adressen anderer Rechner kollidieren. + Daher ist dem Router nicht bekannt, welche Adressen existieren, + weshalb sie hier nicht aufgelistet werden können. Scans mit anderen + Netzwerk-Dienstprogrammen zusammen mit den Diagnoseprotokollen können + dieses Problem lösen. Die beste Methode wĂ€re ein DHCPv6-Server, der + aber aufgrund des höheren Aufwands und der fehlenden KompatibilitĂ€t + mit Android-GerĂ€ten nicht vorhanden ist. Zudem können GerĂ€te + beliebig viele Adressen beanspruchen, was aufgrund der GröĂe des + Adressraums (2â¶âŽ) nicht problematisch, sondern insbesondere auf + Servern sogar hilfreich ist. Die PrĂ€fixableitung fĂŒr die + verschiedenen Subnetze ist unter dem Punkt "Virtuelle Netzwerke" + beschrieben.</p> + + <p>Statische Adressen bzw. Suffixe können auf den GerĂ€ten selbst + eingestellt werden. Auch andere Parameter können dort ĂŒberschrieben + werden. PrĂ€fixbezogenes dynamisches DNS ist unter dem MenĂŒpunkt + "Dynamisches DNS (INWX)" konfigurierbar.</p> + </fieldset> + </form> + + <br /> + + <form id="dns-form"> + <fieldset> + <legend>DNS</legend> + + <span class="row" id="dns-primary-us">PrimĂ€rer DNS-Server: 2620:fe::fe (Quad9)</span> + <span class="row" id="dns-secondary-us">SekundĂ€rer DNS-Server: 9.9.9.9 (Quad9)</span> + <span class="row" id="dns-primary-ds">Lokaler DNS-Server (IPv4): 10.128.SUBNETZ.254</span> + <span class="row" id="dns-secondary-ds">Lokaler DNS-Server (IPv6): fe80::1</span> + + <br /> + + <div class="row"> + <button id="dns-kill">đ Forwarder-Neustart</button> + </div> + + <p>Information: Anfragen lokaler GerĂ€te werden an die oberen beiden + DNS-Server weitergeleitet (zuerst an den primĂ€ren Server, bei Fehlern + nach max. 3 Sekunden an den sekundĂ€ren Server). Auf lokalen GerĂ€ten + wird automatisch dieser Router als DNS-Server eingestellt. Sollte die + manuelle Konfiguration eines GerĂ€ts nötig sein, nutzen Sie die + unteren beiden Werte. FĂŒr IPv4 ist SUBNETZ je nach Netzwerkname mit + folgender Zahl zu ersetzen: 10 fĂŒr "WLAN-Kabel", 20 fĂŒr "Internet", + 30 fĂŒr "Schwefelhexafluorid", 40 fĂŒr "Neuland" und 0 fĂŒr direkte + Kabelverbindungen ohne VLAN (s. "Virtuelle Netzwerke"). Ein Neustart + des DNS-Forwarders dauert ca. 30 Sekunden. WĂ€hrend dieser Zeit können + abgehende Verbindungen ins Internet fehlschlagen und bestehende + Verbindungen unterbrochen werden. Möglicherweise muss danach der + Telefonadapter ebenfalls neu gestartet werden, um den Telefoniedienst + wiederherzustellen (falls beeintrĂ€chtigt). Im Normalfall sind + derartige Neustarts nicht nötig, mit Ausnahme von Ănderungen an der + lokalen Domain.</p> + + <label for="dns-domain" form="dns-form">Lokale Domain:</label> + <div class="row"> + <input id="dns-domain" placeholder="z.B. local, lan, home etc." /> + </div> + + <br /> + + <div class="row"> + <button id="dns-unset">Domain entfernen</button> + <button id="dns-submit" type="submit">Domain Ă€ndern</button> + </div> + + <p id="dns-status">Warte auf Initialisierung...</p> + + <p>Information: Die lokale Domain ist ein optionales, empfohlenes + Suffix, unter dem GerĂ€te in den lokalen Netzwerken erreichbar sind. + Beispielsweise wĂ€re ein GerĂ€t mit dem Namen "Arbeitsrechner" bei der + lokalen Domain "example.com" sowohl unter "Arbeitsrechner" als auch + unter "Arbeitsrechner.example.com" erreichbar. Diese Erreichbarkeit + gilt nicht fĂŒr Zugriffe aus dem öffentlichen Internet. Die lokale + Domain sollte fĂŒr dieses Netzwerk nur verĂ€ndert werden, wenn dies + wirklich nötig ist.</p> + </fieldset> + </form> + </div> + </body> +</html> diff --git a/src/lan.js b/src/lan.js new file mode 100644 index 0000000..e0c99e7 --- /dev/null +++ b/src/lan.js @@ -0,0 +1,202 @@ +const { invoke } = window.__TAURI__.tauri; +const { ask, message } = window.__TAURI__.dialog; + +let dnsDomainEl; +let dnsUnsetEl; +let dnsSubmitEl; +let dnsStatusEl; + +function dashboard() { + window.location = "dashboard.html"; +} + +function wanOpen() { + window.location = "wan.html"; +} + +function lanOpen() { + window.location = "lan.html"; +} + +function ddnsOpen() { + window.location = "ddns.html"; +} + +function logOpen() { + window.location = "log.html"; +} + +function sysOpen() { + window.location = "sys.html"; +} + +async function disconnect() { + // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command + await invoke("disconnect", {}); + window.location = "index.html"; +} + +async function loadAllLeases() { + for (let subnet of ["management", "trusted", "untrusted", "isolated", "exposed"]) { + loadLeases(subnet); + } +} + +async function loadLeases(subnet) { + let statusEl = document.querySelector("#dhcpv4-status-" + subnet); + let tableEl = document.querySelector("#dhcpv4-clients-" + subnet); + + const leases = await invoke("leases", { subnet: subnet }); + + statusEl.innerText = leases.status_text; + + let first = true; + for (let child of tableEl.querySelectorAll("tr")) { + if (first) { + first = false; + continue; + } + + tableEl.removeChild(child); + child.remove(); + } + + for (let lease of leases.clients) { + let addr = document.createElement("td"); + addr.innerText = lease.addr; + + let clientId = document.createElement("td"); + clientId.innerText = lease.client_id; + + let hostname = document.createElement("td"); + hostname.innerText = lease.hostname; + + let expires = document.createElement("td"); + expires.innerText = lease.expires; + + let row = document.createElement("tr"); + row.appendChild(addr); + row.appendChild(clientId); + row.appendChild(hostname); + row.appendChild(expires); + + tableEl.appendChild(row); + } +} + +async function killDns() { + const error = await invoke("kill", { process: "rsdsl_dnsd", signal: "term" }); + + if (error !== "") { + await message("Befehl konnte nicht erteilt werden: " + error, { + kind: "error", + title: "DNS-Forwarder-Neustart nicht erfolgt", + }); + } +} + +async function unsetDomain() { + dnsDomainEl.disabled = true; + dnsUnsetEl.disabled = true; + dnsSubmitEl.disabled = true; + dnsStatusEl.innerText = "Löschanfrage..."; + document.body.style.cursor = "progress"; + + dnsStatusEl.innerText = await invoke("delete", { filePath: "/data/dnsd.domain" }); + + dnsDomainEl.value = ""; + dnsDomainEl.disabled = false; + dnsUnsetEl.disabled = false; + dnsSubmitEl.disabled = false; + document.body.style.cursor = "default"; +} + +async function loadDomain() { + dnsStatusEl.innerText = "Lade aktuelle Domain..."; + document.body.style.cursor = "progress"; + + const currentDomain = await invoke("load_domain", {}); + + dnsDomainEl.value = currentDomain.domain; + dnsStatusEl.innerText = currentDomain.status_text; + document.body.style.cursor = "default"; +} + +async function changeDomain() { + dnsDomainEl.disabled = true; + dnsUnsetEl.disabled = true; + dnsSubmitEl.disabled = true; + dnsStatusEl.innerText = "Ănderungsanfrage..."; + document.body.style.cursor = "progress"; + + dnsStatusEl.innerText = await invoke("change_domain", { domain: dnsDomainEl.value }); + + dnsDomainEl.disabled = false; + dnsUnsetEl.disabled = false; + dnsSubmitEl.disabled = false; + document.body.style.cursor = "default"; + + const reload = await ask("Zum Ăbernehmen der neuen lokalen Domain muss der DNS-Forwarder neu gestartet werden. Dies dauert ca. 30 Sekunden. WĂ€hrend dieser Zeit können abgehende Verbindungen ins Internet fehlschlagen und bestehende Verbindungen unterbrochen werden. Möglicherweis muss danach der Telefonadapter ebenfalls neu gestartet werden, um den Telefoniediesnt wiederherzustellen (falls beeintrĂ€chtigt). Möchten Sie den DNS-Forwarder jetzt neu starten?", { + kind: "info", + title: "DNS-Forwarder-Neustart erforderlich" + }); + + if (reload) { + await killDns(); + } +} + +window.addEventListener("DOMContentLoaded", () => { + document.querySelector("#dashboard-form").addEventListener("submit", (e) => { + e.preventDefault(); + dashboard(); + }); + document.querySelector("#wan-open-form").addEventListener("submit", (e) => { + e.preventDefault(); + wanOpen(); + }); + document.querySelector("#lan-open-form").addEventListener("submit", (e) => { + e.preventDefault(); + lanOpen(); + }); + document.querySelector("#ddns-open-form").addEventListener("submit", (e) => { + e.preventDefault(); + ddnsOpen(); + }); + document.querySelector("#log-open-form").addEventListener("submit", (e) => { + e.preventDefault(); + logOpen(); + }); + document.querySelector("#sys-open-form").addEventListener("submit", (e) => { + e.preventDefault(); + sysOpen(); + }); + document.querySelector("#disconnect-form").addEventListener("submit", (e) => { + e.preventDefault(); + disconnect(); + }); + + loadAllLeases(); + + dnsDomainEl = document.querySelector("#dns-domain"); + dnsUnsetEl = document.querySelector("#dns-unset"); + dnsSubmitEl = document.querySelector("#dns-submit"); + dnsStatusEl = document.querySelector("#dns-status"); + + document.querySelector("#dns-kill").addEventListener("click", (e) => { + e.preventDefault(); + killDns(); + }); + document.querySelector("#dns-unset").addEventListener("click", (e) => { + e.preventDefault(); + unsetDomain(); + }); + document.querySelector("#dns-form").addEventListener("submit", (e) => { + e.preventDefault(); + changeDomain(); + }); + + loadDomain(); +}); + +setInterval(loadAllLeases, 10000); |