@serfab/cadre-host 1.13.0 → 1.15.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.
Files changed (352) hide show
  1. package/README.md +347 -423
  2. package/dist/bin/host.d.ts +6 -3
  3. package/dist/bin/host.js +332 -524
  4. package/dist/bin/host.js.map +1 -1
  5. package/dist/bin/join-output.d.ts +56 -0
  6. package/dist/bin/join-output.js +92 -0
  7. package/dist/bin/join-output.js.map +1 -0
  8. package/dist/bin/nat-output.d.ts +54 -0
  9. package/dist/bin/nat-output.js +138 -0
  10. package/dist/bin/nat-output.js.map +1 -0
  11. package/dist/bin/ui-access.d.ts +22 -0
  12. package/dist/bin/ui-access.js +44 -0
  13. package/dist/bin/ui-access.js.map +1 -0
  14. package/dist/hosted/claim-details.d.ts +66 -0
  15. package/dist/hosted/claim-details.js +60 -0
  16. package/dist/hosted/claim-details.js.map +1 -0
  17. package/dist/hosted/hosted-node-service.d.ts +325 -0
  18. package/dist/hosted/hosted-node-service.js +678 -0
  19. package/dist/hosted/hosted-node-service.js.map +1 -0
  20. package/dist/hosted/hosted-node-store.d.ts +40 -0
  21. package/dist/hosted/hosted-node-store.js +126 -0
  22. package/dist/hosted/hosted-node-store.js.map +1 -0
  23. package/dist/{donation/donation-supervisor.d.ts → hosted/hosted-node-supervisor.d.ts} +70 -53
  24. package/dist/hosted/hosted-node-supervisor.js +396 -0
  25. package/dist/hosted/hosted-node-supervisor.js.map +1 -0
  26. package/dist/hosted/hosted-node-watcher.d.ts +95 -0
  27. package/dist/hosted/hosted-node-watcher.js +260 -0
  28. package/dist/hosted/hosted-node-watcher.js.map +1 -0
  29. package/dist/hosted/index.d.ts +16 -0
  30. package/dist/hosted/index.js +13 -0
  31. package/dist/hosted/index.js.map +1 -0
  32. package/dist/hosted/node-status.d.ts +51 -0
  33. package/dist/hosted/node-status.js +77 -0
  34. package/dist/hosted/node-status.js.map +1 -0
  35. package/dist/hosted/types.d.ts +141 -0
  36. package/dist/hosted/types.js +22 -0
  37. package/dist/hosted/types.js.map +1 -0
  38. package/dist/index.d.ts +12 -24
  39. package/dist/index.js +9 -19
  40. package/dist/index.js.map +1 -1
  41. package/dist/installer/config.d.ts +27 -34
  42. package/dist/installer/config.js +46 -15
  43. package/dist/installer/config.js.map +1 -1
  44. package/dist/installer/index.d.ts +9 -19
  45. package/dist/installer/index.js +21 -85
  46. package/dist/installer/index.js.map +1 -1
  47. package/dist/installer/paths.d.ts +0 -2
  48. package/dist/installer/paths.js +0 -4
  49. package/dist/installer/paths.js.map +1 -1
  50. package/dist/installer/wizard.d.ts +12 -9
  51. package/dist/installer/wizard.js +15 -14
  52. package/dist/installer/wizard.js.map +1 -1
  53. package/dist/nat/address-resolver.d.ts +18 -17
  54. package/dist/nat/address-resolver.js +79 -25
  55. package/dist/nat/address-resolver.js.map +1 -1
  56. package/dist/nat/address-watch.d.ts +37 -0
  57. package/dist/nat/address-watch.js +84 -0
  58. package/dist/nat/address-watch.js.map +1 -0
  59. package/dist/nat/ddns/duckdns.d.ts +2 -1
  60. package/dist/nat/ddns/duckdns.js +5 -1
  61. package/dist/nat/ddns/duckdns.js.map +1 -1
  62. package/dist/nat/ddns/index.d.ts +6 -0
  63. package/dist/nat/ddns/index.js.map +1 -1
  64. package/dist/nat/index.d.ts +13 -9
  65. package/dist/nat/index.js +7 -5
  66. package/dist/nat/index.js.map +1 -1
  67. package/dist/nat/nat-service.d.ts +220 -77
  68. package/dist/nat/nat-service.js +653 -239
  69. package/dist/nat/nat-service.js.map +1 -1
  70. package/dist/nat/nat-store.d.ts +12 -5
  71. package/dist/nat/nat-store.js +124 -29
  72. package/dist/nat/nat-store.js.map +1 -1
  73. package/dist/nat/network-mode.d.ts +19 -0
  74. package/dist/nat/network-mode.js +32 -0
  75. package/dist/nat/network-mode.js.map +1 -0
  76. package/dist/nat/port-mapper.d.ts +83 -79
  77. package/dist/nat/port-mapper.js +148 -189
  78. package/dist/nat/port-mapper.js.map +1 -1
  79. package/dist/nat/reachability.d.ts +32 -15
  80. package/dist/nat/reachability.js +110 -22
  81. package/dist/nat/reachability.js.map +1 -1
  82. package/dist/nat/types.d.ts +98 -27
  83. package/dist/nat/types.js +8 -5
  84. package/dist/nat/types.js.map +1 -1
  85. package/dist/orchestrator/host-process-orchestrator.d.ts +32 -74
  86. package/dist/orchestrator/host-process-orchestrator.js +84 -233
  87. package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
  88. package/dist/{installer/identity.d.ts → orchestrator/identity-file.d.ts} +5 -5
  89. package/dist/{installer/identity.js → orchestrator/identity-file.js} +6 -6
  90. package/dist/orchestrator/identity-file.js.map +1 -0
  91. package/dist/orchestrator/index.d.ts +2 -3
  92. package/dist/orchestrator/index.js +1 -1
  93. package/dist/orchestrator/index.js.map +1 -1
  94. package/dist/orchestrator/node-identity.d.ts +6 -6
  95. package/dist/orchestrator/node-identity.js +7 -7
  96. package/dist/orchestrator/node-identity.js.map +1 -1
  97. package/dist/orchestrator/port-allocator.d.ts +5 -6
  98. package/dist/orchestrator/port-allocator.js +9 -11
  99. package/dist/orchestrator/port-allocator.js.map +1 -1
  100. package/dist/orchestrator/state-store.d.ts +3 -5
  101. package/dist/orchestrator/state-store.js +2 -10
  102. package/dist/orchestrator/state-store.js.map +1 -1
  103. package/dist/orchestrator/types.d.ts +33 -22
  104. package/dist/orchestrator/types.js.map +1 -1
  105. package/dist/package-version.d.ts +1 -0
  106. package/dist/package-version.js +4 -0
  107. package/dist/package-version.js.map +1 -0
  108. package/dist/server/error-handler.d.ts +5 -25
  109. package/dist/server/error-handler.js +11 -74
  110. package/dist/server/error-handler.js.map +1 -1
  111. package/dist/server/events/types.d.ts +7 -11
  112. package/dist/server/events/types.js +2 -1
  113. package/dist/server/events/types.js.map +1 -1
  114. package/dist/server/index.d.ts +11 -37
  115. package/dist/server/index.js +30 -48
  116. package/dist/server/index.js.map +1 -1
  117. package/dist/server/routes/hosted-nodes.d.ts +28 -0
  118. package/dist/server/routes/hosted-nodes.js +68 -0
  119. package/dist/server/routes/hosted-nodes.js.map +1 -0
  120. package/dist/server/routes/nat.d.ts +4 -1
  121. package/dist/server/routes/nat.js +13 -2
  122. package/dist/server/routes/nat.js.map +1 -1
  123. package/dist/server/routes/nodes.d.ts +6 -20
  124. package/dist/server/routes/nodes.js +7 -86
  125. package/dist/server/routes/nodes.js.map +1 -1
  126. package/dist/server/routes/settings.d.ts +3 -7
  127. package/dist/server/routes/settings.js +18 -8
  128. package/dist/server/routes/settings.js.map +1 -1
  129. package/dist/server/routes/status.d.ts +2 -25
  130. package/dist/server/routes/status.js +3 -43
  131. package/dist/server/routes/status.js.map +1 -1
  132. package/dist/server/server.js +8 -8
  133. package/dist/server/settings-store.d.ts +2 -2
  134. package/dist/server/settings-store.js +2 -2
  135. package/dist/server/static.js +1 -2
  136. package/dist/server/static.js.map +1 -1
  137. package/dist/ui/assets/index-BHd8CWZA.css +1 -0
  138. package/dist/ui/assets/index-Ccr2GjPT.js +23 -0
  139. package/dist/ui/assets/index-Ccr2GjPT.js.map +1 -0
  140. package/dist/ui/index.html +2 -2
  141. package/dist/update/index.d.ts +2 -2
  142. package/dist/update/index.js +2 -2
  143. package/dist/update/store.d.ts +1 -1
  144. package/dist/update/store.js +1 -1
  145. package/dist/update/types.d.ts +1 -1
  146. package/dist/update/types.js +1 -1
  147. package/package.json +5 -7
  148. package/src/__tests__/cli-nat.smoke.test.ts +219 -212
  149. package/src/__tests__/cli.smoke.test.ts +2 -2
  150. package/src/__tests__/global-setup.ts +2 -3
  151. package/src/__tests__/orchestrator-claim-secret.test.ts +111 -0
  152. package/src/__tests__/orchestrator-env-scrub.test.ts +160 -163
  153. package/src/__tests__/orchestrator-handle-contract.ts +134 -134
  154. package/src/__tests__/orchestrator-node-identity.test.ts +177 -203
  155. package/src/__tests__/orchestrator-ports.test.ts +124 -143
  156. package/src/__tests__/orchestrator-push.test.ts +138 -146
  157. package/src/__tests__/orchestrator.test.ts +1017 -1063
  158. package/src/__tests__/ui-access.test.ts +30 -0
  159. package/src/bin/host.ts +397 -572
  160. package/src/bin/join-output.ts +120 -0
  161. package/src/bin/nat-output.ts +188 -0
  162. package/src/bin/ui-access.ts +50 -0
  163. package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +172 -173
  164. package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +43 -23
  165. package/src/hosted/__tests__/hosted-node-service.test.ts +708 -0
  166. package/src/hosted/__tests__/hosted-node-supervisor.test.ts +475 -0
  167. package/src/hosted/__tests__/test-invitation.ts +33 -0
  168. package/src/hosted/claim-details.ts +103 -0
  169. package/src/hosted/hosted-node-service.ts +814 -0
  170. package/src/hosted/hosted-node-store.ts +144 -0
  171. package/src/hosted/hosted-node-supervisor.ts +439 -0
  172. package/src/hosted/hosted-node-watcher.ts +288 -0
  173. package/src/hosted/index.ts +53 -0
  174. package/src/hosted/node-status.ts +97 -0
  175. package/src/hosted/types.ts +160 -0
  176. package/src/index.ts +72 -91
  177. package/src/installer/__tests__/config.test.ts +15 -40
  178. package/src/installer/__tests__/installer.smoke.test.ts +184 -263
  179. package/src/installer/__tests__/wizard.test.ts +25 -13
  180. package/src/installer/config.ts +201 -175
  181. package/src/installer/index.ts +252 -327
  182. package/src/installer/paths.ts +104 -109
  183. package/src/installer/wizard.ts +24 -26
  184. package/src/nat/__tests__/address-resolver.test.ts +107 -82
  185. package/src/nat/__tests__/nat-service.test.ts +608 -515
  186. package/src/nat/__tests__/nat-store.test.ts +49 -12
  187. package/src/nat/__tests__/network-mode.test.ts +38 -0
  188. package/src/nat/__tests__/port-mapper.test.ts +30 -191
  189. package/src/nat/__tests__/reachability.test.ts +96 -13
  190. package/src/nat/address-resolver.ts +94 -52
  191. package/src/nat/address-watch.ts +92 -0
  192. package/src/nat/ddns/duckdns.ts +5 -1
  193. package/src/nat/ddns/index.ts +6 -0
  194. package/src/nat/index.ts +33 -13
  195. package/src/nat/nat-service.ts +942 -501
  196. package/src/nat/nat-store.ts +126 -31
  197. package/src/nat/network-mode.ts +36 -0
  198. package/src/nat/port-mapper.ts +241 -329
  199. package/src/nat/reachability.ts +155 -34
  200. package/src/nat/types.ts +110 -37
  201. package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts} +34 -34
  202. package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
  203. package/src/orchestrator/host-process-orchestrator.ts +1089 -1260
  204. package/src/{installer/identity.ts → orchestrator/identity-file.ts} +56 -56
  205. package/src/orchestrator/index.ts +9 -10
  206. package/src/orchestrator/node-identity.ts +41 -41
  207. package/src/orchestrator/port-allocator.ts +71 -73
  208. package/src/orchestrator/state-store.ts +76 -90
  209. package/src/orchestrator/types.ts +34 -23
  210. package/src/package-version.ts +4 -0
  211. package/src/server/__tests__/bus.test.ts +38 -38
  212. package/src/server/__tests__/error-handler.test.ts +81 -118
  213. package/src/server/__tests__/fakes.ts +15 -18
  214. package/src/server/__tests__/hosted-nodes-route.test.ts +161 -0
  215. package/src/server/__tests__/nodes-route.test.ts +114 -283
  216. package/src/server/__tests__/publishers.test.ts +193 -180
  217. package/src/server/__tests__/server.smoke.test.ts +134 -223
  218. package/src/server/__tests__/settings-route.test.ts +210 -195
  219. package/src/server/__tests__/sse-route.test.ts +119 -119
  220. package/src/server/__tests__/status-route.test.ts +82 -121
  221. package/src/server/error-handler.ts +112 -178
  222. package/src/server/events/types.ts +20 -24
  223. package/src/server/index.ts +223 -267
  224. package/src/server/routes/hosted-nodes.ts +83 -0
  225. package/src/server/routes/nat.ts +75 -63
  226. package/src/server/routes/nodes.ts +96 -188
  227. package/src/server/routes/settings.ts +20 -15
  228. package/src/server/routes/status.ts +5 -67
  229. package/src/server/server.ts +8 -8
  230. package/src/server/settings-store.ts +2 -2
  231. package/src/server/static.ts +1 -2
  232. package/src/update/index.ts +2 -2
  233. package/src/update/store.ts +1 -1
  234. package/src/update/types.ts +1 -1
  235. package/dist/auth/duration.d.ts +0 -1
  236. package/dist/auth/duration.js +0 -32
  237. package/dist/auth/duration.js.map +0 -1
  238. package/dist/auth/index.d.ts +0 -11
  239. package/dist/auth/index.js +0 -9
  240. package/dist/auth/index.js.map +0 -1
  241. package/dist/auth/self-label.d.ts +0 -36
  242. package/dist/auth/self-label.js +0 -57
  243. package/dist/auth/self-label.js.map +0 -1
  244. package/dist/auth/trust-circle-store.d.ts +0 -26
  245. package/dist/auth/trust-circle-store.js +0 -129
  246. package/dist/auth/trust-circle-store.js.map +0 -1
  247. package/dist/auth/trust-circle.d.ts +0 -156
  248. package/dist/auth/trust-circle.js +0 -307
  249. package/dist/auth/trust-circle.js.map +0 -1
  250. package/dist/auth/types.d.ts +0 -84
  251. package/dist/auth/types.js +0 -24
  252. package/dist/auth/types.js.map +0 -1
  253. package/dist/donation/donation-service.d.ts +0 -389
  254. package/dist/donation/donation-service.js +0 -828
  255. package/dist/donation/donation-service.js.map +0 -1
  256. package/dist/donation/donation-store.d.ts +0 -51
  257. package/dist/donation/donation-store.js +0 -152
  258. package/dist/donation/donation-store.js.map +0 -1
  259. package/dist/donation/donation-supervisor.js +0 -364
  260. package/dist/donation/donation-supervisor.js.map +0 -1
  261. package/dist/donation/grant-service.d.ts +0 -80
  262. package/dist/donation/grant-service.js +0 -210
  263. package/dist/donation/grant-service.js.map +0 -1
  264. package/dist/donation/grant-store.d.ts +0 -38
  265. package/dist/donation/grant-store.js +0 -135
  266. package/dist/donation/grant-store.js.map +0 -1
  267. package/dist/donation/index.d.ts +0 -20
  268. package/dist/donation/index.js +0 -17
  269. package/dist/donation/index.js.map +0 -1
  270. package/dist/donation/types.d.ts +0 -224
  271. package/dist/donation/types.js +0 -40
  272. package/dist/donation/types.js.map +0 -1
  273. package/dist/installer/identity.js.map +0 -1
  274. package/dist/owner/index.d.ts +0 -11
  275. package/dist/owner/index.js +0 -11
  276. package/dist/owner/index.js.map +0 -1
  277. package/dist/owner/owner-node-client.d.ts +0 -110
  278. package/dist/owner/owner-node-client.js +0 -184
  279. package/dist/owner/owner-node-client.js.map +0 -1
  280. package/dist/server/routes/bootstrap-node-validation.d.ts +0 -115
  281. package/dist/server/routes/bootstrap-node-validation.js +0 -197
  282. package/dist/server/routes/bootstrap-node-validation.js.map +0 -1
  283. package/dist/server/routes/grants-admin.d.ts +0 -29
  284. package/dist/server/routes/grants-admin.js +0 -58
  285. package/dist/server/routes/grants-admin.js.map +0 -1
  286. package/dist/server/routes/grants.d.ts +0 -28
  287. package/dist/server/routes/grants.js +0 -165
  288. package/dist/server/routes/grants.js.map +0 -1
  289. package/dist/server/routes/provision-request-validation.d.ts +0 -45
  290. package/dist/server/routes/provision-request-validation.js +0 -97
  291. package/dist/server/routes/provision-request-validation.js.map +0 -1
  292. package/dist/server/routes/strands.d.ts +0 -20
  293. package/dist/server/routes/strands.js +0 -38
  294. package/dist/server/routes/strands.js.map +0 -1
  295. package/dist/server/routes/trust-circle.d.ts +0 -17
  296. package/dist/server/routes/trust-circle.js +0 -35
  297. package/dist/server/routes/trust-circle.js.map +0 -1
  298. package/dist/strands/index.d.ts +0 -7
  299. package/dist/strands/index.js +0 -6
  300. package/dist/strands/index.js.map +0 -1
  301. package/dist/strands/strand-service.d.ts +0 -50
  302. package/dist/strands/strand-service.js +0 -103
  303. package/dist/strands/strand-service.js.map +0 -1
  304. package/dist/strands/types.d.ts +0 -93
  305. package/dist/strands/types.js +0 -23
  306. package/dist/strands/types.js.map +0 -1
  307. package/dist/ui/assets/index-C6nELNfC.js +0 -23
  308. package/dist/ui/assets/index-C6nELNfC.js.map +0 -1
  309. package/dist/ui/assets/index-TtIYSeZO.css +0 -1
  310. package/src/__tests__/cli-invite.smoke.test.ts +0 -192
  311. package/src/__tests__/host-owner.smoke.test.ts +0 -123
  312. package/src/__tests__/orchestrator-owner.test.ts +0 -234
  313. package/src/__tests__/orchestrator-pin-keys.test.ts +0 -152
  314. package/src/auth/__tests__/duration.test.ts +0 -28
  315. package/src/auth/__tests__/self-label.test.ts +0 -85
  316. package/src/auth/__tests__/trust-circle-integration.test.ts +0 -152
  317. package/src/auth/__tests__/trust-circle-store.test.ts +0 -115
  318. package/src/auth/__tests__/trust-circle.test.ts +0 -445
  319. package/src/auth/duration.ts +0 -33
  320. package/src/auth/index.ts +0 -21
  321. package/src/auth/self-label.ts +0 -74
  322. package/src/auth/trust-circle-store.ts +0 -155
  323. package/src/auth/trust-circle.ts +0 -395
  324. package/src/auth/types.ts +0 -99
  325. package/src/donation/__tests__/donation-service.test.ts +0 -1193
  326. package/src/donation/__tests__/donation-supervisor.test.ts +0 -548
  327. package/src/donation/__tests__/grant-service.test.ts +0 -213
  328. package/src/donation/__tests__/grant-store.test.ts +0 -113
  329. package/src/donation/donation-service.ts +0 -1033
  330. package/src/donation/donation-store.ts +0 -176
  331. package/src/donation/donation-supervisor.ts +0 -411
  332. package/src/donation/grant-service.ts +0 -252
  333. package/src/donation/grant-store.ts +0 -152
  334. package/src/donation/index.ts +0 -65
  335. package/src/donation/types.ts +0 -260
  336. package/src/owner/__tests__/owner-node-client.test.ts +0 -194
  337. package/src/owner/index.ts +0 -12
  338. package/src/owner/owner-node-client.ts +0 -259
  339. package/src/server/__tests__/bootstrap-node-validation.test.ts +0 -138
  340. package/src/server/__tests__/grants-admin-route.test.ts +0 -171
  341. package/src/server/__tests__/grants-route.test.ts +0 -394
  342. package/src/server/__tests__/strands-route.test.ts +0 -234
  343. package/src/server/routes/bootstrap-node-validation.ts +0 -206
  344. package/src/server/routes/grants-admin.ts +0 -74
  345. package/src/server/routes/grants.ts +0 -193
  346. package/src/server/routes/provision-request-validation.ts +0 -105
  347. package/src/server/routes/strands.ts +0 -54
  348. package/src/server/routes/trust-circle.ts +0 -49
  349. package/src/strands/__tests__/strand-service.test.ts +0 -165
  350. package/src/strands/index.ts +0 -14
  351. package/src/strands/strand-service.ts +0 -135
  352. package/src/strands/types.ts +0 -102
