feat(fls): DNS records from /dns/hosts, as Java's server answers them

Java's DNS server reads its records from the text file /dns/hosts on every
query. dnsHosts.ts ports that reading (DNSServer.leseRecordListe,
ResourceRecord, Dateisystem.holeDatei) and keeps in the model the records
Java answers: type exactly A, MX or NS, and a name Java accepted. The file
stays in the file system as it is.

Both oracles read 76 hand-picked hosts files (dns-hosts.cases.txt): the
Java oracle through DNSServer.holeResourceRecords (DnsHostsCases), the
Swift oracle through TopologyDNSHostsFileCodec (--dns-hosts). The Java view
matches Java on every case; the model matches Swift except for 33 named
cases in twelve reasons: Java splits at single spaces and LF only, has no
comments or "IN", turns a TTL that is no int into 3600, keeps duplicates,
and empties names with a leading digit. The Swift dump now carries each
server's records; the corpus comparison covers them on 16 files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 06:38:01 +00:00
co-authored by Claude Opus 5.5
parent 1cc0dfd899
commit c80d212c90
17 changed files with 2518 additions and 11 deletions
+13
View File
@@ -64,3 +64,16 @@ Java-Filius schickt jede IP-Adresse und Maske beim Laden durch
zählen nicht (`10..0.0.1`), Vorzeichen und führende Nullen gehen, ebenso
Ziffern jeder Schrift in der BMP (Thai, Devanagari, Vollbreite) — aber kein
Leerzeichen und kein 𝟏 (Ersatzpaar).
## `/dns/hosts`
Javas DNS-Server liest seine Einträge bei jeder Anfrage aus der Textdatei
`/dns/hosts`. `DnsHostsCases` legt für jede Zeile von
`packages/fls/test/dns-hosts.cases.txt` (eine ganze Datei je Zeile, ASCII,
Zeilenumbruch als `\u000A`) einen Rechner mit dieser Datei an und schreibt,
was `DNSServer.holeResourceRecords()` liefert, nach `dns-hosts.java.json`.
Gemessen: Felder trennt nur ein einzelnes Leerzeichen (Tab und NBSP nicht),
Zeilen nur LF; ein Name, den Javas Muster ablehnt (`1und1.de`, `müller.de`),
wird leer, der Eintrag bleibt trotzdem; Typen außer A/MX/NS (auch `a`,
`CNAME`, eine Kommentarzeile) gelten als gültig; eine TTL, die kein `int`
ist, wird still 3600.
+8 -1
View File
@@ -21,7 +21,7 @@ if [ ! -f "$upstream/target/filius.jar" ]; then
fi
classpath="$upstream/target/filius.jar:$upstream/target/lib/*"
mkdir -p "$here/.build"
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java"
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java"
# FILIUS writes its settings below $HOME; keep them out of the real one.
mkdir -p "$here/.build/home"
# Removed first: a crashed run must not leave the previous result standing.
@@ -66,3 +66,10 @@ ipcheck="$root/packages/fls/test/ip-check.java.json"
rm -f "$ipcheck"
HOME="$here/.build/home" java -cp "$classpath:$here/.build" IpCheckCases "$cases" "$ipcheck" 2>&1 | grep -v 'logback\|^$' || true
test -s "$ipcheck"
# DNSServer's own reading of /dns/hosts on hand-picked files (packages/fls/src/dnsHosts.ts).
cases="$root/packages/fls/test/dns-hosts.cases.txt"
dnshosts="$root/packages/fls/test/dns-hosts.java.json"
rm -f "$dnshosts"
HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" DnsHostsCases "$cases" "$dnshosts" 2>&1 | grep -v 'logback\|^$' || true
test -s "$dnshosts"
+50
View File
@@ -0,0 +1,50 @@
// Runs FILIUS's own reading of /dns/hosts (DNSServer.holeResourceRecords)
// on every case of a list and writes the records Java keeps — the reference
// for packages/fls/src/dnsHosts.ts. One case per line: a whole hosts file,
// ASCII with Java-style unicode escapes (backslash, u, four hex digits), so
// line breaks and odd spaces survive editors and hooks.
//
// Usage: java -cp filius.jar:lib/*:classes DnsHostsCases <cases txt> <output json>
import filius.hardware.knoten.Rechner;
import filius.software.dns.DNSServer;
import filius.software.dns.ResourceRecord;
import filius.software.system.Datei;
import filius.software.system.Dateisystem;
import filius.software.system.InternetKnotenBetriebssystem;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class DnsHostsCases {
public static void main(String[] args) throws Exception {
List<Object> result = new ArrayList<>();
for (String line : Files.readAllLines(Path.of(args[0]), StandardCharsets.US_ASCII)) {
InternetKnotenBetriebssystem system = (InternetKnotenBetriebssystem) new Rechner().getSystemSoftware();
Dateisystem fileSystem = system.getDateisystem();
fileSystem.erstelleVerzeichnis(fileSystem.getRoot(), "dns");
Datei hosts = new Datei();
hosts.setName("hosts");
hosts.setDateiInhalt(IpCheckCases.unescape(line));
fileSystem.speicherDatei(fileSystem.holeRootPfad() + Dateisystem.FILE_SEPARATOR + "dns", hosts);
DNSServer server = new DNSServer();
server.setSystemSoftware(system);
List<Object> records = new ArrayList<>();
for (ResourceRecord record : server.holeResourceRecords()) {
records.add(Arrays.asList(record.getDomainname(), record.getType(), record.getTtl(), record.getRdata()));
}
result.add(Arrays.asList(line, records));
}
try (FileOutputStream out = new FileOutputStream(args[1])) {
out.write((FlsOracle.Json.write(result, "") + "\n").getBytes(StandardCharsets.UTF_8));
}
System.out.println("dns hosts cases: " + result.size());
System.exit(0);
}
}
+5
View File
@@ -38,6 +38,11 @@ Sortierung, Base64 — und die ganzen Mengen `whitespacesAndNewlines` und
unter Linux; ihre Steuerzeichen-Tabelle hat in Ebene 14 einen Fehler, den
der Port nicht übernimmt.
`--dns-hosts` liest dieselben `/dns/hosts`-Fälle wie das Java-Orakel
(`packages/fls/test/dns-hosts.cases.txt`) mit Swifts
`TopologyDNSHostsFileCodec`; unter `state` stehen die DNS-Einträge jedes
Servers (`dnsServer.records`).
## Wie es gebaut wird
`fetch-upstream.sh` holt `filius-ipad` am festen Stand `3b5ded0` und kopiert
@@ -401,6 +401,7 @@ func describeSystem(_ state: TopologyEditorState, _ node: TopologyNode) -> JSON
json["dnsServer"] = [
"recursiveResolutionEnabled": dns.recursiveResolutionEnabled,
"forwardingServerIPAddress": dns.forwardingServerIPAddress ?? NSNull(),
"records": dns.typedRecords.map { dnsRecordJSON($0) },
] as JSON
}
if let link = state.remoteLinkConfigurationsByNodeID[node.id] {
@@ -614,6 +615,32 @@ func foldPaths(_ cases: [PathCase]) -> [JSON] {
}
}
// MARK: - /dns/hosts cases (packages/fls/test/dns-hosts.cases.txt)
func dnsRecordJSON(_ record: TopologyDNSResourceRecord) -> [Any] {
[record.name.rawValue, record.type.rawValue, Int(record.ttlSeconds), record.target]
}
/// One case per line: a whole hosts file, ASCII with Java-style unicode
/// escapes (backslash, u, four hex digits) — the file the Java oracle reads.
func readDNSHostsCases(_ url: URL) throws -> [(line: String, text: String)] {
let lines = try String(contentsOf: url, encoding: .ascii).components(separatedBy: "\n")
return lines.dropLast().map { line in
var units: [UInt16] = []
var rest = Substring(line)
while let first = rest.first {
if rest.hasPrefix("\\u"), rest.count >= 6, let unit = UInt16(rest.dropFirst(2).prefix(4), radix: 16) {
units.append(unit)
rest = rest.dropFirst(6)
} else {
units.append(contentsOf: String(first).utf16)
rest = rest.dropFirst()
}
}
return (line, String(decoding: units, as: UTF16.self))
}
}
// MARK: - Main
if CommandLine.arguments.count == 4, CommandLine.arguments[1] == "--path-folding" {
@@ -641,6 +668,15 @@ if CommandLine.arguments.count == 4, CommandLine.arguments[1] == "--strings" {
exit(0)
}
if CommandLine.arguments.count == 4, CommandLine.arguments[1] == "--dns-hosts" {
let cases = try readDNSHostsCases(URL(fileURLWithPath: CommandLine.arguments[2]))
let output: [[Any]] = cases.map { [$0.line, TopologyDNSHostsFileCodec.records(from: $0.text).map { dnsRecordJSON($0) }] }
let data = try JSONSerialization.data(withJSONObject: output, options: [.prettyPrinted, .sortedKeys])
try (data + Data("\n".utf8)).write(to: URL(fileURLWithPath: CommandLine.arguments[3]))
print("dns hosts: \(cases.count) cases")
exit(0)
}
if CommandLine.arguments.count == 4, CommandLine.arguments[1] == "--corpus" {
let summary = try readCorpus(root: URL(fileURLWithPath: CommandLine.arguments[2]))
let data = try JSONSerialization.data(withJSONObject: summary, options: [.prettyPrinted, .sortedKeys])
+2 -1
View File
@@ -14,4 +14,5 @@ docker run --rm -v "$root:/work" -w /work/tools/swift-oracle swift:6.1-jammy sh
.build/debug/Oracle --corpus /work/corpus /work/corpus/corpus.swift.json
.build/debug/Oracle --vfs /work/packages/core/test/vfs.cases.json /work/packages/core/test/vfs.swift.json
.build/debug/Oracle --strings /work/packages/core/test/strings.cases.json /work/packages/core/test/strings.swift.json
.build/debug/Oracle --path-folding /work/packages/fls/test/path-folding.cases.json /work/packages/fls/test/path-folding.swift.json'
.build/debug/Oracle --path-folding /work/packages/fls/test/path-folding.cases.json /work/packages/fls/test/path-folding.swift.json
.build/debug/Oracle --dns-hosts /work/packages/fls/test/dns-hosts.cases.txt /work/packages/fls/test/dns-hosts.swift.json'