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
+31 -3
View File
@@ -145,6 +145,7 @@ function systemOf(project: ImportedProject, nodeId: Id) {
result.dnsServer = {
recursiveResolutionEnabled: dns.recursiveResolutionEnabled,
forwardingServerIPAddress: dns.forwardingServerIPAddress ?? null,
records: dns.records.map((record) => [record.name, record.type, record.ttlSeconds, record.target]),
};
}
const remoteLink = project.remoteLinkConfigurations.get(nodeId);
@@ -787,12 +788,39 @@ describe('importProject rules', () => {
expect(project.simpleClientConfigurations.get(notebook!)).toEqual({ targetAddress: '10.0.0.9', targetPort: 7 });
expect(project.firewallConfigurationsByNodeId.get(notebook!)).toMatchObject({ isActive: true, defaultPolicy: 'accept' });
expect([forwarding, self, iterative].map((id) => project.dnsServerConfigurations.get(id!))).toEqual([
{ recursiveResolutionEnabled: true, forwardingServerIPAddress: '8.8.8.8' },
{ recursiveResolutionEnabled: true },
{ recursiveResolutionEnabled: false },
{ recursiveResolutionEnabled: true, forwardingServerIPAddress: '8.8.8.8', records: [] },
{ recursiveResolutionEnabled: true, records: [] },
{ recursiveResolutionEnabled: false, records: [] },
]);
});
it("takes a DNS server's records from its /dns/hosts, and names the node in a warning", () => {
const hosts = 'www.filius.de A 3600 141.99.2.10\nfilius.de MX 3600 mail.filius.de\nbroken.de A 3600 .1.2.3\n';
const server = flsNode('Rechner', {
name: 'DNS',
interfaces: [nic('10.0.0.1')],
system: {
applications: [{ key: 'dns', javaClass: 'filius.software.dns.DNSServer', source: flsNode('Rechner').source, isActive: true }],
fileSystem: {
kind: 'directory',
name: 'root',
children: [
{ kind: 'directory', name: 'dns', children: [{ kind: 'file', name: 'hosts', type: 'text', content: hosts, children: [] }] },
],
},
},
});
const project = importProject(topology({ nodes: [server] }), counter());
const [id] = project.graph.nodes.map((node) => node.id);
expect(project.dnsServerConfigurations.get(id!)?.records).toEqual([
{ name: 'www.filius.de', type: 'A', ttlSeconds: 3600, target: '141.99.2.10' },
{ name: 'filius.de', type: 'MX', ttlSeconds: 3600, target: 'mail.filius.de.' },
]);
expect(project.virtualFileSystems.get(id!)?.textFile('/dns/hosts')).toBe(hosts);
expect(project.warnings).toEqual(["node 'DNS': DNS record 'broken.de A .1.2.3' has no usable IPv4 address, left out (it stays in /dns/hosts)"]);
});
it('leaves out a cable to itself and one to a port that already has a cable', () => {
const project = importProject(
topology({