@push.rocks/smartproxy 19.3.2 → 19.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/forwarding/factory/forwarding-factory.js +29 -1
- package/dist_ts/http/index.d.ts +1 -3
- package/dist_ts/http/index.js +4 -10
- package/dist_ts/http/models/http-types.d.ts +4 -91
- package/dist_ts/http/models/http-types.js +5 -60
- package/dist_ts/http/router/proxy-router.d.ts +1 -1
- package/dist_ts/http/router/route-router.d.ts +1 -1
- package/dist_ts/index.d.ts +9 -7
- package/dist_ts/index.js +10 -7
- package/dist_ts/proxies/{network-proxy → http-proxy}/certificate-manager.d.ts +2 -2
- package/dist_ts/proxies/{network-proxy → http-proxy}/certificate-manager.js +1 -1
- package/dist_ts/proxies/{network-proxy → http-proxy}/connection-pool.d.ts +2 -2
- package/dist_ts/proxies/http-proxy/connection-pool.js +210 -0
- package/dist_ts/proxies/http-proxy/context-creator.js +108 -0
- package/dist_ts/proxies/{network-proxy → http-proxy}/function-cache.js +1 -1
- package/dist_ts/proxies/http-proxy/handlers/index.d.ts +5 -0
- package/dist_ts/proxies/http-proxy/handlers/index.js +6 -0
- package/dist_ts/proxies/http-proxy/handlers/redirect-handler.d.ts +18 -0
- package/dist_ts/proxies/http-proxy/handlers/redirect-handler.js +78 -0
- package/dist_ts/proxies/http-proxy/handlers/static-handler.d.ts +19 -0
- package/dist_ts/proxies/http-proxy/handlers/static-handler.js +203 -0
- package/dist_ts/proxies/{network-proxy/network-proxy.d.ts → http-proxy/http-proxy.d.ts} +10 -9
- package/dist_ts/proxies/{network-proxy/network-proxy.js → http-proxy/http-proxy.js} +13 -12
- package/dist_ts/proxies/{network-proxy → http-proxy}/http-request-handler.js +1 -1
- package/dist_ts/proxies/http-proxy/http2-request-handler.js +201 -0
- package/dist_ts/proxies/{network-proxy → http-proxy}/index.d.ts +2 -2
- package/dist_ts/proxies/http-proxy/index.js +12 -0
- package/dist_ts/proxies/http-proxy/models/http-types.d.ts +119 -0
- package/dist_ts/proxies/http-proxy/models/http-types.js +112 -0
- package/dist_ts/proxies/http-proxy/models/index.d.ts +5 -0
- package/dist_ts/proxies/http-proxy/models/index.js +6 -0
- package/dist_ts/proxies/{network-proxy → http-proxy}/models/types.d.ts +2 -2
- package/dist_ts/proxies/http-proxy/models/types.js +276 -0
- package/dist_ts/proxies/{network-proxy → http-proxy}/request-handler.d.ts +3 -3
- package/dist_ts/proxies/{network-proxy → http-proxy}/request-handler.js +2 -2
- package/dist_ts/proxies/http-proxy/security-manager.js +255 -0
- package/dist_ts/proxies/{network-proxy → http-proxy}/websocket-handler.d.ts +3 -3
- package/dist_ts/proxies/{network-proxy → http-proxy}/websocket-handler.js +2 -2
- package/dist_ts/proxies/index.d.ts +5 -5
- package/dist_ts/proxies/index.js +5 -5
- package/dist_ts/proxies/smart-proxy/certificate-manager.d.ts +4 -4
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +11 -11
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.d.ts +41 -0
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +121 -0
- package/dist_ts/proxies/smart-proxy/index.d.ts +2 -1
- package/dist_ts/proxies/smart-proxy/index.js +4 -2
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +2 -2
- package/dist_ts/proxies/smart-proxy/port-manager.js +3 -3
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +3 -3
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +24 -265
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +25 -25
- package/dist_ts/routing/index.d.ts +5 -0
- package/dist_ts/routing/index.js +8 -0
- package/dist_ts/routing/models/http-types.d.ts +6 -0
- package/dist_ts/routing/models/http-types.js +7 -0
- package/dist_ts/routing/router/index.d.ts +8 -0
- package/dist_ts/routing/router/index.js +7 -0
- package/dist_ts/{classes.router.d.ts → routing/router/proxy-router.d.ts} +14 -11
- package/dist_ts/{classes.router.js → routing/router/proxy-router.js} +2 -2
- package/dist_ts/routing/router/route-router.d.ts +108 -0
- package/dist_ts/routing/router/route-router.js +393 -0
- package/package.json +1 -1
- package/readme.md +12 -12
- package/readme.plan.md +152 -257
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/forwarding/factory/forwarding-factory.ts +28 -0
- package/ts/index.ts +13 -9
- package/ts/proxies/{network-proxy → http-proxy}/certificate-manager.ts +2 -2
- package/ts/proxies/{network-proxy → http-proxy}/connection-pool.ts +2 -2
- package/ts/proxies/http-proxy/handlers/index.ts +6 -0
- package/ts/proxies/http-proxy/handlers/redirect-handler.ts +105 -0
- package/ts/proxies/http-proxy/handlers/static-handler.ts +251 -0
- package/ts/proxies/{network-proxy/network-proxy.ts → http-proxy/http-proxy.ts} +15 -14
- package/ts/proxies/{network-proxy → http-proxy}/index.ts +3 -3
- package/ts/proxies/http-proxy/models/http-types.ts +165 -0
- package/ts/proxies/http-proxy/models/index.ts +5 -0
- package/ts/proxies/{network-proxy → http-proxy}/models/types.ts +2 -2
- package/ts/proxies/{network-proxy → http-proxy}/request-handler.ts +3 -3
- package/ts/proxies/{network-proxy → http-proxy}/websocket-handler.ts +3 -3
- package/ts/proxies/index.ts +7 -7
- package/ts/proxies/smart-proxy/certificate-manager.ts +10 -10
- package/ts/proxies/smart-proxy/{network-proxy-bridge.ts → http-proxy-bridge.ts} +44 -44
- package/ts/proxies/smart-proxy/index.ts +4 -1
- package/ts/proxies/smart-proxy/models/interfaces.ts +3 -3
- package/ts/proxies/smart-proxy/port-manager.ts +2 -2
- package/ts/proxies/smart-proxy/route-connection-handler.ts +23 -307
- package/ts/proxies/smart-proxy/smart-proxy.ts +25 -25
- package/ts/routing/index.ts +9 -0
- package/ts/routing/models/http-types.ts +6 -0
- package/ts/{http → routing}/router/proxy-router.ts +1 -1
- package/ts/{http → routing}/router/route-router.ts +1 -1
- package/dist_ts/certificate/acme/acme-factory.d.ts +0 -17
- package/dist_ts/certificate/acme/acme-factory.js +0 -40
- package/dist_ts/certificate/acme/challenge-handler.d.ts +0 -44
- package/dist_ts/certificate/acme/challenge-handler.js +0 -92
- package/dist_ts/certificate/acme/index.d.ts +0 -4
- package/dist_ts/certificate/acme/index.js +0 -5
- package/dist_ts/certificate/certificate-manager.d.ts +0 -150
- package/dist_ts/certificate/certificate-manager.js +0 -505
- package/dist_ts/certificate/events/certificate-events.d.ts +0 -33
- package/dist_ts/certificate/events/certificate-events.js +0 -38
- package/dist_ts/certificate/events/simplified-events.d.ts +0 -56
- package/dist_ts/certificate/events/simplified-events.js +0 -13
- package/dist_ts/certificate/index.d.ts +0 -30
- package/dist_ts/certificate/index.js +0 -37
- package/dist_ts/certificate/models/certificate-errors.d.ts +0 -69
- package/dist_ts/certificate/models/certificate-errors.js +0 -141
- package/dist_ts/certificate/models/certificate-strategy.d.ts +0 -60
- package/dist_ts/certificate/models/certificate-strategy.js +0 -73
- package/dist_ts/certificate/models/certificate-types.d.ts +0 -97
- package/dist_ts/certificate/models/certificate-types.js +0 -2
- package/dist_ts/certificate/providers/cert-provisioner.d.ts +0 -119
- package/dist_ts/certificate/providers/cert-provisioner.js +0 -422
- package/dist_ts/certificate/providers/index.d.ts +0 -4
- package/dist_ts/certificate/providers/index.js +0 -5
- package/dist_ts/certificate/simplified-certificate-manager.d.ts +0 -150
- package/dist_ts/certificate/simplified-certificate-manager.js +0 -501
- package/dist_ts/certificate/storage/file-storage.d.ts +0 -66
- package/dist_ts/certificate/storage/file-storage.js +0 -194
- package/dist_ts/certificate/storage/index.d.ts +0 -4
- package/dist_ts/certificate/storage/index.js +0 -5
- package/dist_ts/certificate/utils/certificate-helpers.d.ts +0 -17
- package/dist_ts/certificate/utils/certificate-helpers.js +0 -45
- package/dist_ts/classes.iptablesproxy.d.ts +0 -112
- package/dist_ts/classes.iptablesproxy.js +0 -765
- package/dist_ts/classes.networkproxy.d.ts +0 -243
- package/dist_ts/classes.networkproxy.js +0 -1424
- package/dist_ts/classes.nftablesproxy.d.ts +0 -219
- package/dist_ts/classes.nftablesproxy.js +0 -1542
- package/dist_ts/classes.port80handler.d.ts +0 -215
- package/dist_ts/classes.port80handler.js +0 -736
- package/dist_ts/classes.portproxy.d.ts +0 -171
- package/dist_ts/classes.portproxy.js +0 -1802
- package/dist_ts/classes.pp.acmemanager.d.ts +0 -34
- package/dist_ts/classes.pp.acmemanager.js +0 -123
- package/dist_ts/classes.pp.connectionhandler.d.ts +0 -39
- package/dist_ts/classes.pp.connectionhandler.js +0 -754
- package/dist_ts/classes.pp.connectionmanager.d.ts +0 -78
- package/dist_ts/classes.pp.connectionmanager.js +0 -378
- package/dist_ts/classes.pp.domainconfigmanager.d.ts +0 -55
- package/dist_ts/classes.pp.domainconfigmanager.js +0 -103
- package/dist_ts/classes.pp.interfaces.d.ts +0 -133
- package/dist_ts/classes.pp.interfaces.js +0 -2
- package/dist_ts/classes.pp.networkproxybridge.d.ts +0 -57
- package/dist_ts/classes.pp.networkproxybridge.js +0 -306
- package/dist_ts/classes.pp.portproxy.d.ts +0 -64
- package/dist_ts/classes.pp.portproxy.js +0 -567
- package/dist_ts/classes.pp.portrangemanager.d.ts +0 -56
- package/dist_ts/classes.pp.portrangemanager.js +0 -179
- package/dist_ts/classes.pp.securitymanager.d.ts +0 -47
- package/dist_ts/classes.pp.securitymanager.js +0 -126
- package/dist_ts/classes.pp.snihandler.d.ts +0 -153
- package/dist_ts/classes.pp.snihandler.js +0 -1053
- package/dist_ts/classes.pp.timeoutmanager.d.ts +0 -47
- package/dist_ts/classes.pp.timeoutmanager.js +0 -154
- package/dist_ts/classes.pp.tlsalert.d.ts +0 -149
- package/dist_ts/classes.pp.tlsalert.js +0 -225
- package/dist_ts/classes.pp.tlsmanager.d.ts +0 -57
- package/dist_ts/classes.pp.tlsmanager.js +0 -132
- package/dist_ts/classes.snihandler.d.ts +0 -198
- package/dist_ts/classes.snihandler.js +0 -1210
- package/dist_ts/classes.sslredirect.d.ts +0 -8
- package/dist_ts/classes.sslredirect.js +0 -28
- package/dist_ts/common/acmeFactory.d.ts +0 -9
- package/dist_ts/common/acmeFactory.js +0 -20
- package/dist_ts/common/port80-adapter.d.ts +0 -11
- package/dist_ts/common/port80-adapter.js +0 -87
- package/dist_ts/examples/forwarding-example.d.ts +0 -1
- package/dist_ts/examples/forwarding-example.js +0 -96
- package/dist_ts/forwarding/config/domain-config.d.ts +0 -12
- package/dist_ts/forwarding/config/domain-config.js +0 -12
- package/dist_ts/forwarding/config/domain-manager.d.ts +0 -86
- package/dist_ts/forwarding/config/domain-manager.js +0 -242
- package/dist_ts/helpers.certificates.d.ts +0 -5
- package/dist_ts/helpers.certificates.js +0 -23
- package/dist_ts/http/port80/acme-interfaces.d.ts +0 -108
- package/dist_ts/http/port80/acme-interfaces.js +0 -51
- package/dist_ts/http/port80/challenge-responder.d.ts +0 -53
- package/dist_ts/http/port80/challenge-responder.js +0 -203
- package/dist_ts/http/port80/index.d.ts +0 -6
- package/dist_ts/http/port80/index.js +0 -9
- package/dist_ts/http/port80/port80-handler.d.ts +0 -136
- package/dist_ts/http/port80/port80-handler.js +0 -592
- package/dist_ts/http/redirects/index.d.ts +0 -4
- package/dist_ts/http/redirects/index.js +0 -5
- package/dist_ts/networkproxy/classes.np.certificatemanager.d.ts +0 -77
- package/dist_ts/networkproxy/classes.np.certificatemanager.js +0 -372
- package/dist_ts/networkproxy/classes.np.connectionpool.d.ts +0 -47
- package/dist_ts/networkproxy/classes.np.connectionpool.js +0 -210
- package/dist_ts/networkproxy/classes.np.networkproxy.d.ts +0 -118
- package/dist_ts/networkproxy/classes.np.networkproxy.js +0 -387
- package/dist_ts/networkproxy/classes.np.requesthandler.d.ts +0 -56
- package/dist_ts/networkproxy/classes.np.requesthandler.js +0 -393
- package/dist_ts/networkproxy/classes.np.types.d.ts +0 -83
- package/dist_ts/networkproxy/classes.np.types.js +0 -35
- package/dist_ts/networkproxy/classes.np.websockethandler.d.ts +0 -38
- package/dist_ts/networkproxy/classes.np.websockethandler.js +0 -188
- package/dist_ts/networkproxy/index.d.ts +0 -1
- package/dist_ts/networkproxy/index.js +0 -4
- package/dist_ts/nfttablesproxy/classes.nftablesproxy.d.ts +0 -219
- package/dist_ts/nfttablesproxy/classes.nftablesproxy.js +0 -1542
- package/dist_ts/port80handler/classes.port80handler.d.ts +0 -10
- package/dist_ts/port80handler/classes.port80handler.js +0 -16
- package/dist_ts/proxies/network-proxy/connection-pool.js +0 -210
- package/dist_ts/proxies/network-proxy/context-creator.js +0 -108
- package/dist_ts/proxies/network-proxy/http2-request-handler.js +0 -201
- package/dist_ts/proxies/network-proxy/index.js +0 -12
- package/dist_ts/proxies/network-proxy/models/index.d.ts +0 -4
- package/dist_ts/proxies/network-proxy/models/index.js +0 -5
- package/dist_ts/proxies/network-proxy/models/types.js +0 -276
- package/dist_ts/proxies/network-proxy/security-manager.js +0 -255
- package/dist_ts/proxies/network-proxy/simplified-certificate-bridge.d.ts +0 -48
- package/dist_ts/proxies/network-proxy/simplified-certificate-bridge.js +0 -76
- package/dist_ts/proxies/smart-proxy/connection-handler.d.ts +0 -39
- package/dist_ts/proxies/smart-proxy/connection-handler.js +0 -894
- package/dist_ts/proxies/smart-proxy/domain-config-manager.d.ts +0 -110
- package/dist_ts/proxies/smart-proxy/domain-config-manager.js +0 -386
- package/dist_ts/proxies/smart-proxy/legacy-smart-proxy.d.ts +0 -168
- package/dist_ts/proxies/smart-proxy/legacy-smart-proxy.js +0 -642
- package/dist_ts/proxies/smart-proxy/models/simplified-smartproxy-config.d.ts +0 -65
- package/dist_ts/proxies/smart-proxy/models/simplified-smartproxy-config.js +0 -31
- package/dist_ts/proxies/smart-proxy/models/smartproxy-options.d.ts +0 -102
- package/dist_ts/proxies/smart-proxy/models/smartproxy-options.js +0 -73
- package/dist_ts/proxies/smart-proxy/network-proxy-bridge.d.ts +0 -41
- package/dist_ts/proxies/smart-proxy/network-proxy-bridge.js +0 -121
- package/dist_ts/proxies/smart-proxy/port-range-manager.d.ts +0 -56
- package/dist_ts/proxies/smart-proxy/port-range-manager.js +0 -176
- package/dist_ts/proxies/smart-proxy/route-helpers/index.d.ts +0 -9
- package/dist_ts/proxies/smart-proxy/route-helpers/index.js +0 -11
- package/dist_ts/proxies/smart-proxy/route-helpers.d.ts +0 -7
- package/dist_ts/proxies/smart-proxy/route-helpers.js +0 -9
- package/dist_ts/proxies/smart-proxy/simplified-smart-proxy.d.ts +0 -41
- package/dist_ts/proxies/smart-proxy/simplified-smart-proxy.js +0 -132
- package/dist_ts/proxies/smart-proxy/utils/route-migration-utils.d.ts +0 -51
- package/dist_ts/proxies/smart-proxy/utils/route-migration-utils.js +0 -124
- package/dist_ts/redirect/classes.redirect.d.ts +0 -96
- package/dist_ts/redirect/classes.redirect.js +0 -194
- package/dist_ts/smartproxy/classes.pp.certprovisioner.d.ts +0 -54
- package/dist_ts/smartproxy/classes.pp.certprovisioner.js +0 -179
- package/dist_ts/smartproxy/classes.pp.connectionhandler.d.ts +0 -39
- package/dist_ts/smartproxy/classes.pp.connectionhandler.js +0 -894
- package/dist_ts/smartproxy/classes.pp.connectionmanager.d.ts +0 -78
- package/dist_ts/smartproxy/classes.pp.connectionmanager.js +0 -378
- package/dist_ts/smartproxy/classes.pp.domainconfigmanager.d.ts +0 -94
- package/dist_ts/smartproxy/classes.pp.domainconfigmanager.js +0 -255
- package/dist_ts/smartproxy/classes.pp.interfaces.d.ts +0 -103
- package/dist_ts/smartproxy/classes.pp.interfaces.js +0 -2
- package/dist_ts/smartproxy/classes.pp.networkproxybridge.d.ts +0 -62
- package/dist_ts/smartproxy/classes.pp.networkproxybridge.js +0 -316
- package/dist_ts/smartproxy/classes.pp.portrangemanager.d.ts +0 -56
- package/dist_ts/smartproxy/classes.pp.portrangemanager.js +0 -176
- package/dist_ts/smartproxy/classes.pp.securitymanager.d.ts +0 -64
- package/dist_ts/smartproxy/classes.pp.securitymanager.js +0 -149
- package/dist_ts/smartproxy/classes.pp.snihandler.d.ts +0 -153
- package/dist_ts/smartproxy/classes.pp.snihandler.js +0 -1053
- package/dist_ts/smartproxy/classes.pp.timeoutmanager.d.ts +0 -47
- package/dist_ts/smartproxy/classes.pp.timeoutmanager.js +0 -154
- package/dist_ts/smartproxy/classes.pp.tlsalert.d.ts +0 -149
- package/dist_ts/smartproxy/classes.pp.tlsalert.js +0 -225
- package/dist_ts/smartproxy/classes.pp.tlsmanager.d.ts +0 -57
- package/dist_ts/smartproxy/classes.pp.tlsmanager.js +0 -132
- package/dist_ts/smartproxy/classes.smartproxy.d.ts +0 -63
- package/dist_ts/smartproxy/classes.smartproxy.js +0 -521
- package/dist_ts/smartproxy/forwarding/domain-config.d.ts +0 -12
- package/dist_ts/smartproxy/forwarding/domain-config.js +0 -12
- package/dist_ts/smartproxy/forwarding/domain-manager.d.ts +0 -86
- package/dist_ts/smartproxy/forwarding/domain-manager.js +0 -241
- package/dist_ts/smartproxy/forwarding/forwarding.factory.d.ts +0 -24
- package/dist_ts/smartproxy/forwarding/forwarding.factory.js +0 -137
- package/dist_ts/smartproxy/forwarding/forwarding.handler.d.ts +0 -55
- package/dist_ts/smartproxy/forwarding/forwarding.handler.js +0 -94
- package/dist_ts/smartproxy/forwarding/http.handler.d.ts +0 -25
- package/dist_ts/smartproxy/forwarding/http.handler.js +0 -123
- package/dist_ts/smartproxy/forwarding/https-passthrough.handler.d.ts +0 -24
- package/dist_ts/smartproxy/forwarding/https-passthrough.handler.js +0 -154
- package/dist_ts/smartproxy/forwarding/https-terminate-to-http.handler.d.ts +0 -36
- package/dist_ts/smartproxy/forwarding/https-terminate-to-http.handler.js +0 -229
- package/dist_ts/smartproxy/forwarding/https-terminate-to-https.handler.d.ts +0 -35
- package/dist_ts/smartproxy/forwarding/https-terminate-to-https.handler.js +0 -254
- package/dist_ts/smartproxy/forwarding/index.d.ts +0 -16
- package/dist_ts/smartproxy/forwarding/index.js +0 -23
- package/dist_ts/smartproxy/types/forwarding.types.d.ts +0 -104
- package/dist_ts/smartproxy/types/forwarding.types.js +0 -50
- package/dist_ts/smartproxy.classes.networkproxy.d.ts +0 -31
- package/dist_ts/smartproxy.classes.networkproxy.js +0 -305
- package/dist_ts/smartproxy.classes.router.d.ts +0 -13
- package/dist_ts/smartproxy.classes.router.js +0 -33
- package/dist_ts/smartproxy.classes.sslredirect.d.ts +0 -8
- package/dist_ts/smartproxy.classes.sslredirect.js +0 -28
- package/dist_ts/smartproxy.helpers.certificates.d.ts +0 -5
- package/dist_ts/smartproxy.helpers.certificates.js +0 -23
- package/dist_ts/smartproxy.plugins.d.ts +0 -18
- package/dist_ts/smartproxy.plugins.js +0 -23
- package/dist_ts/smartproxy.portproxy.d.ts +0 -26
- package/dist_ts/smartproxy.portproxy.js +0 -295
- package/ts/http/index.ts +0 -16
- package/ts/http/models/http-types.ts +0 -108
- package/ts/http/redirects/index.ts +0 -3
- package/ts/proxies/network-proxy/models/index.ts +0 -4
- package/ts/redirect/classes.redirect.ts +0 -295
- /package/dist_ts/proxies/{network-proxy → http-proxy}/context-creator.d.ts +0 -0
- /package/dist_ts/proxies/{network-proxy → http-proxy}/function-cache.d.ts +0 -0
- /package/dist_ts/proxies/{network-proxy → http-proxy}/http-request-handler.d.ts +0 -0
- /package/dist_ts/proxies/{network-proxy → http-proxy}/http2-request-handler.d.ts +0 -0
- /package/dist_ts/proxies/{network-proxy → http-proxy}/security-manager.d.ts +0 -0
- /package/ts/proxies/{network-proxy → http-proxy}/context-creator.ts +0 -0
- /package/ts/proxies/{network-proxy → http-proxy}/function-cache.ts +0 -0
- /package/ts/proxies/{network-proxy → http-proxy}/http-request-handler.ts +0 -0
- /package/ts/proxies/{network-proxy → http-proxy}/http2-request-handler.ts +0 -0
- /package/ts/proxies/{network-proxy → http-proxy}/security-manager.ts +0 -0
- /package/ts/{http → routing}/router/index.ts +0 -0
|
@@ -1,1424 +0,0 @@
|
|
|
1
|
-
import * as plugins from './plugins.js';
|
|
2
|
-
import { ProxyRouter } from './classes.router.js';
|
|
3
|
-
import { Port80Handler, Port80HandlerEvents } from './classes.port80handler.js';
|
|
4
|
-
import * as fs from 'fs';
|
|
5
|
-
import * as path from 'path';
|
|
6
|
-
import { fileURLToPath } from 'url';
|
|
7
|
-
export class NetworkProxy {
|
|
8
|
-
/**
|
|
9
|
-
* Creates a new NetworkProxy instance
|
|
10
|
-
*/
|
|
11
|
-
constructor(optionsArg) {
|
|
12
|
-
this.proxyConfigs = [];
|
|
13
|
-
this.defaultHeaders = {};
|
|
14
|
-
// State tracking
|
|
15
|
-
this.router = new ProxyRouter();
|
|
16
|
-
this.socketMap = new plugins.lik.ObjectMap();
|
|
17
|
-
this.activeContexts = new Set();
|
|
18
|
-
this.connectedClients = 0;
|
|
19
|
-
this.startTime = 0;
|
|
20
|
-
this.requestsServed = 0;
|
|
21
|
-
this.failedRequests = 0;
|
|
22
|
-
// New tracking for PortProxy integration
|
|
23
|
-
this.portProxyConnections = 0;
|
|
24
|
-
this.tlsTerminatedConnections = 0;
|
|
25
|
-
this.certificateCache = new Map();
|
|
26
|
-
// Port80Handler for certificate management
|
|
27
|
-
this.port80Handler = null;
|
|
28
|
-
// New connection pool for backend connections
|
|
29
|
-
this.connectionPool = new Map();
|
|
30
|
-
// Track round-robin positions for load balancing
|
|
31
|
-
this.roundRobinPositions = new Map();
|
|
32
|
-
// Set default options
|
|
33
|
-
this.options = {
|
|
34
|
-
port: optionsArg.port,
|
|
35
|
-
maxConnections: optionsArg.maxConnections || 10000,
|
|
36
|
-
keepAliveTimeout: optionsArg.keepAliveTimeout || 120000, // 2 minutes
|
|
37
|
-
headersTimeout: optionsArg.headersTimeout || 60000, // 1 minute
|
|
38
|
-
logLevel: optionsArg.logLevel || 'info',
|
|
39
|
-
cors: optionsArg.cors || {
|
|
40
|
-
allowOrigin: '*',
|
|
41
|
-
allowMethods: 'GET, POST, PUT, DELETE, OPTIONS',
|
|
42
|
-
allowHeaders: 'Content-Type, Authorization',
|
|
43
|
-
maxAge: 86400
|
|
44
|
-
},
|
|
45
|
-
// New defaults for PortProxy integration
|
|
46
|
-
connectionPoolSize: optionsArg.connectionPoolSize || 50,
|
|
47
|
-
portProxyIntegration: optionsArg.portProxyIntegration || false,
|
|
48
|
-
// Default ACME options
|
|
49
|
-
acme: {
|
|
50
|
-
enabled: optionsArg.acme?.enabled || false,
|
|
51
|
-
port: optionsArg.acme?.port || 80,
|
|
52
|
-
contactEmail: optionsArg.acme?.contactEmail || 'admin@example.com',
|
|
53
|
-
useProduction: optionsArg.acme?.useProduction || false, // Default to staging for safety
|
|
54
|
-
renewThresholdDays: optionsArg.acme?.renewThresholdDays || 30,
|
|
55
|
-
autoRenew: optionsArg.acme?.autoRenew !== false, // Default to true
|
|
56
|
-
certificateStore: optionsArg.acme?.certificateStore || './certs',
|
|
57
|
-
skipConfiguredCerts: optionsArg.acme?.skipConfiguredCerts || false
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
// Set up certificate store directory
|
|
61
|
-
this.certificateStoreDir = path.resolve(this.options.acme.certificateStore);
|
|
62
|
-
// Ensure certificate store directory exists
|
|
63
|
-
try {
|
|
64
|
-
if (!fs.existsSync(this.certificateStoreDir)) {
|
|
65
|
-
fs.mkdirSync(this.certificateStoreDir, { recursive: true });
|
|
66
|
-
this.log('info', `Created certificate store directory: ${this.certificateStoreDir}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
this.log('warn', `Failed to create certificate store directory: ${error}`);
|
|
71
|
-
}
|
|
72
|
-
this.loadDefaultCertificates();
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Loads default certificates from the filesystem
|
|
76
|
-
*/
|
|
77
|
-
loadDefaultCertificates() {
|
|
78
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
79
|
-
const certPath = path.join(__dirname, '..', 'assets', 'certs');
|
|
80
|
-
try {
|
|
81
|
-
this.defaultCertificates = {
|
|
82
|
-
key: fs.readFileSync(path.join(certPath, 'key.pem'), 'utf8'),
|
|
83
|
-
cert: fs.readFileSync(path.join(certPath, 'cert.pem'), 'utf8')
|
|
84
|
-
};
|
|
85
|
-
this.log('info', 'Default certificates loaded successfully');
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
this.log('error', 'Error loading default certificates', error);
|
|
89
|
-
// Generate self-signed fallback certificates
|
|
90
|
-
try {
|
|
91
|
-
// This is a placeholder for actual certificate generation code
|
|
92
|
-
// In a real implementation, you would use a library like selfsigned to generate certs
|
|
93
|
-
this.defaultCertificates = {
|
|
94
|
-
key: "FALLBACK_KEY_CONTENT",
|
|
95
|
-
cert: "FALLBACK_CERT_CONTENT"
|
|
96
|
-
};
|
|
97
|
-
this.log('warn', 'Using fallback self-signed certificates');
|
|
98
|
-
}
|
|
99
|
-
catch (fallbackError) {
|
|
100
|
-
this.log('error', 'Failed to generate fallback certificates', fallbackError);
|
|
101
|
-
throw new Error('Could not load or generate SSL certificates');
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Returns the port number this NetworkProxy is listening on
|
|
107
|
-
* Useful for PortProxy to determine where to forward connections
|
|
108
|
-
*/
|
|
109
|
-
getListeningPort() {
|
|
110
|
-
return this.options.port;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Updates the server capacity settings
|
|
114
|
-
* @param maxConnections Maximum number of simultaneous connections
|
|
115
|
-
* @param keepAliveTimeout Keep-alive timeout in milliseconds
|
|
116
|
-
* @param connectionPoolSize Size of the connection pool per backend
|
|
117
|
-
*/
|
|
118
|
-
updateCapacity(maxConnections, keepAliveTimeout, connectionPoolSize) {
|
|
119
|
-
if (maxConnections !== undefined) {
|
|
120
|
-
this.options.maxConnections = maxConnections;
|
|
121
|
-
this.log('info', `Updated max connections to ${maxConnections}`);
|
|
122
|
-
}
|
|
123
|
-
if (keepAliveTimeout !== undefined) {
|
|
124
|
-
this.options.keepAliveTimeout = keepAliveTimeout;
|
|
125
|
-
if (this.httpsServer) {
|
|
126
|
-
this.httpsServer.keepAliveTimeout = keepAliveTimeout;
|
|
127
|
-
this.log('info', `Updated keep-alive timeout to ${keepAliveTimeout}ms`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
if (connectionPoolSize !== undefined) {
|
|
131
|
-
this.options.connectionPoolSize = connectionPoolSize;
|
|
132
|
-
this.log('info', `Updated connection pool size to ${connectionPoolSize}`);
|
|
133
|
-
// Cleanup excess connections in the pool if the size was reduced
|
|
134
|
-
this.cleanupConnectionPool();
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Returns current server metrics
|
|
139
|
-
* Useful for PortProxy to determine which NetworkProxy to use for load balancing
|
|
140
|
-
*/
|
|
141
|
-
getMetrics() {
|
|
142
|
-
return {
|
|
143
|
-
activeConnections: this.connectedClients,
|
|
144
|
-
totalRequests: this.requestsServed,
|
|
145
|
-
failedRequests: this.failedRequests,
|
|
146
|
-
portProxyConnections: this.portProxyConnections,
|
|
147
|
-
tlsTerminatedConnections: this.tlsTerminatedConnections,
|
|
148
|
-
connectionPoolSize: Array.from(this.connectionPool.entries()).reduce((acc, [host, connections]) => {
|
|
149
|
-
acc[host] = connections.length;
|
|
150
|
-
return acc;
|
|
151
|
-
}, {}),
|
|
152
|
-
uptime: Math.floor((Date.now() - this.startTime) / 1000),
|
|
153
|
-
memoryUsage: process.memoryUsage(),
|
|
154
|
-
activeWebSockets: this.wsServer?.clients.size || 0
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Cleanup the connection pool by removing idle connections
|
|
159
|
-
* or reducing pool size if it exceeds the configured maximum
|
|
160
|
-
*/
|
|
161
|
-
cleanupConnectionPool() {
|
|
162
|
-
const now = Date.now();
|
|
163
|
-
const idleTimeout = this.options.keepAliveTimeout || 120000; // 2 minutes default
|
|
164
|
-
for (const [host, connections] of this.connectionPool.entries()) {
|
|
165
|
-
// Sort by last used time (oldest first)
|
|
166
|
-
connections.sort((a, b) => a.lastUsed - b.lastUsed);
|
|
167
|
-
// Remove idle connections older than the idle timeout
|
|
168
|
-
let removed = 0;
|
|
169
|
-
while (connections.length > 0) {
|
|
170
|
-
const connection = connections[0];
|
|
171
|
-
// Remove if idle and exceeds timeout, or if pool is too large
|
|
172
|
-
if ((connection.isIdle && now - connection.lastUsed > idleTimeout) ||
|
|
173
|
-
connections.length > this.options.connectionPoolSize) {
|
|
174
|
-
try {
|
|
175
|
-
if (!connection.socket.destroyed) {
|
|
176
|
-
connection.socket.end();
|
|
177
|
-
connection.socket.destroy();
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
this.log('error', `Error destroying pooled connection to ${host}`, err);
|
|
182
|
-
}
|
|
183
|
-
connections.shift(); // Remove from pool
|
|
184
|
-
removed++;
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
break; // Stop removing if we've reached active or recent connections
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
if (removed > 0) {
|
|
191
|
-
this.log('debug', `Removed ${removed} idle connections from pool for ${host}, ${connections.length} remaining`);
|
|
192
|
-
}
|
|
193
|
-
// Update the pool with the remaining connections
|
|
194
|
-
if (connections.length === 0) {
|
|
195
|
-
this.connectionPool.delete(host);
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
this.connectionPool.set(host, connections);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Get a connection from the pool or create a new one
|
|
204
|
-
*/
|
|
205
|
-
getConnectionFromPool(host, port) {
|
|
206
|
-
return new Promise((resolve, reject) => {
|
|
207
|
-
const poolKey = `${host}:${port}`;
|
|
208
|
-
const connectionList = this.connectionPool.get(poolKey) || [];
|
|
209
|
-
// Look for an idle connection
|
|
210
|
-
const idleConnectionIndex = connectionList.findIndex(c => c.isIdle);
|
|
211
|
-
if (idleConnectionIndex >= 0) {
|
|
212
|
-
// Get existing connection from pool
|
|
213
|
-
const connection = connectionList[idleConnectionIndex];
|
|
214
|
-
connection.isIdle = false;
|
|
215
|
-
connection.lastUsed = Date.now();
|
|
216
|
-
this.log('debug', `Reusing connection from pool for ${poolKey}`);
|
|
217
|
-
// Update the pool
|
|
218
|
-
this.connectionPool.set(poolKey, connectionList);
|
|
219
|
-
resolve(connection.socket);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
// No idle connection available, create a new one if pool isn't full
|
|
223
|
-
if (connectionList.length < this.options.connectionPoolSize) {
|
|
224
|
-
this.log('debug', `Creating new connection to ${host}:${port}`);
|
|
225
|
-
try {
|
|
226
|
-
const socket = plugins.net.connect({
|
|
227
|
-
host,
|
|
228
|
-
port,
|
|
229
|
-
keepAlive: true,
|
|
230
|
-
keepAliveInitialDelay: 30000 // 30 seconds
|
|
231
|
-
});
|
|
232
|
-
socket.once('connect', () => {
|
|
233
|
-
// Add to connection pool
|
|
234
|
-
const connection = {
|
|
235
|
-
socket,
|
|
236
|
-
lastUsed: Date.now(),
|
|
237
|
-
isIdle: false
|
|
238
|
-
};
|
|
239
|
-
connectionList.push(connection);
|
|
240
|
-
this.connectionPool.set(poolKey, connectionList);
|
|
241
|
-
// Setup cleanup when the connection is closed
|
|
242
|
-
socket.once('close', () => {
|
|
243
|
-
const idx = connectionList.findIndex(c => c.socket === socket);
|
|
244
|
-
if (idx >= 0) {
|
|
245
|
-
connectionList.splice(idx, 1);
|
|
246
|
-
this.connectionPool.set(poolKey, connectionList);
|
|
247
|
-
this.log('debug', `Removed closed connection from pool for ${poolKey}`);
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
resolve(socket);
|
|
251
|
-
});
|
|
252
|
-
socket.once('error', (err) => {
|
|
253
|
-
this.log('error', `Error creating connection to ${host}:${port}`, err);
|
|
254
|
-
reject(err);
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
catch (err) {
|
|
258
|
-
this.log('error', `Failed to create connection to ${host}:${port}`, err);
|
|
259
|
-
reject(err);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
// Pool is full, wait for an idle connection or reject
|
|
264
|
-
this.log('warn', `Connection pool for ${poolKey} is full (${connectionList.length})`);
|
|
265
|
-
reject(new Error(`Connection pool for ${poolKey} is full`));
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Return a connection to the pool for reuse
|
|
271
|
-
*/
|
|
272
|
-
returnConnectionToPool(socket, host, port) {
|
|
273
|
-
const poolKey = `${host}:${port}`;
|
|
274
|
-
const connectionList = this.connectionPool.get(poolKey) || [];
|
|
275
|
-
// Find this connection in the pool
|
|
276
|
-
const connectionIndex = connectionList.findIndex(c => c.socket === socket);
|
|
277
|
-
if (connectionIndex >= 0) {
|
|
278
|
-
// Mark as idle and update last used time
|
|
279
|
-
connectionList[connectionIndex].isIdle = true;
|
|
280
|
-
connectionList[connectionIndex].lastUsed = Date.now();
|
|
281
|
-
this.log('debug', `Returned connection to pool for ${poolKey}`);
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
this.log('warn', `Attempted to return unknown connection to pool for ${poolKey}`);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Initializes the Port80Handler for ACME certificate management
|
|
289
|
-
* @private
|
|
290
|
-
*/
|
|
291
|
-
async initializePort80Handler() {
|
|
292
|
-
if (!this.options.acme.enabled) {
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
// Create certificate manager
|
|
296
|
-
this.port80Handler = new Port80Handler({
|
|
297
|
-
port: this.options.acme.port,
|
|
298
|
-
contactEmail: this.options.acme.contactEmail,
|
|
299
|
-
useProduction: this.options.acme.useProduction,
|
|
300
|
-
renewThresholdDays: this.options.acme.renewThresholdDays,
|
|
301
|
-
httpsRedirectPort: this.options.port, // Redirect to our HTTPS port
|
|
302
|
-
renewCheckIntervalHours: 24 // Check daily for renewals
|
|
303
|
-
});
|
|
304
|
-
// Register event handlers
|
|
305
|
-
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, this.handleCertificateIssued.bind(this));
|
|
306
|
-
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, this.handleCertificateIssued.bind(this));
|
|
307
|
-
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_FAILED, this.handleCertificateFailed.bind(this));
|
|
308
|
-
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_EXPIRING, (data) => {
|
|
309
|
-
this.log('info', `Certificate for ${data.domain} expires in ${data.daysRemaining} days`);
|
|
310
|
-
});
|
|
311
|
-
// Start the handler
|
|
312
|
-
try {
|
|
313
|
-
await this.port80Handler.start();
|
|
314
|
-
this.log('info', `Port80Handler started on port ${this.options.acme.port}`);
|
|
315
|
-
// Add domains from proxy configs
|
|
316
|
-
this.registerDomainsWithPort80Handler();
|
|
317
|
-
}
|
|
318
|
-
catch (error) {
|
|
319
|
-
this.log('error', `Failed to start Port80Handler: ${error}`);
|
|
320
|
-
this.port80Handler = null;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Registers domains from proxy configs with the Port80Handler
|
|
325
|
-
* @private
|
|
326
|
-
*/
|
|
327
|
-
registerDomainsWithPort80Handler() {
|
|
328
|
-
if (!this.port80Handler)
|
|
329
|
-
return;
|
|
330
|
-
// Get all hostnames from proxy configs
|
|
331
|
-
this.proxyConfigs.forEach(config => {
|
|
332
|
-
const hostname = config.hostName;
|
|
333
|
-
// Skip wildcard domains - can't get certs for these with HTTP-01 validation
|
|
334
|
-
if (hostname.includes('*')) {
|
|
335
|
-
this.log('info', `Skipping wildcard domain for ACME: ${hostname}`);
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
// Skip domains already with certificates if configured to do so
|
|
339
|
-
if (this.options.acme.skipConfiguredCerts) {
|
|
340
|
-
const cachedCert = this.certificateCache.get(hostname);
|
|
341
|
-
if (cachedCert) {
|
|
342
|
-
this.log('info', `Skipping domain with existing certificate: ${hostname}`);
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
// Check for existing certificate in the store
|
|
347
|
-
const certPath = path.join(this.certificateStoreDir, `${hostname}.cert.pem`);
|
|
348
|
-
const keyPath = path.join(this.certificateStoreDir, `${hostname}.key.pem`);
|
|
349
|
-
try {
|
|
350
|
-
if (fs.existsSync(certPath) && fs.existsSync(keyPath)) {
|
|
351
|
-
// Load existing certificate and key
|
|
352
|
-
const cert = fs.readFileSync(certPath, 'utf8');
|
|
353
|
-
const key = fs.readFileSync(keyPath, 'utf8');
|
|
354
|
-
// Extract expiry date from certificate if possible
|
|
355
|
-
let expiryDate;
|
|
356
|
-
try {
|
|
357
|
-
const matches = cert.match(/Not After\s*:\s*(.*?)(?:\n|$)/i);
|
|
358
|
-
if (matches && matches[1]) {
|
|
359
|
-
expiryDate = new Date(matches[1]);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
catch (error) {
|
|
363
|
-
this.log('warn', `Failed to extract expiry date from certificate for ${hostname}`);
|
|
364
|
-
}
|
|
365
|
-
// Update the certificate in the handler
|
|
366
|
-
this.port80Handler.setCertificate(hostname, cert, key, expiryDate);
|
|
367
|
-
// Also update our own certificate cache
|
|
368
|
-
this.updateCertificateCache(hostname, cert, key, expiryDate);
|
|
369
|
-
this.log('info', `Loaded existing certificate for ${hostname}`);
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
// Register the domain for certificate issuance with new domain options format
|
|
373
|
-
const domainOptions = {
|
|
374
|
-
domainName: hostname,
|
|
375
|
-
sslRedirect: true,
|
|
376
|
-
acmeMaintenance: true
|
|
377
|
-
};
|
|
378
|
-
this.port80Handler.addDomain(domainOptions);
|
|
379
|
-
this.log('info', `Registered domain for ACME certificate issuance: ${hostname}`);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
catch (error) {
|
|
383
|
-
this.log('error', `Error registering domain ${hostname} with Port80Handler: ${error}`);
|
|
384
|
-
}
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Handles newly issued or renewed certificates from Port80Handler
|
|
389
|
-
* @private
|
|
390
|
-
*/
|
|
391
|
-
handleCertificateIssued(data) {
|
|
392
|
-
const { domain, certificate, privateKey, expiryDate } = data;
|
|
393
|
-
this.log('info', `Certificate ${this.certificateCache.has(domain) ? 'renewed' : 'issued'} for ${domain}, valid until ${expiryDate.toISOString()}`);
|
|
394
|
-
// Update certificate in HTTPS server
|
|
395
|
-
this.updateCertificateCache(domain, certificate, privateKey, expiryDate);
|
|
396
|
-
// Save the certificate to the filesystem
|
|
397
|
-
this.saveCertificateToStore(domain, certificate, privateKey);
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* Handles certificate issuance failures
|
|
401
|
-
* @private
|
|
402
|
-
*/
|
|
403
|
-
handleCertificateFailed(data) {
|
|
404
|
-
this.log('error', `Certificate issuance failed for ${data.domain}: ${data.error}`);
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Saves certificate and private key to the filesystem
|
|
408
|
-
* @private
|
|
409
|
-
*/
|
|
410
|
-
saveCertificateToStore(domain, certificate, privateKey) {
|
|
411
|
-
try {
|
|
412
|
-
const certPath = path.join(this.certificateStoreDir, `${domain}.cert.pem`);
|
|
413
|
-
const keyPath = path.join(this.certificateStoreDir, `${domain}.key.pem`);
|
|
414
|
-
fs.writeFileSync(certPath, certificate);
|
|
415
|
-
fs.writeFileSync(keyPath, privateKey);
|
|
416
|
-
// Ensure private key has restricted permissions
|
|
417
|
-
try {
|
|
418
|
-
fs.chmodSync(keyPath, 0o600);
|
|
419
|
-
}
|
|
420
|
-
catch (error) {
|
|
421
|
-
this.log('warn', `Failed to set permissions on private key for ${domain}: ${error}`);
|
|
422
|
-
}
|
|
423
|
-
this.log('info', `Saved certificate for ${domain} to ${certPath}`);
|
|
424
|
-
}
|
|
425
|
-
catch (error) {
|
|
426
|
-
this.log('error', `Failed to save certificate for ${domain}: ${error}`);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* Handles SNI (Server Name Indication) for TLS connections
|
|
431
|
-
* Used by the HTTPS server to select the correct certificate for each domain
|
|
432
|
-
* @private
|
|
433
|
-
*/
|
|
434
|
-
handleSNI(domain, cb) {
|
|
435
|
-
this.log('debug', `SNI request for domain: ${domain}`);
|
|
436
|
-
// Check if we have a certificate for this domain
|
|
437
|
-
const certs = this.certificateCache.get(domain);
|
|
438
|
-
if (certs) {
|
|
439
|
-
try {
|
|
440
|
-
// Create TLS context with the cached certificate
|
|
441
|
-
const context = plugins.tls.createSecureContext({
|
|
442
|
-
key: certs.key,
|
|
443
|
-
cert: certs.cert
|
|
444
|
-
});
|
|
445
|
-
this.log('debug', `Using cached certificate for ${domain}`);
|
|
446
|
-
cb(null, context);
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
449
|
-
catch (err) {
|
|
450
|
-
this.log('error', `Error creating secure context for ${domain}:`, err);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
// Check if we should trigger certificate issuance
|
|
454
|
-
if (this.options.acme?.enabled && this.port80Handler && !domain.includes('*')) {
|
|
455
|
-
// Check if this domain is already registered
|
|
456
|
-
const certData = this.port80Handler.getCertificate(domain);
|
|
457
|
-
if (!certData) {
|
|
458
|
-
this.log('info', `No certificate found for ${domain}, registering for issuance`);
|
|
459
|
-
// Register with new domain options format
|
|
460
|
-
const domainOptions = {
|
|
461
|
-
domainName: domain,
|
|
462
|
-
sslRedirect: true,
|
|
463
|
-
acmeMaintenance: true
|
|
464
|
-
};
|
|
465
|
-
this.port80Handler.addDomain(domainOptions);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
// Fall back to default certificate
|
|
469
|
-
try {
|
|
470
|
-
const context = plugins.tls.createSecureContext({
|
|
471
|
-
key: this.defaultCertificates.key,
|
|
472
|
-
cert: this.defaultCertificates.cert
|
|
473
|
-
});
|
|
474
|
-
this.log('debug', `Using default certificate for ${domain}`);
|
|
475
|
-
cb(null, context);
|
|
476
|
-
}
|
|
477
|
-
catch (err) {
|
|
478
|
-
this.log('error', `Error creating default secure context:`, err);
|
|
479
|
-
cb(new Error('Cannot create secure context'), null);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* Starts the proxy server
|
|
484
|
-
*/
|
|
485
|
-
async start() {
|
|
486
|
-
this.startTime = Date.now();
|
|
487
|
-
// Initialize Port80Handler if enabled
|
|
488
|
-
if (this.options.acme.enabled) {
|
|
489
|
-
await this.initializePort80Handler();
|
|
490
|
-
}
|
|
491
|
-
// Create the HTTPS server
|
|
492
|
-
this.httpsServer = plugins.https.createServer({
|
|
493
|
-
key: this.defaultCertificates.key,
|
|
494
|
-
cert: this.defaultCertificates.cert,
|
|
495
|
-
SNICallback: (domain, cb) => this.handleSNI(domain, cb)
|
|
496
|
-
}, (req, res) => this.handleRequest(req, res));
|
|
497
|
-
// Configure server timeouts
|
|
498
|
-
this.httpsServer.keepAliveTimeout = this.options.keepAliveTimeout;
|
|
499
|
-
this.httpsServer.headersTimeout = this.options.headersTimeout;
|
|
500
|
-
// Setup connection tracking
|
|
501
|
-
this.setupConnectionTracking();
|
|
502
|
-
// Setup WebSocket support
|
|
503
|
-
this.setupWebsocketSupport();
|
|
504
|
-
// Start metrics collection
|
|
505
|
-
this.setupMetricsCollection();
|
|
506
|
-
// Setup connection pool cleanup interval
|
|
507
|
-
this.setupConnectionPoolCleanup();
|
|
508
|
-
// Start the server
|
|
509
|
-
return new Promise((resolve) => {
|
|
510
|
-
this.httpsServer.listen(this.options.port, () => {
|
|
511
|
-
this.log('info', `NetworkProxy started on port ${this.options.port}`);
|
|
512
|
-
resolve();
|
|
513
|
-
});
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
/**
|
|
517
|
-
* Sets up tracking of TCP connections
|
|
518
|
-
*/
|
|
519
|
-
setupConnectionTracking() {
|
|
520
|
-
this.httpsServer.on('connection', (connection) => {
|
|
521
|
-
// Check if max connections reached
|
|
522
|
-
if (this.socketMap.getArray().length >= this.options.maxConnections) {
|
|
523
|
-
this.log('warn', `Max connections (${this.options.maxConnections}) reached, rejecting new connection`);
|
|
524
|
-
connection.destroy();
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
// Add connection to tracking
|
|
528
|
-
this.socketMap.add(connection);
|
|
529
|
-
this.connectedClients = this.socketMap.getArray().length;
|
|
530
|
-
// Check for connection from PortProxy by inspecting the source port
|
|
531
|
-
// This is a heuristic - in a production environment you might use a more robust method
|
|
532
|
-
const localPort = connection.localPort;
|
|
533
|
-
const remotePort = connection.remotePort;
|
|
534
|
-
// If this connection is from a PortProxy (usually indicated by it coming from localhost)
|
|
535
|
-
if (this.options.portProxyIntegration && connection.remoteAddress?.includes('127.0.0.1')) {
|
|
536
|
-
this.portProxyConnections++;
|
|
537
|
-
this.log('debug', `New connection from PortProxy (local: ${localPort}, remote: ${remotePort})`);
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
this.log('debug', `New direct connection (local: ${localPort}, remote: ${remotePort})`);
|
|
541
|
-
}
|
|
542
|
-
// Setup connection cleanup handlers
|
|
543
|
-
const cleanupConnection = () => {
|
|
544
|
-
if (this.socketMap.checkForObject(connection)) {
|
|
545
|
-
this.socketMap.remove(connection);
|
|
546
|
-
this.connectedClients = this.socketMap.getArray().length;
|
|
547
|
-
// If this was a PortProxy connection, decrement the counter
|
|
548
|
-
if (this.options.portProxyIntegration && connection.remoteAddress?.includes('127.0.0.1')) {
|
|
549
|
-
this.portProxyConnections--;
|
|
550
|
-
}
|
|
551
|
-
this.log('debug', `Connection closed. ${this.connectedClients} connections remaining`);
|
|
552
|
-
}
|
|
553
|
-
};
|
|
554
|
-
connection.on('close', cleanupConnection);
|
|
555
|
-
connection.on('error', (err) => {
|
|
556
|
-
this.log('debug', 'Connection error', err);
|
|
557
|
-
cleanupConnection();
|
|
558
|
-
});
|
|
559
|
-
connection.on('end', cleanupConnection);
|
|
560
|
-
connection.on('timeout', () => {
|
|
561
|
-
this.log('debug', 'Connection timeout');
|
|
562
|
-
cleanupConnection();
|
|
563
|
-
});
|
|
564
|
-
});
|
|
565
|
-
// Track TLS handshake completions
|
|
566
|
-
this.httpsServer.on('secureConnection', (tlsSocket) => {
|
|
567
|
-
this.tlsTerminatedConnections++;
|
|
568
|
-
this.log('debug', 'TLS handshake completed, connection secured');
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
|
-
/**
|
|
572
|
-
* Sets up WebSocket support
|
|
573
|
-
*/
|
|
574
|
-
setupWebsocketSupport() {
|
|
575
|
-
// Create WebSocket server
|
|
576
|
-
this.wsServer = new plugins.ws.WebSocketServer({
|
|
577
|
-
server: this.httpsServer,
|
|
578
|
-
// Add WebSocket specific timeout
|
|
579
|
-
clientTracking: true
|
|
580
|
-
});
|
|
581
|
-
// Handle WebSocket connections
|
|
582
|
-
this.wsServer.on('connection', (wsIncoming, reqArg) => {
|
|
583
|
-
this.handleWebSocketConnection(wsIncoming, reqArg);
|
|
584
|
-
});
|
|
585
|
-
// Set up the heartbeat interval (check every 30 seconds, terminate after 2 minutes of inactivity)
|
|
586
|
-
this.heartbeatInterval = setInterval(() => {
|
|
587
|
-
if (this.wsServer.clients.size === 0) {
|
|
588
|
-
return; // Skip if no active connections
|
|
589
|
-
}
|
|
590
|
-
this.log('debug', `WebSocket heartbeat check for ${this.wsServer.clients.size} clients`);
|
|
591
|
-
this.wsServer.clients.forEach((ws) => {
|
|
592
|
-
const wsWithHeartbeat = ws;
|
|
593
|
-
if (wsWithHeartbeat.isAlive === false) {
|
|
594
|
-
this.log('debug', 'Terminating inactive WebSocket connection');
|
|
595
|
-
return wsWithHeartbeat.terminate();
|
|
596
|
-
}
|
|
597
|
-
wsWithHeartbeat.isAlive = false;
|
|
598
|
-
wsWithHeartbeat.ping();
|
|
599
|
-
});
|
|
600
|
-
}, 30000);
|
|
601
|
-
}
|
|
602
|
-
/**
|
|
603
|
-
* Sets up metrics collection
|
|
604
|
-
*/
|
|
605
|
-
setupMetricsCollection() {
|
|
606
|
-
this.metricsInterval = setInterval(() => {
|
|
607
|
-
const uptime = Math.floor((Date.now() - this.startTime) / 1000);
|
|
608
|
-
const metrics = {
|
|
609
|
-
uptime,
|
|
610
|
-
activeConnections: this.connectedClients,
|
|
611
|
-
totalRequests: this.requestsServed,
|
|
612
|
-
failedRequests: this.failedRequests,
|
|
613
|
-
portProxyConnections: this.portProxyConnections,
|
|
614
|
-
tlsTerminatedConnections: this.tlsTerminatedConnections,
|
|
615
|
-
activeWebSockets: this.wsServer?.clients.size || 0,
|
|
616
|
-
memoryUsage: process.memoryUsage(),
|
|
617
|
-
activeContexts: Array.from(this.activeContexts),
|
|
618
|
-
connectionPool: Object.fromEntries(Array.from(this.connectionPool.entries()).map(([host, connections]) => [
|
|
619
|
-
host,
|
|
620
|
-
{
|
|
621
|
-
total: connections.length,
|
|
622
|
-
idle: connections.filter(c => c.isIdle).length
|
|
623
|
-
}
|
|
624
|
-
]))
|
|
625
|
-
};
|
|
626
|
-
this.log('debug', 'Proxy metrics', metrics);
|
|
627
|
-
}, 60000); // Log metrics every minute
|
|
628
|
-
}
|
|
629
|
-
/**
|
|
630
|
-
* Sets up connection pool cleanup
|
|
631
|
-
*/
|
|
632
|
-
setupConnectionPoolCleanup() {
|
|
633
|
-
// Clean up idle connections every minute
|
|
634
|
-
this.connectionPoolCleanupInterval = setInterval(() => {
|
|
635
|
-
this.cleanupConnectionPool();
|
|
636
|
-
}, 60000); // 1 minute
|
|
637
|
-
}
|
|
638
|
-
/**
|
|
639
|
-
* Handles an incoming WebSocket connection
|
|
640
|
-
*/
|
|
641
|
-
handleWebSocketConnection(wsIncoming, reqArg) {
|
|
642
|
-
const wsPath = reqArg.url;
|
|
643
|
-
const wsHost = reqArg.headers.host;
|
|
644
|
-
this.log('info', `WebSocket connection for ${wsHost}${wsPath}`);
|
|
645
|
-
// Setup heartbeat tracking
|
|
646
|
-
wsIncoming.isAlive = true;
|
|
647
|
-
wsIncoming.lastPong = Date.now();
|
|
648
|
-
wsIncoming.on('pong', () => {
|
|
649
|
-
wsIncoming.isAlive = true;
|
|
650
|
-
wsIncoming.lastPong = Date.now();
|
|
651
|
-
});
|
|
652
|
-
// Get the destination configuration
|
|
653
|
-
const wsDestinationConfig = this.router.routeReq(reqArg);
|
|
654
|
-
if (!wsDestinationConfig) {
|
|
655
|
-
this.log('warn', `No route found for WebSocket ${wsHost}${wsPath}`);
|
|
656
|
-
wsIncoming.terminate();
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
// Check authentication if required
|
|
660
|
-
if (wsDestinationConfig.authentication) {
|
|
661
|
-
try {
|
|
662
|
-
if (!this.authenticateRequest(reqArg, wsDestinationConfig)) {
|
|
663
|
-
this.log('warn', `WebSocket authentication failed for ${wsHost}${wsPath}`);
|
|
664
|
-
wsIncoming.terminate();
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
catch (error) {
|
|
669
|
-
this.log('error', 'WebSocket authentication error', error);
|
|
670
|
-
wsIncoming.terminate();
|
|
671
|
-
return;
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
// Setup outgoing WebSocket connection
|
|
675
|
-
let wsOutgoing;
|
|
676
|
-
const outGoingDeferred = plugins.smartpromise.defer();
|
|
677
|
-
try {
|
|
678
|
-
// Select destination IP and port for WebSocket
|
|
679
|
-
const wsDestinationIp = this.selectDestinationIp(wsDestinationConfig);
|
|
680
|
-
const wsDestinationPort = this.selectDestinationPort(wsDestinationConfig);
|
|
681
|
-
const wsTarget = `ws://${wsDestinationIp}:${wsDestinationPort}${reqArg.url}`;
|
|
682
|
-
this.log('debug', `Proxying WebSocket to ${wsTarget}`);
|
|
683
|
-
wsOutgoing = new plugins.wsDefault(wsTarget);
|
|
684
|
-
wsOutgoing.on('open', () => {
|
|
685
|
-
this.log('debug', 'Outgoing WebSocket connection established');
|
|
686
|
-
outGoingDeferred.resolve();
|
|
687
|
-
});
|
|
688
|
-
wsOutgoing.on('error', (error) => {
|
|
689
|
-
this.log('error', 'Outgoing WebSocket error', error);
|
|
690
|
-
outGoingDeferred.reject(error);
|
|
691
|
-
if (wsIncoming.readyState === wsIncoming.OPEN) {
|
|
692
|
-
wsIncoming.terminate();
|
|
693
|
-
}
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
|
-
catch (err) {
|
|
697
|
-
this.log('error', 'Failed to create outgoing WebSocket connection', err);
|
|
698
|
-
wsIncoming.terminate();
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
// Handle message forwarding from client to backend
|
|
702
|
-
wsIncoming.on('message', async (message, isBinary) => {
|
|
703
|
-
try {
|
|
704
|
-
// Wait for outgoing connection to be ready
|
|
705
|
-
await outGoingDeferred.promise;
|
|
706
|
-
// Only forward if both connections are still open
|
|
707
|
-
if (wsOutgoing.readyState === wsOutgoing.OPEN) {
|
|
708
|
-
wsOutgoing.send(message, { binary: isBinary });
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
catch (error) {
|
|
712
|
-
this.log('error', 'Error forwarding WebSocket message to backend', error);
|
|
713
|
-
}
|
|
714
|
-
});
|
|
715
|
-
// Handle message forwarding from backend to client
|
|
716
|
-
wsOutgoing.on('message', (message, isBinary) => {
|
|
717
|
-
try {
|
|
718
|
-
// Only forward if the incoming connection is still open
|
|
719
|
-
if (wsIncoming.readyState === wsIncoming.OPEN) {
|
|
720
|
-
wsIncoming.send(message, { binary: isBinary });
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
catch (error) {
|
|
724
|
-
this.log('error', 'Error forwarding WebSocket message to client', error);
|
|
725
|
-
}
|
|
726
|
-
});
|
|
727
|
-
// Clean up connections when either side closes
|
|
728
|
-
wsIncoming.on('close', (code, reason) => {
|
|
729
|
-
this.log('debug', `Incoming WebSocket closed: ${code} - ${reason}`);
|
|
730
|
-
if (wsOutgoing && wsOutgoing.readyState !== wsOutgoing.CLOSED) {
|
|
731
|
-
try {
|
|
732
|
-
// Validate close code (must be 1000-4999) or use 1000 as default
|
|
733
|
-
const validCode = (code >= 1000 && code <= 4999) ? code : 1000;
|
|
734
|
-
wsOutgoing.close(validCode, reason.toString() || '');
|
|
735
|
-
}
|
|
736
|
-
catch (error) {
|
|
737
|
-
this.log('error', 'Error closing outgoing WebSocket', error);
|
|
738
|
-
wsOutgoing.terminate();
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
});
|
|
742
|
-
wsOutgoing.on('close', (code, reason) => {
|
|
743
|
-
this.log('debug', `Outgoing WebSocket closed: ${code} - ${reason}`);
|
|
744
|
-
if (wsIncoming && wsIncoming.readyState !== wsIncoming.CLOSED) {
|
|
745
|
-
try {
|
|
746
|
-
// Validate close code (must be 1000-4999) or use 1000 as default
|
|
747
|
-
const validCode = (code >= 1000 && code <= 4999) ? code : 1000;
|
|
748
|
-
wsIncoming.close(validCode, reason.toString() || '');
|
|
749
|
-
}
|
|
750
|
-
catch (error) {
|
|
751
|
-
this.log('error', 'Error closing incoming WebSocket', error);
|
|
752
|
-
wsIncoming.terminate();
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
|
-
/**
|
|
758
|
-
* Handles an HTTP/HTTPS request
|
|
759
|
-
*/
|
|
760
|
-
async handleRequest(originRequest, originResponse) {
|
|
761
|
-
this.requestsServed++;
|
|
762
|
-
const startTime = Date.now();
|
|
763
|
-
const reqId = `req_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
|
|
764
|
-
try {
|
|
765
|
-
const reqPath = plugins.url.parse(originRequest.url).path;
|
|
766
|
-
this.log('info', `[${reqId}] ${originRequest.method} ${originRequest.headers.host}${reqPath}`);
|
|
767
|
-
// Handle preflight OPTIONS requests for CORS
|
|
768
|
-
if (originRequest.method === 'OPTIONS' && this.options.cors) {
|
|
769
|
-
this.handleCorsRequest(originRequest, originResponse);
|
|
770
|
-
return;
|
|
771
|
-
}
|
|
772
|
-
// Get destination configuration
|
|
773
|
-
const destinationConfig = this.router.routeReq(originRequest);
|
|
774
|
-
if (!destinationConfig) {
|
|
775
|
-
this.log('warn', `[${reqId}] No route found for ${originRequest.headers.host}`);
|
|
776
|
-
this.sendErrorResponse(originResponse, 404, 'Not Found: No matching route');
|
|
777
|
-
this.failedRequests++;
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
// Handle authentication if configured
|
|
781
|
-
if (destinationConfig.authentication) {
|
|
782
|
-
try {
|
|
783
|
-
if (!this.authenticateRequest(originRequest, destinationConfig)) {
|
|
784
|
-
this.sendErrorResponse(originResponse, 401, 'Unauthorized', {
|
|
785
|
-
'WWW-Authenticate': 'Basic realm="Access to the proxy site", charset="UTF-8"'
|
|
786
|
-
});
|
|
787
|
-
this.failedRequests++;
|
|
788
|
-
return;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
catch (error) {
|
|
792
|
-
this.log('error', `[${reqId}] Authentication error`, error);
|
|
793
|
-
this.sendErrorResponse(originResponse, 500, 'Internal Server Error: Authentication failed');
|
|
794
|
-
this.failedRequests++;
|
|
795
|
-
return;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
// Determine if we should use connection pooling
|
|
799
|
-
const useConnectionPool = this.options.portProxyIntegration &&
|
|
800
|
-
originRequest.socket.remoteAddress?.includes('127.0.0.1');
|
|
801
|
-
// Select destination IP and port from the arrays
|
|
802
|
-
const destinationIp = this.selectDestinationIp(destinationConfig);
|
|
803
|
-
const destinationPort = this.selectDestinationPort(destinationConfig);
|
|
804
|
-
// Construct destination URL
|
|
805
|
-
const destinationUrl = `http://${destinationIp}:${destinationPort}${originRequest.url}`;
|
|
806
|
-
if (useConnectionPool) {
|
|
807
|
-
this.log('debug', `[${reqId}] Proxying to ${destinationUrl} (using connection pool)`);
|
|
808
|
-
await this.forwardRequestUsingConnectionPool(reqId, originRequest, originResponse, destinationIp, destinationPort, originRequest.url);
|
|
809
|
-
}
|
|
810
|
-
else {
|
|
811
|
-
this.log('debug', `[${reqId}] Proxying to ${destinationUrl}`);
|
|
812
|
-
await this.forwardRequest(reqId, originRequest, originResponse, destinationUrl);
|
|
813
|
-
}
|
|
814
|
-
const processingTime = Date.now() - startTime;
|
|
815
|
-
this.log('debug', `[${reqId}] Request completed in ${processingTime}ms`);
|
|
816
|
-
}
|
|
817
|
-
catch (error) {
|
|
818
|
-
this.log('error', `[${reqId}] Unhandled error in request handler`, error);
|
|
819
|
-
try {
|
|
820
|
-
this.sendErrorResponse(originResponse, 502, 'Bad Gateway: Server error');
|
|
821
|
-
}
|
|
822
|
-
catch (responseError) {
|
|
823
|
-
this.log('error', `[${reqId}] Failed to send error response`, responseError);
|
|
824
|
-
}
|
|
825
|
-
this.failedRequests++;
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
/**
|
|
829
|
-
* Handles a CORS preflight request
|
|
830
|
-
*/
|
|
831
|
-
handleCorsRequest(req, res) {
|
|
832
|
-
const cors = this.options.cors;
|
|
833
|
-
// Set CORS headers
|
|
834
|
-
res.setHeader('Access-Control-Allow-Origin', cors.allowOrigin);
|
|
835
|
-
res.setHeader('Access-Control-Allow-Methods', cors.allowMethods);
|
|
836
|
-
res.setHeader('Access-Control-Allow-Headers', cors.allowHeaders);
|
|
837
|
-
res.setHeader('Access-Control-Max-Age', String(cors.maxAge));
|
|
838
|
-
// Handle preflight request
|
|
839
|
-
res.statusCode = 204;
|
|
840
|
-
res.end();
|
|
841
|
-
// Count this as a request served
|
|
842
|
-
this.requestsServed++;
|
|
843
|
-
}
|
|
844
|
-
/**
|
|
845
|
-
* Authenticates a request against the destination config
|
|
846
|
-
*/
|
|
847
|
-
authenticateRequest(req, config) {
|
|
848
|
-
const authInfo = config.authentication;
|
|
849
|
-
if (!authInfo) {
|
|
850
|
-
return true; // No authentication required
|
|
851
|
-
}
|
|
852
|
-
switch (authInfo.type) {
|
|
853
|
-
case 'Basic': {
|
|
854
|
-
const authHeader = req.headers.authorization;
|
|
855
|
-
if (!authHeader || !authHeader.includes('Basic ')) {
|
|
856
|
-
return false;
|
|
857
|
-
}
|
|
858
|
-
const authStringBase64 = authHeader.replace('Basic ', '');
|
|
859
|
-
const authString = plugins.smartstring.base64.decode(authStringBase64);
|
|
860
|
-
const [user, pass] = authString.split(':');
|
|
861
|
-
// Use constant-time comparison to prevent timing attacks
|
|
862
|
-
const userMatch = user === authInfo.user;
|
|
863
|
-
const passMatch = pass === authInfo.pass;
|
|
864
|
-
return userMatch && passMatch;
|
|
865
|
-
}
|
|
866
|
-
default:
|
|
867
|
-
throw new Error(`Unsupported authentication method: ${authInfo.type}`);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
/**
|
|
871
|
-
* Forwards a request to the destination using connection pool
|
|
872
|
-
* for optimized connection reuse from PortProxy
|
|
873
|
-
*/
|
|
874
|
-
async forwardRequestUsingConnectionPool(reqId, originRequest, originResponse, host, port, path) {
|
|
875
|
-
try {
|
|
876
|
-
// Try to get a connection from the pool
|
|
877
|
-
const socket = await this.getConnectionFromPool(host, port);
|
|
878
|
-
// Create an HTTP client request using the pooled socket
|
|
879
|
-
const reqOptions = {
|
|
880
|
-
createConnection: () => socket,
|
|
881
|
-
host,
|
|
882
|
-
port,
|
|
883
|
-
path,
|
|
884
|
-
method: originRequest.method,
|
|
885
|
-
headers: this.prepareForwardHeaders(originRequest),
|
|
886
|
-
timeout: 30000 // 30 second timeout
|
|
887
|
-
};
|
|
888
|
-
const proxyReq = plugins.http.request(reqOptions);
|
|
889
|
-
// Handle timeouts
|
|
890
|
-
proxyReq.on('timeout', () => {
|
|
891
|
-
this.log('warn', `[${reqId}] Request to ${host}:${port}${path} timed out`);
|
|
892
|
-
proxyReq.destroy();
|
|
893
|
-
});
|
|
894
|
-
// Handle errors
|
|
895
|
-
proxyReq.on('error', (err) => {
|
|
896
|
-
this.log('error', `[${reqId}] Error in proxy request to ${host}:${port}${path}`, err);
|
|
897
|
-
// Check if the client response is still writable
|
|
898
|
-
if (!originResponse.writableEnded) {
|
|
899
|
-
this.sendErrorResponse(originResponse, 502, 'Bad Gateway: Error communicating with upstream server');
|
|
900
|
-
}
|
|
901
|
-
// Don't return the socket to the pool on error
|
|
902
|
-
try {
|
|
903
|
-
if (!socket.destroyed) {
|
|
904
|
-
socket.destroy();
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
catch (socketErr) {
|
|
908
|
-
this.log('error', `[${reqId}] Error destroying socket after request error`, socketErr);
|
|
909
|
-
}
|
|
910
|
-
});
|
|
911
|
-
// Forward request body
|
|
912
|
-
originRequest.pipe(proxyReq);
|
|
913
|
-
// Handle response
|
|
914
|
-
proxyReq.on('response', (proxyRes) => {
|
|
915
|
-
// Copy status and headers
|
|
916
|
-
originResponse.statusCode = proxyRes.statusCode;
|
|
917
|
-
for (const [name, value] of Object.entries(proxyRes.headers)) {
|
|
918
|
-
if (value !== undefined) {
|
|
919
|
-
originResponse.setHeader(name, value);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
// Forward the response body
|
|
923
|
-
proxyRes.pipe(originResponse);
|
|
924
|
-
// Return connection to pool when the response completes
|
|
925
|
-
proxyRes.on('end', () => {
|
|
926
|
-
if (!socket.destroyed) {
|
|
927
|
-
this.returnConnectionToPool(socket, host, port);
|
|
928
|
-
}
|
|
929
|
-
});
|
|
930
|
-
proxyRes.on('error', (err) => {
|
|
931
|
-
this.log('error', `[${reqId}] Error in proxy response from ${host}:${port}${path}`, err);
|
|
932
|
-
// Don't return the socket to the pool on error
|
|
933
|
-
try {
|
|
934
|
-
if (!socket.destroyed) {
|
|
935
|
-
socket.destroy();
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
catch (socketErr) {
|
|
939
|
-
this.log('error', `[${reqId}] Error destroying socket after response error`, socketErr);
|
|
940
|
-
}
|
|
941
|
-
});
|
|
942
|
-
});
|
|
943
|
-
}
|
|
944
|
-
catch (error) {
|
|
945
|
-
this.log('error', `[${reqId}] Error setting up pooled connection to ${host}:${port}`, error);
|
|
946
|
-
this.sendErrorResponse(originResponse, 502, 'Bad Gateway: Unable to reach upstream server');
|
|
947
|
-
throw error;
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
/**
|
|
951
|
-
* Forwards a request to the destination (standard method)
|
|
952
|
-
*/
|
|
953
|
-
async forwardRequest(reqId, originRequest, originResponse, destinationUrl) {
|
|
954
|
-
try {
|
|
955
|
-
const proxyRequest = await plugins.smartrequest.request(destinationUrl, {
|
|
956
|
-
method: originRequest.method,
|
|
957
|
-
headers: this.prepareForwardHeaders(originRequest),
|
|
958
|
-
keepAlive: true,
|
|
959
|
-
timeout: 30000 // 30 second timeout
|
|
960
|
-
}, true, // streaming
|
|
961
|
-
(proxyRequestStream) => this.setupRequestStreaming(originRequest, proxyRequestStream));
|
|
962
|
-
// Handle the response
|
|
963
|
-
this.processProxyResponse(reqId, originResponse, proxyRequest);
|
|
964
|
-
}
|
|
965
|
-
catch (error) {
|
|
966
|
-
this.log('error', `[${reqId}] Error forwarding request`, error);
|
|
967
|
-
this.sendErrorResponse(originResponse, 502, 'Bad Gateway: Unable to reach upstream server');
|
|
968
|
-
throw error; // Let the main handler catch this
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
/**
|
|
972
|
-
* Prepares headers to forward to the backend
|
|
973
|
-
*/
|
|
974
|
-
prepareForwardHeaders(req) {
|
|
975
|
-
const safeHeaders = { ...req.headers };
|
|
976
|
-
// Add forwarding headers
|
|
977
|
-
safeHeaders['X-Forwarded-Host'] = req.headers.host;
|
|
978
|
-
safeHeaders['X-Forwarded-Proto'] = 'https';
|
|
979
|
-
safeHeaders['X-Forwarded-For'] = (req.socket.remoteAddress || '').replace(/^::ffff:/, '');
|
|
980
|
-
// Add proxy-specific headers
|
|
981
|
-
safeHeaders['X-Proxy-Id'] = `NetworkProxy-${this.options.port}`;
|
|
982
|
-
// If this is coming from PortProxy, add a header to indicate that
|
|
983
|
-
if (this.options.portProxyIntegration && req.socket.remoteAddress?.includes('127.0.0.1')) {
|
|
984
|
-
safeHeaders['X-PortProxy-Forwarded'] = 'true';
|
|
985
|
-
}
|
|
986
|
-
// Remove sensitive headers we don't want to forward
|
|
987
|
-
const sensitiveHeaders = ['connection', 'upgrade', 'http2-settings'];
|
|
988
|
-
for (const header of sensitiveHeaders) {
|
|
989
|
-
delete safeHeaders[header];
|
|
990
|
-
}
|
|
991
|
-
return safeHeaders;
|
|
992
|
-
}
|
|
993
|
-
/**
|
|
994
|
-
* Sets up request streaming for the proxy
|
|
995
|
-
*/
|
|
996
|
-
setupRequestStreaming(originRequest, proxyRequest) {
|
|
997
|
-
// Forward request body data
|
|
998
|
-
originRequest.on('data', (chunk) => {
|
|
999
|
-
proxyRequest.write(chunk);
|
|
1000
|
-
});
|
|
1001
|
-
// End the request when done
|
|
1002
|
-
originRequest.on('end', () => {
|
|
1003
|
-
proxyRequest.end();
|
|
1004
|
-
});
|
|
1005
|
-
// Handle request errors
|
|
1006
|
-
originRequest.on('error', (error) => {
|
|
1007
|
-
this.log('error', 'Error in client request stream', error);
|
|
1008
|
-
proxyRequest.destroy(error);
|
|
1009
|
-
});
|
|
1010
|
-
// Handle client abort/timeout
|
|
1011
|
-
originRequest.on('close', () => {
|
|
1012
|
-
if (!originRequest.complete) {
|
|
1013
|
-
this.log('debug', 'Client closed connection before request completed');
|
|
1014
|
-
proxyRequest.destroy();
|
|
1015
|
-
}
|
|
1016
|
-
});
|
|
1017
|
-
originRequest.on('timeout', () => {
|
|
1018
|
-
this.log('debug', 'Client request timeout');
|
|
1019
|
-
proxyRequest.destroy(new Error('Client request timeout'));
|
|
1020
|
-
});
|
|
1021
|
-
// Handle proxy request errors
|
|
1022
|
-
proxyRequest.on('error', (error) => {
|
|
1023
|
-
this.log('error', 'Error in outgoing proxy request', error);
|
|
1024
|
-
});
|
|
1025
|
-
}
|
|
1026
|
-
/**
|
|
1027
|
-
* Processes a proxy response
|
|
1028
|
-
*/
|
|
1029
|
-
processProxyResponse(reqId, originResponse, proxyResponse) {
|
|
1030
|
-
this.log('debug', `[${reqId}] Received upstream response: ${proxyResponse.statusCode}`);
|
|
1031
|
-
// Set status code
|
|
1032
|
-
originResponse.statusCode = proxyResponse.statusCode;
|
|
1033
|
-
// Add default headers
|
|
1034
|
-
for (const [headerName, headerValue] of Object.entries(this.defaultHeaders)) {
|
|
1035
|
-
originResponse.setHeader(headerName, headerValue);
|
|
1036
|
-
}
|
|
1037
|
-
// Add CORS headers if enabled
|
|
1038
|
-
if (this.options.cors) {
|
|
1039
|
-
originResponse.setHeader('Access-Control-Allow-Origin', this.options.cors.allowOrigin);
|
|
1040
|
-
}
|
|
1041
|
-
// Copy response headers
|
|
1042
|
-
for (const [headerName, headerValue] of Object.entries(proxyResponse.headers)) {
|
|
1043
|
-
// Skip hop-by-hop headers
|
|
1044
|
-
const hopByHopHeaders = ['connection', 'keep-alive', 'transfer-encoding', 'te',
|
|
1045
|
-
'trailer', 'upgrade', 'proxy-authorization', 'proxy-authenticate'];
|
|
1046
|
-
if (!hopByHopHeaders.includes(headerName.toLowerCase())) {
|
|
1047
|
-
originResponse.setHeader(headerName, headerValue);
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
// Stream response body
|
|
1051
|
-
proxyResponse.on('data', (chunk) => {
|
|
1052
|
-
const canContinue = originResponse.write(chunk);
|
|
1053
|
-
// Apply backpressure if needed
|
|
1054
|
-
if (!canContinue) {
|
|
1055
|
-
proxyResponse.pause();
|
|
1056
|
-
originResponse.once('drain', () => {
|
|
1057
|
-
proxyResponse.resume();
|
|
1058
|
-
});
|
|
1059
|
-
}
|
|
1060
|
-
});
|
|
1061
|
-
// End the response when done
|
|
1062
|
-
proxyResponse.on('end', () => {
|
|
1063
|
-
originResponse.end();
|
|
1064
|
-
});
|
|
1065
|
-
// Handle response errors
|
|
1066
|
-
proxyResponse.on('error', (error) => {
|
|
1067
|
-
this.log('error', `[${reqId}] Error in proxy response stream`, error);
|
|
1068
|
-
originResponse.destroy(error);
|
|
1069
|
-
});
|
|
1070
|
-
originResponse.on('error', (error) => {
|
|
1071
|
-
this.log('error', `[${reqId}] Error in client response stream`, error);
|
|
1072
|
-
proxyResponse.destroy();
|
|
1073
|
-
});
|
|
1074
|
-
}
|
|
1075
|
-
/**
|
|
1076
|
-
* Sends an error response to the client
|
|
1077
|
-
*/
|
|
1078
|
-
sendErrorResponse(res, statusCode = 500, message = 'Internal Server Error', headers = {}) {
|
|
1079
|
-
try {
|
|
1080
|
-
// If headers already sent, just end the response
|
|
1081
|
-
if (res.headersSent) {
|
|
1082
|
-
res.end();
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
// Add default headers
|
|
1086
|
-
for (const [key, value] of Object.entries(this.defaultHeaders)) {
|
|
1087
|
-
res.setHeader(key, value);
|
|
1088
|
-
}
|
|
1089
|
-
// Add provided headers
|
|
1090
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
1091
|
-
res.setHeader(key, value);
|
|
1092
|
-
}
|
|
1093
|
-
// Send error response
|
|
1094
|
-
res.writeHead(statusCode, message);
|
|
1095
|
-
// Send error body as JSON for API clients
|
|
1096
|
-
if (res.getHeader('Content-Type') === 'application/json') {
|
|
1097
|
-
res.end(JSON.stringify({ error: { status: statusCode, message } }));
|
|
1098
|
-
}
|
|
1099
|
-
else {
|
|
1100
|
-
// Send as plain text
|
|
1101
|
-
res.end(message);
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
catch (error) {
|
|
1105
|
-
this.log('error', 'Error sending error response', error);
|
|
1106
|
-
try {
|
|
1107
|
-
res.destroy();
|
|
1108
|
-
}
|
|
1109
|
-
catch (destroyError) {
|
|
1110
|
-
// Last resort - nothing more we can do
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
/**
|
|
1115
|
-
* Selects a destination IP from the array using round-robin
|
|
1116
|
-
* @param config The proxy configuration
|
|
1117
|
-
* @returns A destination IP address
|
|
1118
|
-
*/
|
|
1119
|
-
selectDestinationIp(config) {
|
|
1120
|
-
// For array-based configs
|
|
1121
|
-
if (Array.isArray(config.destinationIps) && config.destinationIps.length > 0) {
|
|
1122
|
-
// Get the current position or initialize it
|
|
1123
|
-
const key = `ip_${config.hostName}`;
|
|
1124
|
-
let position = this.roundRobinPositions.get(key) || 0;
|
|
1125
|
-
// Select the IP using round-robin
|
|
1126
|
-
const selectedIp = config.destinationIps[position];
|
|
1127
|
-
// Update the position for next time
|
|
1128
|
-
position = (position + 1) % config.destinationIps.length;
|
|
1129
|
-
this.roundRobinPositions.set(key, position);
|
|
1130
|
-
return selectedIp;
|
|
1131
|
-
}
|
|
1132
|
-
// For backward compatibility with test suites that rely on specific behavior
|
|
1133
|
-
// Check if there's a proxyConfigs entry that matches this hostname
|
|
1134
|
-
const matchingConfig = this.proxyConfigs.find(cfg => cfg.hostName === config.hostName &&
|
|
1135
|
-
cfg.destinationIp);
|
|
1136
|
-
if (matchingConfig) {
|
|
1137
|
-
return matchingConfig.destinationIp;
|
|
1138
|
-
}
|
|
1139
|
-
// Fallback to localhost
|
|
1140
|
-
return 'localhost';
|
|
1141
|
-
}
|
|
1142
|
-
/**
|
|
1143
|
-
* Selects a destination port from the array using round-robin
|
|
1144
|
-
* @param config The proxy configuration
|
|
1145
|
-
* @returns A destination port number
|
|
1146
|
-
*/
|
|
1147
|
-
selectDestinationPort(config) {
|
|
1148
|
-
// For array-based configs
|
|
1149
|
-
if (Array.isArray(config.destinationPorts) && config.destinationPorts.length > 0) {
|
|
1150
|
-
// Get the current position or initialize it
|
|
1151
|
-
const key = `port_${config.hostName}`;
|
|
1152
|
-
let position = this.roundRobinPositions.get(key) || 0;
|
|
1153
|
-
// Select the port using round-robin
|
|
1154
|
-
const selectedPort = config.destinationPorts[position];
|
|
1155
|
-
// Update the position for next time
|
|
1156
|
-
position = (position + 1) % config.destinationPorts.length;
|
|
1157
|
-
this.roundRobinPositions.set(key, position);
|
|
1158
|
-
return selectedPort;
|
|
1159
|
-
}
|
|
1160
|
-
// For backward compatibility with test suites that rely on specific behavior
|
|
1161
|
-
// Check if there's a proxyConfigs entry that matches this hostname
|
|
1162
|
-
const matchingConfig = this.proxyConfigs.find(cfg => cfg.hostName === config.hostName &&
|
|
1163
|
-
cfg.destinationPort);
|
|
1164
|
-
if (matchingConfig) {
|
|
1165
|
-
return parseInt(matchingConfig.destinationPort, 10);
|
|
1166
|
-
}
|
|
1167
|
-
// Fallback to port 80
|
|
1168
|
-
return 80;
|
|
1169
|
-
}
|
|
1170
|
-
/**
|
|
1171
|
-
* Updates proxy configurations
|
|
1172
|
-
*/
|
|
1173
|
-
async updateProxyConfigs(proxyConfigsArg) {
|
|
1174
|
-
this.log('info', `Updating proxy configurations (${proxyConfigsArg.length} configs)`);
|
|
1175
|
-
// Update internal configs
|
|
1176
|
-
this.proxyConfigs = proxyConfigsArg;
|
|
1177
|
-
this.router.setNewProxyConfigs(proxyConfigsArg);
|
|
1178
|
-
// Collect all hostnames for cleanup later
|
|
1179
|
-
const currentHostNames = new Set();
|
|
1180
|
-
// Add/update SSL contexts for each host
|
|
1181
|
-
for (const config of proxyConfigsArg) {
|
|
1182
|
-
currentHostNames.add(config.hostName);
|
|
1183
|
-
try {
|
|
1184
|
-
// Check if we need to update the cert
|
|
1185
|
-
const currentCert = this.certificateCache.get(config.hostName);
|
|
1186
|
-
const shouldUpdate = !currentCert ||
|
|
1187
|
-
currentCert.key !== config.privateKey ||
|
|
1188
|
-
currentCert.cert !== config.publicKey;
|
|
1189
|
-
if (shouldUpdate) {
|
|
1190
|
-
this.log('debug', `Updating SSL context for ${config.hostName}`);
|
|
1191
|
-
// Update the HTTPS server context
|
|
1192
|
-
this.httpsServer.addContext(config.hostName, {
|
|
1193
|
-
key: config.privateKey,
|
|
1194
|
-
cert: config.publicKey
|
|
1195
|
-
});
|
|
1196
|
-
// Update the cache
|
|
1197
|
-
this.certificateCache.set(config.hostName, {
|
|
1198
|
-
key: config.privateKey,
|
|
1199
|
-
cert: config.publicKey
|
|
1200
|
-
});
|
|
1201
|
-
this.activeContexts.add(config.hostName);
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
catch (error) {
|
|
1205
|
-
this.log('error', `Failed to add SSL context for ${config.hostName}`, error);
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
// Clean up removed contexts
|
|
1209
|
-
// Note: Node.js doesn't officially support removing contexts
|
|
1210
|
-
// This would require server restart in production
|
|
1211
|
-
for (const hostname of this.activeContexts) {
|
|
1212
|
-
if (!currentHostNames.has(hostname)) {
|
|
1213
|
-
this.log('info', `Hostname ${hostname} removed from configuration`);
|
|
1214
|
-
this.activeContexts.delete(hostname);
|
|
1215
|
-
this.certificateCache.delete(hostname);
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
/**
|
|
1220
|
-
* Converts PortProxy domain configurations to NetworkProxy configs
|
|
1221
|
-
* @param domainConfigs PortProxy domain configs
|
|
1222
|
-
* @param sslKeyPair Default SSL key pair to use if not specified
|
|
1223
|
-
* @returns Array of NetworkProxy configs
|
|
1224
|
-
*/
|
|
1225
|
-
convertPortProxyConfigs(domainConfigs, sslKeyPair) {
|
|
1226
|
-
const proxyConfigs = [];
|
|
1227
|
-
// Use default certificates if not provided
|
|
1228
|
-
const sslKey = sslKeyPair?.key || this.defaultCertificates.key;
|
|
1229
|
-
const sslCert = sslKeyPair?.cert || this.defaultCertificates.cert;
|
|
1230
|
-
for (const domainConfig of domainConfigs) {
|
|
1231
|
-
// Each domain in the domains array gets its own config
|
|
1232
|
-
for (const domain of domainConfig.domains) {
|
|
1233
|
-
// Skip non-hostname patterns (like IP addresses)
|
|
1234
|
-
if (domain.match(/^\d+\.\d+\.\d+\.\d+$/) || domain === '*' || domain === 'localhost') {
|
|
1235
|
-
continue;
|
|
1236
|
-
}
|
|
1237
|
-
proxyConfigs.push({
|
|
1238
|
-
hostName: domain,
|
|
1239
|
-
destinationIps: domainConfig.targetIPs || ['localhost'],
|
|
1240
|
-
destinationPorts: [this.options.port], // Use the NetworkProxy port
|
|
1241
|
-
privateKey: sslKey,
|
|
1242
|
-
publicKey: sslCert
|
|
1243
|
-
});
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
this.log('info', `Converted ${domainConfigs.length} PortProxy configs to ${proxyConfigs.length} NetworkProxy configs`);
|
|
1247
|
-
return proxyConfigs;
|
|
1248
|
-
}
|
|
1249
|
-
/**
|
|
1250
|
-
* Adds default headers to be included in all responses
|
|
1251
|
-
*/
|
|
1252
|
-
async addDefaultHeaders(headersArg) {
|
|
1253
|
-
this.log('info', 'Adding default headers', headersArg);
|
|
1254
|
-
this.defaultHeaders = {
|
|
1255
|
-
...this.defaultHeaders,
|
|
1256
|
-
...headersArg
|
|
1257
|
-
};
|
|
1258
|
-
}
|
|
1259
|
-
/**
|
|
1260
|
-
* Stops the proxy server
|
|
1261
|
-
*/
|
|
1262
|
-
async stop() {
|
|
1263
|
-
this.log('info', 'Stopping NetworkProxy server');
|
|
1264
|
-
// Clear intervals
|
|
1265
|
-
if (this.heartbeatInterval) {
|
|
1266
|
-
clearInterval(this.heartbeatInterval);
|
|
1267
|
-
}
|
|
1268
|
-
if (this.metricsInterval) {
|
|
1269
|
-
clearInterval(this.metricsInterval);
|
|
1270
|
-
}
|
|
1271
|
-
if (this.connectionPoolCleanupInterval) {
|
|
1272
|
-
clearInterval(this.connectionPoolCleanupInterval);
|
|
1273
|
-
}
|
|
1274
|
-
// Close WebSocket server if exists
|
|
1275
|
-
if (this.wsServer) {
|
|
1276
|
-
for (const client of this.wsServer.clients) {
|
|
1277
|
-
try {
|
|
1278
|
-
client.terminate();
|
|
1279
|
-
}
|
|
1280
|
-
catch (error) {
|
|
1281
|
-
this.log('error', 'Error terminating WebSocket client', error);
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
// Close all tracked sockets
|
|
1286
|
-
for (const socket of this.socketMap.getArray()) {
|
|
1287
|
-
try {
|
|
1288
|
-
socket.destroy();
|
|
1289
|
-
}
|
|
1290
|
-
catch (error) {
|
|
1291
|
-
this.log('error', 'Error destroying socket', error);
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
// Close all connection pool connections
|
|
1295
|
-
for (const [host, connections] of this.connectionPool.entries()) {
|
|
1296
|
-
for (const connection of connections) {
|
|
1297
|
-
try {
|
|
1298
|
-
if (!connection.socket.destroyed) {
|
|
1299
|
-
connection.socket.destroy();
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
catch (error) {
|
|
1303
|
-
this.log('error', `Error destroying pooled connection to ${host}`, error);
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
this.connectionPool.clear();
|
|
1308
|
-
// Stop Port80Handler if it's running
|
|
1309
|
-
if (this.port80Handler) {
|
|
1310
|
-
try {
|
|
1311
|
-
await this.port80Handler.stop();
|
|
1312
|
-
this.log('info', 'Port80Handler stopped');
|
|
1313
|
-
}
|
|
1314
|
-
catch (error) {
|
|
1315
|
-
this.log('error', 'Error stopping Port80Handler', error);
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
// Close the HTTPS server
|
|
1319
|
-
return new Promise((resolve) => {
|
|
1320
|
-
this.httpsServer.close(() => {
|
|
1321
|
-
this.log('info', 'NetworkProxy server stopped successfully');
|
|
1322
|
-
resolve();
|
|
1323
|
-
});
|
|
1324
|
-
});
|
|
1325
|
-
}
|
|
1326
|
-
/**
|
|
1327
|
-
* Requests a new certificate for a domain
|
|
1328
|
-
* This can be used to manually trigger certificate issuance
|
|
1329
|
-
* @param domain The domain to request a certificate for
|
|
1330
|
-
* @returns A promise that resolves when the request is submitted (not when the certificate is issued)
|
|
1331
|
-
*/
|
|
1332
|
-
async requestCertificate(domain) {
|
|
1333
|
-
if (!this.options.acme.enabled) {
|
|
1334
|
-
this.log('warn', 'ACME certificate management is not enabled');
|
|
1335
|
-
return false;
|
|
1336
|
-
}
|
|
1337
|
-
if (!this.port80Handler) {
|
|
1338
|
-
this.log('error', 'Port80Handler is not initialized');
|
|
1339
|
-
return false;
|
|
1340
|
-
}
|
|
1341
|
-
// Skip wildcard domains - can't get certs for these with HTTP-01 validation
|
|
1342
|
-
if (domain.includes('*')) {
|
|
1343
|
-
this.log('error', `Cannot request certificate for wildcard domain: ${domain}`);
|
|
1344
|
-
return false;
|
|
1345
|
-
}
|
|
1346
|
-
try {
|
|
1347
|
-
// Use the new domain options format
|
|
1348
|
-
const domainOptions = {
|
|
1349
|
-
domainName: domain,
|
|
1350
|
-
sslRedirect: true,
|
|
1351
|
-
acmeMaintenance: true
|
|
1352
|
-
};
|
|
1353
|
-
this.port80Handler.addDomain(domainOptions);
|
|
1354
|
-
this.log('info', `Certificate request submitted for domain: ${domain}`);
|
|
1355
|
-
return true;
|
|
1356
|
-
}
|
|
1357
|
-
catch (error) {
|
|
1358
|
-
this.log('error', `Error requesting certificate for domain ${domain}:`, error);
|
|
1359
|
-
return false;
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
/**
|
|
1363
|
-
* Updates the certificate cache for a domain
|
|
1364
|
-
* @param domain The domain name
|
|
1365
|
-
* @param certificate The certificate (PEM format)
|
|
1366
|
-
* @param privateKey The private key (PEM format)
|
|
1367
|
-
* @param expiryDate Optional expiry date
|
|
1368
|
-
*/
|
|
1369
|
-
updateCertificateCache(domain, certificate, privateKey, expiryDate) {
|
|
1370
|
-
// Update certificate context in HTTPS server if it's running
|
|
1371
|
-
if (this.httpsServer) {
|
|
1372
|
-
try {
|
|
1373
|
-
this.httpsServer.addContext(domain, {
|
|
1374
|
-
key: privateKey,
|
|
1375
|
-
cert: certificate
|
|
1376
|
-
});
|
|
1377
|
-
this.log('debug', `Updated SSL context for domain: ${domain}`);
|
|
1378
|
-
}
|
|
1379
|
-
catch (error) {
|
|
1380
|
-
this.log('error', `Error updating SSL context for domain ${domain}:`, error);
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
// Update certificate in cache
|
|
1384
|
-
this.certificateCache.set(domain, {
|
|
1385
|
-
key: privateKey,
|
|
1386
|
-
cert: certificate,
|
|
1387
|
-
expires: expiryDate
|
|
1388
|
-
});
|
|
1389
|
-
// Add to active contexts set
|
|
1390
|
-
this.activeContexts.add(domain);
|
|
1391
|
-
}
|
|
1392
|
-
/**
|
|
1393
|
-
* Logs a message according to the configured log level
|
|
1394
|
-
*/
|
|
1395
|
-
log(level, message, data) {
|
|
1396
|
-
const logLevels = {
|
|
1397
|
-
error: 0,
|
|
1398
|
-
warn: 1,
|
|
1399
|
-
info: 2,
|
|
1400
|
-
debug: 3
|
|
1401
|
-
};
|
|
1402
|
-
// Skip if log level is higher than configured
|
|
1403
|
-
if (logLevels[level] > logLevels[this.options.logLevel]) {
|
|
1404
|
-
return;
|
|
1405
|
-
}
|
|
1406
|
-
const timestamp = new Date().toISOString();
|
|
1407
|
-
const prefix = `[${timestamp}] [${level.toUpperCase()}]`;
|
|
1408
|
-
switch (level) {
|
|
1409
|
-
case 'error':
|
|
1410
|
-
console.error(`${prefix} ${message}`, data || '');
|
|
1411
|
-
break;
|
|
1412
|
-
case 'warn':
|
|
1413
|
-
console.warn(`${prefix} ${message}`, data || '');
|
|
1414
|
-
break;
|
|
1415
|
-
case 'info':
|
|
1416
|
-
console.log(`${prefix} ${message}`, data || '');
|
|
1417
|
-
break;
|
|
1418
|
-
case 'debug':
|
|
1419
|
-
console.log(`${prefix} ${message}`, data || '');
|
|
1420
|
-
break;
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5uZXR3b3JrcHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLm5ldHdvcmtwcm94eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDbEQsT0FBTyxFQUFFLGFBQWEsRUFBRSxtQkFBbUIsRUFBdUIsTUFBTSw0QkFBNEIsQ0FBQztBQUNyRyxPQUFPLEtBQUssRUFBRSxNQUFNLElBQUksQ0FBQztBQUN6QixPQUFPLEtBQUssSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUM3QixPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBcUNwQyxNQUFNLE9BQU8sWUFBWTtJQThDdkI7O09BRUc7SUFDSCxZQUFZLFVBQWdDO1FBOUNyQyxpQkFBWSxHQUFrRCxFQUFFLENBQUM7UUFDakUsbUJBQWMsR0FBOEIsRUFBRSxDQUFDO1FBTXRELGlCQUFpQjtRQUNWLFdBQU0sR0FBRyxJQUFJLFdBQVcsRUFBRSxDQUFDO1FBQzNCLGNBQVMsR0FBRyxJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsU0FBUyxFQUFzQixDQUFDO1FBQzVELG1CQUFjLEdBQWdCLElBQUksR0FBRyxFQUFFLENBQUM7UUFDeEMscUJBQWdCLEdBQVcsQ0FBQyxDQUFDO1FBQzdCLGNBQVMsR0FBVyxDQUFDLENBQUM7UUFDdEIsbUJBQWMsR0FBVyxDQUFDLENBQUM7UUFDM0IsbUJBQWMsR0FBVyxDQUFDLENBQUM7UUFFbEMseUNBQXlDO1FBQ2pDLHlCQUFvQixHQUFXLENBQUMsQ0FBQztRQUNqQyw2QkFBd0IsR0FBVyxDQUFDLENBQUM7UUFTckMscUJBQWdCLEdBQStELElBQUksR0FBRyxFQUFFLENBQUM7UUFFakcsMkNBQTJDO1FBQ25DLGtCQUFhLEdBQXlCLElBQUksQ0FBQztRQUduRCw4Q0FBOEM7UUFD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
|