@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.
Files changed (313) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/forwarding/factory/forwarding-factory.js +29 -1
  3. package/dist_ts/http/index.d.ts +1 -3
  4. package/dist_ts/http/index.js +4 -10
  5. package/dist_ts/http/models/http-types.d.ts +4 -91
  6. package/dist_ts/http/models/http-types.js +5 -60
  7. package/dist_ts/http/router/proxy-router.d.ts +1 -1
  8. package/dist_ts/http/router/route-router.d.ts +1 -1
  9. package/dist_ts/index.d.ts +9 -7
  10. package/dist_ts/index.js +10 -7
  11. package/dist_ts/proxies/{network-proxy → http-proxy}/certificate-manager.d.ts +2 -2
  12. package/dist_ts/proxies/{network-proxy → http-proxy}/certificate-manager.js +1 -1
  13. package/dist_ts/proxies/{network-proxy → http-proxy}/connection-pool.d.ts +2 -2
  14. package/dist_ts/proxies/http-proxy/connection-pool.js +210 -0
  15. package/dist_ts/proxies/http-proxy/context-creator.js +108 -0
  16. package/dist_ts/proxies/{network-proxy → http-proxy}/function-cache.js +1 -1
  17. package/dist_ts/proxies/http-proxy/handlers/index.d.ts +5 -0
  18. package/dist_ts/proxies/http-proxy/handlers/index.js +6 -0
  19. package/dist_ts/proxies/http-proxy/handlers/redirect-handler.d.ts +18 -0
  20. package/dist_ts/proxies/http-proxy/handlers/redirect-handler.js +78 -0
  21. package/dist_ts/proxies/http-proxy/handlers/static-handler.d.ts +19 -0
  22. package/dist_ts/proxies/http-proxy/handlers/static-handler.js +203 -0
  23. package/dist_ts/proxies/{network-proxy/network-proxy.d.ts → http-proxy/http-proxy.d.ts} +10 -9
  24. package/dist_ts/proxies/{network-proxy/network-proxy.js → http-proxy/http-proxy.js} +13 -12
  25. package/dist_ts/proxies/{network-proxy → http-proxy}/http-request-handler.js +1 -1
  26. package/dist_ts/proxies/http-proxy/http2-request-handler.js +201 -0
  27. package/dist_ts/proxies/{network-proxy → http-proxy}/index.d.ts +2 -2
  28. package/dist_ts/proxies/http-proxy/index.js +12 -0
  29. package/dist_ts/proxies/http-proxy/models/http-types.d.ts +119 -0
  30. package/dist_ts/proxies/http-proxy/models/http-types.js +112 -0
  31. package/dist_ts/proxies/http-proxy/models/index.d.ts +5 -0
  32. package/dist_ts/proxies/http-proxy/models/index.js +6 -0
  33. package/dist_ts/proxies/{network-proxy → http-proxy}/models/types.d.ts +2 -2
  34. package/dist_ts/proxies/http-proxy/models/types.js +276 -0
  35. package/dist_ts/proxies/{network-proxy → http-proxy}/request-handler.d.ts +3 -3
  36. package/dist_ts/proxies/{network-proxy → http-proxy}/request-handler.js +2 -2
  37. package/dist_ts/proxies/http-proxy/security-manager.js +255 -0
  38. package/dist_ts/proxies/{network-proxy → http-proxy}/websocket-handler.d.ts +3 -3
  39. package/dist_ts/proxies/{network-proxy → http-proxy}/websocket-handler.js +2 -2
  40. package/dist_ts/proxies/index.d.ts +5 -5
  41. package/dist_ts/proxies/index.js +5 -5
  42. package/dist_ts/proxies/smart-proxy/certificate-manager.d.ts +4 -4
  43. package/dist_ts/proxies/smart-proxy/certificate-manager.js +11 -11
  44. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.d.ts +41 -0
  45. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +121 -0
  46. package/dist_ts/proxies/smart-proxy/index.d.ts +2 -1
  47. package/dist_ts/proxies/smart-proxy/index.js +4 -2
  48. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +2 -2
  49. package/dist_ts/proxies/smart-proxy/port-manager.js +3 -3
  50. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +3 -3
  51. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +24 -265
  52. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +1 -1
  53. package/dist_ts/proxies/smart-proxy/smart-proxy.js +25 -25
  54. package/dist_ts/routing/index.d.ts +5 -0
  55. package/dist_ts/routing/index.js +8 -0
  56. package/dist_ts/routing/models/http-types.d.ts +6 -0
  57. package/dist_ts/routing/models/http-types.js +7 -0
  58. package/dist_ts/routing/router/index.d.ts +8 -0
  59. package/dist_ts/routing/router/index.js +7 -0
  60. package/dist_ts/{classes.router.d.ts → routing/router/proxy-router.d.ts} +14 -11
  61. package/dist_ts/{classes.router.js → routing/router/proxy-router.js} +2 -2
  62. package/dist_ts/routing/router/route-router.d.ts +108 -0
  63. package/dist_ts/routing/router/route-router.js +393 -0
  64. package/package.json +1 -1
  65. package/readme.md +18 -35
  66. package/readme.plan.md +173 -271
  67. package/ts/00_commitinfo_data.ts +1 -1
  68. package/ts/forwarding/factory/forwarding-factory.ts +28 -0
  69. package/ts/index.ts +13 -9
  70. package/ts/proxies/{network-proxy → http-proxy}/certificate-manager.ts +2 -2
  71. package/ts/proxies/{network-proxy → http-proxy}/connection-pool.ts +2 -2
  72. package/ts/proxies/http-proxy/handlers/index.ts +6 -0
  73. package/ts/proxies/http-proxy/handlers/redirect-handler.ts +105 -0
  74. package/ts/proxies/http-proxy/handlers/static-handler.ts +251 -0
  75. package/ts/proxies/{network-proxy/network-proxy.ts → http-proxy/http-proxy.ts} +15 -14
  76. package/ts/proxies/{network-proxy → http-proxy}/index.ts +3 -3
  77. package/ts/proxies/http-proxy/models/http-types.ts +165 -0
  78. package/ts/proxies/http-proxy/models/index.ts +5 -0
  79. package/ts/proxies/{network-proxy → http-proxy}/models/types.ts +2 -2
  80. package/ts/proxies/{network-proxy → http-proxy}/request-handler.ts +3 -3
  81. package/ts/proxies/{network-proxy → http-proxy}/websocket-handler.ts +3 -3
  82. package/ts/proxies/index.ts +7 -7
  83. package/ts/proxies/smart-proxy/certificate-manager.ts +10 -10
  84. package/ts/proxies/smart-proxy/{network-proxy-bridge.ts → http-proxy-bridge.ts} +44 -44
  85. package/ts/proxies/smart-proxy/index.ts +4 -1
  86. package/ts/proxies/smart-proxy/models/interfaces.ts +3 -3
  87. package/ts/proxies/smart-proxy/port-manager.ts +2 -2
  88. package/ts/proxies/smart-proxy/route-connection-handler.ts +23 -307
  89. package/ts/proxies/smart-proxy/smart-proxy.ts +25 -25
  90. package/ts/routing/index.ts +9 -0
  91. package/ts/routing/models/http-types.ts +6 -0
  92. package/ts/{http → routing}/router/proxy-router.ts +1 -1
  93. package/ts/{http → routing}/router/route-router.ts +1 -1
  94. package/dist_ts/certificate/acme/acme-factory.d.ts +0 -17
  95. package/dist_ts/certificate/acme/acme-factory.js +0 -40
  96. package/dist_ts/certificate/acme/challenge-handler.d.ts +0 -44
  97. package/dist_ts/certificate/acme/challenge-handler.js +0 -92
  98. package/dist_ts/certificate/acme/index.d.ts +0 -4
  99. package/dist_ts/certificate/acme/index.js +0 -5
  100. package/dist_ts/certificate/certificate-manager.d.ts +0 -150
  101. package/dist_ts/certificate/certificate-manager.js +0 -505
  102. package/dist_ts/certificate/events/certificate-events.d.ts +0 -33
  103. package/dist_ts/certificate/events/certificate-events.js +0 -38
  104. package/dist_ts/certificate/events/simplified-events.d.ts +0 -56
  105. package/dist_ts/certificate/events/simplified-events.js +0 -13
  106. package/dist_ts/certificate/index.d.ts +0 -30
  107. package/dist_ts/certificate/index.js +0 -37
  108. package/dist_ts/certificate/models/certificate-errors.d.ts +0 -69
  109. package/dist_ts/certificate/models/certificate-errors.js +0 -141
  110. package/dist_ts/certificate/models/certificate-strategy.d.ts +0 -60
  111. package/dist_ts/certificate/models/certificate-strategy.js +0 -73
  112. package/dist_ts/certificate/models/certificate-types.d.ts +0 -97
  113. package/dist_ts/certificate/models/certificate-types.js +0 -2
  114. package/dist_ts/certificate/providers/cert-provisioner.d.ts +0 -119
  115. package/dist_ts/certificate/providers/cert-provisioner.js +0 -422
  116. package/dist_ts/certificate/providers/index.d.ts +0 -4
  117. package/dist_ts/certificate/providers/index.js +0 -5
  118. package/dist_ts/certificate/simplified-certificate-manager.d.ts +0 -150
  119. package/dist_ts/certificate/simplified-certificate-manager.js +0 -501
  120. package/dist_ts/certificate/storage/file-storage.d.ts +0 -66
  121. package/dist_ts/certificate/storage/file-storage.js +0 -194
  122. package/dist_ts/certificate/storage/index.d.ts +0 -4
  123. package/dist_ts/certificate/storage/index.js +0 -5
  124. package/dist_ts/certificate/utils/certificate-helpers.d.ts +0 -17
  125. package/dist_ts/certificate/utils/certificate-helpers.js +0 -45
  126. package/dist_ts/classes.iptablesproxy.d.ts +0 -112
  127. package/dist_ts/classes.iptablesproxy.js +0 -765
  128. package/dist_ts/classes.networkproxy.d.ts +0 -243
  129. package/dist_ts/classes.networkproxy.js +0 -1424
  130. package/dist_ts/classes.nftablesproxy.d.ts +0 -219
  131. package/dist_ts/classes.nftablesproxy.js +0 -1542
  132. package/dist_ts/classes.port80handler.d.ts +0 -215
  133. package/dist_ts/classes.port80handler.js +0 -736
  134. package/dist_ts/classes.portproxy.d.ts +0 -171
  135. package/dist_ts/classes.portproxy.js +0 -1802
  136. package/dist_ts/classes.pp.acmemanager.d.ts +0 -34
  137. package/dist_ts/classes.pp.acmemanager.js +0 -123
  138. package/dist_ts/classes.pp.connectionhandler.d.ts +0 -39
  139. package/dist_ts/classes.pp.connectionhandler.js +0 -754
  140. package/dist_ts/classes.pp.connectionmanager.d.ts +0 -78
  141. package/dist_ts/classes.pp.connectionmanager.js +0 -378
  142. package/dist_ts/classes.pp.domainconfigmanager.d.ts +0 -55
  143. package/dist_ts/classes.pp.domainconfigmanager.js +0 -103
  144. package/dist_ts/classes.pp.interfaces.d.ts +0 -133
  145. package/dist_ts/classes.pp.interfaces.js +0 -2
  146. package/dist_ts/classes.pp.networkproxybridge.d.ts +0 -57
  147. package/dist_ts/classes.pp.networkproxybridge.js +0 -306
  148. package/dist_ts/classes.pp.portproxy.d.ts +0 -64
  149. package/dist_ts/classes.pp.portproxy.js +0 -567
  150. package/dist_ts/classes.pp.portrangemanager.d.ts +0 -56
  151. package/dist_ts/classes.pp.portrangemanager.js +0 -179
  152. package/dist_ts/classes.pp.securitymanager.d.ts +0 -47
  153. package/dist_ts/classes.pp.securitymanager.js +0 -126
  154. package/dist_ts/classes.pp.snihandler.d.ts +0 -153
  155. package/dist_ts/classes.pp.snihandler.js +0 -1053
  156. package/dist_ts/classes.pp.timeoutmanager.d.ts +0 -47
  157. package/dist_ts/classes.pp.timeoutmanager.js +0 -154
  158. package/dist_ts/classes.pp.tlsalert.d.ts +0 -149
  159. package/dist_ts/classes.pp.tlsalert.js +0 -225
  160. package/dist_ts/classes.pp.tlsmanager.d.ts +0 -57
  161. package/dist_ts/classes.pp.tlsmanager.js +0 -132
  162. package/dist_ts/classes.snihandler.d.ts +0 -198
  163. package/dist_ts/classes.snihandler.js +0 -1210
  164. package/dist_ts/classes.sslredirect.d.ts +0 -8
  165. package/dist_ts/classes.sslredirect.js +0 -28
  166. package/dist_ts/common/acmeFactory.d.ts +0 -9
  167. package/dist_ts/common/acmeFactory.js +0 -20
  168. package/dist_ts/common/port80-adapter.d.ts +0 -11
  169. package/dist_ts/common/port80-adapter.js +0 -87
  170. package/dist_ts/examples/forwarding-example.d.ts +0 -1
  171. package/dist_ts/examples/forwarding-example.js +0 -96
  172. package/dist_ts/forwarding/config/domain-config.d.ts +0 -12
  173. package/dist_ts/forwarding/config/domain-config.js +0 -12
  174. package/dist_ts/forwarding/config/domain-manager.d.ts +0 -86
  175. package/dist_ts/forwarding/config/domain-manager.js +0 -242
  176. package/dist_ts/helpers.certificates.d.ts +0 -5
  177. package/dist_ts/helpers.certificates.js +0 -23
  178. package/dist_ts/http/port80/acme-interfaces.d.ts +0 -108
  179. package/dist_ts/http/port80/acme-interfaces.js +0 -51
  180. package/dist_ts/http/port80/challenge-responder.d.ts +0 -53
  181. package/dist_ts/http/port80/challenge-responder.js +0 -203
  182. package/dist_ts/http/port80/index.d.ts +0 -6
  183. package/dist_ts/http/port80/index.js +0 -9
  184. package/dist_ts/http/port80/port80-handler.d.ts +0 -136
  185. package/dist_ts/http/port80/port80-handler.js +0 -592
  186. package/dist_ts/http/redirects/index.d.ts +0 -4
  187. package/dist_ts/http/redirects/index.js +0 -5
  188. package/dist_ts/networkproxy/classes.np.certificatemanager.d.ts +0 -77
  189. package/dist_ts/networkproxy/classes.np.certificatemanager.js +0 -372
  190. package/dist_ts/networkproxy/classes.np.connectionpool.d.ts +0 -47
  191. package/dist_ts/networkproxy/classes.np.connectionpool.js +0 -210
  192. package/dist_ts/networkproxy/classes.np.networkproxy.d.ts +0 -118
  193. package/dist_ts/networkproxy/classes.np.networkproxy.js +0 -387
  194. package/dist_ts/networkproxy/classes.np.requesthandler.d.ts +0 -56
  195. package/dist_ts/networkproxy/classes.np.requesthandler.js +0 -393
  196. package/dist_ts/networkproxy/classes.np.types.d.ts +0 -83
  197. package/dist_ts/networkproxy/classes.np.types.js +0 -35
  198. package/dist_ts/networkproxy/classes.np.websockethandler.d.ts +0 -38
  199. package/dist_ts/networkproxy/classes.np.websockethandler.js +0 -188
  200. package/dist_ts/networkproxy/index.d.ts +0 -1
  201. package/dist_ts/networkproxy/index.js +0 -4
  202. package/dist_ts/nfttablesproxy/classes.nftablesproxy.d.ts +0 -219
  203. package/dist_ts/nfttablesproxy/classes.nftablesproxy.js +0 -1542
  204. package/dist_ts/port80handler/classes.port80handler.d.ts +0 -10
  205. package/dist_ts/port80handler/classes.port80handler.js +0 -16
  206. package/dist_ts/proxies/network-proxy/connection-pool.js +0 -210
  207. package/dist_ts/proxies/network-proxy/context-creator.js +0 -108
  208. package/dist_ts/proxies/network-proxy/http2-request-handler.js +0 -201
  209. package/dist_ts/proxies/network-proxy/index.js +0 -12
  210. package/dist_ts/proxies/network-proxy/models/index.d.ts +0 -4
  211. package/dist_ts/proxies/network-proxy/models/index.js +0 -5
  212. package/dist_ts/proxies/network-proxy/models/types.js +0 -276
  213. package/dist_ts/proxies/network-proxy/security-manager.js +0 -255
  214. package/dist_ts/proxies/network-proxy/simplified-certificate-bridge.d.ts +0 -48
  215. package/dist_ts/proxies/network-proxy/simplified-certificate-bridge.js +0 -76
  216. package/dist_ts/proxies/smart-proxy/connection-handler.d.ts +0 -39
  217. package/dist_ts/proxies/smart-proxy/connection-handler.js +0 -894
  218. package/dist_ts/proxies/smart-proxy/domain-config-manager.d.ts +0 -110
  219. package/dist_ts/proxies/smart-proxy/domain-config-manager.js +0 -386
  220. package/dist_ts/proxies/smart-proxy/legacy-smart-proxy.d.ts +0 -168
  221. package/dist_ts/proxies/smart-proxy/legacy-smart-proxy.js +0 -642
  222. package/dist_ts/proxies/smart-proxy/models/simplified-smartproxy-config.d.ts +0 -65
  223. package/dist_ts/proxies/smart-proxy/models/simplified-smartproxy-config.js +0 -31
  224. package/dist_ts/proxies/smart-proxy/models/smartproxy-options.d.ts +0 -102
  225. package/dist_ts/proxies/smart-proxy/models/smartproxy-options.js +0 -73
  226. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.d.ts +0 -41
  227. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.js +0 -121
  228. package/dist_ts/proxies/smart-proxy/port-range-manager.d.ts +0 -56
  229. package/dist_ts/proxies/smart-proxy/port-range-manager.js +0 -176
  230. package/dist_ts/proxies/smart-proxy/route-helpers/index.d.ts +0 -9
  231. package/dist_ts/proxies/smart-proxy/route-helpers/index.js +0 -11
  232. package/dist_ts/proxies/smart-proxy/route-helpers.d.ts +0 -7
  233. package/dist_ts/proxies/smart-proxy/route-helpers.js +0 -9
  234. package/dist_ts/proxies/smart-proxy/simplified-smart-proxy.d.ts +0 -41
  235. package/dist_ts/proxies/smart-proxy/simplified-smart-proxy.js +0 -132
  236. package/dist_ts/proxies/smart-proxy/utils/route-migration-utils.d.ts +0 -51
  237. package/dist_ts/proxies/smart-proxy/utils/route-migration-utils.js +0 -124
  238. package/dist_ts/redirect/classes.redirect.d.ts +0 -96
  239. package/dist_ts/redirect/classes.redirect.js +0 -194
  240. package/dist_ts/smartproxy/classes.pp.certprovisioner.d.ts +0 -54
  241. package/dist_ts/smartproxy/classes.pp.certprovisioner.js +0 -179
  242. package/dist_ts/smartproxy/classes.pp.connectionhandler.d.ts +0 -39
  243. package/dist_ts/smartproxy/classes.pp.connectionhandler.js +0 -894
  244. package/dist_ts/smartproxy/classes.pp.connectionmanager.d.ts +0 -78
  245. package/dist_ts/smartproxy/classes.pp.connectionmanager.js +0 -378
  246. package/dist_ts/smartproxy/classes.pp.domainconfigmanager.d.ts +0 -94
  247. package/dist_ts/smartproxy/classes.pp.domainconfigmanager.js +0 -255
  248. package/dist_ts/smartproxy/classes.pp.interfaces.d.ts +0 -103
  249. package/dist_ts/smartproxy/classes.pp.interfaces.js +0 -2
  250. package/dist_ts/smartproxy/classes.pp.networkproxybridge.d.ts +0 -62
  251. package/dist_ts/smartproxy/classes.pp.networkproxybridge.js +0 -316
  252. package/dist_ts/smartproxy/classes.pp.portrangemanager.d.ts +0 -56
  253. package/dist_ts/smartproxy/classes.pp.portrangemanager.js +0 -176
  254. package/dist_ts/smartproxy/classes.pp.securitymanager.d.ts +0 -64
  255. package/dist_ts/smartproxy/classes.pp.securitymanager.js +0 -149
  256. package/dist_ts/smartproxy/classes.pp.snihandler.d.ts +0 -153
  257. package/dist_ts/smartproxy/classes.pp.snihandler.js +0 -1053
  258. package/dist_ts/smartproxy/classes.pp.timeoutmanager.d.ts +0 -47
  259. package/dist_ts/smartproxy/classes.pp.timeoutmanager.js +0 -154
  260. package/dist_ts/smartproxy/classes.pp.tlsalert.d.ts +0 -149
  261. package/dist_ts/smartproxy/classes.pp.tlsalert.js +0 -225
  262. package/dist_ts/smartproxy/classes.pp.tlsmanager.d.ts +0 -57
  263. package/dist_ts/smartproxy/classes.pp.tlsmanager.js +0 -132
  264. package/dist_ts/smartproxy/classes.smartproxy.d.ts +0 -63
  265. package/dist_ts/smartproxy/classes.smartproxy.js +0 -521
  266. package/dist_ts/smartproxy/forwarding/domain-config.d.ts +0 -12
  267. package/dist_ts/smartproxy/forwarding/domain-config.js +0 -12
  268. package/dist_ts/smartproxy/forwarding/domain-manager.d.ts +0 -86
  269. package/dist_ts/smartproxy/forwarding/domain-manager.js +0 -241
  270. package/dist_ts/smartproxy/forwarding/forwarding.factory.d.ts +0 -24
  271. package/dist_ts/smartproxy/forwarding/forwarding.factory.js +0 -137
  272. package/dist_ts/smartproxy/forwarding/forwarding.handler.d.ts +0 -55
  273. package/dist_ts/smartproxy/forwarding/forwarding.handler.js +0 -94
  274. package/dist_ts/smartproxy/forwarding/http.handler.d.ts +0 -25
  275. package/dist_ts/smartproxy/forwarding/http.handler.js +0 -123
  276. package/dist_ts/smartproxy/forwarding/https-passthrough.handler.d.ts +0 -24
  277. package/dist_ts/smartproxy/forwarding/https-passthrough.handler.js +0 -154
  278. package/dist_ts/smartproxy/forwarding/https-terminate-to-http.handler.d.ts +0 -36
  279. package/dist_ts/smartproxy/forwarding/https-terminate-to-http.handler.js +0 -229
  280. package/dist_ts/smartproxy/forwarding/https-terminate-to-https.handler.d.ts +0 -35
  281. package/dist_ts/smartproxy/forwarding/https-terminate-to-https.handler.js +0 -254
  282. package/dist_ts/smartproxy/forwarding/index.d.ts +0 -16
  283. package/dist_ts/smartproxy/forwarding/index.js +0 -23
  284. package/dist_ts/smartproxy/types/forwarding.types.d.ts +0 -104
  285. package/dist_ts/smartproxy/types/forwarding.types.js +0 -50
  286. package/dist_ts/smartproxy.classes.networkproxy.d.ts +0 -31
  287. package/dist_ts/smartproxy.classes.networkproxy.js +0 -305
  288. package/dist_ts/smartproxy.classes.router.d.ts +0 -13
  289. package/dist_ts/smartproxy.classes.router.js +0 -33
  290. package/dist_ts/smartproxy.classes.sslredirect.d.ts +0 -8
  291. package/dist_ts/smartproxy.classes.sslredirect.js +0 -28
  292. package/dist_ts/smartproxy.helpers.certificates.d.ts +0 -5
  293. package/dist_ts/smartproxy.helpers.certificates.js +0 -23
  294. package/dist_ts/smartproxy.plugins.d.ts +0 -18
  295. package/dist_ts/smartproxy.plugins.js +0 -23
  296. package/dist_ts/smartproxy.portproxy.d.ts +0 -26
  297. package/dist_ts/smartproxy.portproxy.js +0 -295
  298. package/ts/http/index.ts +0 -16
  299. package/ts/http/models/http-types.ts +0 -108
  300. package/ts/http/redirects/index.ts +0 -3
  301. package/ts/proxies/network-proxy/models/index.ts +0 -4
  302. package/ts/redirect/classes.redirect.ts +0 -295
  303. /package/dist_ts/proxies/{network-proxy → http-proxy}/context-creator.d.ts +0 -0
  304. /package/dist_ts/proxies/{network-proxy → http-proxy}/function-cache.d.ts +0 -0
  305. /package/dist_ts/proxies/{network-proxy → http-proxy}/http-request-handler.d.ts +0 -0
  306. /package/dist_ts/proxies/{network-proxy → http-proxy}/http2-request-handler.d.ts +0 -0
  307. /package/dist_ts/proxies/{network-proxy → http-proxy}/security-manager.d.ts +0 -0
  308. /package/ts/proxies/{network-proxy → http-proxy}/context-creator.ts +0 -0
  309. /package/ts/proxies/{network-proxy → http-proxy}/function-cache.ts +0 -0
  310. /package/ts/proxies/{network-proxy → http-proxy}/http-request-handler.ts +0 -0
  311. /package/ts/proxies/{network-proxy → http-proxy}/http2-request-handler.ts +0 -0
  312. /package/ts/proxies/{network-proxy → http-proxy}/security-manager.ts +0 -0
  313. /package/ts/{http → routing}/router/index.ts +0 -0
