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
+16 -1
View File
@@ -180,10 +180,25 @@ export interface GnutellaConfiguration {
readonly maximumKnownPeers: number;
}
/** The DNS records follow with the hosts file (Java's /dns/hosts). */
export type DnsRecordType = 'A' | 'MX' | 'NS';
/**
* One record a DNS server answers with (Swift's TopologyDNSResourceRecord):
* `name` in lower case without the final dot (the root is "."), `target` an
* IPv4 address for A, an absolute name ("mail.filius.de.") for MX and NS.
*/
export interface DnsResourceRecord {
readonly name: string;
readonly type: DnsRecordType;
readonly ttlSeconds: number;
readonly target: string;
}
export interface DnsServerConfiguration {
readonly recursiveResolutionEnabled: boolean;
readonly forwardingServerIPAddress?: string;
/** In the order of Java's /dns/hosts, which stays in the file system as it is. */
readonly records: readonly DnsResourceRecord[];
}
/**
+153
View File
@@ -0,0 +1,153 @@
// Java's DNS server keeps its records in the text file /dns/hosts and reads
// them on every query (DNSServer.leseRecordListe, ResourceRecord(String)).
// Measured on test/dns-hosts.cases.txt against Java (dns-hosts.java.json)
// and Swift (dns-hosts.swift.json); where the two differ, Java decides:
// - fields are split at single spaces only: a tab or NBSP joins two fields;
// - there are no comments, no "IN" and no zone-file order;
// - a TTL that is not a Java int silently becomes 3600;
// - a name Java does not accept becomes the empty name, and the record
// still counts - it just never answers, like any record whose type is
// not exactly A, MX or NS (lower-case "a" included);
// - duplicates stay.
import type { DnsResourceRecord } from '@mdg-filius/core';
import { javaIpCheck, javaParseInt } from './javaIp.js';
import type { FlsFileTree } from './systemSoftware.js';
/** A record as Java's ResourceRecord holds it (the getters). */
export interface JavaResourceRecord {
readonly domainName: string;
readonly type: string;
readonly ttl: number;
readonly rdata: string;
}
export const DNS_HOSTS_PATH = '/dns/hosts';
// EingabenUeberpruefung.musterDomain, including its quirks: labels start with
// a letter, labels after the first may hold a backslash, and the empty
// string is a valid domain.
const JAVA_DOMAIN = /^([a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?)*)?\.?$/;
// EingabenUeberpruefung.musterIpAdresse.
const JAVA_IP_ADDRESS =
/^(0*([1-9]?[0-9]?|1[0-9]{1,2}|2[0-4]?[0-9]|25[0-5]))\.((0*(1?[0-9]{1,2}|2[0-4]?[0-9]|25[0-5]))\.){2}(0*(1?[0-9]{1,2}|2[0-4]?[0-9]|25[0-5]))$/;
/** `String.trim()`: every UTF-16 unit up to U+0020 off both ends. */
function javaTrim(text: string): string {
return text.replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g, '');
}
/**
* The content of /dns/hosts as `Dateisystem.holeDatei` finds it: the first
* node in preorder whose path equals it ignoring case, and only if that node
* is a file. Undefined when there is none; a file without content reads as
* empty (Java would fail on it).
*/
export function javaHostsFileContent(root: FlsFileTree | undefined): string | undefined {
if (root === undefined) return undefined;
const found = firstInPreorder(root, '', (path) => javaEqualsIgnoreCase(path, DNS_HOSTS_PATH));
return found?.kind === 'file' ? (found.content ?? '') : undefined;
}
/** Preorder below the root; a node's path is "/" plus each name after the root's. */
function firstInPreorder(node: FlsFileTree, path: string, matches: (path: string) => boolean): FlsFileTree | undefined {
for (const child of node.children) {
const childPath = `${path}/${child.kind === 'other' ? child.className : String(child.name)}`;
if (matches(childPath)) return child;
const found = firstInPreorder(child, childPath, matches);
if (found !== undefined) return found;
}
return undefined;
}
/**
* `String.equalsIgnoreCase` against an ASCII path, unit by unit: equal after
* Character.toUpperCase, so the long s (U+017F) is an "s". Java also
* compares the lower case of both; for the letters of /dns/hosts that adds
* no unit (measured over the BMP).
*/
function javaEqualsIgnoreCase(text: string, asciiPath: string): boolean {
if (text.length !== asciiPath.length) return false;
for (let index = 0; index < text.length; index++) {
if (text[index]!.toUpperCase() !== asciiPath[index]!.toUpperCase()) return false;
}
return true;
}
/** `DNSServer.leseRecordListe` on the content of /dns/hosts. */
export function readJavaDnsHosts(content: string): JavaResourceRecord[] {
const records: JavaResourceRecord[] = [];
for (const token of content.split('\n')) {
const line = javaTrim(token);
// line.split(" ", 5).length < 4: fewer than three single spaces.
if (line === '' || line.split(' ').length < 4) continue;
const record = javaResourceRecord(line);
if (isValidJavaRecord(record)) records.push(record);
}
return records;
}
/** `new ResourceRecord(line)`: up to four space-separated fields. */
function javaResourceRecord(line: string): JavaResourceRecord {
const fields = line.split(' ').filter((field) => field !== '').map(javaTrim);
let domainName = '';
let type = 'A';
let ttl = 3600;
let rdata = '';
if (fields[0] !== undefined && JAVA_DOMAIN.test(fields[0])) {
domainName = fields[0].endsWith('.') ? fields[0] : `${fields[0]}.`;
}
if (fields[1] !== undefined) type = fields[1];
if (fields[2] !== undefined) ttl = javaParseInt(fields[2]) ?? ttl;
if (fields[3] !== undefined) {
// Java tests ".*\." (no line terminator before the dot); a target with
// one fails the domain check anyway, so endsWith says the same.
const needsDot = (type === 'NS' || type === 'MX') && !fields[3].endsWith('.');
rdata = needsDot ? `${fields[3]}.` : fields[3];
}
return { domainName, type, ttl, rdata };
}
function isValidJavaRecord(record: JavaResourceRecord): boolean {
switch (record.type) {
case 'A':
return JAVA_DOMAIN.test(record.domainName) && JAVA_IP_ADDRESS.test(record.rdata);
case 'MX':
case 'NS':
return JAVA_DOMAIN.test(record.domainName) && JAVA_DOMAIN.test(record.rdata);
default:
return true;
}
}
/**
* The records Java answers with, in the model's form: Java looks them up by
* type (exactly "A", "MX", "NS") and by name ignoring case, so a record with
* the empty name or another type never answers and is left out. The file
* itself stays as it is.
*/
export function dnsRecordsOf(records: readonly JavaResourceRecord[], warn: (message: string) => void): DnsResourceRecord[] {
const answered: DnsResourceRecord[] = [];
for (const record of records) {
if (record.domainName === '') continue;
const name = record.domainName === '.' ? '.' : record.domainName.slice(0, -1).toLowerCase();
switch (record.type) {
case 'A': {
const address = javaIpCheck(record.rdata);
if (address === null) {
warn(`DNS record '${name} A ${record.rdata}' has no usable IPv4 address, left out (it stays in ${DNS_HOSTS_PATH})`);
continue;
}
answered.push({ name, type: 'A', ttlSeconds: record.ttl, target: address });
break;
}
case 'MX':
case 'NS':
answered.push({ name, type: record.type, ttlSeconds: record.ttl, target: record.rdata.toLowerCase() });
break;
default:
break;
}
}
return answered;
}
+11 -3
View File
@@ -33,7 +33,10 @@
// - a web server's files stay in /webserver (its document root), where Swift
// moves them to /www; and no program state is written into a host's files
// (Swift keeps Gnutella and e-mail state there as hidden JSON files).
// Not here yet: DNS records (from /dns/hosts) and e-mail accounts.
// - a DNS server's records are what Java's server answers from /dns/hosts
// (see dnsHosts.ts); Swift reads the file its own way — tabs, comments,
// "IN", names with a leading digit — and drops Java's duplicates.
// Not here yet: e-mail accounts.
import {
canonicalMacAddress,
@@ -88,6 +91,7 @@ import {
type TopologyNode,
type VirtualFileContent,
} from '@mdg-filius/core';
import { dnsRecordsOf, javaHostsFileContent, readJavaDnsHosts } from './dnsHosts.js';
import type { FlsApplicationClass, FlsFileTree, FlsFirewall } from './systemSoftware.js';
import type { FlsDocumentationItem, FlsInterface, FlsNode, FlsNodeClass, FlsTopology } from './topology.js';
@@ -415,11 +419,15 @@ function registerPrograms(source: FlsNode, node: TopologyNode, system: SystemCon
const forwarder = trimmedOrEmpty(source.interfaces[0]?.dns);
const primary = trimmedOrEmpty(source.interfaces[0]?.ip);
const forwarding = recursive && forwarder !== '' && forwarder !== primary;
const hosts = javaHostsFileContent(source.system.fileSystem);
const records = dnsRecordsOf(readJavaDnsHosts(hosts ?? ''), (message) =>
warnings.push(`node '${node.displayName}': ${message}`),
);
system.dnsServerConfigurations.set(
node.id,
forwarding
? { recursiveResolutionEnabled: recursive, forwardingServerIPAddress: forwarder }
: { recursiveResolutionEnabled: recursive },
? { recursiveResolutionEnabled: recursive, forwardingServerIPAddress: forwarder, records }
: { recursiveResolutionEnabled: recursive, records },
);
break;
}
+1
View File
@@ -1,5 +1,6 @@
// Public surface of the .fls codec.
export * from './archive.js';
export * from './dnsHosts.js';
export * from './importProject.js';
export * from './javaIp.js';
export * from './javabeans.js';
+76
View File
@@ -0,0 +1,76 @@
www.filius.de. A 3600 1.2.3.4\u000A
www.filius.de A 3600 1.2.3.4\u000A
www.filius.de A 3600 1.2.3.4
WWW.Filius.DE A 3600 1.2.3.4\u000A
www.filius.de a 3600 1.2.3.4\u000A
filius.de mx 3600 mail.filius.de\u000A
www.filius.de A 3600 1.2.3.4\u000A
www.filius.de\u0009A\u00093600\u00091.2.3.4\u000A
www.filius.de A 3600 1.2.3.4 \u000A
www.filius.de A 3600 1.2.3.4 # comment\u000A
# www.filius.de A 3600 1.2.3.4\u000A
www.filius.de A 3600 1.2.3.4#x\u000A
www.filius.de A IN 3600 1.2.3.4\u000A
www.filius.de 3600 IN A 1.2.3.4\u000A
www.filius.de A x 1.2.3.4\u000A
www.filius.de A -5 1.2.3.4\u000A
www.filius.de A 0 1.2.3.4\u000A
www.filius.de A +5 1.2.3.4\u000A
www.filius.de A 4294967295 1.2.3.4\u000A
www.filius.de A 99999999999 1.2.3.4\u000A
www.filius.de A 3600 010.0.0.1\u000A
www.filius.de A 3600 1.2.3\u000A
www.filius.de A 3600 256.0.0.1\u000A
www.filius.de A 3600 1.2.3.4.\u000A
www.filius.de A 3600 1.2.3.4 extra\u000A
www.filius.de A 3600\u000A
1und1.de A 3600 1.2.3.4\u000A
a_b.de A 3600 1.2.3.4\u000A
-a.de A 3600 1.2.3.4\u000A
a-.de A 3600 1.2.3.4\u000A
www..de A 3600 1.2.3.4\u000A
www.filius.de.. A 3600 1.2.3.4\u000A
www.1und1.de A 3600 1.2.3.4\u000A
www.a\u005Cb.de A 3600 1.2.3.4\u000A
m\u00FCller.de A 3600 1.2.3.4\u000A
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.de A 3600 1.2.3.4\u000A
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.de A 3600 1.2.3.4\u000A
localhost A 3600 127.0.0.1\u000A
. NS 3600 dns-root.\u000A
de NS 3600 dns.de\u000A
de. NS 3600 .\u000A
filius.de MX 3600 mail.filius.de\u000A
filius.de MX 3600 10 mail.filius.de\u000A
filius.de MX 3600 1.2.3.4\u000A
filius.de CNAME 3600 www.filius.de\u000A
filius.de TXT 3600 hello\u000A
www.filius.de A 3600 1.2.3.4\u000Awww.filius.de A 3600 1.2.3.4\u000A
www.filius.de A 3600 1.2.3.4\u000Awww.filius.de A 7200 1.2.3.4\u000A
www.filius.de A 3600 1.2.3.4\u000AWWW.filius.de. A 3600 1.2.3.4\u000A
www.filius.de A 3600 1.2.3.4\u000Awww.filius.de A 3600 1.2.3.5\u000A
www.filius.de A 3600 1.2.3.4\u000D\u000Amail.filius.de A 3600 1.2.3.5\u000D\u000A
www.filius.de A 3600 1.2.3.4\u000Dmail.filius.de A 3600 1.2.3.5\u000D
www.filius.de A 3600 1.2.3.4\u2028mail.filius.de A 3600 1.2.3.5\u000A
www.filius.de\u00A0A 3600 1.2.3.4\u000A
www.filius.de A 3600 1.2.3.4\u0085\u000A
\u000A\u000Awww.filius.de A 3600 1.2.3.4\u000A\u000A
filius.de. MX 3600 smtp.filius.de.\u000Adns-root. A 3600 141.99.5.13\u000A. NS 3600 dns-root.\u000A
www.filius.de A \u0661\u0660 1.2.3.4\u000A
www.filius.de A 2147483647 1.2.3.4\u000A
www.filius.de A -2147483648 1.2.3.4\u000A
www.filius.de A 2147483648 1.2.3.4\u000A
www.filius.de \u0009 A 3600 1.2.3.4\u000A
www.filius.de A \u0009 3600 1.2.3.4\u000A
www.filius.de A 3600 1.2.3.4 \u0009\u000A
www.filius.de A 3600 1.2.3.4\u000A
www.filius.de A 3600 1.2.3.4\u000A
www.filius.de A 3600 0.0.0.0\u000A
www.filius.de A 3600 00000000000000000001.2.3.4\u000A
filius.de NS 3600 dns..de\u000A
filius.de NS 3600 dns.filius.de..\u000A
filius.de. MX 3600 Mail.Filius.DE.\u000A
www.filius.de A 3600 .1.2.3\u000A
www.filius.de A 3600 1.2.3.4\u0001\u000A
www.filius.de A 3600 1.2.3.4\u00A0\u000A
\u001Fwww.filius.de A 3600 1.2.3.4\u000A
+754
View File
@@ -0,0 +1,754 @@
[
[
"www.filius.de. A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"WWW.Filius.DE A 3600 1.2.3.4\\u000A",
[
[
"WWW.Filius.DE.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de a 3600 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"a",
3600,
"1.2.3.4"
]
]
],
[
"filius.de mx 3600 mail.filius.de\\u000A",
[
[
"filius.de.",
"mx",
3600,
"mail.filius.de"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de\\u0009A\\u00093600\\u00091.2.3.4\\u000A",
[]
],
[
" www.filius.de A 3600 1.2.3.4 \\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4 # comment\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"# www.filius.de A 3600 1.2.3.4\\u000A",
[
[
"",
"www.filius.de",
3600,
"3600"
]
]
],
[
"www.filius.de A 3600 1.2.3.4#x\\u000A",
[]
],
[
"www.filius.de A IN 3600 1.2.3.4\\u000A",
[]
],
[
"www.filius.de 3600 IN A 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"3600",
3600,
"A"
]
]
],
[
"www.filius.de A x 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A -5 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
-5,
"1.2.3.4"
]
]
],
[
"www.filius.de A 0 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
0,
"1.2.3.4"
]
]
],
[
"www.filius.de A +5 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
5,
"1.2.3.4"
]
]
],
[
"www.filius.de A 4294967295 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 99999999999 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 010.0.0.1\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"010.0.0.1"
]
]
],
[
"www.filius.de A 3600 1.2.3\\u000A",
[]
],
[
"www.filius.de A 3600 256.0.0.1\\u000A",
[]
],
[
"www.filius.de A 3600 1.2.3.4.\\u000A",
[]
],
[
"www.filius.de A 3600 1.2.3.4 extra\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600\\u000A",
[]
],
[
"1und1.de A 3600 1.2.3.4\\u000A",
[
[
"",
"A",
3600,
"1.2.3.4"
]
]
],
[
"a_b.de A 3600 1.2.3.4\\u000A",
[
[
"",
"A",
3600,
"1.2.3.4"
]
]
],
[
"-a.de A 3600 1.2.3.4\\u000A",
[
[
"",
"A",
3600,
"1.2.3.4"
]
]
],
[
"a-.de A 3600 1.2.3.4\\u000A",
[
[
"",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www..de A 3600 1.2.3.4\\u000A",
[
[
"",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de.. A 3600 1.2.3.4\\u000A",
[
[
"",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.1und1.de A 3600 1.2.3.4\\u000A",
[
[
"",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.a\\u005Cb.de A 3600 1.2.3.4\\u000A",
[
[
"www.a\\b.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"m\\u00FCller.de A 3600 1.2.3.4\\u000A",
[
[
"",
"A",
3600,
"1.2.3.4"
]
]
],
[
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.de A 3600 1.2.3.4\\u000A",
[
[
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.de A 3600 1.2.3.4\\u000A",
[
[
"",
"A",
3600,
"1.2.3.4"
]
]
],
[
"localhost A 3600 127.0.0.1\\u000A",
[
[
"localhost.",
"A",
3600,
"127.0.0.1"
]
]
],
[
". NS 3600 dns-root.\\u000A",
[
[
".",
"NS",
3600,
"dns-root."
]
]
],
[
"de NS 3600 dns.de\\u000A",
[
[
"de.",
"NS",
3600,
"dns.de."
]
]
],
[
"de. NS 3600 .\\u000A",
[
[
"de.",
"NS",
3600,
"."
]
]
],
[
"filius.de MX 3600 mail.filius.de\\u000A",
[
[
"filius.de.",
"MX",
3600,
"mail.filius.de."
]
]
],
[
"filius.de MX 3600 10 mail.filius.de\\u000A",
[]
],
[
"filius.de MX 3600 1.2.3.4\\u000A",
[]
],
[
"filius.de CNAME 3600 www.filius.de\\u000A",
[
[
"filius.de.",
"CNAME",
3600,
"www.filius.de"
]
]
],
[
"filius.de TXT 3600 hello\\u000A",
[
[
"filius.de.",
"TXT",
3600,
"hello"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000Awww.filius.de A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
],
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000Awww.filius.de A 7200 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
],
[
"www.filius.de.",
"A",
7200,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000AWWW.filius.de. A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
],
[
"WWW.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000Awww.filius.de A 3600 1.2.3.5\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
],
[
"www.filius.de.",
"A",
3600,
"1.2.3.5"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000D\\u000Amail.filius.de A 3600 1.2.3.5\\u000D\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
],
[
"mail.filius.de.",
"A",
3600,
"1.2.3.5"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000Dmail.filius.de A 3600 1.2.3.5\\u000D",
[]
],
[
"www.filius.de A 3600 1.2.3.4\\u2028mail.filius.de A 3600 1.2.3.5\\u000A",
[]
],
[
"www.filius.de\\u00A0A 3600 1.2.3.4\\u000A",
[]
],
[
"www.filius.de A 3600 1.2.3.4\\u0085\\u000A",
[]
],
[
"",
[]
],
[
"\\u000A\\u000Awww.filius.de A 3600 1.2.3.4\\u000A\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"filius.de. MX 3600 smtp.filius.de.\\u000Adns-root. A 3600 141.99.5.13\\u000A. NS 3600 dns-root.\\u000A",
[
[
"filius.de.",
"MX",
3600,
"smtp.filius.de."
],
[
"dns-root.",
"A",
3600,
"141.99.5.13"
],
[
".",
"NS",
3600,
"dns-root."
]
]
],
[
"www.filius.de A \\u0661\\u0660 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
10,
"1.2.3.4"
]
]
],
[
"www.filius.de A 2147483647 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
2147483647,
"1.2.3.4"
]
]
],
[
"www.filius.de A -2147483648 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
-2147483648,
"1.2.3.4"
]
]
],
[
"www.filius.de A 2147483648 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de \\u0009 A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"",
3600,
"3600"
]
]
],
[
"www.filius.de A \\u0009 3600 1.2.3.4\\u000A",
[]
],
[
"www.filius.de A 3600 1.2.3.4 \\u0009\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 0.0.0.0\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"0.0.0.0"
]
]
],
[
"www.filius.de A 3600 00000000000000000001.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"00000000000000000001.2.3.4"
]
]
],
[
"filius.de NS 3600 dns..de\\u000A",
[]
],
[
"filius.de NS 3600 dns.filius.de..\\u000A",
[]
],
[
"filius.de. MX 3600 Mail.Filius.DE.\\u000A",
[
[
"filius.de.",
"MX",
3600,
"Mail.Filius.DE."
]
]
],
[
"www.filius.de A 3600 .1.2.3\\u000A",
[
[
"www.filius.de.",
"A",
3600,
".1.2.3"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u0001\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u00A0\\u000A",
[]
],
[
"\\u001Fwww.filius.de A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de.",
"A",
3600,
"1.2.3.4"
]
]
]
]
+749
View File
@@ -0,0 +1,749 @@
[
[
"www.filius.de. A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"WWW.Filius.DE A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de a 3600 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"filius.de mx 3600 mail.filius.de\\u000A",
[
[
"filius.de",
"MX",
3600,
"mail.filius.de."
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de\\u0009A\\u00093600\\u00091.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
" www.filius.de A 3600 1.2.3.4 \\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4 # comment\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"# www.filius.de A 3600 1.2.3.4\\u000A",
[
]
],
[
"www.filius.de A 3600 1.2.3.4#x\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A IN 3600 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de 3600 IN A 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A x 1.2.3.4\\u000A",
[
]
],
[
"www.filius.de A -5 1.2.3.4\\u000A",
[
]
],
[
"www.filius.de A 0 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
0,
"1.2.3.4"
]
]
],
[
"www.filius.de A +5 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
5,
"1.2.3.4"
]
]
],
[
"www.filius.de A 4294967295 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
4294967295,
"1.2.3.4"
]
]
],
[
"www.filius.de A 99999999999 1.2.3.4\\u000A",
[
]
],
[
"www.filius.de A 3600 010.0.0.1\\u000A",
[
[
"www.filius.de",
"A",
3600,
"10.0.0.1"
]
]
],
[
"www.filius.de A 3600 1.2.3\\u000A",
[
]
],
[
"www.filius.de A 3600 256.0.0.1\\u000A",
[
]
],
[
"www.filius.de A 3600 1.2.3.4.\\u000A",
[
]
],
[
"www.filius.de A 3600 1.2.3.4 extra\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600\\u000A",
[
]
],
[
"1und1.de A 3600 1.2.3.4\\u000A",
[
[
"1und1.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"a_b.de A 3600 1.2.3.4\\u000A",
[
]
],
[
"-a.de A 3600 1.2.3.4\\u000A",
[
]
],
[
"a-.de A 3600 1.2.3.4\\u000A",
[
]
],
[
"www..de A 3600 1.2.3.4\\u000A",
[
]
],
[
"www.filius.de.. A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.1und1.de A 3600 1.2.3.4\\u000A",
[
[
"www.1und1.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.a\\u005Cb.de A 3600 1.2.3.4\\u000A",
[
]
],
[
"m\\u00FCller.de A 3600 1.2.3.4\\u000A",
[
]
],
[
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.de A 3600 1.2.3.4\\u000A",
[
[
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.de A 3600 1.2.3.4\\u000A",
[
]
],
[
"localhost A 3600 127.0.0.1\\u000A",
[
[
"localhost",
"A",
3600,
"127.0.0.1"
]
]
],
[
". NS 3600 dns-root.\\u000A",
[
[
".",
"NS",
3600,
"dns-root."
]
]
],
[
"de NS 3600 dns.de\\u000A",
[
[
"de",
"NS",
3600,
"dns.de."
]
]
],
[
"de. NS 3600 .\\u000A",
[
]
],
[
"filius.de MX 3600 mail.filius.de\\u000A",
[
[
"filius.de",
"MX",
3600,
"mail.filius.de."
]
]
],
[
"filius.de MX 3600 10 mail.filius.de\\u000A",
[
[
"filius.de",
"MX",
3600,
"10."
]
]
],
[
"filius.de MX 3600 1.2.3.4\\u000A",
[
[
"filius.de",
"MX",
3600,
"1.2.3.4."
]
]
],
[
"filius.de CNAME 3600 www.filius.de\\u000A",
[
]
],
[
"filius.de TXT 3600 hello\\u000A",
[
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000Awww.filius.de A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000Awww.filius.de A 7200 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000AWWW.filius.de. A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000Awww.filius.de A 3600 1.2.3.5\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
],
[
"www.filius.de",
"A",
3600,
"1.2.3.5"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000D\\u000Amail.filius.de A 3600 1.2.3.5\\u000D\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
],
[
"mail.filius.de",
"A",
3600,
"1.2.3.5"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000Dmail.filius.de A 3600 1.2.3.5\\u000D",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
],
[
"mail.filius.de",
"A",
3600,
"1.2.3.5"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u2028mail.filius.de A 3600 1.2.3.5\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
],
[
"mail.filius.de",
"A",
3600,
"1.2.3.5"
]
]
],
[
"www.filius.de\\u00A0A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u0085\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"",
[
]
],
[
"\\u000A\\u000Awww.filius.de A 3600 1.2.3.4\\u000A\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"filius.de. MX 3600 smtp.filius.de.\\u000Adns-root. A 3600 141.99.5.13\\u000A. NS 3600 dns-root.\\u000A",
[
[
"filius.de",
"MX",
3600,
"smtp.filius.de."
],
[
"dns-root",
"A",
3600,
"141.99.5.13"
],
[
".",
"NS",
3600,
"dns-root."
]
]
],
[
"www.filius.de A \\u0661\\u0660 1.2.3.4\\u000A",
[
]
],
[
"www.filius.de A 2147483647 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
2147483647,
"1.2.3.4"
]
]
],
[
"www.filius.de A -2147483648 1.2.3.4\\u000A",
[
]
],
[
"www.filius.de A 2147483648 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
2147483648,
"1.2.3.4"
]
]
],
[
"www.filius.de \\u0009 A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A \\u0009 3600 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4 \\u0009\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 1.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"www.filius.de A 3600 0.0.0.0\\u000A",
[
[
"www.filius.de",
"A",
3600,
"0.0.0.0"
]
]
],
[
"www.filius.de A 3600 00000000000000000001.2.3.4\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"filius.de NS 3600 dns..de\\u000A",
[
]
],
[
"filius.de NS 3600 dns.filius.de..\\u000A",
[
[
"filius.de",
"NS",
3600,
"dns.filius.de."
]
]
],
[
"filius.de. MX 3600 Mail.Filius.DE.\\u000A",
[
[
"filius.de",
"MX",
3600,
"mail.filius.de."
]
]
],
[
"www.filius.de A 3600 .1.2.3\\u000A",
[
]
],
[
"www.filius.de A 3600 1.2.3.4\\u0001\\u000A",
[
]
],
[
"www.filius.de A 3600 1.2.3.4\\u00A0\\u000A",
[
[
"www.filius.de",
"A",
3600,
"1.2.3.4"
]
]
],
[
"\\u001Fwww.filius.de A 3600 1.2.3.4\\u000A",
[
]
]
]
+209
View File
@@ -0,0 +1,209 @@
// Java's reading of /dns/hosts (DNSServer.leseRecordListe): every case in
// dns-hosts.cases.txt was run through Java (dns-hosts.java.json) and through
// Swift's hosts-file codec (dns-hosts.swift.json, tools/swift-oracle). The
// Java view must match Java exactly; the records the model keeps must match
// Swift except where Java answers differently — each case named below.
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import type { FlsFileTree } from '../src/index.js';
import { dnsRecordsOf, javaHostsFileContent, readJavaDnsHosts } from '../src/dnsHosts.js';
const here = dirname(fileURLToPath(import.meta.url));
type Row = [string, string, number, string];
const javaMeasured = JSON.parse(readFileSync(join(here, 'dns-hosts.java.json'), 'utf8')) as [string, Row[]][];
const swiftMeasured = JSON.parse(readFileSync(join(here, 'dns-hosts.swift.json'), 'utf8')) as [string, Row[]][];
/** The cases file's escapes: a backslash, `u` and four hex digits for one UTF-16 unit. */
function unescape(line: string): string {
return line.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex: string) => String.fromCharCode(Number.parseInt(hex, 16)));
}
function modelRows(line: string, warnings: string[] = []): Row[] {
return dnsRecordsOf(readJavaDnsHosts(unescape(line)), (message) => warnings.push(message)).map((record) => [
record.name,
record.type,
record.ttlSeconds,
record.target,
]);
}
/** Lines Swift reads as records that Java never answers. */
const SWIFT_READS_JAVA_NEVER_ANSWERS: { reason: string; lines: string[] }[] = [
{
reason: 'a type in lower case is another type in Java, which never answers',
lines: [
String.raw`www.filius.de a 3600 1.2.3.4\u000A`,
String.raw`filius.de mx 3600 mail.filius.de\u000A`,
],
},
{
reason: 'Java splits fields at single spaces only: a tab or NBSP is part of a field',
lines: [
String.raw`www.filius.de\u0009A\u00093600\u00091.2.3.4\u000A`,
String.raw`www.filius.de\u00A0A 3600 1.2.3.4\u000A`,
String.raw`www.filius.de A 3600 1.2.3.4\u00A0\u000A`,
String.raw`www.filius.de \u0009 A 3600 1.2.3.4\u000A`,
String.raw`www.filius.de A \u0009 3600 1.2.3.4\u000A`,
],
},
{
reason: 'Java splits lines at LF only: CR, LS and NEL stay in the line',
lines: [
String.raw`www.filius.de A 3600 1.2.3.4\u000Dmail.filius.de A 3600 1.2.3.5\u000D`,
String.raw`www.filius.de A 3600 1.2.3.4\u2028mail.filius.de A 3600 1.2.3.5\u000A`,
String.raw`www.filius.de A 3600 1.2.3.4\u0085\u000A`,
],
},
{
reason: 'Java has no comments, no "IN" and no zone-file order',
lines: [
String.raw`www.filius.de A 3600 1.2.3.4#x\u000A`,
String.raw`www.filius.de A IN 3600 1.2.3.4\u000A`,
String.raw`www.filius.de 3600 IN A 1.2.3.4\u000A`,
],
},
{
reason: 'a label that starts with a digit, or two final dots, make the name empty in Java',
lines: [
String.raw`1und1.de A 3600 1.2.3.4\u000A`,
String.raw`www.1und1.de A 3600 1.2.3.4\u000A`,
String.raw`www.filius.de.. A 3600 1.2.3.4\u000A`,
],
},
{
reason: 'an MX or NS target must be a name Java accepts: no preference, no address, one final dot',
lines: [
String.raw`filius.de MX 3600 10 mail.filius.de\u000A`,
String.raw`filius.de MX 3600 1.2.3.4\u000A`,
String.raw`filius.de NS 3600 dns.filius.de..\u000A`,
],
},
];
/** Lines Java answers that Swift drops or reads otherwise. */
const JAVA_ANSWERS_SWIFT_DROPS: { reason: string; lines: string[] }[] = [
{
reason: 'a TTL that is no Java int is 3600',
lines: [
String.raw`www.filius.de A x 1.2.3.4\u000A`,
String.raw`www.filius.de A 4294967295 1.2.3.4\u000A`,
String.raw`www.filius.de A 99999999999 1.2.3.4\u000A`,
String.raw`www.filius.de A 2147483648 1.2.3.4\u000A`,
],
},
{
reason: 'a TTL is Java\'s Integer.parseInt: negative, and in any script\'s digits',
lines: [
String.raw`www.filius.de A -5 1.2.3.4\u000A`,
String.raw`www.filius.de A -2147483648 1.2.3.4\u000A`,
String.raw`www.filius.de A \u0661\u0660 1.2.3.4\u000A`,
],
},
{
reason: 'labels after the first may hold a backslash (Java\'s domain pattern)',
lines: [
String.raw`www.a\u005Cb.de A 3600 1.2.3.4\u000A`,
],
},
{
reason: 'Java trims control characters off a line (String.trim)',
lines: [
String.raw`www.filius.de A 3600 1.2.3.4\u0001\u000A`,
String.raw`\u001Fwww.filius.de A 3600 1.2.3.4\u000A`,
],
},
{
reason: 'an NS record may name the root',
lines: [
String.raw`de. NS 3600 .\u000A`,
],
},
{
reason: 'duplicates stay, as in Java\'s list',
lines: [
String.raw`www.filius.de A 3600 1.2.3.4\u000Awww.filius.de A 3600 1.2.3.4\u000A`,
String.raw`www.filius.de A 3600 1.2.3.4\u000Awww.filius.de A 7200 1.2.3.4\u000A`,
String.raw`www.filius.de A 3600 1.2.3.4\u000AWWW.filius.de. A 3600 1.2.3.4\u000A`,
],
},
];
const departures = new Map(
[...SWIFT_READS_JAVA_NEVER_ANSWERS, ...JAVA_ANSWERS_SWIFT_DROPS].flatMap(({ reason, lines }) =>
lines.map((line) => [line, reason] as const),
),
);
describe('readJavaDnsHosts against Java', () => {
it('covers every line of the cases file, in both oracles', () => {
const lines = readFileSync(join(here, 'dns-hosts.cases.txt'), 'ascii').split('\n').slice(0, -1);
expect(javaMeasured.map(([line]) => line)).toEqual(lines);
expect(swiftMeasured.map(([line]) => line)).toEqual(lines);
});
for (const [line, expected] of javaMeasured) {
it(JSON.stringify(line), () => {
const records = readJavaDnsHosts(unescape(line));
expect(records.map((record) => [record.domainName, record.type, record.ttl, record.rdata])).toEqual(expected);
});
}
});
describe('the records the model keeps, against Swift', () => {
it('names only cases of the cases file as departures', () => {
const lines = new Set(swiftMeasured.map(([line]) => line));
expect([...departures.keys()].filter((line) => !lines.has(line))).toEqual([]);
});
for (const [line, swift] of swiftMeasured) {
const reason = departures.get(line);
it(reason === undefined ? JSON.stringify(line) : `${JSON.stringify(line)} (departs: ${reason})`, () => {
if (reason === undefined) expect(modelRows(line)).toEqual(swift);
else expect(modelRows(line)).not.toEqual(swift);
});
}
});
describe('the records Java answers', () => {
it('leave out records Java never answers: an empty name, or a type other than A, MX, NS', () => {
expect(modelRows(String.raw`1und1.de A 3600 1.2.3.4\u000Afilius.de CNAME 3600 www.filius.de\u000Afilius.de a 3600 1.2.3.4`)).toEqual([]);
});
it('take names in lower case without the final dot, A targets as FILIUS normalises addresses', () => {
expect(modelRows(String.raw`WWW.Filius.DE. A 60 010.000.0.1\u000Afilius.de MX 3600 Mail.Filius.DE\u000A. NS 3600 dns-root`)).toEqual([
['www.filius.de', 'A', 60, '10.0.0.1'],
['filius.de', 'MX', 3600, 'mail.filius.de.'],
['.', 'NS', 3600, 'dns-root.'],
]);
});
it('warn about an A record Java answers with an address FILIUS cannot use', () => {
const warnings: string[] = [];
expect(modelRows(String.raw`www.filius.de A 3600 .1.2.3`, warnings)).toEqual([]);
expect(warnings).toEqual(["DNS record 'www.filius.de A .1.2.3' has no usable IPv4 address, left out (it stays in /dns/hosts)"]);
});
});
describe('javaHostsFileContent', () => {
const file = (name: string, content: string | null): FlsFileTree => ({ kind: 'file', name, type: 'text', content, children: [] });
const directory = (name: string, ...children: FlsFileTree[]): FlsFileTree => ({ kind: 'directory', name, children });
it('finds /dns/hosts ignoring case, as Java does', () => {
expect(javaHostsFileContent(directory('root', directory('DNS', file('Hosts', 'x'))))).toBe('x');
expect(javaHostsFileContent(directory('root', directory('dns', file('ho\u017Fts', 'long s'))))).toBe('long s');
});
it('takes the first match in preorder, and nothing when that is a directory', () => {
expect(javaHostsFileContent(directory('root', directory('dns', file('hosts', 'first')), directory('DNS', file('hosts', 'second'))))).toBe('first');
expect(javaHostsFileContent(directory('root', directory('dns', directory('hosts'), file('HOSTS', 'later'))))).toBeUndefined();
});
it('is undefined without the file, and empty for a file without content', () => {
expect(javaHostsFileContent(undefined)).toBeUndefined();
expect(javaHostsFileContent(directory('root', file('hosts', 'x')))).toBeUndefined();
expect(javaHostsFileContent(directory('root', directory('dns', file('hosts', null))))).toBe('');
});
});
+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({