@push.rocks/smartproxy 19.3.2 → 19.3.4
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 +18 -35
- package/readme.plan.md +173 -271
- 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,1802 +0,0 @@
|
|
|
1
|
-
import * as plugins from './plugins.js';
|
|
2
|
-
import { NetworkProxy } from './classes.networkproxy.js';
|
|
3
|
-
import { SniHandler } from './classes.snihandler.js';
|
|
4
|
-
// SNI functions are now imported from SniHandler class
|
|
5
|
-
// No need for wrapper functions
|
|
6
|
-
// Helper: Check if a port falls within any of the given port ranges
|
|
7
|
-
const isPortInRanges = (port, ranges) => {
|
|
8
|
-
return ranges.some((range) => port >= range.from && port <= range.to);
|
|
9
|
-
};
|
|
10
|
-
// Helper: Check if a given IP matches any of the glob patterns
|
|
11
|
-
const isAllowed = (ip, patterns) => {
|
|
12
|
-
if (!ip || !patterns || patterns.length === 0)
|
|
13
|
-
return false;
|
|
14
|
-
const normalizeIP = (ip) => {
|
|
15
|
-
if (!ip)
|
|
16
|
-
return [];
|
|
17
|
-
if (ip.startsWith('::ffff:')) {
|
|
18
|
-
const ipv4 = ip.slice(7);
|
|
19
|
-
return [ip, ipv4];
|
|
20
|
-
}
|
|
21
|
-
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
|
|
22
|
-
return [ip, `::ffff:${ip}`];
|
|
23
|
-
}
|
|
24
|
-
return [ip];
|
|
25
|
-
};
|
|
26
|
-
const normalizedIPVariants = normalizeIP(ip);
|
|
27
|
-
if (normalizedIPVariants.length === 0)
|
|
28
|
-
return false;
|
|
29
|
-
const expandedPatterns = patterns.flatMap(normalizeIP);
|
|
30
|
-
return normalizedIPVariants.some((ipVariant) => expandedPatterns.some((pattern) => plugins.minimatch(ipVariant, pattern)));
|
|
31
|
-
};
|
|
32
|
-
// Helper: Check if an IP is allowed considering allowed and blocked glob patterns
|
|
33
|
-
const isGlobIPAllowed = (ip, allowed, blocked = []) => {
|
|
34
|
-
if (!ip)
|
|
35
|
-
return false;
|
|
36
|
-
if (blocked.length > 0 && isAllowed(ip, blocked))
|
|
37
|
-
return false;
|
|
38
|
-
return isAllowed(ip, allowed);
|
|
39
|
-
};
|
|
40
|
-
// Helper: Generate a unique connection ID
|
|
41
|
-
const generateConnectionId = () => {
|
|
42
|
-
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
43
|
-
};
|
|
44
|
-
// SNI functions are now imported from SniHandler class
|
|
45
|
-
// Helper: Ensure timeout values don't exceed Node.js max safe integer
|
|
46
|
-
const ensureSafeTimeout = (timeout) => {
|
|
47
|
-
const MAX_SAFE_TIMEOUT = 2147483647; // Maximum safe value (2^31 - 1)
|
|
48
|
-
return Math.min(Math.floor(timeout), MAX_SAFE_TIMEOUT);
|
|
49
|
-
};
|
|
50
|
-
// Helper: Generate a slightly randomized timeout to prevent thundering herd
|
|
51
|
-
const randomizeTimeout = (baseTimeout, variationPercent = 5) => {
|
|
52
|
-
const safeBaseTimeout = ensureSafeTimeout(baseTimeout);
|
|
53
|
-
const variation = safeBaseTimeout * (variationPercent / 100);
|
|
54
|
-
return ensureSafeTimeout(safeBaseTimeout + Math.floor(Math.random() * variation * 2) - variation);
|
|
55
|
-
};
|
|
56
|
-
export class PortProxy {
|
|
57
|
-
constructor(settingsArg) {
|
|
58
|
-
this.netServers = [];
|
|
59
|
-
this.connectionRecords = new Map();
|
|
60
|
-
this.connectionLogger = null;
|
|
61
|
-
this.isShuttingDown = false;
|
|
62
|
-
// Map to track round robin indices for each domain config
|
|
63
|
-
this.domainTargetIndices = new Map();
|
|
64
|
-
// Enhanced stats tracking
|
|
65
|
-
this.terminationStats = {
|
|
66
|
-
incoming: {},
|
|
67
|
-
outgoing: {},
|
|
68
|
-
};
|
|
69
|
-
// Connection tracking by IP for rate limiting
|
|
70
|
-
this.connectionsByIP = new Map();
|
|
71
|
-
this.connectionRateByIP = new Map();
|
|
72
|
-
// NetworkProxy instance for TLS termination
|
|
73
|
-
this.networkProxy = null;
|
|
74
|
-
// Set reasonable defaults for all settings
|
|
75
|
-
this.settings = {
|
|
76
|
-
...settingsArg,
|
|
77
|
-
targetIP: settingsArg.targetIP || 'localhost',
|
|
78
|
-
// Timeout settings with reasonable defaults
|
|
79
|
-
initialDataTimeout: settingsArg.initialDataTimeout || 120000, // 120 seconds for initial handshake
|
|
80
|
-
socketTimeout: ensureSafeTimeout(settingsArg.socketTimeout || 3600000), // 1 hour socket timeout
|
|
81
|
-
inactivityCheckInterval: settingsArg.inactivityCheckInterval || 60000, // 60 seconds interval
|
|
82
|
-
maxConnectionLifetime: ensureSafeTimeout(settingsArg.maxConnectionLifetime || 86400000), // 24 hours default
|
|
83
|
-
inactivityTimeout: ensureSafeTimeout(settingsArg.inactivityTimeout || 14400000), // 4 hours inactivity timeout
|
|
84
|
-
gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000, // 30 seconds
|
|
85
|
-
// Socket optimization settings
|
|
86
|
-
noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true,
|
|
87
|
-
keepAlive: settingsArg.keepAlive !== undefined ? settingsArg.keepAlive : true,
|
|
88
|
-
keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000, // 10 seconds
|
|
89
|
-
maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024, // 10MB
|
|
90
|
-
// Feature flags
|
|
91
|
-
disableInactivityCheck: settingsArg.disableInactivityCheck || false,
|
|
92
|
-
enableKeepAliveProbes: settingsArg.enableKeepAliveProbes !== undefined ? settingsArg.enableKeepAliveProbes : true,
|
|
93
|
-
enableDetailedLogging: settingsArg.enableDetailedLogging || false,
|
|
94
|
-
enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
|
|
95
|
-
enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
|
|
96
|
-
allowSessionTicket: settingsArg.allowSessionTicket !== undefined ? settingsArg.allowSessionTicket : true,
|
|
97
|
-
// Rate limiting defaults
|
|
98
|
-
maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100,
|
|
99
|
-
connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300,
|
|
100
|
-
// Enhanced keep-alive settings
|
|
101
|
-
keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended',
|
|
102
|
-
keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6,
|
|
103
|
-
extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
104
|
-
// NetworkProxy settings
|
|
105
|
-
networkProxyPort: settingsArg.networkProxyPort || 8443, // Default NetworkProxy port
|
|
106
|
-
// ACME certificate settings with reasonable defaults
|
|
107
|
-
acme: settingsArg.acme || {
|
|
108
|
-
enabled: false,
|
|
109
|
-
port: 80,
|
|
110
|
-
contactEmail: 'admin@example.com',
|
|
111
|
-
useProduction: false,
|
|
112
|
-
renewThresholdDays: 30,
|
|
113
|
-
autoRenew: true,
|
|
114
|
-
certificateStore: './certs',
|
|
115
|
-
skipConfiguredCerts: false,
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
// Initialize NetworkProxy if enabled
|
|
119
|
-
if (this.settings.useNetworkProxy && this.settings.useNetworkProxy.length > 0) {
|
|
120
|
-
this.initializeNetworkProxy();
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Initialize NetworkProxy instance
|
|
125
|
-
*/
|
|
126
|
-
async initializeNetworkProxy() {
|
|
127
|
-
if (!this.networkProxy) {
|
|
128
|
-
// Configure NetworkProxy options based on PortProxy settings
|
|
129
|
-
const networkProxyOptions = {
|
|
130
|
-
port: this.settings.networkProxyPort,
|
|
131
|
-
portProxyIntegration: true,
|
|
132
|
-
logLevel: this.settings.enableDetailedLogging ? 'debug' : 'info',
|
|
133
|
-
};
|
|
134
|
-
// Add ACME settings if configured
|
|
135
|
-
if (this.settings.acme) {
|
|
136
|
-
networkProxyOptions.acme = { ...this.settings.acme };
|
|
137
|
-
}
|
|
138
|
-
this.networkProxy = new NetworkProxy(networkProxyOptions);
|
|
139
|
-
console.log(`Initialized NetworkProxy on port ${this.settings.networkProxyPort}`);
|
|
140
|
-
// Convert and apply domain configurations to NetworkProxy
|
|
141
|
-
await this.syncDomainConfigsToNetworkProxy();
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Updates the domain configurations for the proxy
|
|
146
|
-
* @param newDomainConfigs The new domain configurations
|
|
147
|
-
*/
|
|
148
|
-
async updateDomainConfigs(newDomainConfigs) {
|
|
149
|
-
console.log(`Updating domain configurations (${newDomainConfigs.length} configs)`);
|
|
150
|
-
this.settings.domainConfigs = newDomainConfigs;
|
|
151
|
-
// If NetworkProxy is initialized, resync the configurations
|
|
152
|
-
if (this.networkProxy) {
|
|
153
|
-
await this.syncDomainConfigsToNetworkProxy();
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Updates the ACME certificate settings
|
|
158
|
-
* @param acmeSettings New ACME settings
|
|
159
|
-
*/
|
|
160
|
-
async updateAcmeSettings(acmeSettings) {
|
|
161
|
-
console.log('Updating ACME certificate settings');
|
|
162
|
-
// Update settings
|
|
163
|
-
this.settings.acme = {
|
|
164
|
-
...this.settings.acme,
|
|
165
|
-
...acmeSettings,
|
|
166
|
-
};
|
|
167
|
-
// If NetworkProxy is initialized, update its ACME settings
|
|
168
|
-
if (this.networkProxy) {
|
|
169
|
-
try {
|
|
170
|
-
// Recreate NetworkProxy with new settings if ACME enabled state has changed
|
|
171
|
-
if (this.settings.acme.enabled !== acmeSettings.enabled) {
|
|
172
|
-
console.log(`ACME enabled state changed to: ${acmeSettings.enabled}`);
|
|
173
|
-
// Stop the current NetworkProxy
|
|
174
|
-
await this.networkProxy.stop();
|
|
175
|
-
this.networkProxy = null;
|
|
176
|
-
// Reinitialize with new settings
|
|
177
|
-
await this.initializeNetworkProxy();
|
|
178
|
-
// Use start() to make sure ACME gets initialized if newly enabled
|
|
179
|
-
await this.networkProxy.start();
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
// Update existing NetworkProxy with new settings
|
|
183
|
-
// Note: Some settings may require a restart to take effect
|
|
184
|
-
console.log('Updating ACME settings in NetworkProxy');
|
|
185
|
-
// For certificate renewals, we might want to trigger checks with the new settings
|
|
186
|
-
if (acmeSettings.renewThresholdDays) {
|
|
187
|
-
console.log(`Setting new renewal threshold to ${acmeSettings.renewThresholdDays} days`);
|
|
188
|
-
// This is implementation-dependent but gives an example
|
|
189
|
-
if (this.networkProxy.options.acme) {
|
|
190
|
-
this.networkProxy.options.acme.renewThresholdDays = acmeSettings.renewThresholdDays;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
catch (err) {
|
|
196
|
-
console.log(`Error updating ACME settings: ${err}`);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Synchronizes PortProxy domain configurations to NetworkProxy
|
|
202
|
-
* This allows domains configured in PortProxy to be used by NetworkProxy
|
|
203
|
-
*/
|
|
204
|
-
async syncDomainConfigsToNetworkProxy() {
|
|
205
|
-
if (!this.networkProxy) {
|
|
206
|
-
console.log('Cannot sync configurations - NetworkProxy not initialized');
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
try {
|
|
210
|
-
// Get SSL certificates from assets
|
|
211
|
-
// Import fs directly since it's not in plugins
|
|
212
|
-
const fs = await import('fs');
|
|
213
|
-
let certPair;
|
|
214
|
-
try {
|
|
215
|
-
certPair = {
|
|
216
|
-
key: fs.readFileSync('assets/certs/key.pem', 'utf8'),
|
|
217
|
-
cert: fs.readFileSync('assets/certs/cert.pem', 'utf8'),
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
catch (certError) {
|
|
221
|
-
console.log(`Warning: Could not read default certificates: ${certError}`);
|
|
222
|
-
console.log('Using empty certificate placeholders - ACME will generate proper certificates if enabled');
|
|
223
|
-
// Use empty placeholders - NetworkProxy will use its internal defaults
|
|
224
|
-
// or ACME will generate proper ones if enabled
|
|
225
|
-
certPair = {
|
|
226
|
-
key: '',
|
|
227
|
-
cert: '',
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
// Convert domain configs to NetworkProxy configs
|
|
231
|
-
const proxyConfigs = this.networkProxy.convertPortProxyConfigs(this.settings.domainConfigs, certPair);
|
|
232
|
-
// Log ACME-eligible domains if ACME is enabled
|
|
233
|
-
if (this.settings.acme?.enabled) {
|
|
234
|
-
const acmeEligibleDomains = proxyConfigs
|
|
235
|
-
.filter((config) => !config.hostName.includes('*')) // Exclude wildcards
|
|
236
|
-
.map((config) => config.hostName);
|
|
237
|
-
if (acmeEligibleDomains.length > 0) {
|
|
238
|
-
console.log(`Domains eligible for ACME certificates: ${acmeEligibleDomains.join(', ')}`);
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
console.log('No domains eligible for ACME certificates found in configuration');
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
// Update NetworkProxy with the converted configs
|
|
245
|
-
this.networkProxy
|
|
246
|
-
.updateProxyConfigs(proxyConfigs)
|
|
247
|
-
.then(() => {
|
|
248
|
-
console.log(`Successfully synchronized ${proxyConfigs.length} domain configurations to NetworkProxy`);
|
|
249
|
-
})
|
|
250
|
-
.catch((err) => {
|
|
251
|
-
console.log(`Error synchronizing configurations: ${err.message}`);
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
catch (err) {
|
|
255
|
-
console.log(`Failed to sync configurations: ${err}`);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Requests a certificate for a specific domain
|
|
260
|
-
* @param domain The domain to request a certificate for
|
|
261
|
-
* @returns Promise that resolves to true if the request was successful, false otherwise
|
|
262
|
-
*/
|
|
263
|
-
async requestCertificate(domain) {
|
|
264
|
-
if (!this.networkProxy) {
|
|
265
|
-
console.log('Cannot request certificate - NetworkProxy not initialized');
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
if (!this.settings.acme?.enabled) {
|
|
269
|
-
console.log('Cannot request certificate - ACME is not enabled');
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
try {
|
|
273
|
-
const result = await this.networkProxy.requestCertificate(domain);
|
|
274
|
-
if (result) {
|
|
275
|
-
console.log(`Certificate request for ${domain} submitted successfully`);
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
console.log(`Certificate request for ${domain} failed`);
|
|
279
|
-
}
|
|
280
|
-
return result;
|
|
281
|
-
}
|
|
282
|
-
catch (err) {
|
|
283
|
-
console.log(`Error requesting certificate: ${err}`);
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Forwards a TLS connection to a NetworkProxy for handling
|
|
289
|
-
* @param connectionId - Unique connection identifier
|
|
290
|
-
* @param socket - The incoming client socket
|
|
291
|
-
* @param record - The connection record
|
|
292
|
-
* @param initialData - Initial data chunk (TLS ClientHello)
|
|
293
|
-
* @param customProxyPort - Optional custom port for NetworkProxy (for domain-specific settings)
|
|
294
|
-
*/
|
|
295
|
-
forwardToNetworkProxy(connectionId, socket, record, initialData, customProxyPort) {
|
|
296
|
-
// Ensure NetworkProxy is initialized
|
|
297
|
-
if (!this.networkProxy) {
|
|
298
|
-
console.log(`[${connectionId}] NetworkProxy not initialized. Using fallback direct connection.`);
|
|
299
|
-
// Fall back to direct connection
|
|
300
|
-
return this.setupDirectConnection(connectionId, socket, record, undefined, undefined, initialData);
|
|
301
|
-
}
|
|
302
|
-
// Use the custom port if provided, otherwise use the default NetworkProxy port
|
|
303
|
-
const proxyPort = customProxyPort || this.networkProxy.getListeningPort();
|
|
304
|
-
const proxyHost = 'localhost'; // Assuming NetworkProxy runs locally
|
|
305
|
-
if (this.settings.enableDetailedLogging) {
|
|
306
|
-
console.log(`[${connectionId}] Forwarding TLS connection to NetworkProxy at ${proxyHost}:${proxyPort}`);
|
|
307
|
-
}
|
|
308
|
-
// Create a connection to the NetworkProxy
|
|
309
|
-
const proxySocket = plugins.net.connect({
|
|
310
|
-
host: proxyHost,
|
|
311
|
-
port: proxyPort,
|
|
312
|
-
});
|
|
313
|
-
// Store the outgoing socket in the record
|
|
314
|
-
record.outgoing = proxySocket;
|
|
315
|
-
record.outgoingStartTime = Date.now();
|
|
316
|
-
record.usingNetworkProxy = true;
|
|
317
|
-
// Set up error handlers
|
|
318
|
-
proxySocket.on('error', (err) => {
|
|
319
|
-
console.log(`[${connectionId}] Error connecting to NetworkProxy: ${err.message}`);
|
|
320
|
-
this.cleanupConnection(record, 'network_proxy_connect_error');
|
|
321
|
-
});
|
|
322
|
-
// Handle connection to NetworkProxy
|
|
323
|
-
proxySocket.on('connect', () => {
|
|
324
|
-
if (this.settings.enableDetailedLogging) {
|
|
325
|
-
console.log(`[${connectionId}] Connected to NetworkProxy at ${proxyHost}:${proxyPort}`);
|
|
326
|
-
}
|
|
327
|
-
// First send the initial data that contains the TLS ClientHello
|
|
328
|
-
proxySocket.write(initialData);
|
|
329
|
-
// Now set up bidirectional piping between client and NetworkProxy
|
|
330
|
-
socket.pipe(proxySocket);
|
|
331
|
-
proxySocket.pipe(socket);
|
|
332
|
-
// Setup cleanup handlers
|
|
333
|
-
proxySocket.on('close', () => {
|
|
334
|
-
if (this.settings.enableDetailedLogging) {
|
|
335
|
-
console.log(`[${connectionId}] NetworkProxy connection closed`);
|
|
336
|
-
}
|
|
337
|
-
this.cleanupConnection(record, 'network_proxy_closed');
|
|
338
|
-
});
|
|
339
|
-
socket.on('close', () => {
|
|
340
|
-
if (this.settings.enableDetailedLogging) {
|
|
341
|
-
console.log(`[${connectionId}] Client connection closed after forwarding to NetworkProxy`);
|
|
342
|
-
}
|
|
343
|
-
this.cleanupConnection(record, 'client_closed');
|
|
344
|
-
});
|
|
345
|
-
// Update activity on data transfer
|
|
346
|
-
socket.on('data', () => this.updateActivity(record));
|
|
347
|
-
proxySocket.on('data', () => this.updateActivity(record));
|
|
348
|
-
if (this.settings.enableDetailedLogging) {
|
|
349
|
-
console.log(`[${connectionId}] TLS connection successfully forwarded to NetworkProxy`);
|
|
350
|
-
}
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* Sets up a direct connection to the target (original behavior)
|
|
355
|
-
* This is used when NetworkProxy isn't configured or as a fallback
|
|
356
|
-
*/
|
|
357
|
-
setupDirectConnection(connectionId, socket, record, domainConfig, serverName, initialChunk, overridePort) {
|
|
358
|
-
// Existing connection setup logic
|
|
359
|
-
const targetHost = domainConfig ? this.getTargetIP(domainConfig) : this.settings.targetIP;
|
|
360
|
-
const connectionOptions = {
|
|
361
|
-
host: targetHost,
|
|
362
|
-
port: overridePort !== undefined ? overridePort : this.settings.toPort,
|
|
363
|
-
};
|
|
364
|
-
if (this.settings.preserveSourceIP) {
|
|
365
|
-
connectionOptions.localAddress = record.remoteIP.replace('::ffff:', '');
|
|
366
|
-
}
|
|
367
|
-
// Create a safe queue for incoming data using a Buffer array
|
|
368
|
-
// We'll use this to ensure we don't lose data during handler transitions
|
|
369
|
-
const dataQueue = [];
|
|
370
|
-
let queueSize = 0;
|
|
371
|
-
let processingQueue = false;
|
|
372
|
-
let drainPending = false;
|
|
373
|
-
// Flag to track if we've switched to the final piping mechanism
|
|
374
|
-
// Once this is true, we no longer buffer data in dataQueue
|
|
375
|
-
let pipingEstablished = false;
|
|
376
|
-
// Pause the incoming socket to prevent buffer overflows
|
|
377
|
-
// This ensures we control the flow of data until piping is set up
|
|
378
|
-
socket.pause();
|
|
379
|
-
// Function to safely process the data queue without losing events
|
|
380
|
-
const processDataQueue = () => {
|
|
381
|
-
if (processingQueue || dataQueue.length === 0 || pipingEstablished)
|
|
382
|
-
return;
|
|
383
|
-
processingQueue = true;
|
|
384
|
-
try {
|
|
385
|
-
// Process all queued chunks with the current active handler
|
|
386
|
-
while (dataQueue.length > 0) {
|
|
387
|
-
const chunk = dataQueue.shift();
|
|
388
|
-
queueSize -= chunk.length;
|
|
389
|
-
// Once piping is established, we shouldn't get here,
|
|
390
|
-
// but just in case, pass to the outgoing socket directly
|
|
391
|
-
if (pipingEstablished && record.outgoing) {
|
|
392
|
-
record.outgoing.write(chunk);
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
// Track bytes received
|
|
396
|
-
record.bytesReceived += chunk.length;
|
|
397
|
-
// Check for TLS handshake
|
|
398
|
-
if (!record.isTLS && SniHandler.isTlsHandshake(chunk)) {
|
|
399
|
-
record.isTLS = true;
|
|
400
|
-
if (this.settings.enableTlsDebugLogging) {
|
|
401
|
-
console.log(`[${connectionId}] TLS handshake detected in tempDataHandler, ${chunk.length} bytes`);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
// Check if adding this chunk would exceed the buffer limit
|
|
405
|
-
const newSize = record.pendingDataSize + chunk.length;
|
|
406
|
-
if (this.settings.maxPendingDataSize && newSize > this.settings.maxPendingDataSize) {
|
|
407
|
-
console.log(`[${connectionId}] Buffer limit exceeded for connection from ${record.remoteIP}: ${newSize} bytes > ${this.settings.maxPendingDataSize} bytes`);
|
|
408
|
-
socket.end(); // Gracefully close the socket
|
|
409
|
-
this.initiateCleanupOnce(record, 'buffer_limit_exceeded');
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
// Buffer the chunk and update the size counter
|
|
413
|
-
record.pendingData.push(Buffer.from(chunk));
|
|
414
|
-
record.pendingDataSize = newSize;
|
|
415
|
-
this.updateActivity(record);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
finally {
|
|
419
|
-
processingQueue = false;
|
|
420
|
-
// If there's a pending drain and we've processed everything,
|
|
421
|
-
// signal we're ready for more data if we haven't established piping yet
|
|
422
|
-
if (drainPending && dataQueue.length === 0 && !pipingEstablished) {
|
|
423
|
-
drainPending = false;
|
|
424
|
-
socket.resume();
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
};
|
|
428
|
-
// Unified data handler that safely queues incoming data
|
|
429
|
-
const safeDataHandler = (chunk) => {
|
|
430
|
-
// If piping is already established, just let the pipe handle it
|
|
431
|
-
if (pipingEstablished)
|
|
432
|
-
return;
|
|
433
|
-
// Add to our queue for orderly processing
|
|
434
|
-
dataQueue.push(Buffer.from(chunk)); // Make a copy to be safe
|
|
435
|
-
queueSize += chunk.length;
|
|
436
|
-
// If queue is getting large, pause socket until we catch up
|
|
437
|
-
if (this.settings.maxPendingDataSize && queueSize > this.settings.maxPendingDataSize * 0.8) {
|
|
438
|
-
socket.pause();
|
|
439
|
-
drainPending = true;
|
|
440
|
-
}
|
|
441
|
-
// Process the queue
|
|
442
|
-
processDataQueue();
|
|
443
|
-
};
|
|
444
|
-
// Add our safe data handler
|
|
445
|
-
socket.on('data', safeDataHandler);
|
|
446
|
-
// Add initial chunk to pending data if present
|
|
447
|
-
if (initialChunk) {
|
|
448
|
-
record.bytesReceived += initialChunk.length;
|
|
449
|
-
record.pendingData.push(Buffer.from(initialChunk));
|
|
450
|
-
record.pendingDataSize = initialChunk.length;
|
|
451
|
-
}
|
|
452
|
-
// Create the target socket but don't set up piping immediately
|
|
453
|
-
const targetSocket = plugins.net.connect(connectionOptions);
|
|
454
|
-
record.outgoing = targetSocket;
|
|
455
|
-
record.outgoingStartTime = Date.now();
|
|
456
|
-
// Apply socket optimizations
|
|
457
|
-
targetSocket.setNoDelay(this.settings.noDelay);
|
|
458
|
-
// Apply keep-alive settings to the outgoing connection as well
|
|
459
|
-
if (this.settings.keepAlive) {
|
|
460
|
-
targetSocket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
|
461
|
-
// Apply enhanced TCP keep-alive options if enabled
|
|
462
|
-
if (this.settings.enableKeepAliveProbes) {
|
|
463
|
-
try {
|
|
464
|
-
if ('setKeepAliveProbes' in targetSocket) {
|
|
465
|
-
targetSocket.setKeepAliveProbes(10);
|
|
466
|
-
}
|
|
467
|
-
if ('setKeepAliveInterval' in targetSocket) {
|
|
468
|
-
targetSocket.setKeepAliveInterval(1000);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
catch (err) {
|
|
472
|
-
// Ignore errors - these are optional enhancements
|
|
473
|
-
if (this.settings.enableDetailedLogging) {
|
|
474
|
-
console.log(`[${connectionId}] Enhanced TCP keep-alive not supported for outgoing socket: ${err}`);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
// Setup specific error handler for connection phase
|
|
480
|
-
targetSocket.once('error', (err) => {
|
|
481
|
-
// This handler runs only once during the initial connection phase
|
|
482
|
-
const code = err.code;
|
|
483
|
-
console.log(`[${connectionId}] Connection setup error to ${targetHost}:${connectionOptions.port}: ${err.message} (${code})`);
|
|
484
|
-
// Resume the incoming socket to prevent it from hanging
|
|
485
|
-
socket.resume();
|
|
486
|
-
if (code === 'ECONNREFUSED') {
|
|
487
|
-
console.log(`[${connectionId}] Target ${targetHost}:${connectionOptions.port} refused connection`);
|
|
488
|
-
}
|
|
489
|
-
else if (code === 'ETIMEDOUT') {
|
|
490
|
-
console.log(`[${connectionId}] Connection to ${targetHost}:${connectionOptions.port} timed out`);
|
|
491
|
-
}
|
|
492
|
-
else if (code === 'ECONNRESET') {
|
|
493
|
-
console.log(`[${connectionId}] Connection to ${targetHost}:${connectionOptions.port} was reset`);
|
|
494
|
-
}
|
|
495
|
-
else if (code === 'EHOSTUNREACH') {
|
|
496
|
-
console.log(`[${connectionId}] Host ${targetHost} is unreachable`);
|
|
497
|
-
}
|
|
498
|
-
// Clear any existing error handler after connection phase
|
|
499
|
-
targetSocket.removeAllListeners('error');
|
|
500
|
-
// Re-add the normal error handler for established connections
|
|
501
|
-
targetSocket.on('error', this.handleError('outgoing', record));
|
|
502
|
-
if (record.outgoingTerminationReason === null) {
|
|
503
|
-
record.outgoingTerminationReason = 'connection_failed';
|
|
504
|
-
this.incrementTerminationStat('outgoing', 'connection_failed');
|
|
505
|
-
}
|
|
506
|
-
// Clean up the connection
|
|
507
|
-
this.initiateCleanupOnce(record, `connection_failed_${code}`);
|
|
508
|
-
});
|
|
509
|
-
// Setup close handler
|
|
510
|
-
targetSocket.on('close', this.handleClose('outgoing', record));
|
|
511
|
-
socket.on('close', this.handleClose('incoming', record));
|
|
512
|
-
// Handle timeouts with keep-alive awareness
|
|
513
|
-
socket.on('timeout', () => {
|
|
514
|
-
// For keep-alive connections, just log a warning instead of closing
|
|
515
|
-
if (record.hasKeepAlive) {
|
|
516
|
-
console.log(`[${connectionId}] Timeout event on incoming keep-alive connection from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}. Connection preserved.`);
|
|
517
|
-
// Don't close the connection - just log
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
// For non-keep-alive connections, proceed with normal cleanup
|
|
521
|
-
console.log(`[${connectionId}] Timeout on incoming side from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}`);
|
|
522
|
-
if (record.incomingTerminationReason === null) {
|
|
523
|
-
record.incomingTerminationReason = 'timeout';
|
|
524
|
-
this.incrementTerminationStat('incoming', 'timeout');
|
|
525
|
-
}
|
|
526
|
-
this.initiateCleanupOnce(record, 'timeout_incoming');
|
|
527
|
-
});
|
|
528
|
-
targetSocket.on('timeout', () => {
|
|
529
|
-
// For keep-alive connections, just log a warning instead of closing
|
|
530
|
-
if (record.hasKeepAlive) {
|
|
531
|
-
console.log(`[${connectionId}] Timeout event on outgoing keep-alive connection from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}. Connection preserved.`);
|
|
532
|
-
// Don't close the connection - just log
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
// For non-keep-alive connections, proceed with normal cleanup
|
|
536
|
-
console.log(`[${connectionId}] Timeout on outgoing side from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}`);
|
|
537
|
-
if (record.outgoingTerminationReason === null) {
|
|
538
|
-
record.outgoingTerminationReason = 'timeout';
|
|
539
|
-
this.incrementTerminationStat('outgoing', 'timeout');
|
|
540
|
-
}
|
|
541
|
-
this.initiateCleanupOnce(record, 'timeout_outgoing');
|
|
542
|
-
});
|
|
543
|
-
// Set appropriate timeouts, or disable for immortal keep-alive connections
|
|
544
|
-
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
545
|
-
// Disable timeouts completely for immortal connections
|
|
546
|
-
socket.setTimeout(0);
|
|
547
|
-
targetSocket.setTimeout(0);
|
|
548
|
-
if (this.settings.enableDetailedLogging) {
|
|
549
|
-
console.log(`[${connectionId}] Disabled socket timeouts for immortal keep-alive connection`);
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
else {
|
|
553
|
-
// Set normal timeouts for other connections
|
|
554
|
-
socket.setTimeout(ensureSafeTimeout(this.settings.socketTimeout || 3600000));
|
|
555
|
-
targetSocket.setTimeout(ensureSafeTimeout(this.settings.socketTimeout || 3600000));
|
|
556
|
-
}
|
|
557
|
-
// Track outgoing data for bytes counting
|
|
558
|
-
targetSocket.on('data', (chunk) => {
|
|
559
|
-
record.bytesSent += chunk.length;
|
|
560
|
-
this.updateActivity(record);
|
|
561
|
-
});
|
|
562
|
-
// Wait for the outgoing connection to be ready before setting up piping
|
|
563
|
-
targetSocket.once('connect', () => {
|
|
564
|
-
// Clear the initial connection error handler
|
|
565
|
-
targetSocket.removeAllListeners('error');
|
|
566
|
-
// Add the normal error handler for established connections
|
|
567
|
-
targetSocket.on('error', this.handleError('outgoing', record));
|
|
568
|
-
// Process any remaining data in the queue before switching to piping
|
|
569
|
-
processDataQueue();
|
|
570
|
-
// Set up piping immediately - don't delay this crucial step
|
|
571
|
-
pipingEstablished = true;
|
|
572
|
-
// Flush all pending data to target
|
|
573
|
-
if (record.pendingData.length > 0) {
|
|
574
|
-
const combinedData = Buffer.concat(record.pendingData);
|
|
575
|
-
if (this.settings.enableDetailedLogging) {
|
|
576
|
-
console.log(`[${connectionId}] Forwarding ${combinedData.length} bytes of initial data to target`);
|
|
577
|
-
}
|
|
578
|
-
// Write pending data immediately
|
|
579
|
-
targetSocket.write(combinedData, (err) => {
|
|
580
|
-
if (err) {
|
|
581
|
-
console.log(`[${connectionId}] Error writing pending data to target: ${err.message}`);
|
|
582
|
-
return this.initiateCleanupOnce(record, 'write_error');
|
|
583
|
-
}
|
|
584
|
-
});
|
|
585
|
-
// Clear the buffer now that we've processed it
|
|
586
|
-
record.pendingData = [];
|
|
587
|
-
record.pendingDataSize = 0;
|
|
588
|
-
}
|
|
589
|
-
// Setup piping in both directions without any delays
|
|
590
|
-
socket.pipe(targetSocket);
|
|
591
|
-
targetSocket.pipe(socket);
|
|
592
|
-
// Resume the socket to ensure data flows - CRITICAL!
|
|
593
|
-
socket.resume();
|
|
594
|
-
// Process any data that might be queued in the interim
|
|
595
|
-
if (dataQueue.length > 0) {
|
|
596
|
-
// Write any remaining queued data directly to the target socket
|
|
597
|
-
for (const chunk of dataQueue) {
|
|
598
|
-
targetSocket.write(chunk);
|
|
599
|
-
}
|
|
600
|
-
// Clear the queue
|
|
601
|
-
dataQueue.length = 0;
|
|
602
|
-
queueSize = 0;
|
|
603
|
-
}
|
|
604
|
-
if (this.settings.enableDetailedLogging) {
|
|
605
|
-
console.log(`[${connectionId}] Connection established: ${record.remoteIP} -> ${targetHost}:${connectionOptions.port}` +
|
|
606
|
-
`${serverName
|
|
607
|
-
? ` (SNI: ${serverName})`
|
|
608
|
-
: domainConfig
|
|
609
|
-
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
|
610
|
-
: ''}` +
|
|
611
|
-
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${record.hasKeepAlive ? 'Yes' : 'No'}`);
|
|
612
|
-
}
|
|
613
|
-
else {
|
|
614
|
-
console.log(`Connection established: ${record.remoteIP} -> ${targetHost}:${connectionOptions.port}` +
|
|
615
|
-
`${serverName
|
|
616
|
-
? ` (SNI: ${serverName})`
|
|
617
|
-
: domainConfig
|
|
618
|
-
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
|
619
|
-
: ''}`);
|
|
620
|
-
}
|
|
621
|
-
// Add the renegotiation handler for SNI validation with strict domain enforcement
|
|
622
|
-
// This will be called after we've established piping
|
|
623
|
-
if (serverName) {
|
|
624
|
-
// Define a handler for checking renegotiation with improved detection
|
|
625
|
-
const renegotiationHandler = (renegChunk) => {
|
|
626
|
-
// Only process if this looks like a TLS ClientHello
|
|
627
|
-
if (SniHandler.isClientHello(renegChunk)) {
|
|
628
|
-
try {
|
|
629
|
-
// Extract SNI from ClientHello
|
|
630
|
-
// Create a connection info object for the existing connection
|
|
631
|
-
const connInfo = {
|
|
632
|
-
sourceIp: record.remoteIP,
|
|
633
|
-
sourcePort: record.incoming.remotePort || 0,
|
|
634
|
-
destIp: record.incoming.localAddress || '',
|
|
635
|
-
destPort: record.incoming.localPort || 0,
|
|
636
|
-
};
|
|
637
|
-
// Check for session tickets if allowSessionTicket is disabled
|
|
638
|
-
if (this.settings.allowSessionTicket === false) {
|
|
639
|
-
// Analyze for session resumption attempt (session ticket or PSK)
|
|
640
|
-
const resumptionInfo = SniHandler.hasSessionResumption(renegChunk, this.settings.enableTlsDebugLogging);
|
|
641
|
-
if (resumptionInfo.isResumption) {
|
|
642
|
-
// Always log resumption attempt for easier debugging
|
|
643
|
-
// Try to extract SNI for logging
|
|
644
|
-
const extractedSNI = SniHandler.extractSNI(renegChunk, this.settings.enableTlsDebugLogging);
|
|
645
|
-
console.log(`[${connectionId}] Session resumption detected in renegotiation. ` +
|
|
646
|
-
`Has SNI: ${resumptionInfo.hasSNI ? 'Yes' : 'No'}, ` +
|
|
647
|
-
`SNI value: ${extractedSNI || 'None'}, ` +
|
|
648
|
-
`allowSessionTicket: ${this.settings.allowSessionTicket}`);
|
|
649
|
-
// Block if there's session resumption without SNI
|
|
650
|
-
if (!resumptionInfo.hasSNI) {
|
|
651
|
-
console.log(`[${connectionId}] Session resumption detected in renegotiation without SNI and allowSessionTicket=false. ` +
|
|
652
|
-
`Terminating connection to force new TLS handshake.`);
|
|
653
|
-
this.initiateCleanupOnce(record, 'session_ticket_blocked');
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
else {
|
|
657
|
-
if (this.settings.enableDetailedLogging) {
|
|
658
|
-
console.log(`[${connectionId}] Session resumption with SNI detected in renegotiation. ` +
|
|
659
|
-
`Allowing connection since SNI is present.`);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
const newSNI = SniHandler.extractSNIWithResumptionSupport(renegChunk, connInfo, this.settings.enableTlsDebugLogging);
|
|
665
|
-
// Skip if no SNI was found
|
|
666
|
-
if (!newSNI)
|
|
667
|
-
return;
|
|
668
|
-
// Handle SNI change during renegotiation - always terminate for domain switches
|
|
669
|
-
if (newSNI !== record.lockedDomain) {
|
|
670
|
-
// Log and terminate the connection for any SNI change
|
|
671
|
-
console.log(`[${connectionId}] Renegotiation with different SNI: ${record.lockedDomain} -> ${newSNI}. ` +
|
|
672
|
-
`Terminating connection - SNI domain switching is not allowed.`);
|
|
673
|
-
this.initiateCleanupOnce(record, 'sni_mismatch');
|
|
674
|
-
}
|
|
675
|
-
else if (this.settings.enableDetailedLogging) {
|
|
676
|
-
console.log(`[${connectionId}] Renegotiation detected with same SNI: ${newSNI}. Allowing.`);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
catch (err) {
|
|
680
|
-
console.log(`[${connectionId}] Error processing ClientHello: ${err}. Allowing connection to continue.`);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
};
|
|
684
|
-
// Store the handler in the connection record so we can remove it during cleanup
|
|
685
|
-
record.renegotiationHandler = renegotiationHandler;
|
|
686
|
-
// The renegotiation handler is added when piping is established
|
|
687
|
-
// Making it part of setupPiping ensures proper sequencing of event handlers
|
|
688
|
-
socket.on('data', renegotiationHandler);
|
|
689
|
-
if (this.settings.enableDetailedLogging) {
|
|
690
|
-
console.log(`[${connectionId}] TLS renegotiation handler installed for SNI domain: ${serverName}`);
|
|
691
|
-
if (this.settings.allowSessionTicket === false) {
|
|
692
|
-
console.log(`[${connectionId}] Session ticket usage is disabled. Connection will be reset on reconnection attempts.`);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
// Set connection timeout with simpler logic
|
|
697
|
-
if (record.cleanupTimer) {
|
|
698
|
-
clearTimeout(record.cleanupTimer);
|
|
699
|
-
}
|
|
700
|
-
// For immortal keep-alive connections, skip setting a timeout completely
|
|
701
|
-
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
702
|
-
if (this.settings.enableDetailedLogging) {
|
|
703
|
-
console.log(`[${connectionId}] Keep-alive connection with immortal treatment - no max lifetime`);
|
|
704
|
-
}
|
|
705
|
-
// No cleanup timer for immortal connections
|
|
706
|
-
}
|
|
707
|
-
// For extended keep-alive connections, use extended timeout
|
|
708
|
-
else if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
|
709
|
-
const extendedTimeout = this.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
710
|
-
const safeTimeout = ensureSafeTimeout(extendedTimeout);
|
|
711
|
-
record.cleanupTimer = setTimeout(() => {
|
|
712
|
-
console.log(`[${connectionId}] Keep-alive connection from ${record.remoteIP} exceeded extended lifetime (${plugins.prettyMs(extendedTimeout)}), forcing cleanup.`);
|
|
713
|
-
this.initiateCleanupOnce(record, 'extended_lifetime');
|
|
714
|
-
}, safeTimeout);
|
|
715
|
-
// Make sure timeout doesn't keep the process alive
|
|
716
|
-
if (record.cleanupTimer.unref) {
|
|
717
|
-
record.cleanupTimer.unref();
|
|
718
|
-
}
|
|
719
|
-
if (this.settings.enableDetailedLogging) {
|
|
720
|
-
console.log(`[${connectionId}] Keep-alive connection with extended lifetime of ${plugins.prettyMs(extendedTimeout)}`);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
// For standard connections, use normal timeout
|
|
724
|
-
else {
|
|
725
|
-
// Use domain-specific timeout if available, otherwise use default
|
|
726
|
-
const connectionTimeout = record.domainConfig?.connectionTimeout || this.settings.maxConnectionLifetime;
|
|
727
|
-
const safeTimeout = ensureSafeTimeout(connectionTimeout);
|
|
728
|
-
record.cleanupTimer = setTimeout(() => {
|
|
729
|
-
console.log(`[${connectionId}] Connection from ${record.remoteIP} exceeded max lifetime (${plugins.prettyMs(connectionTimeout)}), forcing cleanup.`);
|
|
730
|
-
this.initiateCleanupOnce(record, 'connection_timeout');
|
|
731
|
-
}, safeTimeout);
|
|
732
|
-
// Make sure timeout doesn't keep the process alive
|
|
733
|
-
if (record.cleanupTimer.unref) {
|
|
734
|
-
record.cleanupTimer.unref();
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
// Mark TLS handshake as complete for TLS connections
|
|
738
|
-
if (record.isTLS) {
|
|
739
|
-
record.tlsHandshakeComplete = true;
|
|
740
|
-
if (this.settings.enableTlsDebugLogging) {
|
|
741
|
-
console.log(`[${connectionId}] TLS handshake complete for connection from ${record.remoteIP}`);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
});
|
|
745
|
-
}
|
|
746
|
-
/**
|
|
747
|
-
* Get connections count by IP
|
|
748
|
-
*/
|
|
749
|
-
getConnectionCountByIP(ip) {
|
|
750
|
-
return this.connectionsByIP.get(ip)?.size || 0;
|
|
751
|
-
}
|
|
752
|
-
/**
|
|
753
|
-
* Check and update connection rate for an IP
|
|
754
|
-
*/
|
|
755
|
-
checkConnectionRate(ip) {
|
|
756
|
-
const now = Date.now();
|
|
757
|
-
const minute = 60 * 1000;
|
|
758
|
-
if (!this.connectionRateByIP.has(ip)) {
|
|
759
|
-
this.connectionRateByIP.set(ip, [now]);
|
|
760
|
-
return true;
|
|
761
|
-
}
|
|
762
|
-
// Get timestamps and filter out entries older than 1 minute
|
|
763
|
-
const timestamps = this.connectionRateByIP.get(ip).filter((time) => now - time < minute);
|
|
764
|
-
timestamps.push(now);
|
|
765
|
-
this.connectionRateByIP.set(ip, timestamps);
|
|
766
|
-
// Check if rate exceeds limit
|
|
767
|
-
return timestamps.length <= this.settings.connectionRateLimitPerMinute;
|
|
768
|
-
}
|
|
769
|
-
/**
|
|
770
|
-
* Track connection by IP
|
|
771
|
-
*/
|
|
772
|
-
trackConnectionByIP(ip, connectionId) {
|
|
773
|
-
if (!this.connectionsByIP.has(ip)) {
|
|
774
|
-
this.connectionsByIP.set(ip, new Set());
|
|
775
|
-
}
|
|
776
|
-
this.connectionsByIP.get(ip).add(connectionId);
|
|
777
|
-
}
|
|
778
|
-
/**
|
|
779
|
-
* Remove connection tracking for an IP
|
|
780
|
-
*/
|
|
781
|
-
removeConnectionByIP(ip, connectionId) {
|
|
782
|
-
if (this.connectionsByIP.has(ip)) {
|
|
783
|
-
const connections = this.connectionsByIP.get(ip);
|
|
784
|
-
connections.delete(connectionId);
|
|
785
|
-
if (connections.size === 0) {
|
|
786
|
-
this.connectionsByIP.delete(ip);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Track connection termination statistic
|
|
792
|
-
*/
|
|
793
|
-
incrementTerminationStat(side, reason) {
|
|
794
|
-
this.terminationStats[side][reason] = (this.terminationStats[side][reason] || 0) + 1;
|
|
795
|
-
}
|
|
796
|
-
/**
|
|
797
|
-
* Cleans up a connection record.
|
|
798
|
-
* Destroys both incoming and outgoing sockets, clears timers, and removes the record.
|
|
799
|
-
* @param record - The connection record to clean up
|
|
800
|
-
* @param reason - Optional reason for cleanup (for logging)
|
|
801
|
-
*/
|
|
802
|
-
cleanupConnection(record, reason = 'normal') {
|
|
803
|
-
if (!record.connectionClosed) {
|
|
804
|
-
record.connectionClosed = true;
|
|
805
|
-
// Track connection termination
|
|
806
|
-
this.removeConnectionByIP(record.remoteIP, record.id);
|
|
807
|
-
if (record.cleanupTimer) {
|
|
808
|
-
clearTimeout(record.cleanupTimer);
|
|
809
|
-
record.cleanupTimer = undefined;
|
|
810
|
-
}
|
|
811
|
-
// Detailed logging data
|
|
812
|
-
const duration = Date.now() - record.incomingStartTime;
|
|
813
|
-
const bytesReceived = record.bytesReceived;
|
|
814
|
-
const bytesSent = record.bytesSent;
|
|
815
|
-
// Remove all data handlers (both standard and renegotiation) to make sure we clean up properly
|
|
816
|
-
if (record.incoming) {
|
|
817
|
-
try {
|
|
818
|
-
// Remove our safe data handler
|
|
819
|
-
record.incoming.removeAllListeners('data');
|
|
820
|
-
// Reset the handler references
|
|
821
|
-
record.renegotiationHandler = undefined;
|
|
822
|
-
}
|
|
823
|
-
catch (err) {
|
|
824
|
-
console.log(`[${record.id}] Error removing data handlers: ${err}`);
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
try {
|
|
828
|
-
if (!record.incoming.destroyed) {
|
|
829
|
-
// Try graceful shutdown first, then force destroy after a short timeout
|
|
830
|
-
record.incoming.end();
|
|
831
|
-
const incomingTimeout = setTimeout(() => {
|
|
832
|
-
try {
|
|
833
|
-
if (record && !record.incoming.destroyed) {
|
|
834
|
-
record.incoming.destroy();
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
catch (err) {
|
|
838
|
-
console.log(`[${record.id}] Error destroying incoming socket: ${err}`);
|
|
839
|
-
}
|
|
840
|
-
}, 1000);
|
|
841
|
-
// Ensure the timeout doesn't block Node from exiting
|
|
842
|
-
if (incomingTimeout.unref) {
|
|
843
|
-
incomingTimeout.unref();
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
catch (err) {
|
|
848
|
-
console.log(`[${record.id}] Error closing incoming socket: ${err}`);
|
|
849
|
-
try {
|
|
850
|
-
if (!record.incoming.destroyed) {
|
|
851
|
-
record.incoming.destroy();
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
catch (destroyErr) {
|
|
855
|
-
console.log(`[${record.id}] Error destroying incoming socket: ${destroyErr}`);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
try {
|
|
859
|
-
if (record.outgoing && !record.outgoing.destroyed) {
|
|
860
|
-
// Try graceful shutdown first, then force destroy after a short timeout
|
|
861
|
-
record.outgoing.end();
|
|
862
|
-
const outgoingTimeout = setTimeout(() => {
|
|
863
|
-
try {
|
|
864
|
-
if (record && record.outgoing && !record.outgoing.destroyed) {
|
|
865
|
-
record.outgoing.destroy();
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
catch (err) {
|
|
869
|
-
console.log(`[${record.id}] Error destroying outgoing socket: ${err}`);
|
|
870
|
-
}
|
|
871
|
-
}, 1000);
|
|
872
|
-
// Ensure the timeout doesn't block Node from exiting
|
|
873
|
-
if (outgoingTimeout.unref) {
|
|
874
|
-
outgoingTimeout.unref();
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
catch (err) {
|
|
879
|
-
console.log(`[${record.id}] Error closing outgoing socket: ${err}`);
|
|
880
|
-
try {
|
|
881
|
-
if (record.outgoing && !record.outgoing.destroyed) {
|
|
882
|
-
record.outgoing.destroy();
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
catch (destroyErr) {
|
|
886
|
-
console.log(`[${record.id}] Error destroying outgoing socket: ${destroyErr}`);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
// Clear pendingData to avoid memory leaks
|
|
890
|
-
record.pendingData = [];
|
|
891
|
-
record.pendingDataSize = 0;
|
|
892
|
-
// Remove the record from the tracking map
|
|
893
|
-
this.connectionRecords.delete(record.id);
|
|
894
|
-
// Log connection details
|
|
895
|
-
if (this.settings.enableDetailedLogging) {
|
|
896
|
-
console.log(`[${record.id}] Connection from ${record.remoteIP} on port ${record.localPort} terminated (${reason}).` +
|
|
897
|
-
` Duration: ${plugins.prettyMs(duration)}, Bytes IN: ${bytesReceived}, OUT: ${bytesSent}, ` +
|
|
898
|
-
`TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${record.hasKeepAlive ? 'Yes' : 'No'}` +
|
|
899
|
-
`${record.usingNetworkProxy ? ', Using NetworkProxy' : ''}` +
|
|
900
|
-
`${record.domainSwitches ? `, Domain switches: ${record.domainSwitches}` : ''}`);
|
|
901
|
-
}
|
|
902
|
-
else {
|
|
903
|
-
console.log(`[${record.id}] Connection from ${record.remoteIP} terminated (${reason}). Active connections: ${this.connectionRecords.size}`);
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
/**
|
|
908
|
-
* Update connection activity timestamp
|
|
909
|
-
*/
|
|
910
|
-
updateActivity(record) {
|
|
911
|
-
record.lastActivity = Date.now();
|
|
912
|
-
// Clear any inactivity warning
|
|
913
|
-
if (record.inactivityWarningIssued) {
|
|
914
|
-
record.inactivityWarningIssued = false;
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
/**
|
|
918
|
-
* Get target IP with round-robin support
|
|
919
|
-
*/
|
|
920
|
-
getTargetIP(domainConfig) {
|
|
921
|
-
if (domainConfig.targetIPs && domainConfig.targetIPs.length > 0) {
|
|
922
|
-
const currentIndex = this.domainTargetIndices.get(domainConfig) || 0;
|
|
923
|
-
const ip = domainConfig.targetIPs[currentIndex % domainConfig.targetIPs.length];
|
|
924
|
-
this.domainTargetIndices.set(domainConfig, currentIndex + 1);
|
|
925
|
-
return ip;
|
|
926
|
-
}
|
|
927
|
-
return this.settings.targetIP;
|
|
928
|
-
}
|
|
929
|
-
/**
|
|
930
|
-
* Initiates cleanup once for a connection
|
|
931
|
-
*/
|
|
932
|
-
initiateCleanupOnce(record, reason = 'normal') {
|
|
933
|
-
if (this.settings.enableDetailedLogging) {
|
|
934
|
-
console.log(`[${record.id}] Connection cleanup initiated for ${record.remoteIP} (${reason})`);
|
|
935
|
-
}
|
|
936
|
-
if (record.incomingTerminationReason === null ||
|
|
937
|
-
record.incomingTerminationReason === undefined) {
|
|
938
|
-
record.incomingTerminationReason = reason;
|
|
939
|
-
this.incrementTerminationStat('incoming', reason);
|
|
940
|
-
}
|
|
941
|
-
this.cleanupConnection(record, reason);
|
|
942
|
-
}
|
|
943
|
-
/**
|
|
944
|
-
* Creates a generic error handler for incoming or outgoing sockets
|
|
945
|
-
*/
|
|
946
|
-
handleError(side, record) {
|
|
947
|
-
return (err) => {
|
|
948
|
-
const code = err.code;
|
|
949
|
-
let reason = 'error';
|
|
950
|
-
const now = Date.now();
|
|
951
|
-
const connectionDuration = now - record.incomingStartTime;
|
|
952
|
-
const lastActivityAge = now - record.lastActivity;
|
|
953
|
-
if (code === 'ECONNRESET') {
|
|
954
|
-
reason = 'econnreset';
|
|
955
|
-
console.log(`[${record.id}] ECONNRESET on ${side} side from ${record.remoteIP}: ${err.message}. Duration: ${plugins.prettyMs(connectionDuration)}, Last activity: ${plugins.prettyMs(lastActivityAge)} ago`);
|
|
956
|
-
}
|
|
957
|
-
else if (code === 'ETIMEDOUT') {
|
|
958
|
-
reason = 'etimedout';
|
|
959
|
-
console.log(`[${record.id}] ETIMEDOUT on ${side} side from ${record.remoteIP}: ${err.message}. Duration: ${plugins.prettyMs(connectionDuration)}, Last activity: ${plugins.prettyMs(lastActivityAge)} ago`);
|
|
960
|
-
}
|
|
961
|
-
else {
|
|
962
|
-
console.log(`[${record.id}] Error on ${side} side from ${record.remoteIP}: ${err.message}. Duration: ${plugins.prettyMs(connectionDuration)}, Last activity: ${plugins.prettyMs(lastActivityAge)} ago`);
|
|
963
|
-
}
|
|
964
|
-
if (side === 'incoming' && record.incomingTerminationReason === null) {
|
|
965
|
-
record.incomingTerminationReason = reason;
|
|
966
|
-
this.incrementTerminationStat('incoming', reason);
|
|
967
|
-
}
|
|
968
|
-
else if (side === 'outgoing' && record.outgoingTerminationReason === null) {
|
|
969
|
-
record.outgoingTerminationReason = reason;
|
|
970
|
-
this.incrementTerminationStat('outgoing', reason);
|
|
971
|
-
}
|
|
972
|
-
this.initiateCleanupOnce(record, reason);
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
/**
|
|
976
|
-
* Creates a generic close handler for incoming or outgoing sockets
|
|
977
|
-
*/
|
|
978
|
-
handleClose(side, record) {
|
|
979
|
-
return () => {
|
|
980
|
-
if (this.settings.enableDetailedLogging) {
|
|
981
|
-
console.log(`[${record.id}] Connection closed on ${side} side from ${record.remoteIP}`);
|
|
982
|
-
}
|
|
983
|
-
if (side === 'incoming' && record.incomingTerminationReason === null) {
|
|
984
|
-
record.incomingTerminationReason = 'normal';
|
|
985
|
-
this.incrementTerminationStat('incoming', 'normal');
|
|
986
|
-
}
|
|
987
|
-
else if (side === 'outgoing' && record.outgoingTerminationReason === null) {
|
|
988
|
-
record.outgoingTerminationReason = 'normal';
|
|
989
|
-
this.incrementTerminationStat('outgoing', 'normal');
|
|
990
|
-
// Record the time when outgoing socket closed.
|
|
991
|
-
record.outgoingClosedTime = Date.now();
|
|
992
|
-
}
|
|
993
|
-
this.initiateCleanupOnce(record, 'closed_' + side);
|
|
994
|
-
};
|
|
995
|
-
}
|
|
996
|
-
/**
|
|
997
|
-
* Main method to start the proxy
|
|
998
|
-
*/
|
|
999
|
-
async start() {
|
|
1000
|
-
// Don't start if already shutting down
|
|
1001
|
-
if (this.isShuttingDown) {
|
|
1002
|
-
console.log("Cannot start PortProxy while it's shutting down");
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
// Initialize NetworkProxy if needed (useNetworkProxy is set but networkProxy isn't initialized)
|
|
1006
|
-
if (this.settings.useNetworkProxy &&
|
|
1007
|
-
this.settings.useNetworkProxy.length > 0 &&
|
|
1008
|
-
!this.networkProxy) {
|
|
1009
|
-
await this.initializeNetworkProxy();
|
|
1010
|
-
}
|
|
1011
|
-
// Start NetworkProxy if configured
|
|
1012
|
-
if (this.networkProxy) {
|
|
1013
|
-
await this.networkProxy.start();
|
|
1014
|
-
console.log(`NetworkProxy started on port ${this.settings.networkProxyPort}`);
|
|
1015
|
-
// Log ACME status
|
|
1016
|
-
if (this.settings.acme?.enabled) {
|
|
1017
|
-
console.log(`ACME certificate management is enabled (${this.settings.acme.useProduction ? 'Production' : 'Staging'} mode)`);
|
|
1018
|
-
console.log(`ACME HTTP challenge server on port ${this.settings.acme.port}`);
|
|
1019
|
-
// Register domains for ACME certificates if enabled
|
|
1020
|
-
if (this.networkProxy.options.acme?.enabled) {
|
|
1021
|
-
console.log('Registering domains with ACME certificate manager...');
|
|
1022
|
-
// The NetworkProxy will handle this internally via registerDomainsWithAcmeManager()
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
// Define a unified connection handler for all listening ports.
|
|
1027
|
-
const connectionHandler = (socket) => {
|
|
1028
|
-
if (this.isShuttingDown) {
|
|
1029
|
-
socket.end();
|
|
1030
|
-
socket.destroy();
|
|
1031
|
-
return;
|
|
1032
|
-
}
|
|
1033
|
-
const remoteIP = socket.remoteAddress || '';
|
|
1034
|
-
const localPort = socket.localPort || 0; // The port on which this connection was accepted.
|
|
1035
|
-
// Check rate limits
|
|
1036
|
-
if (this.settings.maxConnectionsPerIP &&
|
|
1037
|
-
this.getConnectionCountByIP(remoteIP) >= this.settings.maxConnectionsPerIP) {
|
|
1038
|
-
console.log(`Connection rejected from ${remoteIP}: Maximum connections per IP (${this.settings.maxConnectionsPerIP}) exceeded`);
|
|
1039
|
-
socket.end();
|
|
1040
|
-
socket.destroy();
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
|
-
if (this.settings.connectionRateLimitPerMinute && !this.checkConnectionRate(remoteIP)) {
|
|
1044
|
-
console.log(`Connection rejected from ${remoteIP}: Connection rate limit (${this.settings.connectionRateLimitPerMinute}/min) exceeded`);
|
|
1045
|
-
socket.end();
|
|
1046
|
-
socket.destroy();
|
|
1047
|
-
return;
|
|
1048
|
-
}
|
|
1049
|
-
// Apply socket optimizations
|
|
1050
|
-
socket.setNoDelay(this.settings.noDelay);
|
|
1051
|
-
// Create a unique connection ID and record
|
|
1052
|
-
const connectionId = generateConnectionId();
|
|
1053
|
-
const connectionRecord = {
|
|
1054
|
-
id: connectionId,
|
|
1055
|
-
incoming: socket,
|
|
1056
|
-
outgoing: null,
|
|
1057
|
-
incomingStartTime: Date.now(),
|
|
1058
|
-
lastActivity: Date.now(),
|
|
1059
|
-
connectionClosed: false,
|
|
1060
|
-
pendingData: [],
|
|
1061
|
-
pendingDataSize: 0,
|
|
1062
|
-
// Initialize enhanced tracking fields
|
|
1063
|
-
bytesReceived: 0,
|
|
1064
|
-
bytesSent: 0,
|
|
1065
|
-
remoteIP: remoteIP,
|
|
1066
|
-
localPort: localPort,
|
|
1067
|
-
isTLS: false,
|
|
1068
|
-
tlsHandshakeComplete: false,
|
|
1069
|
-
hasReceivedInitialData: false,
|
|
1070
|
-
hasKeepAlive: false, // Will set to true if keep-alive is applied
|
|
1071
|
-
incomingTerminationReason: null,
|
|
1072
|
-
outgoingTerminationReason: null,
|
|
1073
|
-
// Initialize NetworkProxy tracking
|
|
1074
|
-
usingNetworkProxy: false,
|
|
1075
|
-
// Initialize browser connection tracking
|
|
1076
|
-
isBrowserConnection: false,
|
|
1077
|
-
domainSwitches: 0,
|
|
1078
|
-
};
|
|
1079
|
-
// Apply keep-alive settings if enabled
|
|
1080
|
-
if (this.settings.keepAlive) {
|
|
1081
|
-
socket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
|
1082
|
-
connectionRecord.hasKeepAlive = true; // Mark connection as having keep-alive
|
|
1083
|
-
// Apply enhanced TCP keep-alive options if enabled
|
|
1084
|
-
if (this.settings.enableKeepAliveProbes) {
|
|
1085
|
-
try {
|
|
1086
|
-
// These are platform-specific and may not be available
|
|
1087
|
-
if ('setKeepAliveProbes' in socket) {
|
|
1088
|
-
socket.setKeepAliveProbes(10); // More aggressive probing
|
|
1089
|
-
}
|
|
1090
|
-
if ('setKeepAliveInterval' in socket) {
|
|
1091
|
-
socket.setKeepAliveInterval(1000); // 1 second interval between probes
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
catch (err) {
|
|
1095
|
-
// Ignore errors - these are optional enhancements
|
|
1096
|
-
if (this.settings.enableDetailedLogging) {
|
|
1097
|
-
console.log(`[${connectionId}] Enhanced TCP keep-alive settings not supported: ${err}`);
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
// Track connection by IP
|
|
1103
|
-
this.trackConnectionByIP(remoteIP, connectionId);
|
|
1104
|
-
this.connectionRecords.set(connectionId, connectionRecord);
|
|
1105
|
-
if (this.settings.enableDetailedLogging) {
|
|
1106
|
-
console.log(`[${connectionId}] New connection from ${remoteIP} on port ${localPort}. ` +
|
|
1107
|
-
`Keep-Alive: ${connectionRecord.hasKeepAlive ? 'Enabled' : 'Disabled'}. ` +
|
|
1108
|
-
`Active connections: ${this.connectionRecords.size}`);
|
|
1109
|
-
}
|
|
1110
|
-
else {
|
|
1111
|
-
console.log(`New connection from ${remoteIP} on port ${localPort}. Active connections: ${this.connectionRecords.size}`);
|
|
1112
|
-
}
|
|
1113
|
-
// Check if this connection should be forwarded directly to NetworkProxy
|
|
1114
|
-
// First check port-based forwarding settings
|
|
1115
|
-
let shouldUseNetworkProxy = this.settings.useNetworkProxy && this.settings.useNetworkProxy.includes(localPort);
|
|
1116
|
-
// We'll look for domain-specific settings after SNI extraction
|
|
1117
|
-
if (shouldUseNetworkProxy) {
|
|
1118
|
-
// For NetworkProxy ports, we want to capture the TLS handshake and forward directly
|
|
1119
|
-
let initialDataReceived = false;
|
|
1120
|
-
// Set an initial timeout for handshake data
|
|
1121
|
-
let initialTimeout = setTimeout(() => {
|
|
1122
|
-
if (!initialDataReceived) {
|
|
1123
|
-
console.log(`[${connectionId}] Initial data warning (${this.settings.initialDataTimeout}ms) for connection from ${remoteIP}`);
|
|
1124
|
-
// Add a grace period instead of immediate termination
|
|
1125
|
-
setTimeout(() => {
|
|
1126
|
-
if (!initialDataReceived) {
|
|
1127
|
-
console.log(`[${connectionId}] Final initial data timeout after grace period`);
|
|
1128
|
-
if (connectionRecord.incomingTerminationReason === null) {
|
|
1129
|
-
connectionRecord.incomingTerminationReason = 'initial_timeout';
|
|
1130
|
-
this.incrementTerminationStat('incoming', 'initial_timeout');
|
|
1131
|
-
}
|
|
1132
|
-
socket.end();
|
|
1133
|
-
this.cleanupConnection(connectionRecord, 'initial_timeout');
|
|
1134
|
-
}
|
|
1135
|
-
}, 30000); // 30 second grace period
|
|
1136
|
-
}
|
|
1137
|
-
}, this.settings.initialDataTimeout);
|
|
1138
|
-
// Make sure timeout doesn't keep the process alive
|
|
1139
|
-
if (initialTimeout.unref) {
|
|
1140
|
-
initialTimeout.unref();
|
|
1141
|
-
}
|
|
1142
|
-
socket.on('error', this.handleError('incoming', connectionRecord));
|
|
1143
|
-
// First data handler to capture initial TLS handshake for NetworkProxy
|
|
1144
|
-
socket.once('data', (chunk) => {
|
|
1145
|
-
// Clear the initial timeout since we've received data
|
|
1146
|
-
if (initialTimeout) {
|
|
1147
|
-
clearTimeout(initialTimeout);
|
|
1148
|
-
initialTimeout = null;
|
|
1149
|
-
}
|
|
1150
|
-
initialDataReceived = true;
|
|
1151
|
-
connectionRecord.hasReceivedInitialData = true;
|
|
1152
|
-
// Block non-TLS connections on port 443
|
|
1153
|
-
// Always enforce TLS on standard HTTPS port
|
|
1154
|
-
if (!SniHandler.isTlsHandshake(chunk) && localPort === 443) {
|
|
1155
|
-
console.log(`[${connectionId}] Non-TLS connection detected on port 443. ` +
|
|
1156
|
-
`Terminating connection - only TLS traffic is allowed on standard HTTPS port.`);
|
|
1157
|
-
if (connectionRecord.incomingTerminationReason === null) {
|
|
1158
|
-
connectionRecord.incomingTerminationReason = 'non_tls_blocked';
|
|
1159
|
-
this.incrementTerminationStat('incoming', 'non_tls_blocked');
|
|
1160
|
-
}
|
|
1161
|
-
socket.end();
|
|
1162
|
-
this.cleanupConnection(connectionRecord, 'non_tls_blocked');
|
|
1163
|
-
return;
|
|
1164
|
-
}
|
|
1165
|
-
// Check if this looks like a TLS handshake
|
|
1166
|
-
if (SniHandler.isTlsHandshake(chunk)) {
|
|
1167
|
-
connectionRecord.isTLS = true;
|
|
1168
|
-
// Check for TLS ClientHello with either no SNI or session tickets
|
|
1169
|
-
if (this.settings.allowSessionTicket === false && SniHandler.isClientHello(chunk)) {
|
|
1170
|
-
// Extract SNI first
|
|
1171
|
-
const extractedSNI = SniHandler.extractSNI(chunk, this.settings.enableTlsDebugLogging);
|
|
1172
|
-
const hasSNI = !!extractedSNI;
|
|
1173
|
-
// Analyze for session resumption attempt
|
|
1174
|
-
const resumptionInfo = SniHandler.hasSessionResumption(chunk, this.settings.enableTlsDebugLogging);
|
|
1175
|
-
// Always log for debugging purposes
|
|
1176
|
-
console.log(`[${connectionId}] TLS ClientHello detected with allowSessionTicket=false. ` +
|
|
1177
|
-
`Has SNI: ${hasSNI ? 'Yes' : 'No'}, ` +
|
|
1178
|
-
`SNI value: ${extractedSNI || 'None'}, ` +
|
|
1179
|
-
`Has session resumption: ${resumptionInfo.isResumption ? 'Yes' : 'No'}`);
|
|
1180
|
-
// Block if this is a connection with session resumption but no SNI
|
|
1181
|
-
if (resumptionInfo.isResumption && !hasSNI) {
|
|
1182
|
-
console.log(`[${connectionId}] Session resumption detected in initial ClientHello without SNI and allowSessionTicket=false. ` +
|
|
1183
|
-
`Terminating connection to force new TLS handshake.`);
|
|
1184
|
-
if (connectionRecord.incomingTerminationReason === null) {
|
|
1185
|
-
connectionRecord.incomingTerminationReason = 'session_ticket_blocked';
|
|
1186
|
-
this.incrementTerminationStat('incoming', 'session_ticket_blocked');
|
|
1187
|
-
}
|
|
1188
|
-
socket.end();
|
|
1189
|
-
this.cleanupConnection(connectionRecord, 'session_ticket_blocked');
|
|
1190
|
-
return;
|
|
1191
|
-
}
|
|
1192
|
-
// Also block if this is a TLS connection without SNI when allowSessionTicket is false
|
|
1193
|
-
// This forces clients to send SNI which helps with routing
|
|
1194
|
-
if (!hasSNI && localPort === 443) {
|
|
1195
|
-
console.log(`[${connectionId}] TLS ClientHello detected on port 443 without SNI and allowSessionTicket=false. ` +
|
|
1196
|
-
`Terminating connection to force proper SNI in handshake.`);
|
|
1197
|
-
if (connectionRecord.incomingTerminationReason === null) {
|
|
1198
|
-
connectionRecord.incomingTerminationReason = 'no_sni_blocked';
|
|
1199
|
-
this.incrementTerminationStat('incoming', 'no_sni_blocked');
|
|
1200
|
-
}
|
|
1201
|
-
socket.end();
|
|
1202
|
-
this.cleanupConnection(connectionRecord, 'no_sni_blocked');
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
// Try to extract SNI for domain-specific NetworkProxy handling
|
|
1207
|
-
const connInfo = {
|
|
1208
|
-
sourceIp: remoteIP,
|
|
1209
|
-
sourcePort: socket.remotePort || 0,
|
|
1210
|
-
destIp: socket.localAddress || '',
|
|
1211
|
-
destPort: socket.localPort || 0,
|
|
1212
|
-
};
|
|
1213
|
-
// Extract SNI to check for domain-specific NetworkProxy settings
|
|
1214
|
-
const serverName = SniHandler.processTlsPacket(chunk, connInfo, this.settings.enableTlsDebugLogging);
|
|
1215
|
-
if (serverName) {
|
|
1216
|
-
// If we got an SNI, check for domain-specific NetworkProxy settings
|
|
1217
|
-
const domainConfig = this.settings.domainConfigs.find((config) => config.domains.some((d) => plugins.minimatch(serverName, d)));
|
|
1218
|
-
// Save domain config and SNI in connection record
|
|
1219
|
-
connectionRecord.domainConfig = domainConfig;
|
|
1220
|
-
connectionRecord.lockedDomain = serverName;
|
|
1221
|
-
// Use domain-specific NetworkProxy port if configured
|
|
1222
|
-
if (domainConfig?.useNetworkProxy) {
|
|
1223
|
-
const networkProxyPort = domainConfig.networkProxyPort || this.settings.networkProxyPort;
|
|
1224
|
-
if (this.settings.enableDetailedLogging) {
|
|
1225
|
-
console.log(`[${connectionId}] Using domain-specific NetworkProxy for ${serverName} on port ${networkProxyPort}`);
|
|
1226
|
-
}
|
|
1227
|
-
// Forward to NetworkProxy with domain-specific port
|
|
1228
|
-
this.forwardToNetworkProxy(connectionId, socket, connectionRecord, chunk, networkProxyPort);
|
|
1229
|
-
return;
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
// Forward directly to NetworkProxy without domain-specific settings
|
|
1233
|
-
this.forwardToNetworkProxy(connectionId, socket, connectionRecord, chunk);
|
|
1234
|
-
}
|
|
1235
|
-
else {
|
|
1236
|
-
// If not TLS, use normal direct connection
|
|
1237
|
-
console.log(`[${connectionId}] Non-TLS connection on NetworkProxy port ${localPort}`);
|
|
1238
|
-
this.setupDirectConnection(connectionId, socket, connectionRecord, undefined, undefined, chunk);
|
|
1239
|
-
}
|
|
1240
|
-
});
|
|
1241
|
-
}
|
|
1242
|
-
else {
|
|
1243
|
-
// For non-NetworkProxy ports, proceed with normal processing
|
|
1244
|
-
// Define helpers for rejecting connections
|
|
1245
|
-
const rejectIncomingConnection = (reason, logMessage) => {
|
|
1246
|
-
console.log(`[${connectionId}] ${logMessage}`);
|
|
1247
|
-
socket.end();
|
|
1248
|
-
if (connectionRecord.incomingTerminationReason === null) {
|
|
1249
|
-
connectionRecord.incomingTerminationReason = reason;
|
|
1250
|
-
this.incrementTerminationStat('incoming', reason);
|
|
1251
|
-
}
|
|
1252
|
-
this.cleanupConnection(connectionRecord, reason);
|
|
1253
|
-
};
|
|
1254
|
-
let initialDataReceived = false;
|
|
1255
|
-
// Set an initial timeout for SNI data if needed
|
|
1256
|
-
let initialTimeout = null;
|
|
1257
|
-
if (this.settings.sniEnabled) {
|
|
1258
|
-
initialTimeout = setTimeout(() => {
|
|
1259
|
-
if (!initialDataReceived) {
|
|
1260
|
-
console.log(`[${connectionId}] Initial data warning (${this.settings.initialDataTimeout}ms) for connection from ${remoteIP}`);
|
|
1261
|
-
// Add a grace period instead of immediate termination
|
|
1262
|
-
setTimeout(() => {
|
|
1263
|
-
if (!initialDataReceived) {
|
|
1264
|
-
console.log(`[${connectionId}] Final initial data timeout after grace period`);
|
|
1265
|
-
if (connectionRecord.incomingTerminationReason === null) {
|
|
1266
|
-
connectionRecord.incomingTerminationReason = 'initial_timeout';
|
|
1267
|
-
this.incrementTerminationStat('incoming', 'initial_timeout');
|
|
1268
|
-
}
|
|
1269
|
-
socket.end();
|
|
1270
|
-
this.cleanupConnection(connectionRecord, 'initial_timeout');
|
|
1271
|
-
}
|
|
1272
|
-
}, 30000); // 30 second grace period
|
|
1273
|
-
}
|
|
1274
|
-
}, this.settings.initialDataTimeout);
|
|
1275
|
-
// Make sure timeout doesn't keep the process alive
|
|
1276
|
-
if (initialTimeout.unref) {
|
|
1277
|
-
initialTimeout.unref();
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
else {
|
|
1281
|
-
initialDataReceived = true;
|
|
1282
|
-
connectionRecord.hasReceivedInitialData = true;
|
|
1283
|
-
}
|
|
1284
|
-
socket.on('error', this.handleError('incoming', connectionRecord));
|
|
1285
|
-
// Track data for bytes counting
|
|
1286
|
-
socket.on('data', (chunk) => {
|
|
1287
|
-
connectionRecord.bytesReceived += chunk.length;
|
|
1288
|
-
this.updateActivity(connectionRecord);
|
|
1289
|
-
// Check for TLS handshake if this is the first chunk
|
|
1290
|
-
if (!connectionRecord.isTLS && SniHandler.isTlsHandshake(chunk)) {
|
|
1291
|
-
connectionRecord.isTLS = true;
|
|
1292
|
-
if (this.settings.enableTlsDebugLogging) {
|
|
1293
|
-
console.log(`[${connectionId}] TLS handshake detected from ${remoteIP}, ${chunk.length} bytes`);
|
|
1294
|
-
// Try to extract SNI and log detailed debug info
|
|
1295
|
-
// Create connection info for debug logging
|
|
1296
|
-
const debugConnInfo = {
|
|
1297
|
-
sourceIp: remoteIP,
|
|
1298
|
-
sourcePort: socket.remotePort || 0,
|
|
1299
|
-
destIp: socket.localAddress || '',
|
|
1300
|
-
destPort: socket.localPort || 0,
|
|
1301
|
-
};
|
|
1302
|
-
SniHandler.extractSNIWithResumptionSupport(chunk, debugConnInfo, true);
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
});
|
|
1306
|
-
/**
|
|
1307
|
-
* Sets up the connection to the target host.
|
|
1308
|
-
* @param serverName - The SNI hostname (unused when forcedDomain is provided).
|
|
1309
|
-
* @param initialChunk - Optional initial data chunk.
|
|
1310
|
-
* @param forcedDomain - If provided, overrides SNI/domain lookup (used for port-based routing).
|
|
1311
|
-
* @param overridePort - If provided, use this port for the outgoing connection.
|
|
1312
|
-
*/
|
|
1313
|
-
const setupConnection = (serverName, initialChunk, forcedDomain, overridePort) => {
|
|
1314
|
-
// Clear the initial timeout since we've received data
|
|
1315
|
-
if (initialTimeout) {
|
|
1316
|
-
clearTimeout(initialTimeout);
|
|
1317
|
-
initialTimeout = null;
|
|
1318
|
-
}
|
|
1319
|
-
// Mark that we've received initial data
|
|
1320
|
-
initialDataReceived = true;
|
|
1321
|
-
connectionRecord.hasReceivedInitialData = true;
|
|
1322
|
-
// Check if this looks like a TLS handshake
|
|
1323
|
-
const isTlsHandshakeDetected = initialChunk && SniHandler.isTlsHandshake(initialChunk);
|
|
1324
|
-
if (isTlsHandshakeDetected) {
|
|
1325
|
-
connectionRecord.isTLS = true;
|
|
1326
|
-
if (this.settings.enableTlsDebugLogging) {
|
|
1327
|
-
console.log(`[${connectionId}] TLS handshake detected in setup, ${initialChunk.length} bytes`);
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
// If a forcedDomain is provided (port-based routing), use it; otherwise, use SNI-based lookup.
|
|
1331
|
-
const domainConfig = forcedDomain
|
|
1332
|
-
? forcedDomain
|
|
1333
|
-
: serverName
|
|
1334
|
-
? this.settings.domainConfigs.find((config) => config.domains.some((d) => plugins.minimatch(serverName, d)))
|
|
1335
|
-
: undefined;
|
|
1336
|
-
// Save domain config in connection record
|
|
1337
|
-
connectionRecord.domainConfig = domainConfig;
|
|
1338
|
-
// Check if this domain should use NetworkProxy (domain-specific setting)
|
|
1339
|
-
if (domainConfig?.useNetworkProxy && this.networkProxy) {
|
|
1340
|
-
if (this.settings.enableDetailedLogging) {
|
|
1341
|
-
console.log(`[${connectionId}] Domain ${serverName} is configured to use NetworkProxy`);
|
|
1342
|
-
}
|
|
1343
|
-
const networkProxyPort = domainConfig.networkProxyPort || this.settings.networkProxyPort;
|
|
1344
|
-
if (initialChunk && connectionRecord.isTLS) {
|
|
1345
|
-
// For TLS connections with initial chunk, forward to NetworkProxy
|
|
1346
|
-
this.forwardToNetworkProxy(connectionId, socket, connectionRecord, initialChunk, networkProxyPort // Pass the domain-specific NetworkProxy port if configured
|
|
1347
|
-
);
|
|
1348
|
-
return; // Skip normal connection setup
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
// IP validation is skipped if allowedIPs is empty
|
|
1352
|
-
if (domainConfig) {
|
|
1353
|
-
const effectiveAllowedIPs = [
|
|
1354
|
-
...domainConfig.allowedIPs,
|
|
1355
|
-
...(this.settings.defaultAllowedIPs || []),
|
|
1356
|
-
];
|
|
1357
|
-
const effectiveBlockedIPs = [
|
|
1358
|
-
...(domainConfig.blockedIPs || []),
|
|
1359
|
-
...(this.settings.defaultBlockedIPs || []),
|
|
1360
|
-
];
|
|
1361
|
-
// Skip IP validation if allowedIPs is empty
|
|
1362
|
-
if (domainConfig.allowedIPs.length > 0 &&
|
|
1363
|
-
!isGlobIPAllowed(remoteIP, effectiveAllowedIPs, effectiveBlockedIPs)) {
|
|
1364
|
-
return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed for domain ${domainConfig.domains.join(', ')}`);
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
else if (this.settings.defaultAllowedIPs &&
|
|
1368
|
-
this.settings.defaultAllowedIPs.length > 0) {
|
|
1369
|
-
if (!isGlobIPAllowed(remoteIP, this.settings.defaultAllowedIPs, this.settings.defaultBlockedIPs || [])) {
|
|
1370
|
-
return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed by default allowed list`);
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
// Save the initial SNI
|
|
1374
|
-
if (serverName) {
|
|
1375
|
-
connectionRecord.lockedDomain = serverName;
|
|
1376
|
-
}
|
|
1377
|
-
// Set up the direct connection
|
|
1378
|
-
return this.setupDirectConnection(connectionId, socket, connectionRecord, domainConfig, serverName, initialChunk, overridePort);
|
|
1379
|
-
};
|
|
1380
|
-
// --- PORT RANGE-BASED HANDLING ---
|
|
1381
|
-
// Only apply port-based rules if the incoming port is within one of the global port ranges.
|
|
1382
|
-
if (this.settings.globalPortRanges &&
|
|
1383
|
-
isPortInRanges(localPort, this.settings.globalPortRanges)) {
|
|
1384
|
-
if (this.settings.forwardAllGlobalRanges) {
|
|
1385
|
-
if (this.settings.defaultAllowedIPs &&
|
|
1386
|
-
this.settings.defaultAllowedIPs.length > 0 &&
|
|
1387
|
-
!isAllowed(remoteIP, this.settings.defaultAllowedIPs)) {
|
|
1388
|
-
console.log(`[${connectionId}] Connection from ${remoteIP} rejected: IP ${remoteIP} not allowed in global default allowed list.`);
|
|
1389
|
-
socket.end();
|
|
1390
|
-
return;
|
|
1391
|
-
}
|
|
1392
|
-
if (this.settings.enableDetailedLogging) {
|
|
1393
|
-
console.log(`[${connectionId}] Port-based connection from ${remoteIP} on port ${localPort} forwarded to global target IP ${this.settings.targetIP}.`);
|
|
1394
|
-
}
|
|
1395
|
-
setupConnection('', undefined, {
|
|
1396
|
-
domains: ['global'],
|
|
1397
|
-
allowedIPs: this.settings.defaultAllowedIPs || [],
|
|
1398
|
-
blockedIPs: this.settings.defaultBlockedIPs || [],
|
|
1399
|
-
targetIPs: [this.settings.targetIP],
|
|
1400
|
-
portRanges: [],
|
|
1401
|
-
}, localPort);
|
|
1402
|
-
return;
|
|
1403
|
-
}
|
|
1404
|
-
else {
|
|
1405
|
-
// Attempt to find a matching forced domain config based on the local port.
|
|
1406
|
-
const forcedDomain = this.settings.domainConfigs.find((domain) => domain.portRanges &&
|
|
1407
|
-
domain.portRanges.length > 0 &&
|
|
1408
|
-
isPortInRanges(localPort, domain.portRanges));
|
|
1409
|
-
if (forcedDomain) {
|
|
1410
|
-
const effectiveAllowedIPs = [
|
|
1411
|
-
...forcedDomain.allowedIPs,
|
|
1412
|
-
...(this.settings.defaultAllowedIPs || []),
|
|
1413
|
-
];
|
|
1414
|
-
const effectiveBlockedIPs = [
|
|
1415
|
-
...(forcedDomain.blockedIPs || []),
|
|
1416
|
-
...(this.settings.defaultBlockedIPs || []),
|
|
1417
|
-
];
|
|
1418
|
-
if (!isGlobIPAllowed(remoteIP, effectiveAllowedIPs, effectiveBlockedIPs)) {
|
|
1419
|
-
console.log(`[${connectionId}] Connection from ${remoteIP} rejected: IP not allowed for domain ${forcedDomain.domains.join(', ')} on port ${localPort}.`);
|
|
1420
|
-
socket.end();
|
|
1421
|
-
return;
|
|
1422
|
-
}
|
|
1423
|
-
if (this.settings.enableDetailedLogging) {
|
|
1424
|
-
console.log(`[${connectionId}] Port-based connection from ${remoteIP} on port ${localPort} matched domain ${forcedDomain.domains.join(', ')}.`);
|
|
1425
|
-
}
|
|
1426
|
-
setupConnection('', undefined, forcedDomain, localPort);
|
|
1427
|
-
return;
|
|
1428
|
-
}
|
|
1429
|
-
// Fall through to SNI/default handling if no forced domain config is found.
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
// --- FALLBACK: SNI-BASED HANDLING (or default when SNI is disabled) ---
|
|
1433
|
-
if (this.settings.sniEnabled) {
|
|
1434
|
-
initialDataReceived = false;
|
|
1435
|
-
socket.once('data', (chunk) => {
|
|
1436
|
-
// Clear timeout immediately
|
|
1437
|
-
if (initialTimeout) {
|
|
1438
|
-
clearTimeout(initialTimeout);
|
|
1439
|
-
initialTimeout = null;
|
|
1440
|
-
}
|
|
1441
|
-
initialDataReceived = true;
|
|
1442
|
-
// Add debugging ONLY if detailed logging is enabled - avoid heavy processing
|
|
1443
|
-
if (this.settings.enableTlsDebugLogging && SniHandler.isClientHello(chunk)) {
|
|
1444
|
-
// Move heavy debug logging to a separate async task to not block the flow
|
|
1445
|
-
setImmediate(() => {
|
|
1446
|
-
try {
|
|
1447
|
-
const resumptionInfo = SniHandler.hasSessionResumption(chunk, true);
|
|
1448
|
-
const standardSNI = SniHandler.extractSNI(chunk, true);
|
|
1449
|
-
const pskSNI = SniHandler.extractSNIFromPSKExtension(chunk, true);
|
|
1450
|
-
console.log(`[${connectionId}] ClientHello details: isResumption=${resumptionInfo.isResumption}, hasSNI=${resumptionInfo.hasSNI}`);
|
|
1451
|
-
console.log(`[${connectionId}] SNI extraction results: standardSNI=${standardSNI || 'none'}, pskSNI=${pskSNI || 'none'}`);
|
|
1452
|
-
}
|
|
1453
|
-
catch (err) {
|
|
1454
|
-
console.log(`[${connectionId}] Error in debug logging: ${err}`);
|
|
1455
|
-
}
|
|
1456
|
-
});
|
|
1457
|
-
}
|
|
1458
|
-
// Block non-TLS connections on port 443
|
|
1459
|
-
// Always enforce TLS on standard HTTPS port
|
|
1460
|
-
if (!SniHandler.isTlsHandshake(chunk) && localPort === 443) {
|
|
1461
|
-
console.log(`[${connectionId}] Non-TLS connection detected on port 443 in SNI handler. ` +
|
|
1462
|
-
`Terminating connection - only TLS traffic is allowed on standard HTTPS port.`);
|
|
1463
|
-
if (connectionRecord.incomingTerminationReason === null) {
|
|
1464
|
-
connectionRecord.incomingTerminationReason = 'non_tls_blocked';
|
|
1465
|
-
this.incrementTerminationStat('incoming', 'non_tls_blocked');
|
|
1466
|
-
}
|
|
1467
|
-
socket.end();
|
|
1468
|
-
this.cleanupConnection(connectionRecord, 'non_tls_blocked');
|
|
1469
|
-
return;
|
|
1470
|
-
}
|
|
1471
|
-
// Try to extract SNI
|
|
1472
|
-
let serverName = '';
|
|
1473
|
-
if (SniHandler.isTlsHandshake(chunk)) {
|
|
1474
|
-
connectionRecord.isTLS = true;
|
|
1475
|
-
if (this.settings.enableTlsDebugLogging) {
|
|
1476
|
-
console.log(`[${connectionId}] Extracting SNI from TLS handshake, ${chunk.length} bytes`);
|
|
1477
|
-
}
|
|
1478
|
-
// Check for session tickets if allowSessionTicket is disabled
|
|
1479
|
-
if (this.settings.allowSessionTicket === false && SniHandler.isClientHello(chunk)) {
|
|
1480
|
-
// Analyze for session resumption attempt
|
|
1481
|
-
const resumptionInfo = SniHandler.hasSessionResumption(chunk, this.settings.enableTlsDebugLogging);
|
|
1482
|
-
if (resumptionInfo.isResumption) {
|
|
1483
|
-
// Always log resumption attempt for easier debugging
|
|
1484
|
-
// Try to extract SNI for logging
|
|
1485
|
-
const extractedSNI = SniHandler.extractSNI(chunk, this.settings.enableTlsDebugLogging);
|
|
1486
|
-
console.log(`[${connectionId}] Session resumption detected in SNI handler. ` +
|
|
1487
|
-
`Has SNI: ${resumptionInfo.hasSNI ? 'Yes' : 'No'}, ` +
|
|
1488
|
-
`SNI value: ${extractedSNI || 'None'}, ` +
|
|
1489
|
-
`allowSessionTicket: ${this.settings.allowSessionTicket}`);
|
|
1490
|
-
// Block if there's session resumption without SNI
|
|
1491
|
-
if (!resumptionInfo.hasSNI) {
|
|
1492
|
-
console.log(`[${connectionId}] Session resumption detected in SNI handler without SNI and allowSessionTicket=false. ` +
|
|
1493
|
-
`Terminating connection to force new TLS handshake.`);
|
|
1494
|
-
if (connectionRecord.incomingTerminationReason === null) {
|
|
1495
|
-
connectionRecord.incomingTerminationReason = 'session_ticket_blocked';
|
|
1496
|
-
this.incrementTerminationStat('incoming', 'session_ticket_blocked');
|
|
1497
|
-
}
|
|
1498
|
-
socket.end();
|
|
1499
|
-
this.cleanupConnection(connectionRecord, 'session_ticket_blocked');
|
|
1500
|
-
return;
|
|
1501
|
-
}
|
|
1502
|
-
else {
|
|
1503
|
-
if (this.settings.enableDetailedLogging) {
|
|
1504
|
-
console.log(`[${connectionId}] Session resumption with SNI detected in SNI handler. ` +
|
|
1505
|
-
`Allowing connection since SNI is present.`);
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
// Create connection info object for SNI extraction
|
|
1511
|
-
const connInfo = {
|
|
1512
|
-
sourceIp: remoteIP,
|
|
1513
|
-
sourcePort: socket.remotePort || 0,
|
|
1514
|
-
destIp: socket.localAddress || '',
|
|
1515
|
-
destPort: socket.localPort || 0,
|
|
1516
|
-
};
|
|
1517
|
-
// Use the new processTlsPacket method for comprehensive handling
|
|
1518
|
-
serverName =
|
|
1519
|
-
SniHandler.processTlsPacket(chunk, connInfo, this.settings.enableTlsDebugLogging, connectionRecord.lockedDomain // Pass any previously negotiated domain as a hint
|
|
1520
|
-
) || '';
|
|
1521
|
-
}
|
|
1522
|
-
// Lock the connection to the negotiated SNI.
|
|
1523
|
-
connectionRecord.lockedDomain = serverName;
|
|
1524
|
-
if (this.settings.enableDetailedLogging) {
|
|
1525
|
-
console.log(`[${connectionId}] Received connection from ${remoteIP} with SNI: ${serverName || '(empty)'}`);
|
|
1526
|
-
}
|
|
1527
|
-
setupConnection(serverName, chunk);
|
|
1528
|
-
});
|
|
1529
|
-
}
|
|
1530
|
-
else {
|
|
1531
|
-
initialDataReceived = true;
|
|
1532
|
-
connectionRecord.hasReceivedInitialData = true;
|
|
1533
|
-
if (this.settings.defaultAllowedIPs &&
|
|
1534
|
-
this.settings.defaultAllowedIPs.length > 0 &&
|
|
1535
|
-
!isAllowed(remoteIP, this.settings.defaultAllowedIPs)) {
|
|
1536
|
-
return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`);
|
|
1537
|
-
}
|
|
1538
|
-
setupConnection('');
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
};
|
|
1542
|
-
// --- SETUP LISTENERS ---
|
|
1543
|
-
// Determine which ports to listen on.
|
|
1544
|
-
const listeningPorts = new Set();
|
|
1545
|
-
if (this.settings.globalPortRanges && this.settings.globalPortRanges.length > 0) {
|
|
1546
|
-
// Listen on every port defined by the global ranges.
|
|
1547
|
-
for (const range of this.settings.globalPortRanges) {
|
|
1548
|
-
for (let port = range.from; port <= range.to; port++) {
|
|
1549
|
-
listeningPorts.add(port);
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
// Also ensure the default fromPort is listened to if it isn't already in the ranges.
|
|
1553
|
-
listeningPorts.add(this.settings.fromPort);
|
|
1554
|
-
}
|
|
1555
|
-
else {
|
|
1556
|
-
listeningPorts.add(this.settings.fromPort);
|
|
1557
|
-
}
|
|
1558
|
-
// Create a server for each port.
|
|
1559
|
-
for (const port of listeningPorts) {
|
|
1560
|
-
const server = plugins.net.createServer(connectionHandler).on('error', (err) => {
|
|
1561
|
-
console.log(`Server Error on port ${port}: ${err.message}`);
|
|
1562
|
-
});
|
|
1563
|
-
server.listen(port, () => {
|
|
1564
|
-
const isNetworkProxyPort = this.settings.useNetworkProxy?.includes(port);
|
|
1565
|
-
console.log(`PortProxy -> OK: Now listening on port ${port}${this.settings.sniEnabled && !isNetworkProxyPort ? ' (SNI passthrough enabled)' : ''}${isNetworkProxyPort ? ' (NetworkProxy forwarding enabled)' : ''}`);
|
|
1566
|
-
});
|
|
1567
|
-
this.netServers.push(server);
|
|
1568
|
-
}
|
|
1569
|
-
// Log active connection count, longest running durations, and run parity checks periodically
|
|
1570
|
-
this.connectionLogger = setInterval(() => {
|
|
1571
|
-
// Immediately return if shutting down
|
|
1572
|
-
if (this.isShuttingDown)
|
|
1573
|
-
return;
|
|
1574
|
-
const now = Date.now();
|
|
1575
|
-
let maxIncoming = 0;
|
|
1576
|
-
let maxOutgoing = 0;
|
|
1577
|
-
let tlsConnections = 0;
|
|
1578
|
-
let nonTlsConnections = 0;
|
|
1579
|
-
let completedTlsHandshakes = 0;
|
|
1580
|
-
let pendingTlsHandshakes = 0;
|
|
1581
|
-
let keepAliveConnections = 0;
|
|
1582
|
-
let networkProxyConnections = 0;
|
|
1583
|
-
let domainSwitchedConnections = 0;
|
|
1584
|
-
// Create a copy of the keys to avoid modification during iteration
|
|
1585
|
-
const connectionIds = [...this.connectionRecords.keys()];
|
|
1586
|
-
for (const id of connectionIds) {
|
|
1587
|
-
const record = this.connectionRecords.get(id);
|
|
1588
|
-
if (!record)
|
|
1589
|
-
continue;
|
|
1590
|
-
// Track connection stats
|
|
1591
|
-
if (record.isTLS) {
|
|
1592
|
-
tlsConnections++;
|
|
1593
|
-
if (record.tlsHandshakeComplete) {
|
|
1594
|
-
completedTlsHandshakes++;
|
|
1595
|
-
}
|
|
1596
|
-
else {
|
|
1597
|
-
pendingTlsHandshakes++;
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
else {
|
|
1601
|
-
nonTlsConnections++;
|
|
1602
|
-
}
|
|
1603
|
-
if (record.hasKeepAlive) {
|
|
1604
|
-
keepAliveConnections++;
|
|
1605
|
-
}
|
|
1606
|
-
if (record.usingNetworkProxy) {
|
|
1607
|
-
networkProxyConnections++;
|
|
1608
|
-
}
|
|
1609
|
-
if (record.domainSwitches && record.domainSwitches > 0) {
|
|
1610
|
-
domainSwitchedConnections++;
|
|
1611
|
-
}
|
|
1612
|
-
maxIncoming = Math.max(maxIncoming, now - record.incomingStartTime);
|
|
1613
|
-
if (record.outgoingStartTime) {
|
|
1614
|
-
maxOutgoing = Math.max(maxOutgoing, now - record.outgoingStartTime);
|
|
1615
|
-
}
|
|
1616
|
-
// Parity check: if outgoing socket closed and incoming remains active
|
|
1617
|
-
if (record.outgoingClosedTime &&
|
|
1618
|
-
!record.incoming.destroyed &&
|
|
1619
|
-
!record.connectionClosed &&
|
|
1620
|
-
now - record.outgoingClosedTime > 120000) {
|
|
1621
|
-
const remoteIP = record.remoteIP;
|
|
1622
|
-
console.log(`[${id}] Parity check: Incoming socket for ${remoteIP} still active ${plugins.prettyMs(now - record.outgoingClosedTime)} after outgoing closed.`);
|
|
1623
|
-
this.cleanupConnection(record, 'parity_check');
|
|
1624
|
-
}
|
|
1625
|
-
// Check for stalled connections waiting for initial data
|
|
1626
|
-
if (!record.hasReceivedInitialData &&
|
|
1627
|
-
now - record.incomingStartTime > this.settings.initialDataTimeout / 2) {
|
|
1628
|
-
console.log(`[${id}] Warning: Connection from ${record.remoteIP} has not received initial data after ${plugins.prettyMs(now - record.incomingStartTime)}`);
|
|
1629
|
-
}
|
|
1630
|
-
// Skip inactivity check if disabled or for immortal keep-alive connections
|
|
1631
|
-
if (!this.settings.disableInactivityCheck &&
|
|
1632
|
-
!(record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal')) {
|
|
1633
|
-
const inactivityTime = now - record.lastActivity;
|
|
1634
|
-
// Use extended timeout for extended-treatment keep-alive connections
|
|
1635
|
-
let effectiveTimeout = this.settings.inactivityTimeout;
|
|
1636
|
-
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
|
1637
|
-
const multiplier = this.settings.keepAliveInactivityMultiplier || 6;
|
|
1638
|
-
effectiveTimeout = effectiveTimeout * multiplier;
|
|
1639
|
-
}
|
|
1640
|
-
if (inactivityTime > effectiveTimeout && !record.connectionClosed) {
|
|
1641
|
-
// For keep-alive connections, issue a warning first
|
|
1642
|
-
if (record.hasKeepAlive && !record.inactivityWarningIssued) {
|
|
1643
|
-
console.log(`[${id}] Warning: Keep-alive connection from ${record.remoteIP} inactive for ${plugins.prettyMs(inactivityTime)}. ` +
|
|
1644
|
-
`Will close in 10 minutes if no activity.`);
|
|
1645
|
-
// Set warning flag and add grace period
|
|
1646
|
-
record.inactivityWarningIssued = true;
|
|
1647
|
-
record.lastActivity = now - (effectiveTimeout - 600000);
|
|
1648
|
-
// Try to stimulate activity with a probe packet
|
|
1649
|
-
if (record.outgoing && !record.outgoing.destroyed) {
|
|
1650
|
-
try {
|
|
1651
|
-
record.outgoing.write(Buffer.alloc(0));
|
|
1652
|
-
if (this.settings.enableDetailedLogging) {
|
|
1653
|
-
console.log(`[${id}] Sent probe packet to test keep-alive connection`);
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
catch (err) {
|
|
1657
|
-
console.log(`[${id}] Error sending probe packet: ${err}`);
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
else {
|
|
1662
|
-
// For non-keep-alive or after warning, close the connection
|
|
1663
|
-
console.log(`[${id}] Inactivity check: No activity on connection from ${record.remoteIP} ` +
|
|
1664
|
-
`for ${plugins.prettyMs(inactivityTime)}.` +
|
|
1665
|
-
(record.hasKeepAlive ? ' Despite keep-alive being enabled.' : ''));
|
|
1666
|
-
this.cleanupConnection(record, 'inactivity');
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
else if (inactivityTime <= effectiveTimeout && record.inactivityWarningIssued) {
|
|
1670
|
-
// If activity detected after warning, clear the warning
|
|
1671
|
-
if (this.settings.enableDetailedLogging) {
|
|
1672
|
-
console.log(`[${id}] Connection activity detected after inactivity warning, resetting warning`);
|
|
1673
|
-
}
|
|
1674
|
-
record.inactivityWarningIssued = false;
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
// Log detailed stats periodically
|
|
1679
|
-
console.log(`Active connections: ${this.connectionRecords.size}. ` +
|
|
1680
|
-
`Types: TLS=${tlsConnections} (Completed=${completedTlsHandshakes}, Pending=${pendingTlsHandshakes}), ` +
|
|
1681
|
-
`Non-TLS=${nonTlsConnections}, KeepAlive=${keepAliveConnections}, NetworkProxy=${networkProxyConnections}, ` +
|
|
1682
|
-
`DomainSwitched=${domainSwitchedConnections}. ` +
|
|
1683
|
-
`Longest running: IN=${plugins.prettyMs(maxIncoming)}, OUT=${plugins.prettyMs(maxOutgoing)}. ` +
|
|
1684
|
-
`Termination stats: ${JSON.stringify({
|
|
1685
|
-
IN: this.terminationStats.incoming,
|
|
1686
|
-
OUT: this.terminationStats.outgoing,
|
|
1687
|
-
})}`);
|
|
1688
|
-
}, this.settings.inactivityCheckInterval || 60000);
|
|
1689
|
-
// Make sure the interval doesn't keep the process alive
|
|
1690
|
-
if (this.connectionLogger.unref) {
|
|
1691
|
-
this.connectionLogger.unref();
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
/**
|
|
1695
|
-
* Gracefully shut down the proxy
|
|
1696
|
-
*/
|
|
1697
|
-
async stop() {
|
|
1698
|
-
console.log('PortProxy shutting down...');
|
|
1699
|
-
this.isShuttingDown = true;
|
|
1700
|
-
// Stop accepting new connections
|
|
1701
|
-
const closeServerPromises = this.netServers.map((server) => new Promise((resolve) => {
|
|
1702
|
-
if (!server.listening) {
|
|
1703
|
-
resolve();
|
|
1704
|
-
return;
|
|
1705
|
-
}
|
|
1706
|
-
server.close((err) => {
|
|
1707
|
-
if (err) {
|
|
1708
|
-
console.log(`Error closing server: ${err.message}`);
|
|
1709
|
-
}
|
|
1710
|
-
resolve();
|
|
1711
|
-
});
|
|
1712
|
-
}));
|
|
1713
|
-
// Stop the connection logger
|
|
1714
|
-
if (this.connectionLogger) {
|
|
1715
|
-
clearInterval(this.connectionLogger);
|
|
1716
|
-
this.connectionLogger = null;
|
|
1717
|
-
}
|
|
1718
|
-
// Wait for servers to close
|
|
1719
|
-
await Promise.all(closeServerPromises);
|
|
1720
|
-
console.log('All servers closed. Cleaning up active connections...');
|
|
1721
|
-
// Force destroy all active connections immediately
|
|
1722
|
-
const connectionIds = [...this.connectionRecords.keys()];
|
|
1723
|
-
console.log(`Cleaning up ${connectionIds.length} active connections...`);
|
|
1724
|
-
// First pass: End all connections gracefully
|
|
1725
|
-
for (const id of connectionIds) {
|
|
1726
|
-
const record = this.connectionRecords.get(id);
|
|
1727
|
-
if (record) {
|
|
1728
|
-
try {
|
|
1729
|
-
// Clear any timers
|
|
1730
|
-
if (record.cleanupTimer) {
|
|
1731
|
-
clearTimeout(record.cleanupTimer);
|
|
1732
|
-
record.cleanupTimer = undefined;
|
|
1733
|
-
}
|
|
1734
|
-
// End sockets gracefully
|
|
1735
|
-
if (record.incoming && !record.incoming.destroyed) {
|
|
1736
|
-
record.incoming.end();
|
|
1737
|
-
}
|
|
1738
|
-
if (record.outgoing && !record.outgoing.destroyed) {
|
|
1739
|
-
record.outgoing.end();
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
catch (err) {
|
|
1743
|
-
console.log(`Error during graceful connection end for ${id}: ${err}`);
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
// Short delay to allow graceful ends to process
|
|
1748
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1749
|
-
// Second pass: Force destroy everything
|
|
1750
|
-
for (const id of connectionIds) {
|
|
1751
|
-
const record = this.connectionRecords.get(id);
|
|
1752
|
-
if (record) {
|
|
1753
|
-
try {
|
|
1754
|
-
// Remove all listeners to prevent memory leaks
|
|
1755
|
-
if (record.incoming) {
|
|
1756
|
-
record.incoming.removeAllListeners();
|
|
1757
|
-
if (!record.incoming.destroyed) {
|
|
1758
|
-
record.incoming.destroy();
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
if (record.outgoing) {
|
|
1762
|
-
record.outgoing.removeAllListeners();
|
|
1763
|
-
if (!record.outgoing.destroyed) {
|
|
1764
|
-
record.outgoing.destroy();
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
catch (err) {
|
|
1769
|
-
console.log(`Error during forced connection destruction for ${id}: ${err}`);
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
// Stop NetworkProxy if it was started (which also stops ACME manager)
|
|
1774
|
-
if (this.networkProxy) {
|
|
1775
|
-
try {
|
|
1776
|
-
console.log('Stopping NetworkProxy...');
|
|
1777
|
-
await this.networkProxy.stop();
|
|
1778
|
-
console.log('NetworkProxy stopped successfully');
|
|
1779
|
-
// Log ACME shutdown if it was enabled
|
|
1780
|
-
if (this.settings.acme?.enabled) {
|
|
1781
|
-
console.log('ACME certificate manager stopped');
|
|
1782
|
-
}
|
|
1783
|
-
}
|
|
1784
|
-
catch (err) {
|
|
1785
|
-
console.log(`Error stopping NetworkProxy: ${err}`);
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
// Clear all tracking maps
|
|
1789
|
-
this.connectionRecords.clear();
|
|
1790
|
-
this.domainTargetIndices.clear();
|
|
1791
|
-
this.connectionsByIP.clear();
|
|
1792
|
-
this.connectionRateByIP.clear();
|
|
1793
|
-
this.netServers = [];
|
|
1794
|
-
// Reset termination stats
|
|
1795
|
-
this.terminationStats = {
|
|
1796
|
-
incoming: {},
|
|
1797
|
-
outgoing: {},
|
|
1798
|
-
};
|
|
1799
|
-
console.log('PortProxy shutdown complete.');
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5wb3J0cHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLnBvcnRwcm94eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDekQsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBMkhyRCx1REFBdUQ7QUFDdkQsZ0NBQWdDO0FBRWhDLG9FQUFvRTtBQUNwRSxNQUFNLGNBQWMsR0FBRyxDQUFDLElBQVksRUFBRSxNQUEyQyxFQUFXLEVBQUU7SUFDNUYsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxJQUFJLElBQUksS0FBSyxDQUFDLElBQUksSUFBSSxJQUFJLElBQUksS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0FBQ3hFLENBQUMsQ0FBQztBQUVGLCtEQUErRDtBQUMvRCxNQUFNLFNBQVMsR0FBRyxDQUFDLEVBQVUsRUFBRSxRQUFrQixFQUFXLEVBQUU7SUFDNUQsSUFBSSxDQUFDLEVBQUUsSUFBSSxDQUFDLFFBQVEsSUFBSSxRQUFRLENBQUMsTUFBTSxLQUFLLENBQUM7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUU1RCxNQUFNLFdBQVcsR0FBRyxDQUFDLEVBQVUsRUFBWSxFQUFFO1FBQzNDLElBQUksQ0FBQyxFQUFFO1lBQUUsT0FBTyxFQUFFLENBQUM7UUFDbkIsSUFBSSxFQUFFLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUM7WUFDN0IsTUFBTSxJQUFJLEdBQUcsRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUN6QixPQUFPLENBQUMsRUFBRSxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQ3BCLENBQUM7UUFDRCxJQUFJLHlCQUF5QixDQUFDLElBQUksQ0FBQyxFQUFF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
|