@@ -1,225 +1,184 @@
1
+ import { BlockList } from 'node:net';
2
+ import { networkInterfaces } from 'node:os';
1
3
  import debug from 'debug';
4
+ import { upnpNat } from '@achingbrain/nat-port-mapper';
2
5
  import { NatError } from './types.js';
3
6
  const log = debug('cadre:host:nat-port-mapper');
4
- /** Default lease TTL: 1 hour. The router must keep our mapping at least this long. */
7
+ /**
8
+ * Default lease TTL: 1 hour. `@achingbrain/nat-port-mapper` takes the TTL in
9
+ * milliseconds and floors the lease it asks the router for at 3600 s, so a
10
+ * shorter value here would still lease an hour.
11
+ */
5
12
  export const DEFAULT_LEASE_TTL_MS = 60 * 60 * 1000;
6
- /** Default refresh cadence: half the TTL, so two refreshes per lease. */
13
+ /** Default renewal cadence: half the TTL, so two renewals per lease. */
7
14
  export const DEFAULT_REFRESH_MS = 30 * 60 * 1000;
15
+ /** How long to listen for a UPnP gateway's SSDP answer before giving up. */
16
+ export const GATEWAY_DISCOVERY_TIMEOUT_MS = 10000;
17
+ /** Bound on one SOAP call to the router (map, unmap, external IP). */
18
+ export const GATEWAY_OPERATION_TIMEOUT_MS = 10000;
19
+ /** Signal that aborts after `ms`; the timer is cleared by `done()` so it never holds the process open. */
20
+ function deadline(ms) {
21
+ const controller = new AbortController();
22
+ const timer = setTimeout(() => controller.abort(new Error(`timed out after ${ms} ms`)), ms);
23
+ timer.unref();
24
+ return { signal: controller.signal, done: () => clearTimeout(timer) };
25
+ }
8
26
  /**
9
- * Default factory: loads @achingbrain/nat-port-mapper. Wrapped in dynamic
10
- * import so a missing dep at install time doesn't crash the package.
27
+ * Pick the IPv4 address of the local interface whose subnet contains the router.
28
+ *
29
+ * Not `mapAll`: that maps on every local address and leaves stray mappings for
30
+ * VPN and container interfaces. The address returned is the one a user is told
31
+ * to forward to, so it must be the one on the router's own network.
11
32
  */
