@takodotid/circuit 0.17.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +22 -2
- package/dist/presets/index.js +20 -13
- package/dist/types/adapters/devices/routeros/render.d.ts +5 -3
- package/dist/types/presets/index.d.ts +11 -2
- package/dist/types/schema/firewall.d.ts +13 -4
- package/package.json +1 -1
- package/src/adapters/devices/routeros/TRAPS.md +2 -0
- package/src/adapters/devices/routeros/plan.ts +5 -0
- package/src/adapters/devices/routeros/render.ts +30 -5
- package/src/presets/index.ts +31 -10
- package/src/schema/firewall.ts +12 -4
package/dist/cli/index.js
CHANGED
|
@@ -1202,6 +1202,11 @@ var DYNAMIC = new Set([
|
|
|
1202
1202
|
"/ip firewall filter",
|
|
1203
1203
|
"/ipv6 firewall filter",
|
|
1204
1204
|
"/ip firewall nat",
|
|
1205
|
+
"/ipv6 firewall nat",
|
|
1206
|
+
"/ip firewall mangle",
|
|
1207
|
+
"/ipv6 firewall mangle",
|
|
1208
|
+
"/ip firewall raw",
|
|
1209
|
+
"/ipv6 firewall raw",
|
|
1205
1210
|
"/ip dhcp-server lease"
|
|
1206
1211
|
]);
|
|
1207
1212
|
var quote = (value) => `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$")}"`;
|
|
@@ -1472,10 +1477,11 @@ function command(verb, properties) {
|
|
|
1472
1477
|
}
|
|
1473
1478
|
var yesNo = (on) => on ? "yes" : "no";
|
|
1474
1479
|
var familySuffix = (family) => family === "ipv4" ? "V4" : "V6";
|
|
1480
|
+
var eachPort = (ports) => [ports].flat().map((port) => typeof port === "number" ? String(port) : `${port.min}-${port.max}`);
|
|
1475
1481
|
function portList(ports) {
|
|
1476
1482
|
if (ports === undefined)
|
|
1477
1483
|
return;
|
|
1478
|
-
return
|
|
1484
|
+
return eachPort(ports).join(",");
|
|
1479
1485
|
}
|
|
1480
1486
|
function duration(seconds) {
|
|
1481
1487
|
const units = [
|
|
@@ -1940,12 +1946,26 @@ var HELPERS = ["ftp", "tftp", "irc", "h323", "sip", "pptp", "rtsp"];
|
|
|
1940
1946
|
var CHAINS = ["input", "forward", "output"];
|
|
1941
1947
|
function renderFirewall(out, ctx) {
|
|
1942
1948
|
for (const family of ["ipv4", "ipv6"]) {
|
|
1949
|
+
untracked(out, ctx, family);
|
|
1943
1950
|
filter(out, ctx, family);
|
|
1944
1951
|
nat(out, ctx, family);
|
|
1945
1952
|
}
|
|
1946
1953
|
helpers(out, ctx);
|
|
1947
1954
|
switchAcls(out, ctx);
|
|
1948
1955
|
}
|
|
1956
|
+
function untracked(out, ctx, family) {
|
|
1957
|
+
const menu = family === "ipv4" ? "/ip firewall raw" : "/ipv6 firewall raw";
|
|
1958
|
+
for (const prefix of ctx.device.firewall?.untracked ?? []) {
|
|
1959
|
+
if (familyOf(prefix) !== family)
|
|
1960
|
+
continue;
|
|
1961
|
+
for (const [direction, property] of [
|
|
1962
|
+
["to", "dst-address"],
|
|
1963
|
+
["from", "src-address"]
|
|
1964
|
+
]) {
|
|
1965
|
+
out.add(menu, command("add", { action: "notrack", chain: "prerouting", comment: `Untracked ${direction} ${prefix}`, [property]: prefix }));
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1949
1969
|
function families(ctx, match) {
|
|
1950
1970
|
const sets = ctx.device.firewall?.address_sets ?? {};
|
|
1951
1971
|
const address = match?.src ?? match?.dst;
|
|
@@ -2021,7 +2041,7 @@ function helpers(out, ctx) {
|
|
|
2021
2041
|
}
|
|
2022
2042
|
}
|
|
2023
2043
|
function switchAcls(out, ctx) {
|
|
2024
|
-
const each = (ports) => ports === undefined ? [undefined] :
|
|
2044
|
+
const each = (ports) => ports === undefined ? [undefined] : eachPort(ports);
|
|
2025
2045
|
for (const [port, settings] of Object.entries(ctx.ports)) {
|
|
2026
2046
|
if (!settings.acl)
|
|
2027
2047
|
continue;
|
package/dist/presets/index.js
CHANGED
|
@@ -32,19 +32,26 @@ var AMPLIFIERS = {
|
|
|
32
32
|
memcached: 11211,
|
|
33
33
|
dvr: 37810
|
|
34
34
|
};
|
|
35
|
-
var
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
35
|
+
var ASKED = new Set([AMPLIFIERS.dns, AMPLIFIERS.ntp, AMPLIFIERS.snmp]);
|
|
36
|
+
function antiAmplification(destinations, options = {}) {
|
|
37
|
+
const below1024Except = (port) => [
|
|
38
|
+
{ min: 0, max: port - 1 },
|
|
39
|
+
{ min: port + 1, max: 1023 }
|
|
40
|
+
];
|
|
41
|
+
return destinations.flatMap((dst) => [
|
|
42
|
+
...(options.keep_fragments ?? []).filter((prefix) => contains(dst, prefix)).map((prefix) => ({
|
|
43
|
+
description: `UDP fragments to ${prefix} kept`,
|
|
44
|
+
match: { protocol: "udp", dst: prefix, src_port: 0 },
|
|
45
|
+
action: "accept"
|
|
46
|
+
})),
|
|
47
|
+
{ description: `UDP fragments to ${dst}`, match: { protocol: "udp", dst, src_port: 0 }, action: "drop" },
|
|
48
|
+
...Object.entries(AMPLIFIERS).map(([name, port]) => ({
|
|
49
|
+
description: ASKED.has(port) ? `UDP to ${dst} from ${name}, to a port below 1024` : `UDP to ${dst} from ${name}`,
|
|
50
|
+
match: { protocol: "udp", dst, src_port: port, ...ASKED.has(port) ? { dst_port: below1024Except(port) } : {} },
|
|
51
|
+
action: "drop"
|
|
52
|
+
}))
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
48
55
|
var BAD_TCP_FLAGS = [
|
|
49
56
|
["FIN+SYN", ["fin", "syn"], []],
|
|
50
57
|
["SYN+RST", ["syn", "rst"], []],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Device, Family, Interface, Port } from "../../../schema";
|
|
1
|
+
import type { Device, Family, Interface, Port, Ports } from "../../../schema";
|
|
2
2
|
/** A property value as a command takes it. `undefined` and `false` leave the property out. */
|
|
3
3
|
export type Value = string | number | undefined | false;
|
|
4
4
|
/** Collects commands under their menu. */
|
|
@@ -21,8 +21,10 @@ export declare function command(verb: string, properties: Record<string, Value>)
|
|
|
21
21
|
export declare const yesNo: (on: boolean | undefined) => string;
|
|
22
22
|
/** `V4` or `V6`, the suffix of a chain rendered for one family. */
|
|
23
23
|
export declare const familySuffix: (family: Family) => string;
|
|
24
|
-
/**
|
|
25
|
-
export declare
|
|
24
|
+
/** Each port or range of ports as the platform writes it: `53`, `0-52`. */
|
|
25
|
+
export declare const eachPort: (ports: Ports) => string[];
|
|
26
|
+
/** One port, a range, or a list of them, as `53,0-52`. */
|
|
27
|
+
export declare function portList(ports: Ports | undefined): string | undefined;
|
|
26
28
|
/** Seconds as the platform writes a duration: `3600` becomes `1h`, `90` becomes `1m30s`. */
|
|
27
29
|
export declare function duration(seconds: number): string;
|
|
28
30
|
/** A rate as the switch chip takes it: `2G` becomes `2.0Gbps`. */
|
|
@@ -28,10 +28,19 @@ export declare const AMPLIFIERS: {
|
|
|
28
28
|
readonly memcached: 11211;
|
|
29
29
|
readonly dvr: 37810;
|
|
30
30
|
};
|
|
31
|
+
/** What `antiAmplification` leaves alone. */
|
|
32
|
+
export type AntiAmplificationOptions = {
|
|
33
|
+
/** Prefixes inside the destinations whose UDP fragments pass: for a host that receives UDP too large for one packet, such as SIP or some VPNs. Every fragment after the first is dropped when absent. */
|
|
34
|
+
keep_fragments?: readonly Prefix[];
|
|
35
|
+
};
|
|
31
36
|
/**
|
|
32
|
-
* Hardware ACL rules dropping reflected UDP floods toward `destinations
|
|
37
|
+
* Hardware ACL rules dropping reflected UDP floods toward `destinations`, in the switch chip, without limiting anything else.
|
|
38
|
+
*
|
|
39
|
+
* - Replies from an amplifier no host asks across the internet are dropped.
|
|
40
|
+
* - Replies from DNS, NTP and SNMP are dropped only toward a port below 1024 that is not the service's own. A host that runs its own resolver, time server or monitoring keeps its answers.
|
|
41
|
+
* - The fragments after the first carry no port, and a switch chip reads them as port 0. They are dropped, but toward `keep_fragments`.
|
|
33
42
|
*/
|
|
34
|
-
export declare
|
|
43
|
+
export declare function antiAmplification(destinations: readonly Prefix[], options?: AntiAmplificationOptions): AclRule<never>[];
|
|
35
44
|
/** Firewall rules dropping packets with a TCP flag combination no legitimate packet carries, for a forward chain. */
|
|
36
45
|
export declare const badTcpFlags: () => FilterRule<never, never>[];
|
|
37
46
|
/** The prefix lengths a BGP import accepts. */
|
|
@@ -3,6 +3,13 @@ import type { Family, IP, Prefix, Rate } from "./common";
|
|
|
3
3
|
export type Protocol = "tcp" | "udp" | "icmp" | "gre" | "esp" | "ah" | "ospf" | "vrrp";
|
|
4
4
|
/** The state connection tracking assigns a packet. */
|
|
5
5
|
export type ConnectionState = "new" | "established" | "related" | "untracked" | "invalid";
|
|
6
|
+
/** Ports from `min` to `max`, both included. */
|
|
7
|
+
export type PortRange = {
|
|
8
|
+
min: number;
|
|
9
|
+
max: number;
|
|
10
|
+
};
|
|
11
|
+
/** A port, a range of ports, or any of several. */
|
|
12
|
+
export type Ports = number | PortRange | readonly (number | PortRange)[];
|
|
6
13
|
/** A TCP header flag. */
|
|
7
14
|
export type TcpFlag = "fin" | "syn" | "rst" | "psh" | "ack" | "urg";
|
|
8
15
|
/** Conditions on a packet. All must hold. A rule with addresses of one family applies to that family only. */
|
|
@@ -20,9 +27,9 @@ export type FilterMatch<I extends string, AS extends string> = {
|
|
|
20
27
|
/** Destination is in this address set. */
|
|
21
28
|
dst_set?: AS;
|
|
22
29
|
/** Source port, or any of several. TCP and UDP only. */
|
|
23
|
-
src_port?:
|
|
30
|
+
src_port?: Ports;
|
|
24
31
|
/** Destination port, or any of several. TCP and UDP only. */
|
|
25
|
-
dst_port?:
|
|
32
|
+
dst_port?: Ports;
|
|
26
33
|
/** Arrived on this interface. */
|
|
27
34
|
in_interface?: I;
|
|
28
35
|
/** Leaves through this interface. */
|
|
@@ -89,6 +96,8 @@ export type Firewall<I extends string, AS extends string> = {
|
|
|
89
96
|
};
|
|
90
97
|
/** Helpers that run. None when absent. */
|
|
91
98
|
helpers?: readonly Helper[];
|
|
99
|
+
/** Traffic to and from these prefixes passes without connection tracking. A flood toward them cannot fill the connection table, and rules see its packets in the state `untracked`. Everything is tracked when absent. */
|
|
100
|
+
untracked?: readonly Prefix[];
|
|
92
101
|
};
|
|
93
102
|
/** A stateless rule evaluated in hardware where traffic arrives, before routing. */
|
|
94
103
|
export type AclRule<V extends string> = {
|
|
@@ -105,9 +114,9 @@ export type AclRule<V extends string> = {
|
|
|
105
114
|
/** Destination prefix. */
|
|
106
115
|
dst?: Prefix;
|
|
107
116
|
/** Source port, or any of several. */
|
|
108
|
-
src_port?:
|
|
117
|
+
src_port?: Ports;
|
|
109
118
|
/** Destination port, or any of several. */
|
|
110
|
-
dst_port?:
|
|
119
|
+
dst_port?: Ports;
|
|
111
120
|
/** Carried in this VLAN. */
|
|
112
121
|
vlan?: V;
|
|
113
122
|
};
|
package/package.json
CHANGED
|
@@ -17,6 +17,8 @@ Behaviour that looks like something else. Each one has been hit on a real device
|
|
|
17
17
|
- A switch-chip rule's `rate` did not limit traffic the CPU routes. On a CCR2216 on 7.23.5, under a 12 Gbps flood, a rule at `2G` and then at `100M` changed nothing that reached the CPU, while a drop with the same match left 3 Mbps. Drop what you can recognise instead of limiting it.
|
|
18
18
|
- The switch chip reads a UDP fragment after the first as port 0. `src-port=0` drops the rest of a fragmented flood whose first fragments another rule dropped by port; without it they reach the CPU, which holds them waiting for a first fragment that never comes.
|
|
19
19
|
- A switch-chip rule takes one port or a range, `1900-1901`; a list is refused. The render makes one rule for each port in a list.
|
|
20
|
+
- Any rule using fasttrack adds a dynamic "special dummy rule to show fasttrack counters" to the filter and the raw table. It cannot be removed, `cannot remove builtin`, so every selector over those menus skips dynamic entries.
|
|
21
|
+
- The connection table holds 1,048,576 entries on a CCR2216 with 16 GiB, and a full table refuses every new connection through the router, not only the target's. A SYN flood of 210,000 packets a second filled it in five seconds; a UDP flood from random sources fills it about six times faster, because an unanswered UDP entry lives 30 seconds. Traffic in `untracked` prefixes never enters it.
|
|
20
22
|
- With hardware offload on, routed traffic bypasses the forward chain entirely. `hardware_offload: false` on an interface is what makes the firewall apply to it, at the cost of CPU. The only proof of offload is the `H` flag on a route.
|
|
21
23
|
- `/ip cloud ddns-enabled` cannot be turned off on 7.23.5.
|
|
22
24
|
- `mac-server` and `mac-winbox` work at layer 2 and ignore the IP firewall; they are limited by interface list.
|
|
@@ -74,6 +74,11 @@ const DYNAMIC = new Set([
|
|
|
74
74
|
"/ip firewall filter",
|
|
75
75
|
"/ipv6 firewall filter",
|
|
76
76
|
"/ip firewall nat",
|
|
77
|
+
"/ipv6 firewall nat",
|
|
78
|
+
"/ip firewall mangle",
|
|
79
|
+
"/ipv6 firewall mangle",
|
|
80
|
+
"/ip firewall raw",
|
|
81
|
+
"/ipv6 firewall raw",
|
|
77
82
|
"/ip dhcp-server lease",
|
|
78
83
|
]);
|
|
79
84
|
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
NeighborSettings,
|
|
14
14
|
PolicyRule,
|
|
15
15
|
Port,
|
|
16
|
+
Ports,
|
|
16
17
|
Service,
|
|
17
18
|
Severity,
|
|
18
19
|
} from "../../../schema";
|
|
@@ -72,11 +73,15 @@ export const yesNo = (on: boolean | undefined): string => (on ? "yes" : "no");
|
|
|
72
73
|
/** `V4` or `V6`, the suffix of a chain rendered for one family. */
|
|
73
74
|
export const familySuffix = (family: Family): string => (family === "ipv4" ? "V4" : "V6");
|
|
74
75
|
|
|
75
|
-
/**
|
|
76
|
-
export
|
|
76
|
+
/** Each port or range of ports as the platform writes it: `53`, `0-52`. */
|
|
77
|
+
export const eachPort = (ports: Ports): string[] =>
|
|
78
|
+
[ports].flat().map((port) => (typeof port === "number" ? String(port) : `${port.min}-${port.max}`));
|
|
79
|
+
|
|
80
|
+
/** One port, a range, or a list of them, as `53,0-52`. */
|
|
81
|
+
export function portList(ports: Ports | undefined): string | undefined {
|
|
77
82
|
if (ports === undefined) return undefined;
|
|
78
83
|
|
|
79
|
-
return
|
|
84
|
+
return eachPort(ports).join(",");
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
/** Seconds as the platform writes a duration: `3600` becomes `1h`, `90` becomes `1m30s`. */
|
|
@@ -734,6 +739,7 @@ const CHAINS = ["input", "forward", "output"] as const;
|
|
|
734
739
|
|
|
735
740
|
export function renderFirewall(out: Output, ctx: Context): void {
|
|
736
741
|
for (const family of ["ipv4", "ipv6"] as const) {
|
|
742
|
+
untracked(out, ctx, family);
|
|
737
743
|
filter(out, ctx, family);
|
|
738
744
|
nat(out, ctx, family);
|
|
739
745
|
}
|
|
@@ -742,6 +748,25 @@ export function renderFirewall(out: Output, ctx: Context): void {
|
|
|
742
748
|
switchAcls(out, ctx);
|
|
743
749
|
}
|
|
744
750
|
|
|
751
|
+
/** Untracked prefixes skip connection tracking in the raw table, both ways, before a connection is ever made. */
|
|
752
|
+
function untracked(out: Output, ctx: Context, family: Family): void {
|
|
753
|
+
const menu = family === "ipv4" ? "/ip firewall raw" : "/ipv6 firewall raw";
|
|
754
|
+
|
|
755
|
+
for (const prefix of ctx.device.firewall?.untracked ?? []) {
|
|
756
|
+
if (familyOf(prefix) !== family) continue;
|
|
757
|
+
|
|
758
|
+
for (const [direction, property] of [
|
|
759
|
+
["to", "dst-address"],
|
|
760
|
+
["from", "src-address"],
|
|
761
|
+
] as const) {
|
|
762
|
+
out.add(
|
|
763
|
+
menu,
|
|
764
|
+
command("add", { action: "notrack", chain: "prerouting", comment: `Untracked ${direction} ${prefix}`, [property]: prefix })
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
745
770
|
/**
|
|
746
771
|
* The families a rule applies to. An address in the rule decides it; otherwise it applies to both.
|
|
747
772
|
*
|
|
@@ -836,10 +861,10 @@ function helpers(out: Output, ctx: Context): void {
|
|
|
836
861
|
}
|
|
837
862
|
|
|
838
863
|
/**
|
|
839
|
-
* ACLs run in the switch chip on the port traffic arrives on. The chip matches addresses with a dotted mask on IPv4, drops by forwarding to no port, and limits by rate. A rule takes one port, so a list
|
|
864
|
+
* ACLs run in the switch chip on the port traffic arrives on. The chip matches addresses with a dotted mask on IPv4, drops by forwarding to no port, and limits by rate. A rule takes one port or range, so a list becomes one rule for each.
|
|
840
865
|
*/
|
|
841
866
|
function switchAcls(out: Output, ctx: Context): void {
|
|
842
|
-
const each = (ports:
|
|
867
|
+
const each = (ports: Ports | undefined) => (ports === undefined ? [undefined] : eachPort(ports));
|
|
843
868
|
|
|
844
869
|
for (const [port, settings] of Object.entries(ctx.ports)) {
|
|
845
870
|
if (!settings.acl) continue;
|
package/src/presets/index.ts
CHANGED
|
@@ -41,23 +41,44 @@ export const AMPLIFIERS = {
|
|
|
41
41
|
dvr: 37810,
|
|
42
42
|
} as const satisfies Record<string, number>;
|
|
43
43
|
|
|
44
|
+
/** Of the amplifiers, the services hosts do ask across the internet: resolvers, time servers, monitoring. A reply to a real request goes back to the asking port, 1024 or above, or to the service's own port when two servers talk. */
|
|
45
|
+
const ASKED: ReadonlySet<number> = new Set([AMPLIFIERS.dns, AMPLIFIERS.ntp, AMPLIFIERS.snmp]);
|
|
46
|
+
|
|
47
|
+
/** What `antiAmplification` leaves alone. */
|
|
48
|
+
export type AntiAmplificationOptions = {
|
|
49
|
+
/** Prefixes inside the destinations whose UDP fragments pass: for a host that receives UDP too large for one packet, such as SIP or some VPNs. Every fragment after the first is dropped when absent. */
|
|
50
|
+
keep_fragments?: readonly Prefix[];
|
|
51
|
+
};
|
|
52
|
+
|
|
44
53
|
/**
|
|
45
|
-
* Hardware ACL rules dropping reflected UDP floods toward `destinations
|
|
54
|
+
* Hardware ACL rules dropping reflected UDP floods toward `destinations`, in the switch chip, without limiting anything else.
|
|
55
|
+
*
|
|
56
|
+
* - Replies from an amplifier no host asks across the internet are dropped.
|
|
57
|
+
* - Replies from DNS, NTP and SNMP are dropped only toward a port below 1024 that is not the service's own. A host that runs its own resolver, time server or monitoring keeps its answers.
|
|
58
|
+
* - The fragments after the first carry no port, and a switch chip reads them as port 0. They are dropped, but toward `keep_fragments`.
|
|
46
59
|
*/
|
|
47
|
-
export
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
export function antiAmplification(destinations: readonly Prefix[], options: AntiAmplificationOptions = {}): AclRule<never>[] {
|
|
61
|
+
const below1024Except = (port: number) => [
|
|
62
|
+
{ min: 0, max: port - 1 },
|
|
63
|
+
{ min: port + 1, max: 1023 },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
return destinations.flatMap((dst) => [
|
|
67
|
+
...(options.keep_fragments ?? [])
|
|
68
|
+
.filter((prefix) => contains(dst, prefix))
|
|
69
|
+
.map((prefix): AclRule<never> => ({
|
|
70
|
+
description: `UDP fragments to ${prefix} kept`,
|
|
71
|
+
match: { protocol: "udp", dst: prefix, src_port: 0 },
|
|
72
|
+
action: "accept",
|
|
73
|
+
})),
|
|
54
74
|
{ description: `UDP fragments to ${dst}`, match: { protocol: "udp", dst, src_port: 0 }, action: "drop" },
|
|
55
75
|
...Object.entries(AMPLIFIERS).map(([name, port]): AclRule<never> => ({
|
|
56
|
-
description: `UDP to ${dst} from ${name}`,
|
|
57
|
-
match: { protocol: "udp", dst, src_port: port },
|
|
76
|
+
description: ASKED.has(port) ? `UDP to ${dst} from ${name}, to a port below 1024` : `UDP to ${dst} from ${name}`,
|
|
77
|
+
match: { protocol: "udp", dst, src_port: port, ...(ASKED.has(port) ? { dst_port: below1024Except(port) } : {}) },
|
|
58
78
|
action: "drop",
|
|
59
79
|
})),
|
|
60
80
|
]);
|
|
81
|
+
}
|
|
61
82
|
|
|
62
83
|
/** TCP flag combinations no legitimate packet carries: name, flags set, flags unset. */
|
|
63
84
|
const BAD_TCP_FLAGS = [
|
package/src/schema/firewall.ts
CHANGED
|
@@ -6,6 +6,12 @@ export type Protocol = "tcp" | "udp" | "icmp" | "gre" | "esp" | "ah" | "ospf" |
|
|
|
6
6
|
/** The state connection tracking assigns a packet. */
|
|
7
7
|
export type ConnectionState = "new" | "established" | "related" | "untracked" | "invalid";
|
|
8
8
|
|
|
9
|
+
/** Ports from `min` to `max`, both included. */
|
|
10
|
+
export type PortRange = { min: number; max: number };
|
|
11
|
+
|
|
12
|
+
/** A port, a range of ports, or any of several. */
|
|
13
|
+
export type Ports = number | PortRange | readonly (number | PortRange)[];
|
|
14
|
+
|
|
9
15
|
/** A TCP header flag. */
|
|
10
16
|
export type TcpFlag = "fin" | "syn" | "rst" | "psh" | "ack" | "urg";
|
|
11
17
|
|
|
@@ -24,9 +30,9 @@ export type FilterMatch<I extends string, AS extends string> = {
|
|
|
24
30
|
/** Destination is in this address set. */
|
|
25
31
|
dst_set?: AS;
|
|
26
32
|
/** Source port, or any of several. TCP and UDP only. */
|
|
27
|
-
src_port?:
|
|
33
|
+
src_port?: Ports;
|
|
28
34
|
/** Destination port, or any of several. TCP and UDP only. */
|
|
29
|
-
dst_port?:
|
|
35
|
+
dst_port?: Ports;
|
|
30
36
|
/** Arrived on this interface. */
|
|
31
37
|
in_interface?: I;
|
|
32
38
|
/** Leaves through this interface. */
|
|
@@ -98,6 +104,8 @@ export type Firewall<I extends string, AS extends string> = {
|
|
|
98
104
|
};
|
|
99
105
|
/** Helpers that run. None when absent. */
|
|
100
106
|
helpers?: readonly Helper[];
|
|
107
|
+
/** Traffic to and from these prefixes passes without connection tracking. A flood toward them cannot fill the connection table, and rules see its packets in the state `untracked`. Everything is tracked when absent. */
|
|
108
|
+
untracked?: readonly Prefix[];
|
|
101
109
|
};
|
|
102
110
|
|
|
103
111
|
/** A stateless rule evaluated in hardware where traffic arrives, before routing. */
|
|
@@ -115,9 +123,9 @@ export type AclRule<V extends string> = {
|
|
|
115
123
|
/** Destination prefix. */
|
|
116
124
|
dst?: Prefix;
|
|
117
125
|
/** Source port, or any of several. */
|
|
118
|
-
src_port?:
|
|
126
|
+
src_port?: Ports;
|
|
119
127
|
/** Destination port, or any of several. */
|
|
120
|
-
dst_port?:
|
|
128
|
+
dst_port?: Ports;
|
|
121
129
|
/** Carried in this VLAN. */
|
|
122
130
|
vlan?: V;
|
|
123
131
|
};
|