feat: F4.6g2 the DNS server window, its records being /dns/hosts

Oracle DnsWindowRun uses Java's DNS server window as a person does and
asks a computer's resolver after each step. The core's DNS server is a
program its window changes: on and off (its socket on port 53),
recursive resolution, and records that are /dns/hosts, read on every
query as Java's server reads them (the reader moves from fls to the
core). The snapshot carries every installed DNS server with whether it
runs. The room takes the window's actions, sends its view and writes
the server back after the run; the editor shows Java's window with its
three tabs. Not taken from Java: a cell edit spilling into records of
another type, an add losing what another program wrote into the file,
an empty mail domain, a removal hitting the first record of the name.
A table cell no longer takes the room's value over what is being typed
(the web server's too).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 20:36:17 +00:00
co-authored by Claude Opus 5.5
parent e31da62ae0
commit 065b42430d
27 changed files with 2850 additions and 158 deletions
+130
View File
@@ -0,0 +1,130 @@
// FILIUS' DNS server as its window changes it (DNSServer,
// GUIApplicationDNSServerWindow, DNSConfigTable; measured with
// tools/java-oracle's DnsWindowRun): switched on and off (its socket on port
// 53), recursive resolution, and the records — which are /dns/hosts, read on
// every query as Java's server reads them, so that a record another program
// wrote into the file answers at once. A server without the file (a core
// scenario) answers from its settings until its window writes one.
//
// Departures from Java (dnsWindow.test.ts names each):
// - a record added keeps what another program wrote into the file meanwhile
// (Java writes back the list it read before: the file's new lines are lost);
// - a cell changed changes that record only (Java counts only the records of
// the table's type and, past the one changed, writes the value into each
// record of another type up to the next of its own: editing an MX record's
// server turned the following A record's address into that name);
// - a row removed is that record (Java removes the first of the name: with
// two of one name, the other);
// - a record needs a name (Java's pattern takes the empty one: ". MX 3600 x.").
import type { DnsRecordType, DnsResourceRecord, DnsServerConfiguration } from '../config.js';
import { DNS_HOSTS_PATH, dnsHostsText, dnsRecordsOf, hostsRecordsOf, isHostsDomainName, newHostsRecord, readDnsHosts, type HostsRecord } from '../dnsHosts.js';
import type { VirtualFileSystem } from '../../model/virtualFileSystem.js';
import { javaIpCheck } from '../../util/javaIp.js';
import { IP_ADDRESS_PATTERN } from './web.js';
/** What the server gets from the engine: its files (none yet, or made to write), and its socket opened and closed. */
export interface DnsServerHost {
readonly files: () => VirtualFileSystem | undefined;
readonly filesToWrite: () => VirtualFileSystem;
readonly open: () => void;
readonly close: () => void;
}
/** EingabenUeberpruefung.musterIpAdresse with IP.ipCheck, as the window checks an address. */
const isAddress = (text: string) => IP_ADDRESS_PATTERN.test(text) && javaIpCheck(text) !== null;
/** The window's check of a record's two parts: a name, and an address (A) or a name (MX, NS). */
export function validDnsRecord(type: DnsRecordType, domainName: string, rdata: string): boolean {
return domainName !== '' && isHostsDomainName(domainName) && (type === 'A' ? isAddress(rdata) : rdata !== '' && isHostsDomainName(rdata));
}
export class DnsServer {
private active: boolean;
private recursive: boolean;
constructor(
private readonly host: DnsServerHost,
private readonly configuration: DnsServerConfiguration,
isActive: boolean,
) {
this.active = isActive;
this.recursive = configuration.recursiveResolutionEnabled;
}
get isActive(): boolean {
return this.active;
}
get recursiveResolutionEnabled(): boolean {
return this.recursive;
}
/** The start button: the socket on port 53 opened or closed. */
setActive(active: boolean): void {
// Once more alike: the port is taken by its own socket, or there is none to close.
this.active = active;
if (active) this.host.open();
else this.host.close();
}
setRecursiveResolutionEnabled(enabled: boolean): void {
this.recursive = enabled;
}
private file(): string | undefined {
const files = this.host.files();
return files !== undefined && files.contains(DNS_HOSTS_PATH) && files.entry(DNS_HOSTS_PATH).content.kind === 'text' ? files.textFile(DNS_HOSTS_PATH) : undefined;
}
/** The records as the window lists them: the valid lines of /dns/hosts (holeResourceRecords), else the settings'. */
get records(): HostsRecord[] {
const text = this.file();
return text === undefined ? hostsRecordsOf(this.configuration.records) : readDnsHosts(text);
}
/** The records a query is answered from. */
get answering(): DnsResourceRecord[] {
const text = this.file();
return text === undefined ? [...this.configuration.records] : dnsRecordsOf(readDnsHosts(text), () => {});
}
/** The records of a type in the order of the file, each with its place among all (a row of the window's table). */
rows(type: DnsRecordType): { readonly record: HostsRecord; readonly index: number }[] {
return this.records.flatMap((record, index) => (record.type === type ? [{ record, index }] : []));
}
private write(records: readonly HostsRecord[]): void {
const files = this.host.filesToWrite();
if (!files.contains('/dns')) files.createDirectory('/dns', true);
files.writeTextFile(DNS_HOSTS_PATH, dnsHostsText(records));
}
/** "Hinzufügen" (hinzuRecord): at the end of the file as it is now; false when the window's check fails. */
addRecord(type: DnsRecordType, domainName: string, rdata: string): boolean {
if (!validDnsRecord(type, domainName, rdata)) return false;
this.write([...this.records, newHostsRecord(domainName, type, rdata)]);
return true;
}
/** "Auswahl entfernen": the table's row of the type; false when there is none. */
removeRecord(type: DnsRecordType, row: number): boolean {
const target = this.rows(type)[row];
if (target === undefined) return false;
this.write(this.records.filter((_, index) => index !== target.index));
return true;
}
/** A cell of the type's table written (changeSingleEntry): the name (column 0) or the data (column 1) of that record only; false when the value fails the check. */
changeRecord(type: DnsRecordType, row: number, column: 0 | 1, value: string): boolean {
const target = this.rows(type)[row];
if (target === undefined) return false;
const trimmed = value.trim();
const domainName = column === 0 ? trimmed : target.record.domainName;
const rdata = column === 1 ? trimmed : target.record.rdata;
if (!validDnsRecord(type, domainName, rdata)) return false;
const changed = { ...newHostsRecord(domainName, type, rdata), ttl: target.record.ttl };
this.write(this.records.map((record, index) => (index === target.index ? changed : record)));
return true;
}
}