feat(editor): F4.5d the firewall in the device sheet
Measured first: SheetRun drives FILIUS' firewall dialog down to its table editor (Swing's own end of an edit). Java's table writes a cell into the rule before it checks it, so that the firewall holds "abc", a mask of 255.0.255.0 or port 0 while the table shows the old value, and reads a port that is no number as all ports. Here nothing the check refuses reaches the rule. - doc: the three switches, the default action, rules (new as Java's, cells by Java's checks: masks of ones then zeros, the directly connected marker as a source only, an empty port for all ports; protocol, action, moved up or down, removed) - editor: the firewall as a page of the router's and home router's sheet, with Java's explanations; the general tab named so (Java: "network interfaces") Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,14 +24,22 @@
|
||||
// second static assignment of a MAC address without a word, and that a home
|
||||
// router keeps any port forwarding row, valid or not (its firmware then
|
||||
// forwards only the valid ones): here a value passes its rule or is not
|
||||
// taken, and the sheet says why.
|
||||
// taken, and the sheet says why. Nor that Java's firewall table writes a
|
||||
// cell into the rule before it checks it, so that a rule holds "abc" or a
|
||||
// mask of 255.0.255.0 while the table shows the old value, and reads a port
|
||||
// that is no number as "all ports".
|
||||
|
||||
import * as Y from 'yjs';
|
||||
import {
|
||||
DEFAULT_REMOTE_LINK_LATENCY_MILLISECONDS,
|
||||
defaultSwitchConfiguration,
|
||||
ALL_PORTS,
|
||||
dhcpServerConfiguration,
|
||||
firewallConfiguration,
|
||||
firewallRule,
|
||||
forwardsIPv4ByDefault,
|
||||
type FirewallAction,
|
||||
type FirewallProtocol,
|
||||
type DhcpStaticAssignment,
|
||||
type GatewayPortForwardingRow,
|
||||
type Id,
|
||||
@@ -42,7 +50,7 @@ import { modemPair } from '@mdg-filius/fls';
|
||||
import { connect, type ConnectError } from './commands.js';
|
||||
import { checkField, type CheckedField, type FieldRule } from './fields.js';
|
||||
import { inOrder, ROOTS } from './projectDoc.js';
|
||||
import { toY } from './values.js';
|
||||
import { fromY, toY, type PlainValue } from './values.js';
|
||||
|
||||
const nodes = (doc: Y.Doc) => doc.getMap<Y.Map<unknown>>(ROOTS.nodes);
|
||||
const links = (doc: Y.Doc) => doc.getMap<Y.Map<unknown>>(ROOTS.links);
|
||||
@@ -481,3 +489,98 @@ export function removePortForwarding(doc: Y.Doc, id: Id, index: number, origin:
|
||||
if (list.length === 0) settingsOf(node).delete('portForwarding');
|
||||
}, origin);
|
||||
}
|
||||
|
||||
/** A router's or home router's firewall, made as Java's constructor leaves it when the device has none in the document. */
|
||||
function firewallOf(node: Y.Map<unknown>): Y.Map<unknown> {
|
||||
let firewall = settingsOf(node).get('firewall') as Y.Map<unknown> | undefined;
|
||||
if (firewall === undefined) {
|
||||
firewall = toY({ ...firewallConfiguration(), rules: [] }) as Y.Map<unknown>;
|
||||
settingsOf(node).set('firewall', firewall);
|
||||
}
|
||||
return firewall;
|
||||
}
|
||||
|
||||
const rulesOf = (node: Y.Map<unknown>) => {
|
||||
const firewall = firewallOf(node);
|
||||
let rules = firewall.get('rules') as Y.Array<Y.Map<unknown>> | undefined;
|
||||
if (rules === undefined) {
|
||||
rules = new Y.Array<Y.Map<unknown>>();
|
||||
firewall.set('rules', rules);
|
||||
}
|
||||
return rules;
|
||||
};
|
||||
|
||||
/** The firewall switched on, dropping ICMP, or dropping only SYN segments (JFirewallDialog's general tab). */
|
||||
export function setFirewallFlag(doc: Y.Doc, id: Id, flag: 'isActive' | 'dropIcmp' | 'filterSynSegmentsOnly', on: boolean, origin: unknown): void {
|
||||
const node = nodes(doc).get(id);
|
||||
if (node !== undefined) doc.transact(() => firewallOf(node).set(flag, on), origin);
|
||||
}
|
||||
|
||||
/** What the firewall does when no rule matches. */
|
||||
export function setFirewallDefaultPolicy(doc: Y.Doc, id: Id, policy: FirewallAction, origin: unknown): void {
|
||||
const node = nodes(doc).get(id);
|
||||
if (node !== undefined) doc.transact(() => firewallOf(node).set('defaultPolicy', policy), origin);
|
||||
}
|
||||
|
||||
/** A new rule at the end, as Java's makes it (Firewall.addRule: every field empty, TCP, all ports, drop); its place. */
|
||||
export function addFirewallRule(doc: Y.Doc, id: Id, origin: unknown): number | undefined {
|
||||
const node = nodes(doc).get(id);
|
||||
if (node === undefined) return undefined;
|
||||
let place = 0;
|
||||
doc.transact(() => {
|
||||
const rules = rulesOf(node);
|
||||
rules.push([toY({ ...firewallRule() }) as Y.Map<unknown>]);
|
||||
place = rules.length - 1;
|
||||
}, origin);
|
||||
return place;
|
||||
}
|
||||
|
||||
export type FirewallRuleColumn = 'sourceIPAddress' | 'sourceSubnetMask' | 'destinationIPAddress' | 'destinationSubnetMask' | 'port';
|
||||
export const FIREWALL_RULE_COLUMNS: readonly FirewallRuleColumn[] = ['sourceIPAddress', 'sourceSubnetMask', 'destinationIPAddress', 'destinationSubnetMask', 'port'];
|
||||
|
||||
/** Each text column's rule (FirewallRule.validateExistingAttributes; the marker only as a source). */
|
||||
export const firewallCellRule = (column: FirewallRuleColumn): FieldRule =>
|
||||
column === 'sourceIPAddress' ? 'firewallSource' : column === 'destinationIPAddress' ? 'ipAddressOrEmpty' : column === 'port' ? 'portOrEmpty' : 'firewallMask';
|
||||
|
||||
const ruleAt = (node: Y.Map<unknown>, index: number) => ((settingsOf(node).get('firewall') as Y.Map<unknown> | undefined)?.get('rules') as Y.Array<Y.Map<unknown>> | undefined)?.get(index);
|
||||
|
||||
/** A text cell of a rule; an empty port means all ports. */
|
||||
export function setFirewallRuleCell(doc: Y.Doc, id: Id, index: number, column: FirewallRuleColumn, text: string, origin: unknown): CheckedField {
|
||||
return setChecked(
|
||||
doc,
|
||||
id,
|
||||
checkField(firewallCellRule(column), text),
|
||||
(node, value) => ruleAt(node, index)?.set(column, column === 'port' ? (value === '' ? ALL_PORTS : Number(value)) : value),
|
||||
origin,
|
||||
);
|
||||
}
|
||||
|
||||
/** A rule's protocol or action (Java's lists: *, TCP, UDP; accept, drop). */
|
||||
export function setFirewallRuleChoice(doc: Y.Doc, id: Id, index: number, choice: { readonly protocol: FirewallProtocol } | { readonly action: FirewallAction }, origin: unknown): void {
|
||||
const node = nodes(doc).get(id);
|
||||
const rule = node === undefined ? undefined : ruleAt(node, index);
|
||||
if (rule === undefined) return;
|
||||
doc.transact(() => ('protocol' in choice ? rule.set('protocol', choice.protocol) : rule.set('action', choice.action)), origin);
|
||||
}
|
||||
|
||||
/** Moves a rule one place up (-1) or down (+1); the first goes no higher, the last no lower (Firewall.moveUp, moveDown). */
|
||||
export function moveFirewallRule(doc: Y.Doc, id: Id, index: number, step: -1 | 1, origin: unknown): boolean {
|
||||
const node = nodes(doc).get(id);
|
||||
const rules = node === undefined ? undefined : ((settingsOf(node).get('firewall') as Y.Map<unknown> | undefined)?.get('rules') as Y.Array<Y.Map<unknown>> | undefined);
|
||||
const target = index + step;
|
||||
if (rules === undefined || index < 0 || index >= rules.length || target < 0 || target >= rules.length) return false;
|
||||
doc.transact(() => {
|
||||
// A Yjs type cannot be inserted twice: the rule moves as a copy of its fields.
|
||||
const moved = fromY(rules.get(index)) as PlainValue;
|
||||
rules.delete(index, 1);
|
||||
rules.insert(target, [toY(moved) as Y.Map<unknown>]);
|
||||
}, origin);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function removeFirewallRule(doc: Y.Doc, id: Id, index: number, origin: unknown): void {
|
||||
const node = nodes(doc).get(id);
|
||||
const rules = node === undefined ? undefined : ((settingsOf(node).get('firewall') as Y.Map<unknown> | undefined)?.get('rules') as Y.Array<Y.Map<unknown>> | undefined);
|
||||
if (rules === undefined || index < 0 || index >= rules.length) return;
|
||||
doc.transact(() => rules.delete(index, 1), origin);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user