pmcf 2.15.0 → 2.16.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmcf",
3
- "version": "2.15.0",
3
+ "version": "2.16.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/cli.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import { parseArgs } from "node:util";
2
+ import { resolve } from "node:path";
2
3
  import { argv, cwd, env } from "node:process";
3
4
  import { Root } from "./module.mjs";
4
5
 
5
- export async function prepare(options={}) {
6
+ export async function prepare(options = {}) {
6
7
  const { values, positionals } = parseArgs({
7
8
  args: argv.slice(2),
8
9
  options: {
@@ -17,7 +18,7 @@ export async function prepare(options={}) {
17
18
  default: false
18
19
  },
19
20
  publish: {
20
- type: "string",
21
+ type: "string"
21
22
  },
22
23
  root: {
23
24
  type: "string",
@@ -32,9 +33,13 @@ export async function prepare(options={}) {
32
33
  allowPositionals: true
33
34
  });
34
35
 
36
+ if (values.output) {
37
+ values.output = resolve(cwd(), values.output);
38
+ }
39
+
35
40
  const root = new Root(values.root);
36
41
 
37
42
  await root.loadAll();
38
-
43
+
39
44
  return { root, options: values, args: positionals };
40
45
  }
package/src/dns-utils.mjs CHANGED
@@ -1,10 +1,5 @@
1
- import {
2
- asIterator,
3
- decodeIPv6,
4
- encodeIPv6,
5
- isIPv6Address,
6
- normalizeIPAddress
7
- } from "./utils.mjs";
1
+ import { asIterator } from "./utils.mjs";
2
+ import { decodeIPv4, decodeIPv6 } from "./ip.mjs";
8
3
 
9
4
  export function dnsFullName(name) {
10
5
  return name.endsWith(".") ? name : name + ".";
@@ -19,10 +14,10 @@ export function DNSRecord(key, type, ...values) {
19
14
  break;
20
15
 
21
16
  case "A":
22
- values[0] = normalizeIPAddress(values[0]);
17
+ values[0] = decodeIPv4(values[0]);
23
18
  break;
24
19
  case "AAAA":
25
- values[0] = decodeIPv6(encodeIPv6(values[0]));
20
+ values[0] = decodeIPv6(values[0]);
26
21
  break;
27
22
  }
28
23
 
@@ -59,22 +54,3 @@ export function dnsMergeParameters(a, b) {
59
54
  ])
60
55
  );
61
56
  }
62
-
63
- export function reverseAddress(address) {
64
- if (isIPv6Address(address)) {
65
- return normalizeIPAddress(address)
66
- .replaceAll(":", "")
67
- .split("")
68
- .reverse()
69
- .join(".");
70
- }
71
-
72
- return address.split(".").reverse().join(".");
73
- }
74
-
75
- export function reverseArpaAddress(address) {
76
- return (
77
- reverseAddress(address) +
78
- (isIPv6Address(address) ? ".ip6.arpa" : ".in-addr.arpa")
79
- );
80
- }
@@ -1,13 +1,5 @@
1
- import {
2
- writeFile,
3
- mkdir,
4
- copyFile,
5
- glob,
6
- chmod,
7
- stat
8
- } from "node:fs/promises";
1
+ import { writeFile, mkdir } from "node:fs/promises";
9
2
  import { join } from "node:path";
10
- import { FileContentProvider } from "npm-pkgbuild";
11
3
  import { writeLines, sectionLines } from "../src/utils.mjs";
12
4
  import { addHook } from "./hooks.mjs";
13
5
 
package/src/host.mjs CHANGED
@@ -6,16 +6,14 @@ import {
6
6
  networkProperties,
7
7
  networkAddressProperties
8
8
  } from "./network-support.mjs";
9
+ import { asArray, domainFromDominName, domainName } from "./utils.mjs";
9
10
  import {
10
- asArray,
11
- isIPv4Address,
12
- isIPv6Address,
13
- normalizeIPAddress,
11
+ isIPv4,
12
+ isIPv6,
14
13
  formatCIDR,
15
14
  hasWellKnownSubnet,
16
- domainFromDominName,
17
- domainName
18
- } from "./utils.mjs";
15
+ normalizeIP
16
+ } from "./ip.mjs";
19
17
  import { objectFilter } from "./filter.mjs";
20
18
  import { addType, types } from "./types.mjs";
21
19
  import { loadHooks } from "./hooks.mjs";
@@ -582,7 +580,7 @@ export class NetworkInterface extends Base {
582
580
  set ipAddresses(value) {
583
581
  for (const address of asArray(value)) {
584
582
  this._ipAddresses.set(
585
- normalizeIPAddress(address),
583
+ normalizeIP(address),
586
584
  this.addSubnet(address)
587
585
  );
588
586
  }
@@ -593,11 +591,11 @@ export class NetworkInterface extends Base {
593
591
  }
594
592
 
595
593
  get rawIPv4Address() {
596
- return this.rawAddresses.filter(a => isIPv4Address(a))[0];
594
+ return this.rawAddresses.filter(a => isIPv4(a))[0];
597
595
  }
598
596
 
599
597
  get rawIPv6Address() {
600
- return this.rawAddresses.filter(a => isIPv6Address(a))[0];
598
+ return this.rawAddresses.filter(a => isIPv6(a))[0];
601
599
  }
602
600
 
603
601
  get rawAddresses() {
@@ -616,13 +614,13 @@ export class NetworkInterface extends Base {
616
614
 
617
615
  get rawIPv4Addresses() {
618
616
  return [...this.ipAddresses]
619
- .filter(([address]) => isIPv4Address(address))
617
+ .filter(([address]) => isIPv4(address))
620
618
  .map(([address]) => address);
621
619
  }
622
620
 
623
621
  get rawIPv6Addresses() {
624
622
  return [...this.ipAddresses]
625
- .filter(([address]) => isIPv6Address(address))
623
+ .filter(([address]) => isIPv6(address))
626
624
  .map(([address]) => address);
627
625
  }
628
626
 
package/src/ip.mjs ADDED
@@ -0,0 +1,298 @@
1
+ const ipv4 = {
2
+ factory: Uint8Array,
3
+ normalize(address) {
4
+ return address;
5
+ },
6
+ separator: ".",
7
+ bitLength: 32,
8
+ byteLength: 4,
9
+ segments: 4,
10
+ segmentLength: 8,
11
+ segmentMask: 0xffn,
12
+ mask: 0xffffffffn,
13
+ base: 10
14
+ };
15
+
16
+ const ipv6 = {
17
+ factory: Uint16Array,
18
+ normalize(address) {
19
+ const parts = address.split(":");
20
+ const i = parts.indexOf("");
21
+ if (i >= 0) {
22
+ parts.splice(i, 1, ..."0".repeat(9 - parts.length));
23
+ }
24
+ return parts /*.map(s => s.padStart(4, "0"))*/
25
+ .join(":");
26
+ },
27
+ separator: ":",
28
+ compressor: "::",
29
+ bitLength: 128,
30
+ byteLength: 8,
31
+ segments: 8,
32
+ segmentLength: 16,
33
+ segmentMask: 0xffffn,
34
+ mask: 0xffffffffffffffffffffffffffffffffn,
35
+ base: 16
36
+ };
37
+
38
+ export function IPV4(...args) {
39
+ return _create(ipv4, ...args);
40
+ }
41
+
42
+ export function IPV6(...args) {
43
+ return _create(ipv6, ...args);
44
+ }
45
+
46
+ function _create(definition, ...args) {
47
+ if (args.length === 1) {
48
+ return _encode(definition, args[0]);
49
+ }
50
+ return new definition.factory(args);
51
+ }
52
+
53
+ export function encodeIP(address) {
54
+ return _encode(isIPv4(address) ? ipv4 : ipv6, address);
55
+ }
56
+
57
+ export function encodeIPv6(address) {
58
+ return _encode(ipv6, address);
59
+ }
60
+
61
+ export function encodeIPv4(address) {
62
+ return _encode(ipv4, address);
63
+ }
64
+
65
+ export function _encode(definition, address) {
66
+ switch (typeof address) {
67
+ case "string":
68
+ const res = new definition.factory(definition.segments);
69
+
70
+ let i = 0;
71
+ for (const segment of definition
72
+ .normalize(address)
73
+ .split(definition.separator)) {
74
+ res[i++] = parseInt(segment, definition.base);
75
+ }
76
+
77
+ return res;
78
+
79
+ case "object":
80
+ if (
81
+ address instanceof definition.factory &&
82
+ address.length === definition.byteLength
83
+ ) {
84
+ return address;
85
+ }
86
+ }
87
+ }
88
+
89
+ function _decode(definition, address, length) {
90
+ switch (typeof address) {
91
+ case "string":
92
+ if (length === undefined) {
93
+ return address;
94
+ }
95
+ address = _encode(definition, address);
96
+ break;
97
+ case "bigint":
98
+ address = _encodeBigInt(definition, address);
99
+ }
100
+
101
+ let result = "";
102
+ let compressed = 0;
103
+ let word;
104
+ let last = address?.length;
105
+
106
+ if (length !== undefined) {
107
+ length /= definition.segmentLength;
108
+
109
+ if (length < last) {
110
+ last = length;
111
+ }
112
+ }
113
+ for (let i = 0, j = 0; i < last; j = j + 1, i = j) {
114
+ for (; j < last; j++) {
115
+ word = address[j];
116
+
117
+ if (word !== 0 || !definition.compressor || compressed > 0) {
118
+ break;
119
+ }
120
+ }
121
+
122
+ if (j > i + 1) {
123
+ compressed++;
124
+ result += definition.compressor;
125
+ } else {
126
+ if (result.length > 0) {
127
+ result += definition.separator;
128
+ }
129
+ }
130
+
131
+ if (j < last) {
132
+ result += word.toString(definition.base);
133
+ }
134
+ }
135
+
136
+ return result;
137
+ }
138
+
139
+ export function decodeIPv6(address, length) {
140
+ return _decode(ipv6, address, length);
141
+ }
142
+
143
+ export function decodeIPv4(address, length) {
144
+ return _decode(ipv4, address, length);
145
+ }
146
+
147
+ export function decodeIP(address, length) {
148
+ return _decode(isIPv4(address) ? ipv4 : ipv6, address, length);
149
+ }
150
+
151
+ export function isIPv4(address) {
152
+ return _is(ipv4, address);
153
+ }
154
+
155
+ export function isIPv6(address) {
156
+ return _is(ipv6, address);
157
+ }
158
+
159
+ export function _is(definition, address) {
160
+ switch (typeof address) {
161
+ case "string":
162
+ return address.indexOf(definition.separator) >= 0;
163
+
164
+ case "object":
165
+ return (
166
+ address instanceof definition.factory &&
167
+ address.length === definition.byteLength
168
+ );
169
+ }
170
+
171
+ return false;
172
+ }
173
+
174
+ export function asBigInt(address) {
175
+ return _asBigInt(isIPv4(address) ? ipv4 : ipv6, address);
176
+ }
177
+
178
+ function _asBigInt(definition, address) {
179
+ if (typeof address === "bigint") {
180
+ return address;
181
+ }
182
+
183
+ const ea = _encode(definition, address);
184
+
185
+ let result = 0n;
186
+
187
+ for (let i = 0; i < ea.length; i++) {
188
+ result = result << BigInt(definition.segmentLength);
189
+ result += BigInt(ea[i]);
190
+ }
191
+
192
+ return result;
193
+ }
194
+
195
+ function _encodeBigInt(definition, address) {
196
+ const segments = [];
197
+
198
+ for (let i = 0; i < definition.segments; i++) {
199
+ segments.push(Number(address & definition.segmentMask));
200
+ address >>= BigInt(definition.segmentLength);
201
+ }
202
+
203
+ return new definition.factory(segments.reverse());
204
+ }
205
+
206
+ export function prefixIP(address, length) {
207
+ const definition = isIPv4(address) ? ipv4 : ipv6;
208
+ return _decode(definition, _prefix(definition, address, length));
209
+ }
210
+
211
+ export function _prefix(definition, address, length) {
212
+ return (
213
+ _asBigInt(definition, address) &
214
+ (definition.mask << BigInt(definition.bitLength - length))
215
+ );
216
+ }
217
+
218
+ export function normalizeCIDR(address) {
219
+ let [prefix, prefixLength] = address.split(/\//);
220
+ let longPrefix;
221
+
222
+ if (!prefixLength && isLinkLocal(address)) {
223
+ prefix = "fe80::";
224
+ longPrefix = prefix;
225
+ prefixLength = 64;
226
+ } else {
227
+ prefixLength = parseInt(prefixLength);
228
+
229
+ const definition = isIPv6(prefix) ? ipv6 : ipv4;
230
+ let n;
231
+
232
+ if (prefixLength) {
233
+ n = _prefix(definition, prefix, prefixLength);
234
+ } else {
235
+ n = _encode(definition, prefix);
236
+
237
+ if (isLocalhost(n)) {
238
+ prefixLength = definition === ipv6 ? 128 : 8;
239
+ } else {
240
+ return {};
241
+ }
242
+ }
243
+ prefix = _decode(definition, n, prefixLength);
244
+ longPrefix = _decode(definition, n);
245
+ }
246
+
247
+ return { longPrefix, prefix, prefixLength, cidr: `${prefix}/${prefixLength}` };
248
+ }
249
+
250
+ export function formatCIDR(address, subnet) {
251
+ return subnet ? `${address}/${subnet.prefixLength}` : address;
252
+ }
253
+
254
+ export function normalizeIP(address) {
255
+ return decodeIP(encodeIP(address));
256
+ }
257
+
258
+ export function reverseArpa(address) {
259
+ if (isIPv6(address)) {
260
+ const ea = encodeIPv6(address);
261
+ let result = [];
262
+ for (let i = 0; i < ea.length; i++) {
263
+ const v = ea[i];
264
+ for (let i = 0; i < 4; i++) {
265
+ result.push(((v >> (12 - 4 * i)) & 0x000f).toString(16));
266
+ }
267
+ }
268
+ return result.reverse().join(".") + ".ip6.arpa";
269
+ }
270
+
271
+ return address.split(".").reverse().join(".") + ".in-addr.arpa";
272
+ }
273
+
274
+ export function isLocalhost(address) {
275
+ const eaddr = encodeIP(address);
276
+
277
+ if (!eaddr) {
278
+ return false;
279
+ }
280
+
281
+ const str = eaddr.toString();
282
+
283
+ return str === IPV4_LOCALHOST.toString() || str === IPV6_LOCALHOST.toString();
284
+ }
285
+
286
+ export function isLinkLocal(address) {
287
+ const eaddr = encodeIP(address);
288
+ return eaddr[0] === 0xfe80;
289
+ }
290
+
291
+ export function hasWellKnownSubnet(address) {
292
+ return isLocalhost(address) || isLinkLocal(address);
293
+ }
294
+
295
+ export const IPV6_LINK_LOCAL_BROADCAST = _encode(ipv6, "ff02::1");
296
+ export const IPV6_ROUTER_BROADCAST = _encode(ipv6, "ff02::2");
297
+ export const IPV4_LOCALHOST = _encode(ipv4, "127.0.0.1");
298
+ export const IPV6_LOCALHOST = _encode(ipv6, "::1");
package/src/owner.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import { asIterator, normalizeCIDR } from "./utils.mjs";
1
+ import { asIterator } from "./utils.mjs";
2
2
  import { Base } from "./base.mjs";
3
3
  import { Subnet } from "./subnet.mjs";
4
4
  import { addType, types } from "./types.mjs";
5
-
5
+ import { normalizeCIDR } from "./ip.mjs";
6
6
  const OwnerTypeDefinition = {
7
7
  name: "owner",
8
8
  owners: ["location", "owner", "root"],
package/src/service.mjs CHANGED
@@ -1,7 +1,9 @@
1
1
  import { Base } from "./base.mjs";
2
2
  import { addType } from "./types.mjs";
3
- import { asArray, isLocalhost } from "./utils.mjs";
3
+ import { objectFilter } from "./filter.mjs";
4
+ import { asArray } from "./utils.mjs";
4
5
  import { networkAddressProperties } from "./network-support.mjs";
6
+ import { isLocalhost } from "./ip.mjs";
5
7
  import {
6
8
  DNSRecord,
7
9
  dnsFullName,
@@ -59,6 +61,31 @@ const ServiceTypes = {
59
61
  }
60
62
  };
61
63
 
64
+ export const endpointProperties = {
65
+ port: { type: "number", collection: false, writeable: true },
66
+ protocol: {
67
+ type: "string",
68
+ collection: false,
69
+ writeable: true,
70
+ values: ["tcp", "udp"]
71
+ },
72
+ type: { type: "string", collection: false, writeable: true },
73
+ tls: {
74
+ type: "boolean",
75
+ collection: false,
76
+ writeable: false,
77
+ default: false
78
+ }
79
+ };
80
+
81
+ export const EndpointTypeDefinition = {
82
+ name: "endpoint",
83
+ owners: ["service"],
84
+ priority: 0.4,
85
+ specializations: {},
86
+ properties: endpointProperties
87
+ };
88
+
62
89
  export const ServiceTypeDefinition = {
63
90
  name: "service",
64
91
  owners: ["host", "cluster"],
@@ -78,23 +105,10 @@ export const ServiceTypeDefinition = {
78
105
  },
79
106
  properties: {
80
107
  ...networkAddressProperties,
108
+ ...endpointProperties,
81
109
  ipAddresses: { type: "string", collection: true, writeable: true },
82
- port: { type: "number", collection: false, writeable: true },
83
- protocol: {
84
- type: "string",
85
- collection: false,
86
- writeable: true,
87
- values: ["tcp", "udp"]
88
- },
89
110
  alias: { type: "string", collection: false, writeable: true },
90
- type: { type: "string", collection: false, writeable: true },
91
111
  weight: { type: "number", collection: false, writeable: true, default: 1 },
92
- tls: {
93
- type: "string",
94
- collection: false,
95
- writeable: false,
96
- default: false
97
- },
98
112
  systemd: { type: "string", collection: true, writeable: true }
99
113
  }
100
114
  };
@@ -191,6 +205,10 @@ export class Service extends Base {
191
205
  .flat();
192
206
  }
193
207
 
208
+ *findEndpoints(filter) {
209
+ yield* objectFilter(EndpointTypeDefinition, this.endpoints, filter);
210
+ }
211
+
194
212
  set alias(value) {
195
213
  this._alias = value;
196
214
  }
@@ -7,7 +7,8 @@ import {
7
7
  serviceEndpoints
8
8
  } from "../service.mjs";
9
9
  import { addType } from "../types.mjs";
10
- import { writeLines, isIPv4Address, isIPv6Address } from "../utils.mjs";
10
+ import { writeLines } from "../utils.mjs";
11
+ import { isIPv4, isIPv6 } from "../ip.mjs";
11
12
 
12
13
  const DHCPServiceTypeDefinition = {
13
14
  name: "dhcp",
@@ -74,7 +75,7 @@ export class DHCPService extends Service {
74
75
  const dnsServerEndpoints = serviceEndpoints(network, {
75
76
  type: "dns",
76
77
  priority: "<10"
77
- });
78
+ }).filter(endpoint => endpoint.networkInterface.kind !== "loopback");
78
79
 
79
80
  const packageData = {
80
81
  dir,
@@ -144,9 +145,11 @@ export class DHCPService extends Service {
144
145
  domains.map(domain => {
145
146
  return {
146
147
  name: domain,
147
- "dns-servers": dnsServerEndpoints.map(endpoint => {
148
- return { "ip-address": endpoint.rawAddress };
149
- })
148
+ "dns-servers": dnsServerEndpoints
149
+ .filter(endpoint => isIPv4(endpoint.rawAddress))
150
+ .map(endpoint => {
151
+ return { "ip-address": endpoint.rawAddress };
152
+ })
150
153
  };
151
154
  });
152
155
 
@@ -227,7 +230,7 @@ export class DHCPService extends Service {
227
230
  Dhcp4: {
228
231
  ...commonConfig,
229
232
  "interfaces-config": {
230
- interfaces: listenInterfaces(isIPv4Address)
233
+ interfaces: listenInterfaces(isIPv4)
231
234
  },
232
235
  "multi-threading": {
233
236
  "enable-multi-threading": false
@@ -240,6 +243,7 @@ export class DHCPService extends Service {
240
243
  {
241
244
  name: "domain-name-servers",
242
245
  data: dnsServerEndpoints
246
+ .filter(endpoint => isIPv4(endpoint.rawAddress))
243
247
  .map(endpoint => endpoint.rawAddress)
244
248
  .join(",")
245
249
  },
@@ -272,7 +276,7 @@ export class DHCPService extends Service {
272
276
  Dhcp6: {
273
277
  ...commonConfig,
274
278
  "interfaces-config": {
275
- interfaces: listenInterfaces(isIPv6Address)
279
+ interfaces: listenInterfaces(isIPv6)
276
280
  },
277
281
  "control-socket": {
278
282
  "socket-type": "unix",
@@ -282,7 +286,10 @@ export class DHCPService extends Service {
282
286
  "option-data": [
283
287
  {
284
288
  name: "dns-servers",
285
- data: "2001:db8:2::45, 2001:db8:2::100"
289
+ data: dnsServerEndpoints
290
+ .filter(endpoint => isIPv6(endpoint.rawAddress))
291
+ .map(endpoint => endpoint.rawAddress)
292
+ .join(",")
286
293
  }
287
294
  ],
288
295
  subnet6: [
@@ -301,12 +308,6 @@ export class DHCPService extends Service {
301
308
  "delegated-len": 64
302
309
  }
303
310
  ],
304
- "option-data": [
305
- {
306
- name: "dns-servers",
307
- data: "2001:db8:2::dead:beef, 2001:db8:2::cafe:babe"
308
- }
309
- ],
310
311
  reservations: [
311
312
  {
312
313
  duid: "01:02:03:04:05:0A:0B:0C:0D:0E",