@@ -1,1210 +0,0 @@
1
- import { Buffer } from 'buffer';
2
- /**
3
- * SNI (Server Name Indication) handler for TLS connections.
4
- * Provides robust extraction of SNI values from TLS ClientHello messages
5
- * with support for fragmented packets, TLS 1.3 resumption, Chrome-specific
6
- * connection behaviors, and tab hibernation/reactivation scenarios.
7
- */
8
- export class SniHandler {
9
- // TLS record types and constants
10
- static { this.TLS_HANDSHAKE_RECORD_TYPE = 22; }
11
- static { this.TLS_APPLICATION_DATA_TYPE = 23; } // TLS Application Data record type
12
- static { this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE = 1; }
13
- static { this.TLS_SNI_EXTENSION_TYPE = 0x0000; }
14
- static { this.TLS_SESSION_TICKET_EXTENSION_TYPE = 0x0023; }
15
- static { this.TLS_SNI_HOST_NAME_TYPE = 0; }
16
- static { this.TLS_PSK_EXTENSION_TYPE = 0x0029; } // Pre-Shared Key extension type for TLS 1.3
17
- static { this.TLS_PSK_KE_MODES_EXTENSION_TYPE = 0x002d; } // PSK Key Exchange Modes
18
- static { this.TLS_EARLY_DATA_EXTENSION_TYPE = 0x002a; } // Early Data (0-RTT) extension
19
- // Buffer for handling fragmented ClientHello messages
20
- static { this.fragmentedBuffers = new Map(); }
21
- static { this.fragmentTimeout = 1000; } // ms to wait for fragments before cleanup
22
- // Session tracking for tab reactivation scenarios
23
- static { this.sessionCache = new Map(); }
24
- // Longer timeout for session cache (24 hours by default)
25
- static { this.sessionCacheTimeout = 24 * 60 * 60 * 1000; } // 24 hours in milliseconds
26
- // Cleanup interval for session cache (run every hour)
27
- static { this.sessionCleanupInterval = null; }
28
- /**
29
- * Initialize the session cache cleanup mechanism.
30
- * This should be called during application startup.
31
- */
32
- static initSessionCacheCleanup() {
33
- if (this.sessionCleanupInterval === null) {
34
- this.sessionCleanupInterval = setInterval(() => {
35
- this.cleanupSessionCache();
36
- }, 60 * 60 * 1000); // Run every hour
37
- }
38
- }
39
- /**
40
- * Clean up expired entries from the session cache
41
- */
42
- static cleanupSessionCache() {
43
- const now = Date.now();
44
- const expiredKeys = [];
45
- this.sessionCache.forEach((session, key) => {
46
- if (now - session.timestamp > this.sessionCacheTimeout) {
47
- expiredKeys.push(key);
48
- }
49
- });
50
- expiredKeys.forEach((key) => {
51
- this.sessionCache.delete(key);
52
- });
53
- }
54
- /**
55
- * Create a client identity key for session tracking
56
- * Uses source IP and optional client random for uniqueness
57
- *
58
- * @param sourceIp - Client IP address
59
- * @param clientRandom - Optional TLS client random value
60
- * @returns A string key for the session cache
61
- */
62
- static createClientKey(sourceIp, clientRandom) {
63
- if (clientRandom) {
64
- // If we have the client random, use it for more precise tracking
65
- return `${sourceIp}:${clientRandom.toString('hex')}`;
66
- }
67
- // Fall back to just IP-based tracking
68
- return sourceIp;
69
- }
70
- /**
71
- * Store SNI information in the session cache
72
- *
73
- * @param sourceIp - Client IP address
74
- * @param sni - The extracted SNI value
75
- * @param clientRandom - Optional TLS client random value
76
- */
77
- static cacheSession(sourceIp, sni, clientRandom) {
78
- const key = this.createClientKey(sourceIp, clientRandom);
79
- this.sessionCache.set(key, {
80
- sni,
81
- timestamp: Date.now(),
82
- clientRandom,
83
- });
84
- }
85
- /**
86
- * Retrieve SNI information from the session cache
87
- *
88
- * @param sourceIp - Client IP address
89
- * @param clientRandom - Optional TLS client random value
90
- * @returns The cached SNI or undefined if not found
91
- */
92
- static getCachedSession(sourceIp, clientRandom) {
93
- // Try with client random first for precision
94
- if (clientRandom) {
95
- const preciseKey = this.createClientKey(sourceIp, clientRandom);
96
- const preciseSession = this.sessionCache.get(preciseKey);
97
- if (preciseSession) {
98
- return preciseSession.sni;
99
- }
100
- }
101
- // Fall back to IP-only lookup
102
- const ipKey = this.createClientKey(sourceIp);
103
- const session = this.sessionCache.get(ipKey);
104
- if (session) {
105
- // Update the timestamp to keep the session alive
106
- session.timestamp = Date.now();
107
- return session.sni;
108
- }
109
- return undefined;
110
- }
111
- /**
112
- * Extract the client random value from a ClientHello message
113
- *
114
- * @param buffer - The buffer containing the ClientHello
115
- * @returns The 32-byte client random or undefined if extraction fails
116
- */
117
- static extractClientRandom(buffer) {
118
- try {
119
- if (!this.isClientHello(buffer) || buffer.length < 46) {
120
- return undefined;
121
- }
122
- // In a ClientHello message, the client random starts at position 11
123
- // after record header (5 bytes), handshake type (1 byte),
124
- // handshake length (3 bytes), and client version (2 bytes)
125
- return buffer.slice(11, 11 + 32);
126
- }
127
- catch (error) {
128
- return undefined;
129
- }
130
- }
131
- /**
132
- * Checks if a buffer contains a TLS handshake message (record type 22)
133
- * @param buffer - The buffer to check
134
- * @returns true if the buffer starts with a TLS handshake record type
135
- */
136
- static isTlsHandshake(buffer) {
137
- return buffer.length > 0 && buffer[0] === this.TLS_HANDSHAKE_RECORD_TYPE;
138
- }
139
- /**
140
- * Checks if a buffer contains TLS application data (record type 23)
141
- * @param buffer - The buffer to check
142
- * @returns true if the buffer starts with a TLS application data record type
143
- */
144
- static isTlsApplicationData(buffer) {
145
- return buffer.length > 0 && buffer[0] === this.TLS_APPLICATION_DATA_TYPE;
146
- }
147
- /**
148
- * Creates a connection ID based on source/destination information
149
- * Used to track fragmented ClientHello messages across multiple packets
150
- *
151
- * @param connectionInfo - Object containing connection identifiers (IP/port)
152
- * @returns A string ID for the connection
153
- */
154
- static createConnectionId(connectionInfo) {
155
- const { sourceIp, sourcePort, destIp, destPort } = connectionInfo;
156
- return `${sourceIp}:${sourcePort}-${destIp}:${destPort}`;
157
- }
158
- /**
159
- * Handles potential fragmented ClientHello messages by buffering and reassembling
160
- * TLS record fragments that might span multiple TCP packets.
161
- *
162
- * @param buffer - The current buffer fragment
163
- * @param connectionId - Unique identifier for the connection
164
- * @param enableLogging - Whether to enable logging
165
- * @returns A complete buffer if reassembly is successful, or undefined if more fragments are needed
166
- */
167
- static handleFragmentedClientHello(buffer, connectionId, enableLogging = false) {
168
- const log = (message) => {
169
- if (enableLogging) {
170
- console.log(`[SNI Fragment] ${message}`);
171
- }
172
- };
173
- // Check if we've seen this connection before
174
- if (!this.fragmentedBuffers.has(connectionId)) {
175
- // New connection, start with this buffer
176
- this.fragmentedBuffers.set(connectionId, buffer);
177
- // Set timeout to clean up if we don't get a complete ClientHello
178
- setTimeout(() => {
179
- if (this.fragmentedBuffers.has(connectionId)) {
180
- this.fragmentedBuffers.delete(connectionId);
181
- log(`Connection ${connectionId} timed out waiting for complete ClientHello`);
182
- }
183
- }, this.fragmentTimeout);
184
- // Evaluate if this buffer already contains a complete ClientHello
185
- try {
186
- if (buffer.length >= 5) {
187
- // Get the record length from TLS header
188
- const recordLength = (buffer[3] << 8) + buffer[4] + 5; // +5 for the TLS record header itself
189
- log(`Initial buffer size: ${buffer.length}, expected record length: ${recordLength}`);
190
- // Check if this buffer already contains a complete TLS record
191
- if (buffer.length >= recordLength) {
192
- log(`Initial buffer contains complete ClientHello, length: ${buffer.length}`);
193
- return buffer;
194
- }
195
- }
196
- else {
197
- log(`Initial buffer too small (${buffer.length} bytes), needs at least 5 bytes for TLS header`);
198
- }
199
- }
200
- catch (e) {
201
- log(`Error checking initial buffer completeness: ${e}`);
202
- }
203
- log(`Started buffering connection ${connectionId}, initial size: ${buffer.length}`);
204
- return undefined; // Need more fragments
205
- }
206
- else {
207
- // Existing connection, append this buffer
208
- const existingBuffer = this.fragmentedBuffers.get(connectionId);
209
- const newBuffer = Buffer.concat([existingBuffer, buffer]);
210
- this.fragmentedBuffers.set(connectionId, newBuffer);
211
- log(`Appended to buffer for ${connectionId}, new size: ${newBuffer.length}`);
212
- // Check if we now have a complete ClientHello
213
- try {
214
- if (newBuffer.length >= 5) {
215
- // Get the record length from TLS header
216
- const recordLength = (newBuffer[3] << 8) + newBuffer[4] + 5; // +5 for the TLS record header itself
217
- log(`Reassembled buffer size: ${newBuffer.length}, expected record length: ${recordLength}`);
218
- // Check if we have a complete TLS record now
219
- if (newBuffer.length >= recordLength) {
220
- log(`Assembled complete ClientHello, length: ${newBuffer.length}, needed: ${recordLength}`);
221
- // Extract the complete TLS record (might be followed by more data)
222
- const completeRecord = newBuffer.slice(0, recordLength);
223
- // Check if this record is indeed a ClientHello (type 1) at position 5
224
- if (completeRecord.length > 5 &&
225
- completeRecord[5] === this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE) {
226
- log(`Verified record is a ClientHello handshake message`);
227
- // Complete message received, remove from tracking
228
- this.fragmentedBuffers.delete(connectionId);
229
- return completeRecord;
230
- }
231
- else {
232
- log(`Record is complete but not a ClientHello handshake, continuing to buffer`);
233
- // This might be another TLS record type preceding the ClientHello
234
- // Try checking for a ClientHello starting at the end of this record
235
- if (newBuffer.length > recordLength + 5) {
236
- const nextRecordType = newBuffer[recordLength];
237
- log(`Next record type: ${nextRecordType} (looking for ${this.TLS_HANDSHAKE_RECORD_TYPE})`);
238
- if (nextRecordType === this.TLS_HANDSHAKE_RECORD_TYPE) {
239
- const handshakeType = newBuffer[recordLength + 5];
240
- log(`Next handshake type: ${handshakeType} (looking for ${this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE})`);
241
- if (handshakeType === this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE) {
242
- // Found a ClientHello in the next record, return the entire buffer
243
- log(`Found ClientHello in subsequent record, returning full buffer`);
244
- this.fragmentedBuffers.delete(connectionId);
245
- return newBuffer;
246
- }
247
- }
248
- }
249
- }
250
- }
251
- }
252
- }
253
- catch (e) {
254
- log(`Error checking reassembled buffer completeness: ${e}`);
255
- }
256
- return undefined; // Still need more fragments
257
- }
258
- }
259
- /**
260
- * Checks if a buffer contains a TLS ClientHello message
261
- * @param buffer - The buffer to check
262
- * @returns true if the buffer appears to be a ClientHello message
263
- */
264
- static isClientHello(buffer) {
265
- // Minimum ClientHello size (TLS record header + handshake header)
266
- if (buffer.length < 9) {
267
- return false;
268
- }
269
- // Check record type (must be TLS_HANDSHAKE_RECORD_TYPE)
270
- if (buffer[0] !== this.TLS_HANDSHAKE_RECORD_TYPE) {
271
- return false;
272
- }
273
- // Skip version and length in TLS record header (5 bytes total)
274
- // Check handshake type at byte 5 (must be CLIENT_HELLO)
275
- return buffer[5] === this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE;
276
- }
277
- /**
278
- * Checks if a ClientHello message contains session resumption indicators
279
- * such as session tickets or PSK (Pre-Shared Key) extensions.
280
- *
281
- * @param buffer - The buffer containing a ClientHello message
282
- * @param enableLogging - Whether to enable logging
283
- * @returns Object containing details about session resumption and SNI presence
284
- */
285
- static hasSessionResumption(buffer, enableLogging = false) {
286
- const log = (message) => {
287
- if (enableLogging) {
288
- console.log(`[Session Resumption] ${message}`);
289
- }
290
- };
291
- if (!this.isClientHello(buffer)) {
292
- return { isResumption: false, hasSNI: false };
293
- }
294
- try {
295
- // Check for session ID presence first
296
- let pos = 5 + 1 + 3 + 2; // Position after handshake type, length and client version
297
- pos += 32; // Skip client random
298
- if (pos + 1 > buffer.length)
299
- return { isResumption: false, hasSNI: false };
300
- const sessionIdLength = buffer[pos];
301
- let hasNonEmptySessionId = sessionIdLength > 0;
302
- if (hasNonEmptySessionId) {
303
- log(`Detected non-empty session ID (length: ${sessionIdLength})`);
304
- }
305
- // Continue to check for extensions
306
- pos += 1 + sessionIdLength;
307
- // Skip cipher suites
308
- if (pos + 2 > buffer.length)
309
- return { isResumption: false, hasSNI: false };
310
- const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
311
- pos += 2 + cipherSuitesLength;
312
- // Skip compression methods
313
- if (pos + 1 > buffer.length)
314
- return { isResumption: false, hasSNI: false };
315
- const compressionMethodsLength = buffer[pos];
316
- pos += 1 + compressionMethodsLength;
317
- // Check for extensions
318
- if (pos + 2 > buffer.length)
319
- return { isResumption: false, hasSNI: false };
320
- // Look for session resumption extensions
321
- const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
322
- pos += 2;
323
- // Extensions end position
324
- const extensionsEnd = pos + extensionsLength;
325
- if (extensionsEnd > buffer.length)
326
- return { isResumption: false, hasSNI: false };
327
- // Track resumption indicators
328
- let hasSessionTicket = false;
329
- let hasPSK = false;
330
- let hasEarlyData = false;
331
- // Iterate through extensions
332
- while (pos + 4 <= extensionsEnd) {
333
- const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
334
- pos += 2;
335
- const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
336
- pos += 2;
337
- if (extensionType === this.TLS_SESSION_TICKET_EXTENSION_TYPE) {
338
- log('Found session ticket extension');
339
- hasSessionTicket = true;
340
- // Check if session ticket has non-zero length (active ticket)
341
- if (extensionLength > 0) {
342
- log(`Session ticket has length ${extensionLength} - active ticket present`);
343
- }
344
- }
345
- else if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
346
- log('Found PSK extension (TLS 1.3 resumption mechanism)');
347
- hasPSK = true;
348
- }
349
- else if (extensionType === this.TLS_EARLY_DATA_EXTENSION_TYPE) {
350
- log('Found Early Data extension (TLS 1.3 0-RTT)');
351
- hasEarlyData = true;
352
- }
353
- // Skip extension data
354
- pos += extensionLength;
355
- }
356
- // Check if SNI is included
357
- let hasSNI = false;
358
- // Reset position and scan again for SNI extension
359
- pos = 5 + 1 + 3 + 2; // Reset to after handshake type, length and client version
360
- pos += 32; // Skip client random
361
- if (pos + 1 <= buffer.length) {
362
- const sessionIdLength = buffer[pos];
363
- pos += 1 + sessionIdLength;
364
- // Skip cipher suites
365
- if (pos + 2 <= buffer.length) {
366
- const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
367
- pos += 2 + cipherSuitesLength;
368
- // Skip compression methods
369
- if (pos + 1 <= buffer.length) {
370
- const compressionMethodsLength = buffer[pos];
371
- pos += 1 + compressionMethodsLength;
372
- // Check for extensions
373
- if (pos + 2 <= buffer.length) {
374
- const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
375
- pos += 2;
376
- // Extensions end position
377
- const extensionsEnd = pos + extensionsLength;
378
- if (extensionsEnd <= buffer.length) {
379
- // Scan for SNI extension
380
- while (pos + 4 <= extensionsEnd) {
381
- const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
382
- pos += 2;
383
- const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
384
- pos += 2;
385
- if (extensionType === this.TLS_SNI_EXTENSION_TYPE) {
386
- // Check that the SNI extension actually has content
387
- if (extensionLength > 0) {
388
- hasSNI = true;
389
- // Try to extract the actual SNI value for logging
390
- try {
391
- // Skip to server_name_list_length (2 bytes)
392
- const tempPos = pos;
393
- if (tempPos + 2 <= extensionsEnd) {
394
- const nameListLength = (buffer[tempPos] << 8) + buffer[tempPos + 1];
395
- // Skip server_name_list_length (2 bytes)
396
- if (tempPos + 2 + 1 <= extensionsEnd) {
397
- // Check name_type (should be 0 for hostname)
398
- if (buffer[tempPos + 2] === 0) {
399
- // Skip name_type (1 byte)
400
- if (tempPos + 3 + 2 <= extensionsEnd) {
401
- // Get name_length (2 bytes)
402
- const nameLength = (buffer[tempPos + 3] << 8) + buffer[tempPos + 4];
403
- // Extract the hostname
404
- if (tempPos + 5 + nameLength <= extensionsEnd) {
405
- const hostname = buffer
406
- .slice(tempPos + 5, tempPos + 5 + nameLength)
407
- .toString('utf8');
408
- log(`Found SNI extension with server_name: ${hostname}`);
409
- }
410
- }
411
- }
412
- }
413
- }
414
- }
415
- catch (e) {
416
- log(`Error extracting SNI value: ${e}`);
417
- log('Found SNI extension with length: ' + extensionLength);
418
- }
419
- }
420
- else {
421
- log('Found empty SNI extension, treating as no SNI');
422
- }
423
- break;
424
- }
425
- // Skip extension data
426
- pos += extensionLength;
427
- }
428
- }
429
- }
430
- }
431
- }
432
- }
433
- // Consider it a resumption if any resumption mechanism is present
434
- const isResumption = hasSessionTicket || hasPSK || hasEarlyData || (hasNonEmptySessionId && !hasPSK); // Legacy resumption
435
- if (isResumption) {
436
- log('Session resumption detected: ' +
437
- (hasSessionTicket ? 'session ticket, ' : '') +
438
- (hasPSK ? 'PSK, ' : '') +
439
- (hasEarlyData ? 'early data, ' : '') +
440
- (hasNonEmptySessionId ? 'session ID' : '') +
441
- (hasSNI ? ', with SNI' : ', without SNI'));
442
- }
443
- // Return an object with both flags
444
- // For clarity: connections should be blocked if they have session resumption without SNI
445
- if (isResumption) {
446
- log(`Resumption summary - hasSNI: ${hasSNI ? 'yes' : 'no'}, resumption type: ${hasSessionTicket ? 'session ticket, ' : ''}${hasPSK ? 'PSK, ' : ''}${hasEarlyData ? 'early data, ' : ''}${hasNonEmptySessionId ? 'session ID' : ''}`);
447
- }
448
- return {
449
- isResumption,
450
- hasSNI,
451
- };
452
- }
453
- catch (error) {
454
- log(`Error checking for session resumption: ${error}`);
455
- return { isResumption: false, hasSNI: false };
456
- }
457
- }
458
- /**
459
- * Detects characteristics of a tab reactivation TLS handshake
460
- * These often have specific patterns in Chrome and other browsers
461
- *
462
- * @param buffer - The buffer containing a ClientHello message
463
- * @param enableLogging - Whether to enable logging
464
- * @returns true if this appears to be a tab reactivation handshake
465
- */
466
- static isTabReactivationHandshake(buffer, enableLogging = false) {
467
- const log = (message) => {
468
- if (enableLogging) {
469
- console.log(`[Tab Reactivation] ${message}`);
470
- }
471
- };
472
- if (!this.isClientHello(buffer)) {
473
- return false;
474
- }
475
- try {
476
- // Check for session ID presence (tab reactivation often has a session ID)
477
- let pos = 5 + 1 + 3 + 2; // Position after handshake type, length and client version
478
- pos += 32; // Skip client random
479
- if (pos + 1 > buffer.length)
480
- return false;
481
- const sessionIdLength = buffer[pos];
482
- // Non-empty session ID is a good indicator
483
- if (sessionIdLength > 0) {
484
- log(`Detected non-empty session ID (length: ${sessionIdLength})`);
485
- // Skip to extensions
486
- pos += 1 + sessionIdLength;
487
- // Skip cipher suites
488
- if (pos + 2 > buffer.length)
489
- return false;
490
- const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
491
- pos += 2 + cipherSuitesLength;
492
- // Skip compression methods
493
- if (pos + 1 > buffer.length)
494
- return false;
495
- const compressionMethodsLength = buffer[pos];
496
- pos += 1 + compressionMethodsLength;
497
- // Check for extensions
498
- if (pos + 2 > buffer.length)
499
- return false;
500
- // Look for specific extensions that indicate tab reactivation
501
- const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
502
- pos += 2;
503
- // Extensions end position
504
- const extensionsEnd = pos + extensionsLength;
505
- if (extensionsEnd > buffer.length)
506
- return false;
507
- // Tab reactivation often has session tickets but no SNI
508
- let hasSessionTicket = false;
509
- let hasSNI = false;
510
- let hasPSK = false;
511
- // Iterate through extensions
512
- while (pos + 4 <= extensionsEnd) {
513
- const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
514
- pos += 2;
515
- const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
516
- pos += 2;
517
- if (extensionType === this.TLS_SESSION_TICKET_EXTENSION_TYPE) {
518
- hasSessionTicket = true;
519
- }
520
- else if (extensionType === this.TLS_SNI_EXTENSION_TYPE) {
521
- hasSNI = true;
522
- }
523
- else if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
524
- hasPSK = true;
525
- }
526
- // Skip extension data
527
- pos += extensionLength;
528
- }
529
- // Pattern for tab reactivation: session identifier + (ticket or PSK) but no SNI
530
- if ((hasSessionTicket || hasPSK) && !hasSNI) {
531
- log('Detected tab reactivation pattern: session resumption without SNI');
532
- return true;
533
- }
534
- }
535
- }
536
- catch (error) {
537
- log(`Error checking for tab reactivation: ${error}`);
538
- }
539
- return false;
540
- }
541
- /**
542
- * Extracts the SNI (Server Name Indication) from a TLS ClientHello message.
543
- * Implements robust parsing with support for session resumption edge cases.
544
- *
545
- * @param buffer - The buffer containing the TLS ClientHello message
546
- * @param enableLogging - Whether to enable detailed debug logging
547
- * @returns The extracted server name or undefined if not found
548
- */
549
- static extractSNI(buffer, enableLogging = false) {
550
- // Logging helper
551
- const log = (message) => {
552
- if (enableLogging) {
553
- console.log(`[SNI Extraction] ${message}`);
554
- }
555
- };
556
- try {
557
- // Buffer must be at least 5 bytes (TLS record header)
558
- if (buffer.length < 5) {
559
- log('Buffer too small for TLS record header');
560
- return undefined;
561
- }
562
- // Check record type (must be TLS_HANDSHAKE_RECORD_TYPE = 22)
563
- if (buffer[0] !== this.TLS_HANDSHAKE_RECORD_TYPE) {
564
- log(`Not a TLS handshake record: ${buffer[0]}`);
565
- return undefined;
566
- }
567
- // Check TLS version
568
- const majorVersion = buffer[1];
569
- const minorVersion = buffer[2];
570
- log(`TLS version: ${majorVersion}.${minorVersion}`);
571
- // Parse record length (bytes 3-4, big-endian)
572
- const recordLength = (buffer[3] << 8) + buffer[4];
573
- log(`Record length: ${recordLength}`);
574
- // Validate record length against buffer size
575
- if (buffer.length < recordLength + 5) {
576
- log('Buffer smaller than expected record length');
577
- return undefined;
578
- }
579
- // Start of handshake message in the buffer
580
- let pos = 5;
581
- // Check handshake type (must be CLIENT_HELLO = 1)
582
- if (buffer[pos] !== this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE) {
583
- log(`Not a ClientHello message: ${buffer[pos]}`);
584
- return undefined;
585
- }
586
- // Skip handshake type (1 byte)
587
- pos += 1;
588
- // Parse handshake length (3 bytes, big-endian)
589
- const handshakeLength = (buffer[pos] << 16) + (buffer[pos + 1] << 8) + buffer[pos + 2];
590
- log(`Handshake length: ${handshakeLength}`);
591
- // Skip handshake length (3 bytes)
592
- pos += 3;
593
- // Check client version (2 bytes)
594
- const clientMajorVersion = buffer[pos];
595
- const clientMinorVersion = buffer[pos + 1];
596
- log(`Client version: ${clientMajorVersion}.${clientMinorVersion}`);
597
- // Skip client version (2 bytes)
598
- pos += 2;
599
- // Skip client random (32 bytes)
600
- pos += 32;
601
- // Parse session ID
602
- if (pos + 1 > buffer.length) {
603
- log('Buffer too small for session ID length');
604
- return undefined;
605
- }
606
- const sessionIdLength = buffer[pos];
607
- log(`Session ID length: ${sessionIdLength}`);
608
- // Skip session ID length (1 byte) and session ID
609
- pos += 1 + sessionIdLength;
610
- // Check if we have enough bytes left
611
- if (pos + 2 > buffer.length) {
612
- log('Buffer too small for cipher suites length');
613
- return undefined;
614
- }
615
- // Parse cipher suites length (2 bytes, big-endian)
616
- const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
617
- log(`Cipher suites length: ${cipherSuitesLength}`);
618
- // Skip cipher suites length (2 bytes) and cipher suites
619
- pos += 2 + cipherSuitesLength;
620
- // Check if we have enough bytes left
621
- if (pos + 1 > buffer.length) {
622
- log('Buffer too small for compression methods length');
623
- return undefined;
624
- }
625
- // Parse compression methods length (1 byte)
626
- const compressionMethodsLength = buffer[pos];
627
- log(`Compression methods length: ${compressionMethodsLength}`);
628
- // Skip compression methods length (1 byte) and compression methods
629
- pos += 1 + compressionMethodsLength;
630
- // Check if we have enough bytes for extensions length
631
- if (pos + 2 > buffer.length) {
632
- log('No extensions present or buffer too small');
633
- return undefined;
634
- }
635
- // Parse extensions length (2 bytes, big-endian)
636
- const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
637
- log(`Extensions length: ${extensionsLength}`);
638
- // Skip extensions length (2 bytes)
639
- pos += 2;
640
- // Extensions end position
641
- const extensionsEnd = pos + extensionsLength;
642
- // Check if extensions length is valid
643
- if (extensionsEnd > buffer.length) {
644
- log('Extensions length exceeds buffer size');
645
- return undefined;
646
- }
647
- // Track if we found session tickets (for improved resumption handling)
648
- let hasSessionTicket = false;
649
- let hasPskExtension = false;
650
- // Iterate through extensions
651
- while (pos + 4 <= extensionsEnd) {
652
- // Parse extension type (2 bytes, big-endian)
653
- const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
654
- log(`Extension type: 0x${extensionType.toString(16).padStart(4, '0')}`);
655
- // Skip extension type (2 bytes)
656
- pos += 2;
657
- // Parse extension length (2 bytes, big-endian)
658
- const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
659
- log(`Extension length: ${extensionLength}`);
660
- // Skip extension length (2 bytes)
661
- pos += 2;
662
- // Check if this is the SNI extension
663
- if (extensionType === this.TLS_SNI_EXTENSION_TYPE) {
664
- log('Found SNI extension');
665
- // Ensure we have enough bytes for the server name list
666
- if (pos + 2 > extensionsEnd) {
667
- log('Extension too small for server name list length');
668
- pos += extensionLength; // Skip this extension
669
- continue;
670
- }
671
- // Parse server name list length (2 bytes, big-endian)
672
- const serverNameListLength = (buffer[pos] << 8) + buffer[pos + 1];
673
- log(`Server name list length: ${serverNameListLength}`);
674
- // Skip server name list length (2 bytes)
675
- pos += 2;
676
- // Ensure server name list length is valid
677
- if (pos + serverNameListLength > extensionsEnd) {
678
- log('Server name list length exceeds extension size');
679
- break; // Exit the loop, extension parsing is broken
680
- }
681
- // End position of server name list
682
- const serverNameListEnd = pos + serverNameListLength;
683
- // Iterate through server names
684
- while (pos + 3 <= serverNameListEnd) {
685
- // Check name type (must be HOST_NAME_TYPE = 0 for hostname)
686
- const nameType = buffer[pos];
687
- log(`Name type: ${nameType}`);
688
- if (nameType !== this.TLS_SNI_HOST_NAME_TYPE) {
689
- log(`Unsupported name type: ${nameType}`);
690
- pos += 1; // Skip name type (1 byte)
691
- // Skip name length (2 bytes) and name data
692
- if (pos + 2 <= serverNameListEnd) {
693
- const nameLength = (buffer[pos] << 8) + buffer[pos + 1];
694
- pos += 2 + nameLength;
695
- }
696
- else {
697
- log('Invalid server name entry');
698
- break;
699
- }
700
- continue;
701
- }
702
- // Skip name type (1 byte)
703
- pos += 1;
704
- // Ensure we have enough bytes for name length
705
- if (pos + 2 > serverNameListEnd) {
706
- log('Server name entry too small for name length');
707
- break;
708
- }
709
- // Parse name length (2 bytes, big-endian)
710
- const nameLength = (buffer[pos] << 8) + buffer[pos + 1];
711
- log(`Name length: ${nameLength}`);
712
- // Skip name length (2 bytes)
713
- pos += 2;
714
- // Ensure we have enough bytes for the name
715
- if (pos + nameLength > serverNameListEnd) {
716
- log('Name length exceeds server name list size');
717
- break;
718
- }
719
- // Extract server name (hostname)
720
- const serverName = buffer.slice(pos, pos + nameLength).toString('utf8');
721
- log(`Extracted server name: ${serverName}`);
722
- return serverName;
723
- }
724
- }
725
- else if (extensionType === this.TLS_SESSION_TICKET_EXTENSION_TYPE) {
726
- // If we encounter a session ticket extension, mark it for later
727
- log('Found session ticket extension');
728
- hasSessionTicket = true;
729
- pos += extensionLength; // Skip this extension
730
- }
731
- else if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
732
- // TLS 1.3 PSK extension - mark for resumption support
733
- log('Found PSK extension (TLS 1.3 resumption indicator)');
734
- hasPskExtension = true;
735
- // We'll skip the extension here and process it separately if needed
736
- pos += extensionLength;
737
- }
738
- else {
739
- // Skip this extension
740
- pos += extensionLength;
741
- }
742
- }
743
- // Log if we found session resumption indicators but no SNI
744
- if (hasSessionTicket || hasPskExtension) {
745
- log('Session resumption indicators present but no SNI found');
746
- }
747
- log('No SNI extension found in ClientHello');
748
- return undefined;
749
- }
750
- catch (error) {
751
- log(`Error parsing SNI: ${error instanceof Error ? error.message : String(error)}`);
752
- return undefined;
753
- }
754
- }
755
- /**
756
- * Attempts to extract SNI from the PSK extension in a TLS 1.3 ClientHello.
757
- *
758
- * In TLS 1.3, when a client attempts to resume a session, it may include
759
- * the server name in the PSK identity hint rather than in the SNI extension.
760
- *
761
- * @param buffer - The buffer containing the TLS ClientHello message
762
- * @param enableLogging - Whether to enable detailed debug logging
763
- * @returns The extracted server name or undefined if not found
764
- */
765
- static extractSNIFromPSKExtension(buffer, enableLogging = false) {
766
- const log = (message) => {
767
- if (enableLogging) {
768
- console.log(`[PSK-SNI Extraction] ${message}`);
769
- }
770
- };
771
- try {
772
- // Ensure this is a ClientHello
773
- if (!this.isClientHello(buffer)) {
774
- log('Not a ClientHello message');
775
- return undefined;
776
- }
777
- // Find the start position of extensions
778
- let pos = 5; // Start after record header
779
- // Skip handshake type (1 byte)
780
- pos += 1;
781
- // Skip handshake length (3 bytes)
782
- pos += 3;
783
- // Skip client version (2 bytes)
784
- pos += 2;
785
- // Skip client random (32 bytes)
786
- pos += 32;
787
- // Skip session ID
788
- if (pos + 1 > buffer.length)
789
- return undefined;
790
- const sessionIdLength = buffer[pos];
791
- pos += 1 + sessionIdLength;
792
- // Skip cipher suites
793
- if (pos + 2 > buffer.length)
794
- return undefined;
795
- const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
796
- pos += 2 + cipherSuitesLength;
797
- // Skip compression methods
798
- if (pos + 1 > buffer.length)
799
- return undefined;
800
- const compressionMethodsLength = buffer[pos];
801
- pos += 1 + compressionMethodsLength;
802
- // Check if we have extensions
803
- if (pos + 2 > buffer.length) {
804
- log('No extensions present');
805
- return undefined;
806
- }
807
- // Get extensions length
808
- const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
809
- pos += 2;
810
- // Extensions end position
811
- const extensionsEnd = pos + extensionsLength;
812
- if (extensionsEnd > buffer.length)
813
- return undefined;
814
- // Look for PSK extension
815
- while (pos + 4 <= extensionsEnd) {
816
- const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
817
- pos += 2;
818
- const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
819
- pos += 2;
820
- if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
821
- log('Found PSK extension');
822
- // PSK extension structure:
823
- // 2 bytes: identities list length
824
- if (pos + 2 > extensionsEnd)
825
- break;
826
- const identitiesLength = (buffer[pos] << 8) + buffer[pos + 1];
827
- pos += 2;
828
- // End of identities list
829
- const identitiesEnd = pos + identitiesLength;
830
- if (identitiesEnd > extensionsEnd)
831
- break;
832
- // Process each PSK identity
833
- while (pos + 2 <= identitiesEnd) {
834
- // Identity length (2 bytes)
835
- if (pos + 2 > identitiesEnd)
836
- break;
837
- const identityLength = (buffer[pos] << 8) + buffer[pos + 1];
838
- pos += 2;
839
- if (pos + identityLength > identitiesEnd)
840
- break;
841
- // Try to extract hostname from identity
842
- // Chrome often embeds the hostname in the PSK identity
843
- // This is a heuristic as there's no standard format
844
- if (identityLength > 0) {
845
- const identity = buffer.slice(pos, pos + identityLength);
846
- // Skip identity bytes
847
- pos += identityLength;
848
- // Skip obfuscated ticket age (4 bytes)
849
- if (pos + 4 <= identitiesEnd) {
850
- pos += 4;
851
- }
852
- else {
853
- break;
854
- }
855
- // Try to parse the identity as UTF-8
856
- try {
857
- const identityStr = identity.toString('utf8');
858
- log(`PSK identity: ${identityStr}`);
859
- // Check if the identity contains hostname hints
860
- // Chrome often embeds the hostname in a known format
861
- // Try to extract using common patterns
862
- // Pattern 1: Look for domain name pattern
863
- const domainPattern = /([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?/i;
864
- const domainMatch = identityStr.match(domainPattern);
865
- if (domainMatch && domainMatch[0]) {
866
- log(`Found domain in PSK identity: ${domainMatch[0]}`);
867
- return domainMatch[0];
868
- }
869
- // Pattern 2: Chrome sometimes uses a specific format with delimiters
870
- // This is a heuristic approach since the format isn't standardized
871
- const parts = identityStr.split('|');
872
- if (parts.length > 1) {
873
- for (const part of parts) {
874
- if (part.includes('.') && !part.includes('/')) {
875
- const possibleDomain = part.trim();
876
- if (/^[a-z0-9.-]+$/i.test(possibleDomain)) {
877
- log(`Found possible domain in PSK delimiter format: ${possibleDomain}`);
878
- return possibleDomain;
879
- }
880
- }
881
- }
882
- }
883
- }
884
- catch (e) {
885
- log('Failed to parse PSK identity as UTF-8');
886
- }
887
- }
888
- }
889
- }
890
- else {
891
- // Skip this extension
892
- pos += extensionLength;
893
- }
894
- }
895
- log('No hostname found in PSK extension');
896
- return undefined;
897
- }
898
- catch (error) {
899
- log(`Error parsing PSK: ${error instanceof Error ? error.message : String(error)}`);
900
- return undefined;
901
- }
902
- }
903
- /**
904
- * Checks if the buffer contains TLS 1.3 early data (0-RTT)
905
- * @param buffer - The buffer to check
906
- * @param enableLogging - Whether to enable logging
907
- * @returns true if early data is detected
908
- */
909
- static hasEarlyData(buffer, enableLogging = false) {
910
- const log = (message) => {
911
- if (enableLogging) {
912
- console.log(`[Early Data] ${message}`);
913
- }
914
- };
915
- try {
916
- // Check if this is a valid ClientHello first
917
- if (!this.isClientHello(buffer)) {
918
- return false;
919
- }
920
- // Find the extensions section
921
- let pos = 5; // Start after record header
922
- // Skip handshake type (1 byte)
923
- pos += 1;
924
- // Skip handshake length (3 bytes)
925
- pos += 3;
926
- // Skip client version (2 bytes)
927
- pos += 2;
928
- // Skip client random (32 bytes)
929
- pos += 32;
930
- // Skip session ID
931
- if (pos + 1 > buffer.length)
932
- return false;
933
- const sessionIdLength = buffer[pos];
934
- pos += 1 + sessionIdLength;
935
- // Skip cipher suites
936
- if (pos + 2 > buffer.length)
937
- return false;
938
- const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
939
- pos += 2 + cipherSuitesLength;
940
- // Skip compression methods
941
- if (pos + 1 > buffer.length)
942
- return false;
943
- const compressionMethodsLength = buffer[pos];
944
- pos += 1 + compressionMethodsLength;
945
- // Check if we have extensions
946
- if (pos + 2 > buffer.length)
947
- return false;
948
- // Get extensions length
949
- const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
950
- pos += 2;
951
- // Extensions end position
952
- const extensionsEnd = pos + extensionsLength;
953
- if (extensionsEnd > buffer.length)
954
- return false;
955
- // Look for early data extension
956
- while (pos + 4 <= extensionsEnd) {
957
- const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
958
- pos += 2;
959
- const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
960
- pos += 2;
961
- if (extensionType === this.TLS_EARLY_DATA_EXTENSION_TYPE) {
962
- log('Early Data (0-RTT) extension detected');
963
- return true;
964
- }
965
- // Skip to next extension
966
- pos += extensionLength;
967
- }
968
- return false;
969
- }
970
- catch (error) {
971
- log(`Error checking for early data: ${error}`);
972
- return false;
973
- }
974
- }
975
- /**
976
- * Attempts to extract SNI from an initial ClientHello packet and handles
977
- * session resumption edge cases more robustly than the standard extraction.
978
- *
979
- * This method handles:
980
- * 1. Standard SNI extraction
981
- * 2. TLS 1.3 PSK-based resumption (Chrome, Firefox, etc.)
982
- * 3. Session ticket-based resumption
983
- * 4. Fragmented ClientHello messages
984
- * 5. TLS 1.3 Early Data (0-RTT)
985
- * 6. Chrome's connection racing behaviors
986
- * 7. Tab reactivation patterns with session cache
987
- *
988
- * @param buffer - The buffer containing the TLS ClientHello message
989
- * @param connectionInfo - Optional connection information for fragment handling
990
- * @param enableLogging - Whether to enable detailed debug logging
991
- * @returns The extracted server name or undefined if not found
992
- */
993
- static extractSNIWithResumptionSupport(buffer, connectionInfo, enableLogging = false) {
994
- const log = (message) => {
995
- if (enableLogging) {
996
- console.log(`[SNI Extraction] ${message}`);
997
- }
998
- };
999
- // Log buffer details for debugging
1000
- if (enableLogging) {
1001
- log(`Buffer size: ${buffer.length} bytes`);
1002
- log(`Buffer starts with: ${buffer.slice(0, Math.min(10, buffer.length)).toString('hex')}`);
1003
- if (buffer.length >= 5) {
1004
- const recordType = buffer[0];
1005
- const majorVersion = buffer[1];
1006
- const minorVersion = buffer[2];
1007
- const recordLength = (buffer[3] << 8) + buffer[4];
1008
- log(`TLS Record: type=${recordType}, version=${majorVersion}.${minorVersion}, length=${recordLength}`);
1009
- }
1010
- }
1011
- // Check if we need to handle fragmented packets
1012
- let processBuffer = buffer;
1013
- if (connectionInfo) {
1014
- const connectionId = this.createConnectionId(connectionInfo);
1015
- const reassembledBuffer = this.handleFragmentedClientHello(buffer, connectionId, enableLogging);
1016
- if (!reassembledBuffer) {
1017
- log(`Waiting for more fragments on connection ${connectionId}`);
1018
- return undefined; // Need more fragments to complete ClientHello
1019
- }
1020
- processBuffer = reassembledBuffer;
1021
- log(`Using reassembled buffer of length ${processBuffer.length}`);
1022
- }
1023
- // First try the standard SNI extraction
1024
- const standardSni = this.extractSNI(processBuffer, enableLogging);
1025
- if (standardSni) {
1026
- log(`Found standard SNI: ${standardSni}`);
1027
- // If we extracted a standard SNI, cache it for future use
1028
- if (connectionInfo?.sourceIp) {
1029
- const clientRandom = this.extractClientRandom(processBuffer);
1030
- this.cacheSession(connectionInfo.sourceIp, standardSni, clientRandom);
1031
- log(`Cached SNI for future reference: ${standardSni}`);
1032
- }
1033
- return standardSni;
1034
- }
1035
- // Check for session resumption when standard SNI extraction fails
1036
- // This may help in chained proxy scenarios
1037
- if (this.isClientHello(processBuffer)) {
1038
- const resumptionInfo = this.hasSessionResumption(processBuffer, enableLogging);
1039
- if (resumptionInfo.isResumption) {
1040
- log(`Detected session resumption in ClientHello without standard SNI`);
1041
- // Try to extract SNI from PSK extension
1042
- const pskSni = this.extractSNIFromPSKExtension(processBuffer, enableLogging);
1043
- if (pskSni) {
1044
- log(`Extracted SNI from PSK extension: ${pskSni}`);
1045
- // Cache this SNI
1046
- if (connectionInfo?.sourceIp) {
1047
- const clientRandom = this.extractClientRandom(processBuffer);
1048
- this.cacheSession(connectionInfo.sourceIp, pskSni, clientRandom);
1049
- }
1050
- return pskSni;
1051
- }
1052
- // If session resumption has SNI in a non-standard location,
1053
- // we need to apply heuristics
1054
- if (connectionInfo?.sourceIp) {
1055
- const cachedSni = this.getCachedSession(connectionInfo.sourceIp);
1056
- if (cachedSni) {
1057
- log(`Using cached SNI for session resumption: ${cachedSni}`);
1058
- return cachedSni;
1059
- }
1060
- }
1061
- }
1062
- }
1063
- // Try tab reactivation and other recovery methods...
1064
- // (existing code remains unchanged)
1065
- // Log detailed info about the ClientHello when SNI extraction fails
1066
- if (this.isClientHello(processBuffer) && enableLogging) {
1067
- log(`SNI extraction failed for ClientHello. Buffer details:`);
1068
- if (processBuffer.length >= 43) {
1069
- // ClientHello with at least client random
1070
- const clientRandom = processBuffer.slice(11, 11 + 32).toString('hex');
1071
- log(`Client Random: ${clientRandom}`);
1072
- // Log session ID length and presence
1073
- const sessionIdLength = processBuffer[43];
1074
- log(`Session ID length: ${sessionIdLength}`);
1075
- if (sessionIdLength > 0 && processBuffer.length >= 44 + sessionIdLength) {
1076
- const sessionId = processBuffer.slice(44, 44 + sessionIdLength).toString('hex');
1077
- log(`Session ID: ${sessionId}`);
1078
- }
1079
- }
1080
- }
1081
- // Existing code for fallback methods continues...
1082
- return undefined;
1083
- }
1084
- /**
1085
- * Main entry point for SNI extraction that handles all edge cases.
1086
- * This should be called for each TLS packet received from a client.
1087
- *
1088
- * The method uses connection tracking to handle fragmented ClientHello
1089
- * messages and various TLS 1.3 behaviors, including Chrome's connection
1090
- * racing patterns and tab reactivation behaviors.
1091
- *
1092
- * @param buffer - The buffer containing TLS data
1093
- * @param connectionInfo - Connection metadata (IPs and ports)
1094
- * @param enableLogging - Whether to enable detailed debug logging
1095
- * @param cachedSni - Optional cached SNI from previous connections (for racing detection)
1096
- * @returns The extracted server name or undefined if not found or more data needed
1097
- */
1098
- static processTlsPacket(buffer, connectionInfo, enableLogging = false, cachedSni) {
1099
- const log = (message) => {
1100
- if (enableLogging) {
1101
- console.log(`[TLS Packet] ${message}`);
1102
- }
1103
- };
1104
- // Add timestamp if not provided
1105
- if (!connectionInfo.timestamp) {
1106
- connectionInfo.timestamp = Date.now();
1107
- }
1108
- // Check if this is a TLS handshake or application data
1109
- if (!this.isTlsHandshake(buffer) && !this.isTlsApplicationData(buffer)) {
1110
- log('Not a TLS handshake or application data packet');
1111
- return undefined;
1112
- }
1113
- // Create connection ID for tracking
1114
- const connectionId = this.createConnectionId(connectionInfo);
1115
- log(`Processing TLS packet for connection ${connectionId}, buffer length: ${buffer.length}`);
1116
- // Handle application data with cached SNI (for connection racing)
1117
- if (this.isTlsApplicationData(buffer)) {
1118
- // First check if explicit cachedSni was provided
1119
- if (cachedSni) {
1120
- log(`Using provided cached SNI for application data: ${cachedSni}`);
1121
- return cachedSni;
1122
- }
1123
- // Otherwise check our session cache
1124
- const sessionCachedSni = this.getCachedSession(connectionInfo.sourceIp);
1125
- if (sessionCachedSni) {
1126
- log(`Using session-cached SNI for application data: ${sessionCachedSni}`);
1127
- return sessionCachedSni;
1128
- }
1129
- log('Application data packet without cached SNI, cannot determine hostname');
1130
- return undefined;
1131
- }
1132
- // Enhanced session resumption detection
1133
- if (this.isClientHello(buffer)) {
1134
- const resumptionInfo = this.hasSessionResumption(buffer, enableLogging);
1135
- if (resumptionInfo.isResumption) {
1136
- log(`Session resumption detected in TLS packet`);
1137
- // Always try standard SNI extraction first
1138
- const standardSni = this.extractSNI(buffer, enableLogging);
1139
- if (standardSni) {
1140
- log(`Found standard SNI in session resumption: ${standardSni}`);
1141
- // Cache this SNI
1142
- this.cacheSession(connectionInfo.sourceIp, standardSni);
1143
- return standardSni;
1144
- }
1145
- // Enhanced session resumption SNI extraction
1146
- // Try extracting from PSK identity
1147
- const pskSni = this.extractSNIFromPSKExtension(buffer, enableLogging);
1148
- if (pskSni) {
1149
- log(`Extracted SNI from PSK extension: ${pskSni}`);
1150
- this.cacheSession(connectionInfo.sourceIp, pskSni);
1151
- return pskSni;
1152
- }
1153
- // Additional check for SNI in session tickets
1154
- if (enableLogging) {
1155
- log(`Checking for session ticket information to extract server name...`);
1156
- // Log more details for debugging
1157
- try {
1158
- // Look at the raw buffer for patterns
1159
- log(`Buffer hexdump (first 100 bytes): ${buffer.slice(0, 100).toString('hex')}`);
1160
- // Try to find hostname-like patterns in the buffer
1161
- const bufferStr = buffer.toString('utf8', 0, buffer.length);
1162
- const hostnamePattern = /([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?/gi;
1163
- const hostMatches = bufferStr.match(hostnamePattern);
1164
- if (hostMatches && hostMatches.length > 0) {
1165
- log(`Possible hostnames found in buffer: ${hostMatches.join(', ')}`);
1166
- // Check if any match looks like a valid domain
1167
- for (const match of hostMatches) {
1168
- if (match.includes('.') && match.length > 3) {
1169
- log(`Potential SNI found in session data: ${match}`);
1170
- // Don't automatically use this - just log for debugging
1171
- }
1172
- }
1173
- }
1174
- }
1175
- catch (e) {
1176
- log(`Error scanning for patterns: ${e}`);
1177
- }
1178
- }
1179
- // If we still don't have SNI, check for cached sessions
1180
- const cachedSni = this.getCachedSession(connectionInfo.sourceIp);
1181
- if (cachedSni) {
1182
- log(`Using cached SNI for session resumption: ${cachedSni}`);
1183
- return cachedSni;
1184
- }
1185
- log(`Session resumption without extractable SNI`);
1186
- // If allowSessionTicket=false, should be rejected by caller
1187
- }
1188
- }
1189
- // For handshake messages, try the full extraction process
1190
- const sni = this.extractSNIWithResumptionSupport(buffer, connectionInfo, enableLogging);
1191
- if (sni) {
1192
- log(`Successfully extracted SNI: ${sni}`);
1193
- return sni;
1194
- }
1195
- // If we couldn't extract an SNI, check if this is a valid ClientHello
1196
- // If it is, but we couldn't get an SNI, it might be a fragment or
1197
- // a connection race situation
1198
- if (this.isClientHello(buffer)) {
1199
- // Check if we have a cached session for this IP
1200
- const sessionCachedSni = this.getCachedSession(connectionInfo.sourceIp);
1201
- if (sessionCachedSni) {
1202
- log(`Using session cache for ClientHello without SNI: ${sessionCachedSni}`);
1203
- return sessionCachedSni;
1204
- }
1205
- log('Valid ClientHello detected, but no SNI extracted - might need more data');
1206
- }
1207
- return undefined;
1208
- }
1209
- }
1210
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zbmloYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5zbmloYW5kbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFFaEM7Ozs7O0dBS0c7QUFDSCxNQUFNLE9BQU8sVUFBVTtJQUNyQixpQ0FBaUM7YUFDVCw4QkFBeUIsR0FBRyxFQUFFLENBQUM7YUFDL0IsOEJBQXlCLEdBQUcsRUFBRSxDQUFDLEdBQUMsbUNBQW1DO2FBQ25FLG9DQUErQixHQUFHLENBQUMsQ0FBQzthQUNwQywyQkFBc0IsR0FBRyxNQUFNLENBQUM7YUFDaEMsc0NBQWlDLEdBQUcsTUFBTSxDQUFDO2FBQzNDLDJCQUFzQixHQUFHLENBQUMsQ0FBQzthQUMzQiwyQkFBc0IsR0FBRyxNQUFNLENBQUMsR0FBQyw0Q0FBNEM7YUFDN0Usb0NBQStCLEdBQUcsTUFBTSxDQUFDLEdBQUMseUJBQXlCO2FBQ25FLGtDQUE2QixHQUFHLE1BQU0sQ0FBQyxHQUFDLCtCQUErQjtJQUUvRixzREFBc0Q7YUFDdkMsc0JBQWlCLEdBQXdCLElBQUksR0FBRyxFQUFFLENBQUM7YUFDbkQsb0JBQWUsR0FBVyxJQUFJLENBQUMsR0FBQywwQ0FBMEM7SUFFekYsa0RBQWtEO2FBQ25DLGlCQUFZLEdBT3ZCLElBQUksR0FBRyxFQUFFLENBQUM7SUFFZCx5REFBeUQ7YUFDMUMsd0JBQW1CLEdBQVcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLEdBQUMsMkJBQTJCO0lBRTdGLHNEQUFzRDthQUN2QywyQkFBc0IsR0FBMEIsSUFBSSxDQUFDO0lBRXBFOzs7T0FHRztJQUNJLE1BQU0sQ0FBQyx1QkFBdUI7UUFDbkMsSUFBSSxJQUFJLENBQUMsc0JBQXNCLEtBQUssSUFBSSxFQUFFLENBQUM7WUFDekMsSUFBSSxDQUFDLHNCQUFzQixHQUFHLFdBQVcsQ0FBQyxH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