vpn-split 21.0.11 → 21.0.13

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 (96) hide show
  1. package/browser/package.json +1 -1
  2. package/browser-prod/README.md +24 -0
  3. package/browser-prod/fesm2022/vpn-split-browser.mjs +187 -0
  4. package/browser-prod/fesm2022/vpn-split-browser.mjs.map +1 -0
  5. package/browser-prod/package.json +23 -0
  6. package/browser-prod/types/vpn-split-browser.d.ts +65 -0
  7. package/browser-prod.split-namespaces.json +4 -0
  8. package/lib/build-info._auto-generated_.d.ts +5 -1
  9. package/lib/build-info._auto-generated_.js +6 -2
  10. package/lib/build-info._auto-generated_.js.map +1 -1
  11. package/lib/env/env.angular-node-app.d.ts +30 -30
  12. package/lib/env/env.angular-node-app.js +32 -32
  13. package/lib/env/env.angular-node-app.js.map +1 -1
  14. package/lib/env/env.docs-webapp.d.ts +30 -30
  15. package/lib/env/env.docs-webapp.js +32 -32
  16. package/lib/env/env.docs-webapp.js.map +1 -1
  17. package/lib/env/env.electron-app.d.ts +30 -30
  18. package/lib/env/env.electron-app.js +32 -32
  19. package/lib/env/env.electron-app.js.map +1 -1
  20. package/lib/env/env.mobile-app.d.ts +30 -30
  21. package/lib/env/env.mobile-app.js +32 -32
  22. package/lib/env/env.mobile-app.js.map +1 -1
  23. package/lib/env/env.npm-lib-and-cli-tool.d.ts +30 -30
  24. package/lib/env/env.npm-lib-and-cli-tool.js +32 -32
  25. package/lib/env/env.npm-lib-and-cli-tool.js.map +1 -1
  26. package/lib/env/env.vscode-plugin.d.ts +30 -30
  27. package/lib/env/env.vscode-plugin.js +32 -32
  28. package/lib/env/env.vscode-plugin.js.map +1 -1
  29. package/lib/index._auto-generated_.js +1 -1
  30. package/lib/index._auto-generated_.js.map +1 -1
  31. package/lib/migrations/index.d.ts +1 -0
  32. package/lib/migrations/index.js +19 -0
  33. package/lib/migrations/index.js.map +1 -0
  34. package/lib/migrations/migrations_index._auto-generated_.d.ts +0 -0
  35. package/lib/migrations/migrations_index._auto-generated_.js +4 -0
  36. package/lib/migrations/migrations_index._auto-generated_.js.map +1 -0
  37. package/lib/package.json +4 -0
  38. package/lib-prod/build-info._auto-generated_.d.ts +24 -0
  39. package/lib-prod/build-info._auto-generated_.js +30 -0
  40. package/lib-prod/build-info._auto-generated_.js.map +1 -0
  41. package/lib-prod/env/env.angular-node-app.d.ts +64 -0
  42. package/lib-prod/env/env.angular-node-app.js +71 -0
  43. package/lib-prod/env/env.angular-node-app.js.map +1 -0
  44. package/lib-prod/env/env.docs-webapp.d.ts +64 -0
  45. package/lib-prod/env/env.docs-webapp.js +71 -0
  46. package/lib-prod/env/env.docs-webapp.js.map +1 -0
  47. package/lib-prod/env/env.electron-app.d.ts +64 -0
  48. package/lib-prod/env/env.electron-app.js +71 -0
  49. package/lib-prod/env/env.electron-app.js.map +1 -0
  50. package/lib-prod/env/env.mobile-app.d.ts +64 -0
  51. package/lib-prod/env/env.mobile-app.js +71 -0
  52. package/lib-prod/env/env.mobile-app.js.map +1 -0
  53. package/lib-prod/env/env.npm-lib-and-cli-tool.d.ts +64 -0
  54. package/lib-prod/env/env.npm-lib-and-cli-tool.js +71 -0
  55. package/lib-prod/env/env.npm-lib-and-cli-tool.js.map +1 -0
  56. package/lib-prod/env/env.vscode-plugin.d.ts +64 -0
  57. package/lib-prod/env/env.vscode-plugin.js +71 -0
  58. package/lib-prod/env/env.vscode-plugin.js.map +1 -0
  59. package/lib-prod/env/index.d.ts +6 -0
  60. package/lib-prod/env/index.js +23 -0
  61. package/lib-prod/env/index.js.map +1 -0
  62. package/lib-prod/hostile.backend.d.ts +37 -0
  63. package/lib-prod/hostile.backend.js +122 -0
  64. package/lib-prod/hostile.backend.js.map +1 -0
  65. package/lib-prod/index._auto-generated_.js +6 -0
  66. package/lib-prod/index._auto-generated_.js.map +1 -0
  67. package/lib-prod/index.d.ts +2 -0
  68. package/lib-prod/index.js +21 -0
  69. package/lib-prod/index.js.map +1 -0
  70. package/lib-prod/migrations/migrations_index._auto-generated_.d.ts +0 -0
  71. package/lib-prod/models.d.ts +61 -0
  72. package/lib-prod/models.js +182 -0
  73. package/lib-prod/models.js.map +1 -0
  74. package/lib-prod/package.json +4 -0
  75. package/lib-prod/start.backend.d.ts +0 -0
  76. package/lib-prod/start.backend.js +24 -0
  77. package/lib-prod/start.backend.js.map +1 -0
  78. package/lib-prod/vpn-split.backend.d.ts +56 -0
  79. package/lib-prod/vpn-split.backend.js +742 -0
  80. package/lib-prod/vpn-split.backend.js.map +1 -0
  81. package/lib-prod.split-namespaces.json +4 -0
  82. package/package.json +1 -1
  83. package/websql/package.json +1 -1
  84. package/websql-prod/README.md +24 -0
  85. package/websql-prod/fesm2022/vpn-split-websql.mjs +187 -0
  86. package/websql-prod/fesm2022/vpn-split-websql.mjs.map +1 -0
  87. package/websql-prod/package.json +23 -0
  88. package/websql-prod/types/vpn-split-websql.d.ts +65 -0
  89. package/websql-prod.split-namespaces.json +4 -0
  90. package/tmp-environment.json +0 -39
  91. /package/{migrations/migrations_index._auto-generated_.d.ts → lib-prod/index._auto-generated_.d.ts} +0 -0
  92. /package/{migrations → lib-prod/migrations}/index.d.ts +0 -0
  93. /package/{migrations → lib-prod/migrations}/index.js +0 -0
  94. /package/{migrations → lib-prod/migrations}/index.js.map +0 -0
  95. /package/{migrations → lib-prod/migrations}/migrations_index._auto-generated_.js +0 -0
  96. /package/{migrations → lib-prod/migrations}/migrations_index._auto-generated_.js.map +0 -0