12
- export async function createDefaultPortMapper(opts = {}) {
13
- let mod;
14
- try {
15
- mod = (await import('@achingbrain/nat-port-mapper'));
16
- }
17
- catch (err) {
18
- throw new NatError('mapping_failed', `@achingbrain/nat-port-mapper is not installed: ${err.message}`);
19
- }
20
- let client;
21
- try {
22
- client = await mod.upnpNat({
33
+ export function pickLanAddress(routerHost, interfaces = networkInterfaces()) {
34
+ for (const entries of Object.values(interfaces)) {
35
+ for (const entry of entries ?? []) {
36
+ if (entry.family !== 'IPv4' || entry.internal || !entry.cidr)
37
+ continue;
38
+ const prefix = Number(entry.cidr.split('/')[1]);
39
+ if (!Number.isInteger(prefix))
40
+ continue;
41
+ const subnet = new BlockList();
42
+ try {
43
+ subnet.addSubnet(entry.address, prefix, 'ipv4');
44
+ if (subnet.check(routerHost, 'ipv4'))
45
+ return entry.address;
46
+ }
47
+ catch (err) {
48
+ log('skipping interface entry %s (%s): %s', entry.address, entry.cidr, err.message);
49
+ }
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+ /**
55
+ * UPnP port mapper over `@achingbrain/nat-port-mapper` v4: `upnpNat()` hands
56
+ * out a client that discovers gateways; mapping, unmapping and the external IP
57
+ * live on the discovered `Gateway`.
58
+ *
59
+ * The library's auto-refresh stays off: cadre-host renews leases itself so it
60
+ * can report lease expiry and per-mapping failures.
61
+ */
62
+ export class UpnpPortMapper {
63
+ constructor(opts = {}) {
64
+ this.gateway = null;
65
+ this.lanAddress = null;
66
+ this.client = upnpNat({
23
67
  description: opts.description ?? 'sereus-cadre-host',
24
- ttl: Math.round((opts.ttlMs ?? DEFAULT_LEASE_TTL_MS) / 1000),
68
+ autoRefresh: false,
25
69
  });
26
70
  }
27
- catch (err) {
28
- throw new NatError('mapping_failed', `upnpNat() failed: ${err.message}`);
29
- }
30
- return new NatPortMapperAdapter(client, opts.ttlMs ?? DEFAULT_LEASE_TTL_MS);
31
- }
32
- class NatPortMapperAdapter {
33
- constructor(client, defaultTtlMs) {
34
- this.client = client;
35
- this.defaultTtlMs = defaultTtlMs;
36
- }
37
- async map(opts) {
38
- const ttlMs = opts.ttlMs ?? this.defaultTtlMs;
39
- const protocol = opts.protocol ?? 'tcp';
40
- let externalIp = null;
41
- try {
42
- externalIp = await this.client.externalIp();
43
- }
44
- catch (err) {
45
- log('externalIp() failed: %s', err.message);
46
- }
71
+ async discover() {
72
+ const gateway = await this.findIpv4Gateway();
73
+ if (!gateway)
74
+ return null;
75
+ const lanAddress = pickLanAddress(gateway.host);
76
+ if (!lanAddress) {
77
+ throw new NatError('router_unreachable', `found a UPnP router at ${gateway.host} but no local IPv4 interface shares its subnet`);
78
+ }
79
+ this.gateway = gateway;
80
+ this.lanAddress = lanAddress;
81
+ log('discovered gateway %s (%s), mapping from %s', gateway.id, gateway.host, lanAddress);
82
+ return { lanAddress, routerHost: gateway.host };
83
+ }
84
+ async map(req) {
85
+ const { gateway, lanAddress } = this.requireGateway();
86
+ const op = deadline(GATEWAY_OPERATION_TIMEOUT_MS);
47
87
  try {
48
- await this.client.map({
49
- localPort: opts.internalPort,
50
- externalPort: opts.externalPort,
51
- protocol,
52
- ttl: Math.round(ttlMs / 1000),
88
+ const mapping = await gateway.map(req.internalPort, lanAddress, {
89
+ externalPort: req.internalPort,
90
+ protocol: req.protocol,
91
+ ttl: req.ttlMs,
92
+ autoRefresh: false,
93
+ signal: op.signal,
53
94
  });
95
+ return { externalPort: mapping.externalPort, leaseExpiresAt: new Date(Date.now() + req.ttlMs) };
54
96
  }
55
97
  catch (err) {
56
- throw new NatError('mapping_failed', `router refused mapping for port ${opts.externalPort}: ${err.message}`);
57
- }
58
- return {
59
- mode: 'auto-upnp',
60
- externalIp,
61
- externalPort: opts.externalPort,
62
- internalPort: opts.internalPort,
63
- protocol,
64
- leaseExpiresAt: new Date(Date.now() + ttlMs),
65
- };
66
- }
67
- async verify(externalPort, protocol = 'tcp') {
68
- void externalPort;
69
- void protocol;
70
- // The current library doesn't expose an introspection API. Treat a
71
- // working externalIp() as a proxy for "router still reachable" — refresh
72
- // failures will surface via re-running map().
98
+ throw new NatError('mapping_failed', `router refused the mapping for port ${req.internalPort}: ${err.message}`);
99
+ }
100
+ finally {
101
+ op.done();
102
+ }
103
+ }
104
+ // NOTE: the library tracks the mappings this process made and `unmap` only sends a
105
+ // delete for those, so after a host restart a port mapped by the previous process is
106
+ // left to expire with its lease rather than deleted. It also names the INTERNAL port
107
+ // as the external one in that delete, so a mapping the router granted on another
108
+ // external port is not deleted either; it, too, expires with its lease (1 h). And it
109
+ // appends one tracked entry per successful `map` of a port, so after N renewals an
110
+ // unmap sends N deletes (the first is the one that counts); they all run under the
111
+ // one 10 s bound, which caps the cost of a process that has renewed for days.
112
+ async unmap(internalPort, _protocol) {
113
+ if (!this.gateway)
114
+ return;
115
+ const op = deadline(GATEWAY_OPERATION_TIMEOUT_MS);
73
116
  try {
74
- await this.client.externalIp();
75
- return true;
117
+ await this.gateway.unmap(internalPort, { signal: op.signal });
76
118
  }
77
119
  catch (err) {
78
- log('verify() failed: %s', err.message);
79
- return false;
120
+ log('unmap(%d) failed: %s', internalPort, err.message);
121
+ }
122
+ finally {
123
+ op.done();
80
124
  }
81
125
  }
82
126
  async externalIp() {
127
+ if (!this.gateway)
128
+ return null;
129
+ const op = deadline(GATEWAY_OPERATION_TIMEOUT_MS);
83
130
  try {
84
- return await this.client.externalIp();
131
+ return await this.gateway.externalIp({ signal: op.signal });
85
132
  }
86
133
  catch (err) {
87
134
  log('externalIp() failed: %s', err.message);
88
135
  return null;
89
136
  }
90
- }
91
- async unmap(externalPort, protocol = 'tcp') {
92
- try {
93
- await this.client.unmap({ externalPort, protocol });
94
- }
95
- catch (err) {
96
- log('unmap(%d) failed: %s', externalPort, err.message);
97
- }
98
- }
99
- async stop() {
100
- try {
101
- await this.client.stop();
137
+ finally {
138
+ op.done();
102
139
  }
103
- catch (err) {
104
- log('stop() failed: %s', err.message);
105
- }
106
- }
107
- }
108
- /**
109
- * Long-running orchestrator over a PortMapper. Owns the refresh timer and
110
- * surfaces the latest state for the NatService snapshot.
111
- */
112
- export class PortMapperService {
113
- constructor(opts) {
114
- this.refreshHandle = null;
115
- this.started = false;
116
- this.mapper = opts.mapper;
117
- this.externalPort = opts.externalPort;
118
- this.internalPort = opts.internalPort;
119
- this.protocol = opts.protocol ?? 'tcp';
120
- this.ttlMs = opts.ttlMs ?? DEFAULT_LEASE_TTL_MS;
121
- this.refreshMs = opts.refreshMs ?? DEFAULT_REFRESH_MS;
122
- this.nowFn = opts.now ?? (() => new Date());
123
- this.setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
124
- this.clearTimer = opts.clearTimer ?? ((h) => clearTimeout(h));
125
- this.state = {
126
- mode: 'disabled',
127
- externalPort: this.externalPort,
128
- internalPort: this.internalPort,
129
- routerExternalIp: null,
130
- leaseExpiresAt: null,
131
- lastError: null,
132
- };
133
140
  }
134
- getState() {
135
- return { ...this.state };
136
- }
137
- /** Install the mapping and schedule periodic refresh. */
138
- async start() {
139
- if (this.started)
140
- return;
141
- this.started = true;
142
- await this.installMapping();
143
- this.scheduleRefresh();
144
- }
145
- /** Release the mapping and clear timers. Idempotent. */
141
+ /**
142
+ * Forget the gateway. Deliberately not `gateway.stop()`: that deletes every
143
+ * mapping this process made, and hosted children keep running across a host
144
+ * restart, so their mappings must outlive this process.
145
+ */
146
146
  async stop() {
147
- if (!this.started)
148
- return;
149
- this.started = false;
150
- if (this.refreshHandle) {
151
- this.clearTimer(this.refreshHandle);
152
- this.refreshHandle = null;
153
- }
147
+ this.gateway = null;
148
+ this.lanAddress = null;
149
+ }
150
+ requireGateway() {
151
+ if (!this.gateway || !this.lanAddress) {
152
+ throw new NatError('router_unreachable', 'no UPnP gateway discovered');
153
+ }
154
+ return { gateway: this.gateway, lanAddress: this.lanAddress };
155
+ }
156
+ /**
157
+ * First IPv4 gateway to answer the SSDP search, or null when none did within
158
+ * the timeout.
159
+ *
160
+ * NOTE: the library searches for `InternetGatewayDevice:2` only (its
161
+ * `upnp/discovery.js`), and a router that implements only version 1 does not
162
+ * answer a version-2 search, so such a router reads as "not found". There is
163
+ * no library option for it; if it shows up on real hosts, the fix is a fork or
164
+ * another library.
165
+ */
166
+ async findIpv4Gateway() {
167
+ const search = deadline(GATEWAY_DISCOVERY_TIMEOUT_MS);
154
168
  try {
155
- await this.mapper.unmap(this.externalPort, this.protocol);
156
- }
157
- catch (err) {
158
- log('unmap during stop failed: %s', err.message);
159
- }
160
- try {
161
- await this.mapper.stop();
169
+ for await (const gateway of this.client.findGateways({ signal: search.signal })) {
170
+ if (gateway.family === 'IPv4')
171
+ return gateway;
172
+ }
173
+ return null;
162
174
  }
163
175
  catch (err) {
164
- log('mapper.stop() failed: %s', err.message);
165
- }
166
- this.state = {
167
- ...this.state,
168
- mode: 'disabled',
169
- routerExternalIp: null,
170
- leaseExpiresAt: null,
171
- };
172
- }
173
- /** Force an immediate verify+refresh cycle (used by testReachability). */
174
- async refresh() {
175
- if (!this.started)
176
- return this.getState();
177
- const ok = await this.mapper.verify(this.externalPort, this.protocol).catch(() => false);
178
- if (!ok) {
179
- log('verify() returned false; re-mapping');
180
- await this.installMapping();
181
- return this.getState();
176
+ if (search.signal.aborted)
177
+ return null;
178
+ throw new NatError('router_unreachable', `UPnP discovery failed: ${err.message}`);
182
179
  }
183
- // Refresh: re-issue the mapping to extend the lease.
184
- await this.installMapping();
185
- return this.getState();
186
- }
187
- scheduleRefresh() {
188
- if (this.refreshHandle)
189
- this.clearTimer(this.refreshHandle);
190
- this.refreshHandle = this.setTimer(() => {
191
- void this.refresh().catch((err) => log('refresh timer error: %s', err.message));
192
- // Re-arm after the call to avoid drift.
193
- this.scheduleRefresh();
194
- }, this.refreshMs);
195
- }
196
- async installMapping() {
197
- try {
198
- const r = await this.mapper.map({
199
- externalPort: this.externalPort,
200
- internalPort: this.internalPort,
201
- protocol: this.protocol,
202
- ttlMs: this.ttlMs,
203
- });
204
- this.state = {
205
- mode: r.mode,
206
- externalPort: r.externalPort,
207
- internalPort: r.internalPort,
208
- routerExternalIp: r.externalIp,
209
- leaseExpiresAt: r.leaseExpiresAt,
210
- lastError: null,
211
- };
212
- log('mapped port %d (mode=%s, externalIp=%s)', r.externalPort, r.mode, r.externalIp ?? '?');
213
- }
214
- catch (err) {
215
- const message = err.message;
216
- this.state = {
217
- ...this.state,
218
- mode: 'failed',
219
- leaseExpiresAt: null,
220
- lastError: message,
221
- };
222
- log('mapping failed: %s', message);
180
+ finally {
181
+ search.done();
223
182
  }
224
183
  }
225
184
  }
@@ -1 +1 @@
1
- {"version":3,"file":"port-mapper.js","sourceRoot":"","sources":["../../src/nat/port-mapper.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"port-mapper.js","sourceRoot":"","sources":["../../src/nat/port-mapper.ts"],"names":[],"mappings":"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"}
@@ -1,19 +1,36 @@
1
- import type { DirectReachability, PortForwardMode } from './types.js';
2
- export interface ReachabilityInput {
3
- portMode: PortForwardMode;
1
+ /**
2
+ * Reachability verdicts: one per hosted node, and the host-level roll-up the
3
+ * dashboard badge shows. Heuristics over the mapping table, not a dial-back —
4
+ * libp2p AutoNAT is a later ticket.
5
+ */
6
+ import type { DirectReachability, NodeReachability, NodeVerdict, PortRoute } from './types.js';
7
+ export interface NodeVerdictInput {
8
+ tcp: PortRoute;
9
+ /** Null for a handle persisted by an older build without a WebSocket port. */
10
+ ws: PortRoute | null;
11
+ /** Whether a public host part exists: a DDNS hostname, or a public IPv4. */
12
+ hostKnown: boolean;
4
13
  cgnatDetected: boolean;
14
+ upnpEnabled: boolean;
15
+ gatewayFound: boolean;
16
+ /** The address a user forwards to; null when no gateway was found. */
17
+ lanAddress: string | null;
18
+ }
19
+ export interface NodeVerdictResult {
20
+ verdict: NodeVerdict;
21
+ reason: string | null;
5
22
  }
6
23
  /**
7
- * v1 reachability heuristic.
8
- *
9
- * Returns:
10
- * - 'cgnat' when CGNAT was detected (highest signal — UPnP succeeded but our
11
- * external IP isn't actually reachable from the open internet).
12
- * - 'unreachable' when port mapping failed.
13
- * - 'reachable' when an auto mapping succeeded.
14
- * - 'unknown' otherwise (manual config, disabled UPnP, etc).
15
- *
16
- * A future ticket will replace this with libp2p AutoNAT or a sereus-owned
17
- * dial-back probe (see the follow-up section of `6.3-cadre-host-nat`).
24
+ * `unreachable` when either port has no usable route or the host part is
25
+ * unknown; otherwise `manual` when either port is forwarded by hand, `direct`
26
+ * when the ports are reached as-is (public mode, with the firewall reminder as
27
+ * the reason), else `mapped`. A UPnP route under CGNAT is not usable: the
28
+ * router's mapping is on a carrier-private address.
29
+ */
30
+ export declare function evaluateNodeReachability(input: NodeVerdictInput): NodeVerdictResult;
31
+ /**
32
+ * Host roll-up: `cgnat` when CGNAT is detected and no node is reachable
33
+ * through a manual route; `unknown` with no running nodes; `reachable` when
34
+ * every running node is `mapped`/`manual`/`direct`; else `unreachable`.
18
35
  */
19
- export declare function evaluateReachability(input: ReachabilityInput): DirectReachability;
36
+ export declare function evaluateHostReachability(nodes: ReadonlyArray<Pick<NodeReachability, 'running' | 'verdict'>>, cgnatDetected: boolean): DirectReachability;
@@ -1,29 +1,117 @@
1
1
  /**
2
- * v1 reachability heuristic.
3
- *
4
- * Returns:
5
- * - 'cgnat' when CGNAT was detected (highest signal — UPnP succeeded but our
6
- * external IP isn't actually reachable from the open internet).
7
- * - 'unreachable' when port mapping failed.
8
- * - 'reachable' when an auto mapping succeeded.
9
- * - 'unknown' otherwise (manual config, disabled UPnP, etc).
10
- *
11
- * A future ticket will replace this with libp2p AutoNAT or a sereus-owned
12
- * dial-back probe (see the follow-up section of `6.3-cadre-host-nat`).
2
+ * Reachability verdicts: one per hosted node, and the host-level roll-up the
3
+ * dashboard badge shows. Heuristics over the mapping table, not a dial-back —
4
+ * libp2p AutoNAT is a later ticket.
13
5
  */
14
- export function evaluateReachability(input) {
6
+ /**
7
+ * `unreachable` when either port has no usable route or the host part is
8
+ * unknown; otherwise `manual` when either port is forwarded by hand, `direct`
9
+ * when the ports are reached as-is (public mode, with the firewall reminder as
10
+ * the reason), else `mapped`. A UPnP route under CGNAT is not usable: the
11
+ * router's mapping is on a carrier-private address.
12
+ */
13
+ export function evaluateNodeReachability(input) {
14
+ const ports = portRoutes(input);
15
+ const failing = ports.filter(({ route }) => !usable(route, input.cgnatDetected));
16
+ if (!input.hostKnown) {
17
+ return { verdict: 'unreachable', reason: HOST_UNKNOWN_REASON };
18
+ }
19
+ if (failing.length > 0) {
20
+ return { verdict: 'unreachable', reason: describeFailures(failing, input) };
21
+ }
22
+ if (ports.some(({ route }) => route.source === 'manual'))
23
+ return { verdict: 'manual', reason: null };
24
+ if (ports.some(({ route }) => route.source === 'direct'))
25
+ return { verdict: 'direct', reason: firewallReminder(ports) };
26
+ return { verdict: 'mapped', reason: null };
27
+ }
28
+ /**
29
+ * Host roll-up: `cgnat` when CGNAT is detected and no node is reachable
30
+ * through a manual route; `unknown` with no running nodes; `reachable` when
31
+ * every running node is `mapped`/`manual`/`direct`; else `unreachable`.
32
+ */
33
+ export function evaluateHostReachability(nodes, cgnatDetected) {
34
+ const running = nodes.filter((n) => n.running);
35
+ if (cgnatDetected && !running.some((n) => n.verdict === 'manual'))
36
+ return 'cgnat';
37
+ if (running.length === 0)
38
+ return 'unknown';
39
+ return running.every((n) => n.verdict !== 'unreachable') ? 'reachable' : 'unreachable';
40
+ }
41
+ const HOST_UNKNOWN_REASON = "This host's public address is unknown: no dynamic DNS hostname is set and no public IPv4 address was detected. " +
42
+ 'Set a DDNS hostname, or check that this machine can reach the internet.';
43
+ function portRoutes(input) {
44
+ const out = [{ kind: 'tcp', route: input.tcp }];
45
+ if (input.ws)
46
+ out.push({ kind: 'ws', route: input.ws });
47
+ return out;
48
+ }
49
+ function usable(route, cgnatDetected) {
50
+ if (route.externalPort === null || route.source === null)
51
+ return false;
52
+ return !(cgnatDetected && route.source === 'upnp');
53
+ }
54
+ /** Behind CGNAT no router-side remedy helps, so CGNAT outranks whatever else failed the port. */
55
+ function causeOf(route, input) {
15
56
  if (input.cgnatDetected)
16
57
  return 'cgnat';
17
- switch (input.portMode) {
18
- case 'auto-upnp':
19
- case 'auto-natpmp':
20
- return 'reachable';
21
- case 'failed':
22
- return 'unreachable';
23
- case 'manual':
24
- case 'disabled':
25
- default:
26
- return 'unknown';
58
+ if (route.error)
59
+ return 'refused';
60
+ if (!input.upnpEnabled)
61
+ return 'upnp_off';
62
+ if (!input.gatewayFound)
63
+ return 'no_gateway';
64
+ return 'pending';
65
+ }
66
+ /** One sentence per distinct cause, each naming every port that failed for it. */
67
+ function describeFailures(failing, input) {
68
+ const byCause = new Map();
69
+ for (const entry of failing) {
70
+ const cause = causeOf(entry.route, input);
71
+ byCause.set(cause, [...(byCause.get(cause) ?? []), entry]);
27
72
  }
73
+ return [...byCause.entries()].map(([cause, entries]) => describeCause(cause, entries, input)).join(' ');
74
+ }
75
+ function describeCause(cause, entries, input) {
76
+ const names = entries.map(({ kind, route }) => `${kindLabel(kind)} port ${route.internalPort}`);
77
+ const lan = input.lanAddress ?? "this machine's LAN address";
78
+ const forwardHint = `Forward ${entries.length === 1 ? 'port' : 'ports'} ${listPorts(entries)} to ${lan} on your router, then tell cadre-host the external ${entries.length === 1 ? 'port' : 'ports'}.`;
79
+ switch (cause) {
80
+ case 'cgnat':
81
+ return `This host is behind carrier-grade NAT, so the ${joinNames(names)} cannot be reached from outside through your router, and neither UPnP nor a port forward on your router will help; a relay is needed.`;
82
+ case 'refused': {
83
+ const errors = entries.map(({ route }) => route.error).filter((e) => !!e);
84
+ return `Router refused the ${joinNames(names)} mapping (${errors.join('; ')}). ${forwardHint}`;
85
+ }
86
+ case 'upnp_off':
87
+ return `UPnP is off, so the ${joinNames(names)} ${entries.length === 1 ? 'is' : 'are'} not mapped. Turn UPnP on, or: ${lowerFirst(forwardHint)}`;
88
+ case 'no_gateway':
89
+ return `No UPnP router answered, so the ${joinNames(names)} ${entries.length === 1 ? 'is' : 'are'} not mapped. Enable UPnP on your router, or: ${lowerFirst(forwardHint)}`;
90
+ case 'pending':
91
+ return `The ${joinNames(names)} ${entries.length === 1 ? 'is' : 'are'} not mapped yet; the router is being asked.`;
92
+ }
93
+ }
94
+ /** Public mode: nothing to map, but a host or cloud firewall can still block the ports. */
95
+ function firewallReminder(ports) {
96
+ const numbers = ports.map(({ route }) => route.internalPort);
97
+ const ufw = numbers.length === 2 && Math.abs(numbers[0] - numbers[1]) === 1
98
+ ? `${Math.min(...numbers)}:${Math.max(...numbers)}/tcp`
99
+ : `${numbers.join(',')}/tcp`;
100
+ return `Reached directly at this machine's public address. Allow TCP ${ports.length === 1 ? 'port' : 'ports'} ${listPorts(ports)} through any firewall `
101
+ + `(for example: sudo ufw allow ${ufw}; and any cloud-provider firewall).`;
102
+ }
103
+ function kindLabel(kind) {
104
+ return kind === 'tcp' ? 'TCP' : 'WebSocket';
105
+ }
106
+ function listPorts(entries) {
107
+ return joinNames(entries.map(({ route }) => String(route.internalPort)));
108
+ }
109
+ function joinNames(names) {
110
+ if (names.length <= 1)
111
+ return names[0] ?? '';
112
+ return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
113
+ }
114
+ function lowerFirst(s) {
115
+ return s.charAt(0).toLowerCase() + s.slice(1);
28
116
  }
29
117
  //# sourceMappingURL=reachability.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reachability.js","sourceRoot":"","sources":["../../src/nat/reachability.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAwB;IAC3D,IAAI,KAAK,CAAC,aAAa;QAAE,OAAO,OAAO,CAAC;IACxC,QAAQ,KAAK,CAAC,QAAQ,EAAE,CAAC;QACvB,KAAK,WAAW,CAAC;QACjB,KAAK,aAAa;YAChB,OAAO,WAAW,CAAC;QACrB,KAAK,QAAQ;YACX,OAAO,aAAa,CAAC;QACvB,KAAK,QAAQ,CAAC;QACd,KAAK,UAAU,CAAC;QAChB;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"reachability.js","sourceRoot":"","sources":["../../src/nat/reachability.ts"],"names":[],"mappings":"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"}