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:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
// Java's DNS server keeps its records in the text file /dns/hosts and reads
|
||||
// them on every query (DNSServer.leseRecordListe, ResourceRecord(String)) —
|
||||
// so does the engine's (moved here from packages/fls, which reads the file
|
||||
// of a project with it, in F4.6g2).
|
||||
// Measured on packages/fls/test/dns-hosts.cases.txt against Java (dns-hosts.java.json)
|
||||
// and Swift (dns-hosts.swift.json). Java's format decides where the two
|
||||
// differ:
|
||||
// - 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 number silently becomes 3600;
|
||||
// - a name that is not a domain name becomes the empty name, and the record
|
||||
// still counts - it just never answers, like one of another type;
|
||||
// - duplicates stay.
|
||||
// Java's errors are not copied (each named in dnsHosts.test.ts): a label may
|
||||
// start with a digit and holds no backslash (RFC 1123 - Java's pattern has
|
||||
// both wrong; see isHostsDomainName), the type A, MX or NS is read in any case
|
||||
// (RFC 4343), and a negative TTL is invalid (RFC 2181).
|
||||
|
||||
import type { DnsRecordType, DnsResourceRecord } from './config.js';
|
||||
import { javaIpCheck, javaParseInt } from '../util/javaIp.js';
|
||||
import { javaTrim } from './http.js';
|
||||
|
||||
/** A record of /dns/hosts, as Java's ResourceRecord holds it (the getters). */
|
||||
export interface HostsRecord {
|
||||
readonly domainName: string;
|
||||
readonly type: string;
|
||||
readonly ttl: number;
|
||||
readonly rdata: string;
|
||||
}
|
||||
|
||||
export const DNS_HOSTS_PATH = '/dns/hosts';
|
||||
|
||||
// Labels of letters, digits and inner hyphens, an optional final dot; the
|
||||
// empty string counts too.
|
||||
const DOMAIN_LABELS = /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)?\.?$/;
|
||||
/**
|
||||
* A domain name as EingabenUeberpruefung.musterDomain means it (the empty
|
||||
* string included). Java's pattern wants every label to start with a letter
|
||||
* and lets a backslash into the labels after the first; instead a label may
|
||||
* start with a digit (RFC 1123), but the last one must not be all digits
|
||||
* (RFC 3696), so that no IPv4 address passes for a name.
|
||||
*/
|
||||
export function isHostsDomainName(text: string): boolean {
|
||||
if (!DOMAIN_LABELS.test(text)) return false;
|
||||
const labels = text.split('.').filter((label) => label !== '');
|
||||
return labels.length === 0 || !/^[0-9]+$/.test(labels[labels.length - 1]!);
|
||||
}
|
||||
|
||||
// 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]))$/;
|
||||
|
||||
const ANSWERED_TYPES = new Set(['A', 'MX', 'NS']);
|
||||
|
||||
/** `DNSServer.leseRecordListe` on the content of /dns/hosts. */
|
||||
export function readDnsHosts(content: string): HostsRecord[] {
|
||||
const records: HostsRecord[] = [];
|
||||
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 = hostsRecord(line);
|
||||
if (isValidRecord(record)) records.push(record);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
/** `new ResourceRecord(line)`: up to four space-separated fields. */
|
||||
function hostsRecord(line: string): HostsRecord {
|
||||
const fields = line
|
||||
.split(' ')
|
||||
.filter((field) => field !== '')
|
||||
.map(javaTrim);
|
||||
let domainName = '';
|
||||
let type = 'A';
|
||||
let ttl = 3600;
|
||||
let rdata = '';
|
||||
if (fields[0] !== undefined && isHostsDomainName(fields[0])) {
|
||||
domainName = fields[0].endsWith('.') ? fields[0] : `${fields[0]}.`;
|
||||
}
|
||||
if (fields[1] !== undefined) {
|
||||
const upper = fields[1].replace(/[a-z]/g, (letter) => letter.toUpperCase());
|
||||
type = ANSWERED_TYPES.has(upper) ? upper : fields[1];
|
||||
}
|
||||
if (fields[2] !== undefined) {
|
||||
const parsed = javaParseInt(fields[2]);
|
||||
if (parsed !== undefined && parsed >= 0) ttl = parsed;
|
||||
}
|
||||
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 isValidRecord(record: HostsRecord): boolean {
|
||||
switch (record.type) {
|
||||
case 'A':
|
||||
return isHostsDomainName(record.domainName) && JAVA_IP_ADDRESS.test(record.rdata);
|
||||
case 'MX':
|
||||
case 'NS':
|
||||
return isHostsDomainName(record.domainName) && isHostsDomainName(record.rdata);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The records Java answers with, in the model's form: Java looks them up by
|
||||
* type (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 HostsRecord[], 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* A new record as Java's window makes it (new ResourceRecord(name, type,
|
||||
* rdata)): the name with its final dot, an MX's or NS's target too, TTL 3600.
|
||||
*/
|
||||
export function newHostsRecord(domainName: string, type: DnsRecordType, rdata: string): HostsRecord {
|
||||
const dotted = (text: string) => (text.endsWith('.') ? text : `${text}.`);
|
||||
return { domainName: dotted(domainName), type, ttl: 3600, rdata: type === 'A' ? rdata : dotted(rdata) };
|
||||
}
|
||||
|
||||
/** The records as DNSServer.schreibeRecordListe writes them: ResourceRecord.toString, a line each. */
|
||||
export function dnsHostsText(records: readonly HostsRecord[]): string {
|
||||
return records.map((record) => `${record.domainName} ${record.type} ${record.ttl} ${record.rdata}\n`).join('');
|
||||
}
|
||||
|
||||
/** A model's records as lines of /dns/hosts (for a server without the file). */
|
||||
export function hostsRecordsOf(records: readonly DnsResourceRecord[]): HostsRecord[] {
|
||||
return records.filter((record) => record.type === 'A' || record.type === 'MX' || record.type === 'NS').map((record) => newHostsRecord(record.name, record.type, record.target));
|
||||
}
|
||||
@@ -112,11 +112,12 @@ import { PacketCapture, PRODUCTION_CAPTURE_RETENTION, type CaptureRetentionPolic
|
||||
import { ICMP_TYPE_CODE } from './captureRows.js';
|
||||
import { JavaCompatibleRandom } from './random.js';
|
||||
import { routeTableRows, type RouteRow } from './routeTable.js';
|
||||
import { EMPTY_TOPOLOGY_SNAPSHOT, withSnapshot, type NodeSnapshot, type TopologySnapshot } from './snapshot.js';
|
||||
import { EMPTY_TOPOLOGY_SNAPSHOT, withSnapshot, type DnsServerSnapshot, type NodeSnapshot, type TopologySnapshot } from './snapshot.js';
|
||||
import { installationFiles } from './apps/installation.js';
|
||||
import { RuntimeSpeed } from './speed.js';
|
||||
import { ApplicationThreads, waitUntil, type Steps, type Wait } from './threads.js';
|
||||
import { EchoServer, SimpleClient } from './apps/echo.js';
|
||||
import { DnsServer } from './apps/dnsServer.js';
|
||||
import { WebBrowser, WebServer, type WebServerPage } from './apps/web.js';
|
||||
import { EmailClient, type EmailClientFolders } from './apps/emailClient.js';
|
||||
import { EmailServer } from './apps/emailServer.js';
|
||||
@@ -934,6 +935,7 @@ export class NetworkRuntimeEngine {
|
||||
/** The programs, by node (initializePrograms). */
|
||||
private readonly echoServersByNodeId = new Map<Id, EchoServer>();
|
||||
private readonly simpleClientsByNodeId = new Map<Id, SimpleClient>();
|
||||
private readonly dnsServersByNodeId = new Map<Id, DnsServer>();
|
||||
private readonly webServersByNodeId = new Map<Id, WebServer>();
|
||||
private readonly webBrowsersByNodeId = new Map<Id, WebBrowser>();
|
||||
private readonly emailServersByNodeId = new Map<Id, EmailServer>();
|
||||
@@ -1151,6 +1153,11 @@ export class NetworkRuntimeEngine {
|
||||
}
|
||||
|
||||
/** The node's generic client while the simulation runs, if it has one installed. */
|
||||
/** A node's DNS server while the simulation runs (installed, running or not). */
|
||||
dnsServer(nodeId: Id): DnsServer | undefined {
|
||||
return this.dnsServersByNodeId.get(nodeId);
|
||||
}
|
||||
|
||||
simpleClient(nodeId: Id): SimpleClient | undefined {
|
||||
return this.simpleClientsByNodeId.get(nodeId);
|
||||
}
|
||||
@@ -1381,6 +1388,8 @@ export class NetworkRuntimeEngine {
|
||||
firewallConfigurationsByNodeId: program === 'firewall' ? copied(snapshot.firewallConfigurationsByNodeId, source.firewallConfigurationsByNodeId) : snapshot.firewallConfigurationsByNodeId,
|
||||
});
|
||||
if (running !== undefined && this.hasProgram(nodeId, running)) this.startProgram(nodeId, running);
|
||||
const dns = program === 'dnsServer' ? state.topologySnapshot.dnsServerConfigurationsByNodeId.get(nodeId) : undefined;
|
||||
if (dns !== undefined) this.startDnsServer(nodeId, dns);
|
||||
this.applicationThreads.resume();
|
||||
}
|
||||
|
||||
@@ -1432,6 +1441,8 @@ export class NetworkRuntimeEngine {
|
||||
state.topologySnapshot = withSnapshot(snapshot, { gnutellaByNodeId: without(snapshot.gnutellaByNodeId) });
|
||||
break;
|
||||
case 'dnsServer':
|
||||
this.dnsServersByNodeId.get(nodeId)?.setActive(false);
|
||||
this.dnsServersByNodeId.delete(nodeId);
|
||||
state.topologySnapshot = withSnapshot(snapshot, { dnsServerConfigurationsByNodeId: without(snapshot.dnsServerConfigurationsByNodeId) });
|
||||
break;
|
||||
case 'firewall':
|
||||
@@ -2005,14 +2016,34 @@ export class NetworkRuntimeEngine {
|
||||
|
||||
// ── DNS (after Java's DNSServer and Resolver) ────────────────────────────
|
||||
|
||||
/** Every DNS server switched on listens on port 53. */
|
||||
/** Each DNS server installed; the ones switched on listen on port 53. */
|
||||
private initializeDnsState(): void {
|
||||
const state = this.stateValue;
|
||||
state.dnsServerSocketIdsByNodeId.clear();
|
||||
for (const [nodeId, configuration] of state.topologySnapshot.dnsServerConfigurationsByNodeId) {
|
||||
const socketId = this.bindUdpSocket(nodeId, { localPort: DNS_PORT });
|
||||
if (socketId !== undefined) state.dnsServerSocketIdsByNodeId.set(nodeId, socketId);
|
||||
}
|
||||
this.dnsServersByNodeId.clear();
|
||||
for (const [nodeId, configuration] of state.topologySnapshot.dnsServerConfigurationsByNodeId) this.startDnsServer(nodeId, configuration);
|
||||
}
|
||||
|
||||
private startDnsServer(nodeId: Id, configuration: DnsServerSnapshot): void {
|
||||
const server = new DnsServer(
|
||||
{
|
||||
files: () => this.stateValue.topologySnapshot.fileSystemsByNodeId.get(nodeId),
|
||||
filesToWrite: () => this.nodeFiles(nodeId),
|
||||
open: () => {
|
||||
const socketId = this.bindUdpSocket(nodeId, { localPort: DNS_PORT });
|
||||
if (socketId !== undefined) this.stateValue.dnsServerSocketIdsByNodeId.set(nodeId, socketId);
|
||||
},
|
||||
close: () => {
|
||||
const socketId = this.stateValue.dnsServerSocketIdsByNodeId.get(nodeId);
|
||||
if (socketId !== undefined) this.closeSocket(socketId);
|
||||
this.stateValue.dnsServerSocketIdsByNodeId.delete(nodeId);
|
||||
},
|
||||
},
|
||||
configuration,
|
||||
false,
|
||||
);
|
||||
this.dnsServersByNodeId.set(nodeId, server);
|
||||
server.setActive(configuration.isActive !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2064,10 +2095,10 @@ export class NetworkRuntimeEngine {
|
||||
* (the NS record of the question's zone and its address).
|
||||
*/
|
||||
private answerDnsQuestion(nodeId: Id, question: DnsQuestion, done: (answers: DnsMessageRecord[]) => void): void {
|
||||
const configuration = this.stateValue.topologySnapshot.dnsServerConfigurationsByNodeId.get(nodeId);
|
||||
const records = (configuration?.records ?? []).map(messageRecord);
|
||||
const server = this.dnsServersByNodeId.get(nodeId);
|
||||
const records = (server?.answering ?? []).map(messageRecord);
|
||||
const local = localDnsAnswers(question, records);
|
||||
if (local.length > 0 || configuration?.recursiveResolutionEnabled !== true) {
|
||||
if (local.length > 0 || server?.recursiveResolutionEnabled !== true) {
|
||||
done(local.length > 0 ? local : dnsReferral(question, records));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -75,6 +75,9 @@ export interface LinkSnapshot {
|
||||
readonly targetPortId: Id;
|
||||
}
|
||||
|
||||
/** A DNS server as the simulation starts it: its settings, and whether it runs (when not said, it does). */
|
||||
export type DnsServerSnapshot = DnsServerConfiguration & { readonly isActive?: boolean };
|
||||
|
||||
export interface TopologySnapshot {
|
||||
/** Sorted by id (Swift `uuidString` order). */
|
||||
readonly nodes: readonly NodeSnapshot[];
|
||||
@@ -87,7 +90,7 @@ export interface TopologySnapshot {
|
||||
readonly dhcpClientConfigurationsByNodeId: ReadonlyMap<Id, DhcpClientConfiguration>;
|
||||
readonly dhcpServerConfigurationsByNodeId: ReadonlyMap<Id, DhcpServerConfiguration>;
|
||||
/** The DNS servers switched on (Java: aktiv), by node. */
|
||||
readonly dnsServerConfigurationsByNodeId: ReadonlyMap<Id, DnsServerConfiguration>;
|
||||
readonly dnsServerConfigurationsByNodeId: ReadonlyMap<Id, DnsServerSnapshot>;
|
||||
readonly firewallConfigurationsByNodeId: ReadonlyMap<Id, FirewallConfiguration>;
|
||||
/** A home router's port forwarding (Java GatewayFirmware.staticNAT), by node. */
|
||||
readonly portForwardingRowsByNodeId: ReadonlyMap<Id, readonly GatewayPortForwardingRow[]>;
|
||||
@@ -125,7 +128,7 @@ export interface TopologySnapshotInput {
|
||||
ripEnabledByNodeId?: ReadonlyMap<Id, boolean>;
|
||||
dhcpClientConfigurationsByNodeId?: ReadonlyMap<Id, DhcpClientConfiguration>;
|
||||
dhcpServerConfigurationsByNodeId?: ReadonlyMap<Id, DhcpServerConfiguration>;
|
||||
dnsServerConfigurationsByNodeId?: ReadonlyMap<Id, DnsServerConfiguration>;
|
||||
dnsServerConfigurationsByNodeId?: ReadonlyMap<Id, DnsServerSnapshot>;
|
||||
firewallConfigurationsByNodeId?: ReadonlyMap<Id, FirewallConfiguration>;
|
||||
portForwardingRowsByNodeId?: ReadonlyMap<Id, readonly GatewayPortForwardingRow[]>;
|
||||
switchConfigurationsByNodeId?: ReadonlyMap<Id, SwitchConfiguration>;
|
||||
|
||||
Reference in New Issue
Block a user