@@ -0,0 +1,742 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VpnSplit = void 0;
4
+ // TODO I am using TCP ugrade not UDP!
5
+ const dgram = require("dgram"); // <-- For UDP sockets
6
+ const http = require("http");
7
+ const axios_1 = require("axios");
8
+ const express = require("express");
9
+ const httpProxy = require("http-proxy");
10
+ const lib_prod_1 = require("ng2-logger/lib-prod");
11
+ const lib_prod_2 = require("tnp-core/lib-prod");
12
+ const lib_prod_3 = require("tnp-core/lib-prod");
13
+ const lib_prod_4 = require("tnp-core/lib-prod");
14
+ const lib_prod_5 = require("tnp-helpers/lib-prod");
15
+ const hostile_backend_1 = require("./hostile.backend");
16
+ const models_1 = require("./models");
17
+ const child_process_1 = require("child_process");
18
+ const HOST_FILE_PATH = (0, lib_prod_3.UtilsNetwork_getEtcHostsPath)();
19
+ const log = lib_prod_1.Log.create('vpn-split', lib_prod_1.Level.INFO);
20
+ //#endregion
21
+ //#region consts
22
+ const GENERATED = '#GENERATED_BY_CLI#';
23
+ const EOL = process.platform === 'win32' ? '\r\n' : '\n';
24
+ const SERVERS_PATH = '/$$$$servers$$$$';
25
+ const HOST_FILE_PATHUSER = (0, lib_prod_3.crossPlatformPath)([
26
+ (0, lib_prod_3.UtilsOs_getRealHomeDir)(),
27
+ 'hosts-file__vpn-split',
28
+ ]);
29
+ const from = models_1.HostForServer.From;
30
+ const DefaultEtcHosts = {
31
+ 'localhost alias': from({
32
+ ipOrDomain: '127.0.0.1',
33
+ aliases: 'localhost',
34
+ isDefault: true,
35
+ }),
36
+ broadcasthost: from({
37
+ ipOrDomain: '255.255.255.255',
38
+ aliases: 'broadcasthost',
39
+ isDefault: true,
40
+ }),
41
+ 'localhost alias ipv6': from({
42
+ ipOrDomain: '::1',
43
+ aliases: 'localhost',
44
+ isDefault: true,
45
+ }),
46
+ };
47
+ //#endregion
48
+ class VpnSplit {
49
+ portsToPass;
50
+ hosts;
51
+ cwd;
52
+ //#region getters
53
+ get hostsArr() {
54
+ const hosts = this.hosts;
55
+ return lib_prod_3._.keys(hosts).map(hostName => {
56
+ const v = hosts[hostName];
57
+ v.name = hostName;
58
+ return v;
59
+ });
60
+ }
61
+ get hostsArrWithoutDefault() {
62
+ return this.hostsArr.filter(f => !f.isDefault);
63
+ }
64
+ get serveKeyName() {
65
+ return 'tmp-' + lib_prod_2.config.file.server_key;
66
+ }
67
+ get serveKeyPath() {
68
+ return lib_prod_3.path.join(this.cwd, this.serveKeyName);
69
+ }
70
+ get serveCertName() {
71
+ return 'tmp-' + lib_prod_2.config.file.server_cert;
72
+ }
73
+ get serveCertPath() {
74
+ return lib_prod_3.path.join(this.cwd, this.serveCertName);
75
+ }
76
+ get serveCertChainName() {
77
+ return 'tmp-' + lib_prod_2.config.file.server_chain_cert;
78
+ }
79
+ get serveCertChainPath() {
80
+ return lib_prod_3.path.join(this.cwd, this.serveCertChainName);
81
+ }
82
+ //#endregion
83
+ //#region fields
84
+ __hostile;
85
+ //#endregion
86
+ //#region singleton
87
+ static _instances = {};
88
+ constructor(portsToPass, hosts, cwd) {
89
+ this.portsToPass = portsToPass;
90
+ this.hosts = hosts;
91
+ this.cwd = cwd;
92
+ this.__hostile = new hostile_backend_1.Hostile();
93
+ }
94
+ static async Instance({ ports = [80, 443, 4443, 22, 2222, 8180, 8080, 4407, 7999, 9443], additionalDefaultHosts = {}, cwd = process.cwd(), allowNotSudo = false, } = {}) {
95
+ if (!(await (0, lib_prod_3.isElevated)()) && !allowNotSudo) {
96
+ lib_prod_5.Helpers.error(`[vpn-split] Please run this program as sudo (or admin on windows)`, false, true);
97
+ }
98
+ if (!VpnSplit._instances[cwd]) {
99
+ VpnSplit._instances[cwd] = new VpnSplit(ports, lib_prod_3._.merge(DefaultEtcHosts, additionalDefaultHosts), cwd);
100
+ }
101
+ return VpnSplit._instances[cwd];
102
+ }
103
+ //#endregion
104
+ //#region start server
105
+ async startServer(saveHostInUserFolder = false) {
106
+ this.createCertificateIfNotExists();
107
+ const hostsForCert = this.hosts;
108
+ // const domainsForCert: string[] = Utils_uniqArray(
109
+ // Object.keys(hostsForCert).reduce((prev, curr) => {
110
+ // const host = hostsForCert[curr] as HostForServer;
111
+ // host.aliases.forEach(alias => {
112
+ // if (!host.isDefault) {
113
+ // prev.push(alias);
114
+ // }
115
+ // });
116
+ // return prev;
117
+ // }, [] as string[]),
118
+ // );
119
+ // this.ensureMkcertCertificate(domainsForCert);
120
+ //#region modify /etc/host to direct traffic appropriately
121
+ saveHosts(hostsForCert, { saveHostInUserFolder });
122
+ //#endregion
123
+ // Start TCP/HTTPS passthrough
124
+ for (const portToPassthrough of this.portsToPass) {
125
+ await this.serverPassthrough(portToPassthrough);
126
+ }
127
+ // Start UDP passthrough
128
+ for (const portToPassthrough of this.portsToPass) {
129
+ await this.serverUdpPassthrough(portToPassthrough);
130
+ }
131
+ lib_prod_5.Helpers.info(`Activated (server).`);
132
+ }
133
+ //#endregion
134
+ //#region apply hosts
135
+ applyHosts(hosts) {
136
+ // console.log(hosts);
137
+ saveHosts(hosts);
138
+ }
139
+ applyHostsLocal(hosts) {
140
+ // console.log(hosts);
141
+ saveHostsLocal(hosts);
142
+ }
143
+ //#endregion
144
+ //#region start client
145
+ async startClient(vpnServerTargets, options) {
146
+ options = options || {};
147
+ const { saveHostInUserFolder } = options;
148
+ if (!Array.isArray(vpnServerTargets)) {
149
+ vpnServerTargets = [vpnServerTargets];
150
+ }
151
+ for (const vpnServerTarget of vpnServerTargets) {
152
+ await this.preventBadTargetForClient(vpnServerTarget);
153
+ }
154
+ this.createCertificateIfNotExists();
155
+ // Get remote host definitions from remote server
156
+ const hosts = [];
157
+ for (const vpnServerTarget of vpnServerTargets) {
158
+ const newHosts = await this.getRemoteHosts(vpnServerTarget);
159
+ for (const host of newHosts) {
160
+ // Mark the original VPN server domain
161
+ host.originHostname = vpnServerTarget.hostname;
162
+ hosts.push(host);
163
+ }
164
+ }
165
+ // Merge with original, redirecting all non-default entries to 127.0.0.1
166
+ const originalHosts = this.hostsArr;
167
+ const combinedHostsObj = lib_prod_3._.values([
168
+ ...originalHosts,
169
+ ...hosts.map(h => models_1.HostForServer.From({
170
+ aliases: h.alias,
171
+ ipOrDomain: h.ip,
172
+ originHostname: h.originHostname,
173
+ }, `external host ${h.alias} ${h.ip}`)),
174
+ ]
175
+ .map(c => {
176
+ let copy = c.clone();
177
+ if (!copy.isDefault) {
178
+ copy.ip = `127.0.0.1`;
179
+ }
180
+ return copy;
181
+ })
182
+ .reduce((prev, curr) => {
183
+ return lib_prod_3._.merge(prev, {
184
+ [curr.aliases.join(' ')]: curr,
185
+ });
186
+ }, {}));
187
+ const hostsForCert = options.useHost ? options.useHost : combinedHostsObj;
188
+ // const domainsForCert: string[] = Utils_uniqArray(
189
+ // Object.keys(hostsForCert).reduce((prev, curr) => {
190
+ // const host = hostsForCert[curr] as HostForServer;
191
+ // host.aliases.forEach(alias => {
192
+ // if (!host.isDefault) {
193
+ // prev.push(alias);
194
+ // }
195
+ // });
196
+ // return prev;
197
+ // }, [] as string[]),
198
+ // );
199
+ // this.ensureMkcertCertificate(domainsForCert);
200
+ saveHosts(hostsForCert, {
201
+ saveHostInUserFolder,
202
+ });
203
+ // Start TCP/HTTPS passthrough
204
+ for (const portToPassthrough of this.portsToPass) {
205
+ await this.clientPassthrough(portToPassthrough, vpnServerTargets, combinedHostsObj);
206
+ }
207
+ // Start UDP passthrough
208
+ for (const portToPassthrough of this.portsToPass) {
209
+ await this.clientUdpPassthrough(portToPassthrough, vpnServerTargets, combinedHostsObj);
210
+ }
211
+ lib_prod_5.Helpers.info(`Client activated`);
212
+ }
213
+ //#endregion
214
+ //#region private methods / get remote hosts
215
+ async getRemoteHosts(vpnServerTarget) {
216
+ try {
217
+ const url = `http://${vpnServerTarget.hostname}${SERVERS_PATH}`;
218
+ const response = await (0, axios_1.default)({ url, method: 'GET' });
219
+ return response.data;
220
+ }
221
+ catch (err) {
222
+ lib_prod_5.Helpers.error(`Remote server: ${vpnServerTarget.hostname} may be inactive...`, true, true);
223
+ return [];
224
+ }
225
+ }
226
+ //#endregion
227
+ //#region private methods / create certificate
228
+ createCertificateIfNotExists() {
229
+ if (!lib_prod_5.Helpers.exists(this.serveKeyPath) ||
230
+ !lib_prod_5.Helpers.exists(this.serveCertPath)) {
231
+ lib_prod_5.Helpers.info(`[vpn-split] Generating new self-signed certificate for localhost...`);
232
+ const commandGen = (0, lib_prod_3.UtilsOs_isRunningInWindowsPowerShell)()
233
+ ? `powershell -Command "& 'C:\\Program Files\\Git\\usr\\bin\\openssl.exe' req -nodes -new -x509 -keyout ${this.serveKeyName} -out ${this.serveCertName}"`
234
+ : `openssl req -nodes -new -x509 -keyout ${this.serveKeyName} -out ${this.serveCertName}`;
235
+ lib_prod_5.Helpers.run(commandGen, { cwd: this.cwd, output: true }).sync();
236
+ }
237
+ }
238
+ //#endregion
239
+ //#region private methods / TCP & HTTPS passthrough
240
+ getTarget({ req, res, port, hostname, }) {
241
+ return `${req.protocol}://${hostname}:${port}`;
242
+ }
243
+ getProxyConfig({ req, res, port, hostname, isHttps, }) {
244
+ const serverPassthrough = !!hostname;
245
+ const target = this.getTarget({
246
+ req,
247
+ res,
248
+ port,
249
+ hostname: serverPassthrough ? hostname : req.hostname,
250
+ });
251
+ // console.log({
252
+ // target,
253
+ // port,
254
+ // hostname,
255
+ // reqHostname: req.hostname,
256
+ // serverPassthrough,
257
+ // });
258
+ return isHttps
259
+ ? {
260
+ target,
261
+ ssl: {
262
+ key: lib_prod_3.fse.readFileSync(this.serveKeyPath),
263
+ cert: lib_prod_3.fse.readFileSync(this.serveCertPath),
264
+ },
265
+ // agent: new https.Agent({
266
+ // secureOptions: crypto.constants.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION,
267
+ // }),
268
+ // changeOrigin: true,
269
+ secure: true,
270
+ }
271
+ : { target };
272
+ }
273
+ getNotFoundMsg(req, res, port, type) {
274
+ return `[vpn-split] You are requesting a URL that is not in proxy reach [${type}]
275
+ Protocol: ${req.protocol}
276
+ Hostname: ${req.hostname}
277
+ OriginalUrl: ${req.originalUrl}
278
+ Req.method: ${req.method}
279
+ Port: ${port}
280
+ SERVERS_PATH: ${SERVERS_PATH}`;
281
+ }
282
+ getMaybeChangeOriginTrueMsg(req, res, port, type) {
283
+ return `[vpn-split] Possibly need changeOrigin: true in your proxy config
284
+ Protocol: ${req.protocol}
285
+ Hostname: ${req.hostname}
286
+ OriginalUrl: ${req.originalUrl}
287
+ Req.method: ${req.method}
288
+ Port: ${port}`;
289
+ }
290
+ filterHeaders(req, res) {
291
+ // If you have any headers to remove, do it here:
292
+ const headersToRemove = [
293
+ // 'Strict-Transport-Security',
294
+ // 'Content-Security-Policy',
295
+ // ...
296
+ ];
297
+ headersToRemove.forEach(headerName => {
298
+ delete req.headers[headerName];
299
+ res.setHeader(headerName, '');
300
+ });
301
+ }
302
+ isHttpsPort(port) {
303
+ // Decide your logic for “is HTTPS” here
304
+ return [443, 4443, 9443].includes(port);
305
+ }
306
+ //#region server passthrough (TCP/HTTPS)
307
+ async serverPassthrough(portToPassthrough) {
308
+ const isHttps = this.isHttpsPort(portToPassthrough);
309
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
310
+ const app = express();
311
+ const proxy = httpProxy.createProxyServer({});
312
+ const localIp = await (0, lib_prod_3.UtilsNetwork_getLocalIpAddresses)();
313
+ const currentLocalIps = [
314
+ lib_prod_4.CoreModels_localhostDomain,
315
+ lib_prod_4.CoreModels_localhostIp127,
316
+ ...localIp.map(a => a.address),
317
+ ];
318
+ app.use((req, res, next) => {
319
+ this.filterHeaders(req, res);
320
+ if (currentLocalIps.includes(req.hostname)) {
321
+ if (req.method === 'GET' && req.originalUrl === SERVERS_PATH) {
322
+ res.send(JSON.stringify(this.hostsArrWithoutDefault.map(h => ({
323
+ ip: h.ip,
324
+ alias: lib_prod_5.Helpers.arrays.from(h.aliases).join(' '),
325
+ }))));
326
+ }
327
+ else {
328
+ const msg = this.getNotFoundMsg(req, res, portToPassthrough, 'server');
329
+ log.d(msg);
330
+ res.send(msg);
331
+ }
332
+ next();
333
+ }
334
+ else {
335
+ proxy.web(req, res, this.getProxyConfig({ req, res, port: portToPassthrough, isHttps }), next);
336
+ }
337
+ });
338
+ const server = isHttps
339
+ ? lib_prod_3.https.createServer({
340
+ key: lib_prod_3.fse.readFileSync(this.serveKeyPath),
341
+ cert: lib_prod_3.fse.readFileSync(this.serveCertPath),
342
+ }, app)
343
+ : http.createServer(app);
344
+ await lib_prod_5.Helpers.killProcessByPort(portToPassthrough, { silent: true });
345
+ await new Promise((resolve, reject) => {
346
+ server.listen(portToPassthrough, () => {
347
+ console.log(`TCP/HTTPS server listening on port ${portToPassthrough} (secure=${isHttps})`);
348
+ resolve();
349
+ });
350
+ });
351
+ }
352
+ //#endregion
353
+ //#region client passthrough (TCP/HTTPS)
354
+ async clientPassthrough(portToPassthrough, vpnServerTargets, hostsArr) {
355
+ // Map from an alias => the “real” origin hostname (the server domain)
356
+ const aliasToOriginHostname = {};
357
+ for (const h of hostsArr) {
358
+ for (const alias of h.aliases) {
359
+ aliasToOriginHostname[alias] = h.originHostname;
360
+ }
361
+ }
362
+ // Remove defaults from the map so we don't cause collisions
363
+ delete aliasToOriginHostname['localhost'];
364
+ delete aliasToOriginHostname['broadcasthost'];
365
+ // Build a dictionary from originHostname => URL (for quick lookup)
366
+ const originToUrlMap = {};
367
+ for (const url of vpnServerTargets) {
368
+ originToUrlMap[url.hostname] = url;
369
+ }
370
+ const isHttps = this.isHttpsPort(portToPassthrough);
371
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
372
+ const app = express();
373
+ const proxy = httpProxy.createProxyServer({});
374
+ app.use((req, res, next) => {
375
+ this.filterHeaders(req, res);
376
+ // Identify the real origin server based on alias
377
+ const originHostname = aliasToOriginHostname[req.hostname];
378
+ if (!originHostname) {
379
+ // Not one of our known aliases
380
+ const msg = this.getMaybeChangeOriginTrueMsg(req, res, portToPassthrough, 'client');
381
+ res.send(msg);
382
+ next();
383
+ return;
384
+ }
385
+ // Proxy onward to the real server domain at the same port
386
+ const targetUrlObj = originToUrlMap[originHostname];
387
+ if (!targetUrlObj) {
388
+ const notFoundMsg = this.getNotFoundMsg(req, res, portToPassthrough, 'client');
389
+ res.send(notFoundMsg);
390
+ next();
391
+ return;
392
+ }
393
+ // Forward
394
+ proxy.web(req, res, this.getProxyConfig({
395
+ req,
396
+ res,
397
+ port: portToPassthrough,
398
+ isHttps,
399
+ hostname: targetUrlObj.hostname,
400
+ }), next);
401
+ });
402
+ const server = isHttps
403
+ ? lib_prod_3.https.createServer({
404
+ key: lib_prod_3.fse.readFileSync(this.serveKeyPath),
405
+ cert: lib_prod_3.fse.readFileSync(this.serveCertPath),
406
+ }, app)
407
+ : http.createServer(app);
408
+ await lib_prod_5.Helpers.killProcessByPort(portToPassthrough, { silent: true });
409
+ await new Promise((resolve, reject) => {
410
+ server.listen(portToPassthrough, () => {
411
+ log.i(`TCP/HTTPS client listening on port ${portToPassthrough} (secure=${isHttps})`);
412
+ resolve();
413
+ });
414
+ });
415
+ }
416
+ //#endregion
417
+ //#region UDP passthrough
418
+ /**
419
+ * Start a UDP socket for “server” mode on a given port.
420
+ * This example forwards inbound messages right back to the sender,
421
+ * or you can forward them to an external IP:port if desired.
422
+ */
423
+ async serverUdpPassthrough(port) {
424
+ return;
425
+ // Example of a simple “echo-style” server or forwarder
426
+ const socket = dgram.createSocket('udp4');
427
+ // (Optionally) kill existing processes on that port – though for UDP
428
+ // we might not have a direct “listener process.” Adjust as needed.
429
+ // await Helpers.killProcessByPort(port, { silent: true }).catch(() => {});
430
+ socket.on('message', (msg, rinfo) => {
431
+ // rinfo contains { address, port } of the sender
432
+ // In a typical “server” scenario, you might:
433
+ // 1. Inspect msg
434
+ // 2. Possibly forward to some real backend if needed
435
+ // 3. Or just echo it back
436
+ // For a real forward, do something like:
437
+ // const backendHost = 'some-other-host-or-ip';
438
+ // const backendPort = 9999;
439
+ // socket.send(msg, 0, msg.length, backendPort, backendHost);
440
+ // For a basic echo server:
441
+ socket.send(msg, 0, msg.length, rinfo.port, rinfo.address, err => {
442
+ if (err) {
443
+ log.er(`UDP server send error: ${err}`);
444
+ }
445
+ });
446
+ });
447
+ socket.on('listening', () => {
448
+ const address = socket.address();
449
+ log.i(`UDP server listening at ${address.address}:${address.port}`);
450
+ });
451
+ socket.bind(port);
452
+ }
453
+ /**
454
+ * Start a UDP socket for “client” mode on a given port.
455
+ * This example also just does a trivial pass-through or echo,
456
+ * but you can adapt to forward to a remote server.
457
+ */
458
+ async clientUdpPassthrough(port, vpnServerTargets, hostsArr) {
459
+ return;
460
+ // console.log(`Client UDP passthrough, port ${port}` );
461
+ // In client mode, we typically intercept local UDP traffic on “port”
462
+ // and forward it to the remote server. Then we forward the remote
463
+ // server's response back to the local client.
464
+ const socket = dgram.createSocket('udp4');
465
+ // await Helpers.killProcessByPort(port, { silent: true }).catch(() => {});
466
+ // For simplicity, pick the first server from the array
467
+ // Or add your own logic to choose among multiple.
468
+ const primaryTarget = vpnServerTargets[0];
469
+ const targetHost = primaryTarget.hostname;
470
+ // Choose the same port or a custom port
471
+ const targetPort = port;
472
+ // A map to remember who originally sent us a packet,
473
+ // so we can route the response back properly.
474
+ // Key could be “targetHost:targetPort => { address, port }”
475
+ // or “clientAddress:clientPort” => { remoteAddress, remotePort }.
476
+ // Adapt to your scenario.
477
+ const clientMap = new Map();
478
+ socket.on('message', (msg, rinfo) => {
479
+ //
480
+ // If the message is from a local client, forward to server.
481
+ // If the message is from the server, forward back to the correct client.
482
+ //
483
+ // Check if it’s from local or remote by address. This is simplistic:
484
+ const isFromLocal = !lib_prod_3._.includes(hostsArr.map(h => h.ipOrDomain), rinfo.address);
485
+ if (isFromLocal) {
486
+ // Received from local => forward to “server” (the VPN server)
487
+ // Keep track of who we got this from (the local client)
488
+ const key = `local-${rinfo.address}:${rinfo.port}`;
489
+ clientMap.set(key, rinfo);
490
+ // Forward to remote
491
+ socket.send(msg, 0, msg.length, targetPort, targetHost, err => {
492
+ if (err) {
493
+ log.er(`UDP client forward error: ${err}`);
494
+ }
495
+ });
496
+ }
497
+ else {
498
+ // Probably from remote => forward back to whichever local client sent it
499
+ // In a more advanced scenario, parse the payload or maintain a bigger table
500
+ // with NAT-like sessions.
501
+ //
502
+ // For now, we guess it’s from the “VPN server”
503
+ // We'll just route it back to the single local client that we stored
504
+ // or do multiple if we had a better NAT map.
505
+ // If you have multi-target or multi-client logic, adapt here.
506
+ // For example, search clientMap by something in the msg or a NAT key.
507
+ clientMap.forEach((localRinfo, key) => {
508
+ socket.send(msg, 0, msg.length, localRinfo.port, localRinfo.address, err => {
509
+ if (err) {
510
+ log.er(`UDP client re-send error: ${err}`);
511
+ }
512
+ });
513
+ });
514
+ }
515
+ });
516
+ socket.on('listening', () => {
517
+ const address = socket.address();
518
+ log.i(`UDP client listening at ${address.address}:${address.port}, forwarding to ${targetHost}:${targetPort}`);
519
+ });
520
+ socket.bind(port);
521
+ }
522
+ //#endregion
523
+ //#region private methods / get port from request
524
+ getPortFromRequest(req) {
525
+ const host = req.headers.host;
526
+ const protocol = req.protocol;
527
+ if (host) {
528
+ const hostParts = host.split(':');
529
+ if (hostParts.length === 2) {
530
+ return Number(hostParts[1]);
531
+ }
532
+ else {
533
+ return protocol === 'https' ? 443 : 80;
534
+ }
535
+ }
536
+ return 80;
537
+ }
538
+ //#endregion
539
+ // Put this method in your VpnSplit class (or outside as a static helper)
540
+ ensureMkcertCertificate(domains) {
541
+ //#region @backendFunc
542
+ // console.log('Domains for cert:', domains);
543
+ // Helpers.error(``,false,true);
544
+ if (!(0, lib_prod_3.UtilsOs_commandExistsSync)('mkcert')) {
545
+ lib_prod_5.Helpers.error('[vpn-split] mkcert is not installed.', false, true);
546
+ }
547
+ const certPath = this.serveCertPath; // e.g. .../tmp-server_cert
548
+ const keyPath = this.serveKeyPath; // e.g. .../tmp-server_key
549
+ const allNames = [
550
+ 'localhost',
551
+ '127.0.0.1',
552
+ // '::1',
553
+ // optional: common local networks so the cert also works for raw IP access
554
+ // '10.0.0.0/8',
555
+ // '172.16.0.0/12',
556
+ // '192.168.0.0/16',
557
+ ...domains, // <-- your 120+ domains here
558
+ ];
559
+ // Helper: run mkcert and capture output
560
+ const runMkcert = (args) => {
561
+ try {
562
+ return (0, child_process_1.execSync)(`mkcert ${args}`, { stdio: 'pipe' }).toString().trim();
563
+ }
564
+ catch (err) {
565
+ const stderr = err.stderr?.toString() || '';
566
+ if (stderr.includes('command not found')) {
567
+ lib_prod_5.Helpers.error('[vpn-split] mkcert is not installed. Install it first: https://github.com/FiloSottile/mkcert', false, true);
568
+ }
569
+ throw err;
570
+ }
571
+ };
572
+ // 1. Check if the root CA is already installed
573
+ let caInstalled = false;
574
+ try {
575
+ const output = runMkcert('-CAROOT');
576
+ // If -CAROOT succeeds and the root files exist → CA is installed
577
+ const rootDir = (0, lib_prod_3.crossPlatformPath)(output.trim());
578
+ if (rootDir &&
579
+ lib_prod_3.fse.pathExistsSync((0, lib_prod_3.crossPlatformPath)([rootDir, 'rootCA.pem']))) {
580
+ lib_prod_5.Helpers.info('[vpn-split] mkcert local CA is already installed');
581
+ caInstalled = true;
582
+ }
583
+ else {
584
+ lib_prod_5.Helpers.info('[vpn-split] mkcert local CA is NOT installed');
585
+ }
586
+ }
587
+ catch {
588
+ lib_prod_5.Helpers.info('[vpn-split] mkcert error checking local CA installation');
589
+ }
590
+ // 2. Install the local CA if needed (requires sudo once)
591
+ if (!caInstalled) {
592
+ lib_prod_5.Helpers.info('[vpn-split] Installing mkcert local CA (requires sudo once)...');
593
+ try {
594
+ (0, child_process_1.execSync)('mkcert -install', { stdio: 'inherit' });
595
+ lib_prod_5.Helpers.info('[vpn-split] Local CA installed and trusted system-wide');
596
+ }
597
+ catch (err) {
598
+ lib_prod_5.Helpers.error('[vpn-split] Failed to run "mkcert -install". Run it manually with sudo.', false, true);
599
+ }
600
+ }
601
+ const isWindows = process.platform === 'win32';
602
+ // const maxArgLength = isWindows ? 2000 : 8000; // Conservative limits
603
+ let tempCertFiles = [];
604
+ let tempKeyFiles = [];
605
+ if (allNames.length <= 20 || !isWindows) {
606
+ // Small list or non-Windows: one shot
607
+ const namesArg = allNames.map(d => `"${d}"`).join(' ');
608
+ const mkcertCmd = `-key-file "${keyPath}" -cert-file "${certPath}" ${namesArg}`;
609
+ lib_prod_3.fse.ensureDirSync(lib_prod_3.path.dirname(certPath));
610
+ runMkcert(mkcertCmd);
611
+ }
612
+ else {
613
+ // Big list on Windows: Batch into chunks of ~20 domains
614
+ const chunkSize = 20;
615
+ lib_prod_3.fse.ensureDirSync(lib_prod_3.path.dirname(certPath));
616
+ for (let i = 0; i < allNames.length; i += chunkSize) {
617
+ const chunk = allNames.slice(i, i + chunkSize);
618
+ const tempCert = `${certPath}.${i}.pem`;
619
+ const tempKey = `${keyPath}.${i}.pem`;
620
+ const namesArg = chunk.map(d => `"${d}"`).join(' ');
621
+ const mkcertCmd = `-key-file "${tempKey}" -cert-file "${tempCert}" ${namesArg}`;
622
+ lib_prod_5.Helpers.info(`[vpn-split] Generating batch ${Math.floor(i / chunkSize) + 1}/${Math.ceil(allNames.length / chunkSize)}...`);
623
+ runMkcert(mkcertCmd);
624
+ tempCertFiles.push(tempCert);
625
+ tempKeyFiles.push(tempKey);
626
+ }
627
+ // 3. Merge all temp certs into one (cert chain + leaves) and pick first key
628
+ lib_prod_5.Helpers.info('[vpn-split] Merging batches into final cert...');
629
+ const firstKey = tempKeyFiles[0];
630
+ lib_prod_3.fse.copySync(firstKey, keyPath); // Use the first key (all are identical)
631
+ // Concat all certs (mkcert output is already PEM-formatted)
632
+ const mergedCertContent = tempCertFiles
633
+ .map(f => lib_prod_3.fse.readFileSync(f, 'utf8'))
634
+ .join('\n'); // PEM certs stack nicely
635
+ lib_prod_3.fse.writeFileSync(certPath, mergedCertContent);
636
+ // Clean up temps
637
+ tempCertFiles.forEach(f => lib_prod_3.fse.removeSync(f));
638
+ tempKeyFiles.slice(1).forEach(f => lib_prod_3.fse.removeSync(f)); // Keep first key as backup if needed
639
+ lib_prod_5.Helpers.info('[vpn-split] Merged cert ready!');
640
+ }
641
+ // Verify
642
+ if (!lib_prod_3.fse.existsSync(certPath) || !lib_prod_3.fse.existsSync(keyPath)) {
643
+ lib_prod_5.Helpers.error('[vpn-split] Files missing after generation!', false, true);
644
+ }
645
+ else {
646
+ lib_prod_5.Helpers.info(`[vpn-split] Trusted cert ready: ${allNames.length} hostnames covered`);
647
+ }
648
+ //#endregion
649
+ }
650
+ //#region private methods / prevent bad target for client
651
+ async preventBadTargetForClient(vpnServerTarget) {
652
+ if (!vpnServerTarget) {
653
+ const currentLocalIp = await (0, lib_prod_3.UtilsNetwork_getFirstIpV4LocalActiveIpAddress)();
654
+ lib_prod_5.Helpers.error(`[vpn-server] Please provide a correct target server.\n` +
655
+ `Example:\n` +
656
+ `vpn-server ${currentLocalIp}\n\n` +
657
+ `Your local IP is: ${currentLocalIp}`, false, true);
658
+ }
659
+ }
660
+ }
661
+ exports.VpnSplit = VpnSplit;
662
+ //#region helper / gen msg
663
+ const genMsg = `
664
+ ################################################
665
+ ## This file is generated #####################
666
+ ################################################
667
+ `.trim() + EOL;
668
+ //#endregion
669
+ //#region helpers / save hosts
670
+ function saveHosts(hosts, options) {
671
+ const { saveHostInUserFolder } = options || {};
672
+ if (lib_prod_3._.isArray(hosts)) {
673
+ hosts = hosts.reduce((prev, curr) => {
674
+ return lib_prod_3._.merge(prev, {
675
+ [curr.name]: curr,
676
+ });
677
+ }, {});
678
+ }
679
+ const toSave = parseHost(hosts, !!saveHostInUserFolder);
680
+ if (saveHostInUserFolder) {
681
+ lib_prod_5.Helpers.writeFile(HOST_FILE_PATHUSER, toSave);
682
+ }
683
+ else {
684
+ lib_prod_5.Helpers.writeFile(HOST_FILE_PATH, toSave);
685
+ }
686
+ }
687
+ function saveHostsLocal(hosts, options) {
688
+ const { saveHostInUserFolder } = options || {};
689
+ if (lib_prod_3._.isArray(hosts)) {
690
+ hosts = hosts.reduce((prev, curr) => {
691
+ return lib_prod_3._.merge(prev, {
692
+ [curr.name]: curr,
693
+ });
694
+ }, {});
695
+ }
696
+ const toSave = parseHost(hosts, !!saveHostInUserFolder, true);
697
+ if (saveHostInUserFolder) {
698
+ lib_prod_5.Helpers.writeFile(HOST_FILE_PATHUSER, toSave);
699
+ }
700
+ else {
701
+ lib_prod_5.Helpers.writeFile(HOST_FILE_PATH, toSave);
702
+ }
703
+ }
704
+ //#endregion
705
+ //#region helpers / parse hosts
706
+ function parseHost(hosts, saveHostInUserFolder, useLocal = false) {
707
+ // hosts = _.merge(hosts, DefaultEtcHosts);
708
+ hosts = {
709
+ ...DefaultEtcHosts,
710
+ ...hosts,
711
+ };
712
+ lib_prod_3._.keys(hosts).forEach(hostName => {
713
+ const v = hosts[hostName];
714
+ v.name = hostName;
715
+ });
716
+ return (genMsg +
717
+ EOL +
718
+ lib_prod_3._.keys(hosts)
719
+ .map(hostName => {
720
+ const v = hosts[hostName];
721
+ if (v.skipUpdateOfServerEtcHosts) {
722
+ console.warn(`[vpn-split] Skip saving host: ${v.name} (${v.ipOrDomain})`);
723
+ return `# SKIPPING HOST ${v.ipOrDomain} ${v.aliases.join(' ')} ${GENERATED}`; // Skip saving this host
724
+ }
725
+ const aliasesStr = v.aliases.join(' ');
726
+ if (saveHostInUserFolder) {
727
+ // For a user-specific hosts file:
728
+ return useLocal
729
+ ? `127.0.0.1 ${aliasesStr}`
730
+ : `${v.disabled ? '#' : ''}${v.ipOrDomain} ${aliasesStr}`;
731
+ }
732
+ return useLocal
733
+ ? `127.0.0.1 ${aliasesStr}`
734
+ : `${v.disabled ? '#' : ''}${v.ipOrDomain} ${aliasesStr} # ${v.name} ${GENERATED}`;
735
+ })
736
+ .join(EOL) +
737
+ EOL +
738
+ EOL +
739
+ genMsg);
740
+ }
741
+ //#endregion
742
+ //# sourceMappingURL=vpn-split.backend.js.map