feat(socket-handler): implement direct socket passing for DNS and email services
- Add socket-handler mode eliminating internal port binding for improved performance - Add `dnsDomain` config option for automatic DNS-over-HTTPS (DoH) setup - Add `useSocketHandler` flag to email config for direct socket processing - Update SmartProxy route generation to support socket-handler actions - Integrate smartdns with manual HTTPS mode for DoH without port binding - Add automatic route creation for DNS paths when dnsDomain is configured - Update documentation with socket-handler configuration and benefits - Improve resource efficiency by eliminating internal port forwarding
This commit is contained in:
parent
6c8458f63c
commit
b11fea7334
12
changelog.md
12
changelog.md
@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-01-29 - 2.13.0 - feat(socket-handler)
|
||||
Implement socket-handler mode for DNS and email services, enabling direct socket passing from SmartProxy
|
||||
|
||||
- Add `dnsDomain` configuration option that automatically sets up DNS server with DNS-over-HTTPS (DoH) support
|
||||
- Implement socket-handler mode for email services with `useSocketHandler` flag in email configuration
|
||||
- Update SmartProxy route generation to create socket-handler actions instead of port forwarding
|
||||
- Add automatic route creation for DNS paths `/dns-query` and `/resolve` when dnsDomain is configured
|
||||
- Enhance UnifiedEmailServer with `handleSocket` method for direct socket processing
|
||||
- Configure DnsServer with `manualHttpsMode: true` to prevent HTTPS port binding while enabling DoH
|
||||
- Improve performance by eliminating internal port forwarding overhead
|
||||
- Update documentation with socket-handler mode configuration and benefits
|
||||
|
||||
## 2025-05-16 - 2.12.0 - feat(smartproxy)
|
||||
Update documentation and configuration guides to adopt new route-based SmartProxy architecture
|
||||
|
||||
|
10
package.json
10
package.json
@ -18,9 +18,9 @@
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.6.4",
|
||||
"@git.zone/tsrun": "^1.3.3",
|
||||
"@git.zone/tstest": "^2.2.5",
|
||||
"@git.zone/tstest": "^2.3.1",
|
||||
"@git.zone/tswatch": "^2.0.1",
|
||||
"@types/node": "^22.15.21",
|
||||
"@types/node": "^22.15.24",
|
||||
"node-forge": "^1.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -32,13 +32,13 @@
|
||||
"@push.rocks/qenv": "^6.1.0",
|
||||
"@push.rocks/smartacme": "^8.0.0",
|
||||
"@push.rocks/smartdata": "^5.15.1",
|
||||
"@push.rocks/smartdns": "^6.2.2",
|
||||
"@push.rocks/smartdns": "^7.4.0",
|
||||
"@push.rocks/smartfile": "^11.2.5",
|
||||
"@push.rocks/smartlog": "^3.1.8",
|
||||
"@push.rocks/smartmail": "^2.1.0",
|
||||
"@push.rocks/smartpath": "^5.0.5",
|
||||
"@push.rocks/smartpromise": "^4.0.3",
|
||||
"@push.rocks/smartproxy": "^19.4.2",
|
||||
"@push.rocks/smartproxy": "^19.5.4",
|
||||
"@push.rocks/smartrequest": "^2.1.0",
|
||||
"@push.rocks/smartrule": "^2.0.1",
|
||||
"@push.rocks/smartrx": "^3.0.10",
|
||||
@ -48,7 +48,7 @@
|
||||
"@types/mailparser": "^3.4.6",
|
||||
"ip": "^2.0.1",
|
||||
"lru-cache": "^11.1.0",
|
||||
"mailauth": "^4.8.5",
|
||||
"mailauth": "^4.8.6",
|
||||
"mailparser": "^3.7.3",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
|
143
pnpm-lock.yaml
generated
143
pnpm-lock.yaml
generated
@ -33,8 +33,8 @@ importers:
|
||||
specifier: ^5.15.1
|
||||
version: 5.15.1(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4)
|
||||
'@push.rocks/smartdns':
|
||||
specifier: ^6.2.2
|
||||
version: 6.2.2
|
||||
specifier: ^7.4.0
|
||||
version: 7.4.0
|
||||
'@push.rocks/smartfile':
|
||||
specifier: ^11.2.5
|
||||
version: 11.2.5
|
||||
@ -51,8 +51,8 @@ importers:
|
||||
specifier: ^4.0.3
|
||||
version: 4.2.3
|
||||
'@push.rocks/smartproxy':
|
||||
specifier: ^19.4.2
|
||||
version: 19.4.2(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4)
|
||||
specifier: ^19.5.4
|
||||
version: 19.5.4(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4)
|
||||
'@push.rocks/smartrequest':
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0
|
||||
@ -81,8 +81,8 @@ importers:
|
||||
specifier: ^11.1.0
|
||||
version: 11.1.0
|
||||
mailauth:
|
||||
specifier: ^4.8.5
|
||||
version: 4.8.5
|
||||
specifier: ^4.8.6
|
||||
version: 4.8.6
|
||||
mailparser:
|
||||
specifier: ^3.7.3
|
||||
version: 3.7.3
|
||||
@ -97,14 +97,14 @@ importers:
|
||||
specifier: ^1.3.3
|
||||
version: 1.3.3
|
||||
'@git.zone/tstest':
|
||||
specifier: ^2.2.5
|
||||
version: 2.2.5(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4)(typescript@5.8.3)
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4)(typescript@5.8.3)
|
||||
'@git.zone/tswatch':
|
||||
specifier: ^2.0.1
|
||||
version: 2.1.0
|
||||
'@types/node':
|
||||
specifier: ^22.15.21
|
||||
version: 22.15.21
|
||||
specifier: ^22.15.24
|
||||
version: 22.15.24
|
||||
node-forge:
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1
|
||||
@ -642,8 +642,8 @@ packages:
|
||||
resolution: {integrity: sha512-DDzWunkxXLtXJTxBf4EioXLwhuqdA2VzdTmOzWrw4Z4Qnms/YM67q36yajwNohAajPYyRz5DayU0ikrceFXyVw==}
|
||||
hasBin: true
|
||||
|
||||
'@git.zone/tstest@2.2.5':
|
||||
resolution: {integrity: sha512-KLj32yIznLIFMX6U9eEumEKI7NLNYpEHeGzD/BfqF+GvfVL8eVmdmI3GR6Cdj013C9F9nQBKnpDG5eDJnxBZEA==}
|
||||
'@git.zone/tstest@2.3.1':
|
||||
resolution: {integrity: sha512-VrgVhh3xJFIuBd0nRyujrXvCMaPZokGzbGesOCLDs4Qs4cGvUkf6WVMwKT5A73fn6YPZK79iTp9OqBHdV67OPw==}
|
||||
hasBin: true
|
||||
|
||||
'@git.zone/tswatch@2.1.0':
|
||||
@ -833,6 +833,9 @@ packages:
|
||||
'@push.rocks/smartdns@6.2.2':
|
||||
resolution: {integrity: sha512-MhJcHujbyIuwIIFdnXb2OScGtRjNsliLUS8GoAurFsKtcCOaA0ytfP+PNzkukyBufjb1nMiJF3rjhswXdHakAQ==}
|
||||
|
||||
'@push.rocks/smartdns@7.4.0':
|
||||
resolution: {integrity: sha512-dedSy946AUOeT2EzgbcEC1IVeT+3wNDzw/28anQXL3juMdA05T1aP7TkSOee3uU8AUjVx2ZASz/2D/TpdBDWsw==}
|
||||
|
||||
'@push.rocks/smartenv@5.0.12':
|
||||
resolution: {integrity: sha512-tDEFwywzq0FNzRYc9qY2dRl2pgQuZG0G2/yml2RLWZWSW+Fn1EHshnKOGHz8o77W7zvu4hTgQQX42r/JY5XHTg==}
|
||||
|
||||
@ -929,8 +932,8 @@ packages:
|
||||
'@push.rocks/smartpromise@4.2.3':
|
||||
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
|
||||
|
||||
'@push.rocks/smartproxy@19.4.2':
|
||||
resolution: {integrity: sha512-x7UuD4beEa/QAFP+zYCpmB3xl2f9KNXVN+8xSsCTIflhFUfQT6jWk5PlicsddYN9i6e2O3gWcjikTHvpAw37QQ==}
|
||||
'@push.rocks/smartproxy@19.5.4':
|
||||
resolution: {integrity: sha512-iNbPQ0s2cco7X+Y++U5ebh1/G/0/HClCxCRPUxiOMj5wbRzlNCnLuW6X6lNk5DX49WhOAWd1tilQ52k+woXEXg==}
|
||||
|
||||
'@push.rocks/smartpuppeteer@2.0.5':
|
||||
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
|
||||
@ -1497,11 +1500,11 @@ packages:
|
||||
'@types/node-forge@1.3.11':
|
||||
resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==}
|
||||
|
||||
'@types/node@18.19.103':
|
||||
resolution: {integrity: sha512-hHTHp+sEz6SxFsp+SA+Tqrua3AbmlAw+Y//aEwdHrdZkYVRWdvWD3y5uPZ0flYOkgskaFWqZ/YGFm3FaFQ0pRw==}
|
||||
'@types/node@18.19.105':
|
||||
resolution: {integrity: sha512-a+DrwD2VyzqQR2W0EVF8EaCh6Em4ilQAYLEPZnMNkQHXR7ziWW7RUhZMWZAgRpkDDAdUIcJOXSPJT/zBEwz3sA==}
|
||||
|
||||
'@types/node@22.15.21':
|
||||
resolution: {integrity: sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==}
|
||||
'@types/node@22.15.24':
|
||||
resolution: {integrity: sha512-w9CZGm9RDjzTh/D+hFwlBJ3ziUaVw7oufKA3vOFSOZlzmW9AkZnfjPb+DLnrV6qtgL/LNmP0/2zBNCFHL3F0ng==}
|
||||
|
||||
'@types/ping@0.4.4':
|
||||
resolution: {integrity: sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==}
|
||||
@ -2866,8 +2869,8 @@ packages:
|
||||
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
mailauth@4.8.5:
|
||||
resolution: {integrity: sha512-LhhU6jYSaVednvrkFZm6pAhGfxPe3UNHt3TRDMCfRTREbkWz9skUYuX1dCp1hH7bTjca8OBcEIJVtJZlDheuKw==}
|
||||
mailauth@4.8.6:
|
||||
resolution: {integrity: sha512-Ler6XMLCrXyCf3kmNOMA/1aUJN6let/w9HBtjl+2KzXOUxKIl4WPJM1FwqC4IHdVJO8kmHUrvyFIKIiEGj6mvg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
@ -3953,8 +3956,8 @@ packages:
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
undici@7.9.0:
|
||||
resolution: {integrity: sha512-e696y354tf5cFZPXsF26Yg+5M63+5H3oE6Vtkh2oqbvsE2Oe7s2nIbcQh5lmG7Lp/eS29vJtTpw9+p6PX0qNSg==}
|
||||
undici@7.10.0:
|
||||
resolution: {integrity: sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
unified@11.0.5:
|
||||
@ -4154,8 +4157,8 @@ packages:
|
||||
resolution: {integrity: sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==}
|
||||
engines: {node: '>= 4.0.0'}
|
||||
|
||||
zod@3.25.28:
|
||||
resolution: {integrity: sha512-/nt/67WYKnr5by3YS7LroZJbtcCBurDKKPBPWWzaxvVCGuG/NOsiKkrjoOhI8mJ+SQUXEbUzeB3S+6XDUEEj7Q==}
|
||||
zod@3.25.32:
|
||||
resolution: {integrity: sha512-OSm2xTIRfW8CV5/QKgngwmQW/8aPfGdaQFlrGoErlgg/Epm7cjb6K6VEyExfe65a3VybUOnu381edLb0dfJl0g==}
|
||||
|
||||
zwitch@2.0.4:
|
||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||
@ -5067,7 +5070,7 @@ snapshots:
|
||||
'@push.rocks/smartshell': 3.2.3
|
||||
tsx: 4.19.4
|
||||
|
||||
'@git.zone/tstest@2.2.5(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4)(typescript@5.8.3)':
|
||||
'@git.zone/tstest@2.3.1(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4)(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@api.global/typedserver': 3.0.74
|
||||
'@git.zone/tsbundle': 2.2.5
|
||||
@ -5128,8 +5131,10 @@ snapshots:
|
||||
'@push.rocks/taskbuffer': 3.1.7
|
||||
transitivePeerDependencies:
|
||||
- '@nuxt/kit'
|
||||
- bufferutil
|
||||
- react
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
- vue
|
||||
|
||||
'@hapi/hoek@9.3.0': {}
|
||||
@ -5425,6 +5430,7 @@ snapshots:
|
||||
- '@aws-sdk/credential-providers'
|
||||
- '@mongodb-js/zstd'
|
||||
- '@nuxt/kit'
|
||||
- aws-crt
|
||||
- encoding
|
||||
- gcp-metadata
|
||||
- kerberos
|
||||
@ -5565,6 +5571,22 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@push.rocks/smartdns@7.4.0':
|
||||
dependencies:
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartenv': 5.0.12
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrequest': 2.1.0
|
||||
'@tsclass/tsclass': 9.2.0
|
||||
'@types/dns-packet': 5.6.5
|
||||
'@types/elliptic': 6.4.18
|
||||
acme-client: 5.4.0
|
||||
dns-packet: 5.6.1
|
||||
elliptic: 6.6.1
|
||||
minimatch: 10.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@push.rocks/smartenv@5.0.12':
|
||||
dependencies:
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
@ -5826,7 +5848,7 @@ snapshots:
|
||||
|
||||
'@push.rocks/smartpromise@4.2.3': {}
|
||||
|
||||
'@push.rocks/smartproxy@19.4.2(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4)':
|
||||
'@push.rocks/smartproxy@19.5.4(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4)':
|
||||
dependencies:
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartacme': 8.0.0(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4)
|
||||
@ -5849,6 +5871,7 @@ snapshots:
|
||||
- '@aws-sdk/credential-providers'
|
||||
- '@mongodb-js/zstd'
|
||||
- '@nuxt/kit'
|
||||
- aws-crt
|
||||
- bufferutil
|
||||
- encoding
|
||||
- gcp-metadata
|
||||
@ -6628,27 +6651,27 @@ snapshots:
|
||||
|
||||
'@types/bn.js@5.1.6':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/body-parser@1.19.5':
|
||||
dependencies:
|
||||
'@types/connect': 3.4.38
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/buffer-json@2.0.3': {}
|
||||
|
||||
'@types/clean-css@4.2.11':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
source-map: 0.6.1
|
||||
|
||||
'@types/connect@3.4.38':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/cors@2.8.18':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/debug@4.1.12':
|
||||
dependencies:
|
||||
@ -6660,7 +6683,7 @@ snapshots:
|
||||
|
||||
'@types/dns-packet@5.6.5':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/elliptic@6.4.18':
|
||||
dependencies:
|
||||
@ -6668,7 +6691,7 @@ snapshots:
|
||||
|
||||
'@types/express-serve-static-core@5.0.6':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
'@types/qs': 6.14.0
|
||||
'@types/range-parser': 1.2.7
|
||||
'@types/send': 0.17.4
|
||||
@ -6685,30 +6708,30 @@ snapshots:
|
||||
|
||||
'@types/from2@2.3.5':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/fs-extra@11.0.4':
|
||||
dependencies:
|
||||
'@types/jsonfile': 6.1.4
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/fs-extra@9.0.13':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/glob@7.2.0':
|
||||
dependencies:
|
||||
'@types/minimatch': 5.1.2
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/glob@8.1.0':
|
||||
dependencies:
|
||||
'@types/minimatch': 5.1.2
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/gunzip-maybe@1.4.2':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/hast@3.0.4':
|
||||
dependencies:
|
||||
@ -6730,11 +6753,11 @@ snapshots:
|
||||
|
||||
'@types/jsonfile@6.1.4':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/mailparser@3.4.6':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
iconv-lite: 0.6.3
|
||||
|
||||
'@types/mdast@4.0.4':
|
||||
@ -6753,18 +6776,18 @@ snapshots:
|
||||
|
||||
'@types/node-fetch@2.6.12':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
form-data: 4.0.2
|
||||
|
||||
'@types/node-forge@1.3.11':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/node@18.19.103':
|
||||
'@types/node@18.19.105':
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
'@types/node@22.15.21':
|
||||
'@types/node@22.15.24':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
@ -6780,30 +6803,30 @@ snapshots:
|
||||
|
||||
'@types/s3rver@3.7.4':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/semver@7.7.0': {}
|
||||
|
||||
'@types/send@0.17.4':
|
||||
dependencies:
|
||||
'@types/mime': 1.3.5
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/serve-static@1.15.7':
|
||||
dependencies:
|
||||
'@types/http-errors': 2.0.4
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
'@types/send': 0.17.4
|
||||
|
||||
'@types/symbol-tree@3.2.5': {}
|
||||
|
||||
'@types/tar-stream@2.2.3':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/through2@2.0.41':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/triple-beam@1.3.5': {}
|
||||
|
||||
@ -6827,18 +6850,18 @@ snapshots:
|
||||
|
||||
'@types/whatwg-url@8.2.2':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
'@types/webidl-conversions': 7.0.3
|
||||
|
||||
'@types/which@3.0.4': {}
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
|
||||
'@types/yauzl@2.10.3':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
optional: true
|
||||
|
||||
'@ungap/structured-clone@1.3.0': {}
|
||||
@ -7094,7 +7117,7 @@ snapshots:
|
||||
dependencies:
|
||||
devtools-protocol: 0.0.1439962
|
||||
mitt: 3.0.1
|
||||
zod: 3.25.28
|
||||
zod: 3.25.32
|
||||
|
||||
clean-css@4.2.4:
|
||||
dependencies:
|
||||
@ -7118,7 +7141,7 @@ snapshots:
|
||||
|
||||
cloudflare@4.2.0:
|
||||
dependencies:
|
||||
'@types/node': 18.19.103
|
||||
'@types/node': 18.19.105
|
||||
'@types/node-fetch': 2.6.12
|
||||
abort-controller: 3.0.0
|
||||
agentkeepalive: 4.6.0
|
||||
@ -7389,7 +7412,7 @@ snapshots:
|
||||
engine.io@6.6.4:
|
||||
dependencies:
|
||||
'@types/cors': 2.8.18
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 22.15.24
|
||||
accepts: 1.3.8
|
||||
base64id: 2.0.0
|
||||
cookie: 0.7.2
|
||||
@ -8282,7 +8305,7 @@ snapshots:
|
||||
|
||||
lru-cache@7.18.3: {}
|
||||
|
||||
mailauth@4.8.5:
|
||||
mailauth@4.8.6:
|
||||
dependencies:
|
||||
'@postalsys/vmc': 1.1.2
|
||||
fast-xml-parser: 4.5.2
|
||||
@ -8292,7 +8315,7 @@ snapshots:
|
||||
nodemailer: 7.0.3
|
||||
punycode.js: 2.3.1
|
||||
tldts: 7.0.7
|
||||
undici: 7.9.0
|
||||
undici: 7.10.0
|
||||
yargs: 17.7.2
|
||||
|
||||
mailparser@3.7.3:
|
||||
@ -9653,7 +9676,7 @@ snapshots:
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
undici@7.9.0: {}
|
||||
undici@7.10.0: {}
|
||||
|
||||
unified@11.0.5:
|
||||
dependencies:
|
||||
@ -9844,6 +9867,6 @@ snapshots:
|
||||
|
||||
ylru@1.4.0: {}
|
||||
|
||||
zod@3.25.28: {}
|
||||
zod@3.25.32: {}
|
||||
|
||||
zwitch@2.0.4: {}
|
||||
|
69
readme.md
69
readme.md
@ -212,6 +212,7 @@ interface IDcRouterOptions {
|
||||
tls?: ITlsConfig;
|
||||
maxMessageSize?: number;
|
||||
rateLimits?: IRateLimitConfig;
|
||||
useSocketHandler?: boolean; // Enable socket-handler mode (no port binding)
|
||||
};
|
||||
|
||||
// DNS server configuration
|
||||
@ -221,6 +222,9 @@ interface IDcRouterOptions {
|
||||
records?: IDnsRecord[];
|
||||
};
|
||||
|
||||
// DNS domain for automatic DNS-over-HTTPS setup
|
||||
dnsDomain?: string; // e.g., 'dns.example.com'
|
||||
|
||||
// TLS and certificate configuration
|
||||
tls?: {
|
||||
contactEmail: string;
|
||||
@ -262,6 +266,71 @@ interface IRouteConfig {
|
||||
}
|
||||
```
|
||||
|
||||
## Socket-Handler Mode
|
||||
|
||||
DcRouter supports an advanced socket-handler mode that eliminates internal port binding for both DNS and email services. Instead of services listening on internal ports, SmartProxy passes sockets directly to the services.
|
||||
|
||||
### DNS Socket-Handler
|
||||
|
||||
When `dnsDomain` is configured, DcRouter automatically:
|
||||
- Sets up DNS server for UDP on port 53
|
||||
- Creates SmartProxy routes for DNS-over-HTTPS (DoH) on the specified domain
|
||||
- Uses socket-handler for HTTPS/DoH traffic (no HTTPS port binding)
|
||||
|
||||
```typescript
|
||||
const router = new DcRouter({
|
||||
dnsDomain: 'dns.example.com', // Enables DNS with DoH
|
||||
smartProxyConfig: {
|
||||
// DNS routes are automatically created
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
This creates:
|
||||
- UDP DNS service on port 53 (standard DNS queries)
|
||||
- HTTPS routes for `dns.example.com/dns-query` and `dns.example.com/resolve`
|
||||
- Automatic TLS certificates via Let's Encrypt
|
||||
|
||||
### Email Socket-Handler
|
||||
|
||||
When `useSocketHandler` is enabled in email config:
|
||||
- Email server doesn't bind to any ports
|
||||
- SmartProxy passes sockets directly to email handlers
|
||||
- Reduces latency and resource usage
|
||||
|
||||
```typescript
|
||||
const router = new DcRouter({
|
||||
emailConfig: {
|
||||
ports: [25, 587, 465],
|
||||
hostname: 'mail.example.com',
|
||||
useSocketHandler: true, // Enable socket-handler mode
|
||||
routes: [/* email routes */]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Benefits of Socket-Handler Mode
|
||||
|
||||
1. **Performance**: Eliminates internal port forwarding overhead
|
||||
2. **Security**: No exposed internal ports
|
||||
3. **Resource Efficiency**: Fewer open ports and listeners
|
||||
4. **Simplified Networking**: Direct socket passing
|
||||
5. **Automatic Configuration**: Routes created automatically
|
||||
|
||||
### Traditional vs Socket-Handler Mode
|
||||
|
||||
**Traditional Mode (default):**
|
||||
```
|
||||
External Port → SmartProxy → Internal Port → Service
|
||||
25 → 10025 → Email
|
||||
```
|
||||
|
||||
**Socket-Handler Mode:**
|
||||
```
|
||||
External Port → SmartProxy → Socket Handler → Service
|
||||
25 → (direct socket) → Email
|
||||
```
|
||||
|
||||
## Email System
|
||||
|
||||
### Email Route Actions
|
||||
|
529
readme.plan.md
529
readme.plan.md
@ -1,298 +1,293 @@
|
||||
# Simplified Email Routing Plan
|
||||
# DcRouter Socket-Handler Integration Plan
|
||||
|
||||
## Core Principle
|
||||
Following SmartProxy's elegant pattern: **One array of routes with match/action pairs**. No complex nested configurations, no scattered features - just simple, powerful routing.
|
||||
First line: Remember to reread CLAUDE.md file for guidelines.
|
||||
|
||||
## Architecture Overview
|
||||
## Overview
|
||||
Integrate socket-handler support for both DNS and Mail services in DcRouter, allowing smartproxy to pass sockets directly instead of servers listening on ports.
|
||||
|
||||
### Single Router Class
|
||||
- **EmailRouter** - The ONLY routing class
|
||||
- Handles ALL routing logic: domains, IPs, content, relay, etc.
|
||||
- No separate RelayManager, no RecipientExpander - all built-in
|
||||
## Core Logic
|
||||
- **DNS**: If dnsDomain is set, DnsServer is instantiated with socket-handler for HTTPS/DoH
|
||||
- **Mail**: If useSocketHandler is enabled in emailConfig, mail servers accept sockets from smartproxy
|
||||
- **Automatic Routing**: Setting these options automatically configures smartproxy routes
|
||||
|
||||
### Core Interfaces (Just 3!)
|
||||
## Architecture
|
||||
|
||||
### DNS Architecture
|
||||
- **HTTPS/DoH Traffic**: smartproxy → socket-handler → smartdns (no port listening)
|
||||
- **UDP Traffic**: Direct to smartdns DnsServer on VM IP:53 (bypasses smartproxy)
|
||||
- **Automatic Setup**: Setting dnsDomain triggers all DNS infrastructure
|
||||
|
||||
### Mail Architecture
|
||||
- **Socket-Handler Mode**: smartproxy → socket-handler → mail server (no port listening)
|
||||
- **Traditional Mode**: smartproxy → forward → mail server (listening on ports)
|
||||
- **Configurable**: useSocketHandler flag determines the mode
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### 1. Analyze Current Architecture
|
||||
- [x] Examine how DcRouter currently integrates services
|
||||
- [x] Study email server socket handling in SmtpServer
|
||||
- [x] Check existing smartproxy socket-handler patterns
|
||||
- [x] Review UnifiedEmailServer connection handling
|
||||
|
||||
### 2. DNS Socket-Handler Implementation
|
||||
|
||||
#### 2.1 Create DNS Socket Handler
|
||||
- [x] Create a custom socket handler that processes DNS-over-HTTPS requests
|
||||
- [x] Handler should use smartdns without port binding
|
||||
- [x] Use built-in handleHttpsSocket method from smartdns
|
||||
- [x] Set manualHttpsMode: true in DnsServer options
|
||||
|
||||
#### 2.2 Configure smartdns DnsServer
|
||||
- [x] Create DnsServer instance with UDP only on VM IP interface
|
||||
- [x] Set manualHttpsMode: true to disable HTTPS port binding
|
||||
- [x] Configure to listen on port 53 for UDP traffic only
|
||||
- [x] Use handleHttpsSocket method for DoH sockets
|
||||
|
||||
### 3. Mail Socket-Handler Implementation
|
||||
|
||||
#### 3.1 Modify SmtpServer
|
||||
- [x] ConnectionManager already has handleConnection method
|
||||
- [x] Connection handling already works with provided sockets
|
||||
- [x] Session management works with socket-handler mode
|
||||
- [x] Backward compatibility maintained
|
||||
|
||||
#### 3.2 Update UnifiedEmailServer
|
||||
- [x] Add useSocketHandler flag to IUnifiedEmailServerOptions
|
||||
- [x] Modify start() to skip server creation when useSocketHandler is true
|
||||
- [x] Add handleSocket method to accept sockets from smartproxy
|
||||
- [x] All email processing works in socket-handler mode
|
||||
|
||||
#### 3.3 Create Mail Socket Handler
|
||||
- [x] Create socket handler for SMTP/submission/SMTPS
|
||||
- [x] Handle TLS for port 465 (immediate TLS wrapping)
|
||||
- [x] Pass sockets to UnifiedEmailServer.handleSocket
|
||||
|
||||
### 4. DcRouter Integration
|
||||
|
||||
#### 4.1 Configuration Updates
|
||||
- [x] Add `dnsDomain` property to DcRouter config
|
||||
- [x] Add `useSocketHandler` to email config options
|
||||
- [x] Update interface definitions
|
||||
|
||||
#### 4.2 Route Generation Updates
|
||||
- [x] Modify generateEmailRoutes to create socket-handler actions when enabled
|
||||
- [x] Create DNS routes with socket-handler actions
|
||||
- [x] Ensure proper domain and path matching
|
||||
|
||||
#### 4.3 Service Lifecycle
|
||||
- [x] Update setupSmartProxy to handle socket-handler routes
|
||||
- [x] Services start correctly without port binding
|
||||
- [x] Socket cleanup handled by connection managers
|
||||
|
||||
### 5. SmartProxy Route Configuration
|
||||
- [x] DNS routes: match dnsDomain with paths /dns-query and /resolve
|
||||
- [x] Mail routes: match ports 25, 587, 465 with socket-handler actions
|
||||
- [x] Configure TLS handling for each protocol appropriately
|
||||
- [x] Automatic Let's Encrypt via smartproxy certificate: 'auto'
|
||||
|
||||
### 6. Testing (To be done)
|
||||
|
||||
#### 6.1 DNS Testing
|
||||
- [ ] Test that DNS server is NOT instantiated when dnsDomain is not set
|
||||
- [ ] Test that DNS server IS instantiated when dnsDomain is set
|
||||
- [ ] Test UDP DNS queries on port 53
|
||||
- [ ] Test DoH queries through smartproxy socket-handler
|
||||
- [ ] Verify HTTP/2 support for DoH
|
||||
|
||||
#### 6.2 Mail Testing
|
||||
- [ ] Test traditional port-based mail delivery
|
||||
- [ ] Test socket-handler mail delivery
|
||||
- [ ] Verify STARTTLS works in socket-handler mode
|
||||
- [ ] Test SMTPS (port 465) with socket-handler
|
||||
- [ ] Ensure connection pooling works correctly
|
||||
|
||||
#### 6.3 Integration Testing
|
||||
- [ ] Test both DNS and Mail with socket-handlers simultaneously
|
||||
- [ ] Verify no port conflicts
|
||||
- [ ] Test automatic startup/shutdown
|
||||
- [ ] Load test socket-handler performance
|
||||
|
||||
## Technical Details
|
||||
|
||||
### DcRouter Configuration
|
||||
```typescript
|
||||
interface IEmailRoute {
|
||||
name: string; // Route identifier
|
||||
priority?: number; // Order of evaluation (default: 0)
|
||||
match: IEmailMatch; // What to match
|
||||
action: IEmailAction; // What to do
|
||||
// DcRouter config with socket-handler support
|
||||
const dcRouterConfig = {
|
||||
// ... other config
|
||||
dnsDomain: 'example.com', // Optional - if set, DNS server is enabled
|
||||
emailConfig: {
|
||||
ports: [25, 587, 465],
|
||||
domains: ['mail.example.com'],
|
||||
useSocketHandler: true, // Enable socket-handler mode for mail
|
||||
routes: [/* email routing rules */]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
interface IEmailMatch {
|
||||
// Simple pattern matching
|
||||
recipients?: string | string[]; // "*@example.com", "admin@*"
|
||||
senders?: string | string[];
|
||||
clientIp?: string | string[]; // IPs or CIDR ranges
|
||||
authenticated?: boolean;
|
||||
|
||||
// Optional advanced matching
|
||||
headers?: Record<string, string | RegExp>;
|
||||
sizeRange?: { min?: number; max?: number };
|
||||
}
|
||||
### DNS Implementation
|
||||
|
||||
interface IEmailAction {
|
||||
type: 'forward' | 'deliver' | 'reject' | 'process';
|
||||
#### Conditional DNS Instantiation
|
||||
```typescript
|
||||
// In DcRouter startup logic
|
||||
if (config.dnsDomain) {
|
||||
// Create DNS server instance
|
||||
this.dnsServer = new DnsServer({
|
||||
udpPort: 53,
|
||||
udpBindAddress: vmIpAddress,
|
||||
httpsPort: undefined, // No HTTPS listening
|
||||
dnssecZone: config.dnsDomain
|
||||
});
|
||||
|
||||
// Action-specific options
|
||||
forward?: { host: string; port?: number; };
|
||||
reject?: { code: number; message: string; };
|
||||
process?: {
|
||||
scan?: boolean;
|
||||
dkim?: boolean;
|
||||
queue?: 'normal' | 'priority';
|
||||
// Create smartproxy route for DoH
|
||||
const dnsRoute = {
|
||||
match: {
|
||||
domain: config.dnsDomain,
|
||||
path: ['/dns-query', '/resolve']
|
||||
},
|
||||
action: {
|
||||
type: 'socket-handler',
|
||||
handler: this.createDnsSocketHandler()
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Core Functionality (1-2 weeks)
|
||||
|
||||
1. **Create new EmailRouter**
|
||||
- Fresh implementation with route-based design
|
||||
- Implement `evaluateRoutes(context)` method
|
||||
- Clean, modern API only
|
||||
|
||||
2. **Update UnifiedEmailServer**
|
||||
- Replace domainRules with routes array
|
||||
- Simplify `processEmailByMode()` to use actions
|
||||
- Implement forward mode (currently missing)
|
||||
|
||||
3. **Essential Features Only**
|
||||
- Forward emails (using existing SmtpClient)
|
||||
- Process emails (existing flow)
|
||||
- Reject emails (simple SMTP response)
|
||||
- IP-based relay (just another match condition)
|
||||
|
||||
### Phase 2: Progressive Enhancement (2-3 weeks)
|
||||
|
||||
Only add features that are actually needed:
|
||||
- Multi-target forwarding (array of hosts)
|
||||
- Basic transformations (headers only)
|
||||
- Rate limiting per route
|
||||
|
||||
## Configuration Example
|
||||
|
||||
#### DNS Socket Handler
|
||||
```typescript
|
||||
const emailServer = new UnifiedEmailServer({
|
||||
ports: [25, 587, 465],
|
||||
hostname: 'mail.example.com',
|
||||
const createDnsSocketHandler = () => {
|
||||
return async (socket: net.Socket) => {
|
||||
// Handle HTTP/2 for DoH
|
||||
const session = http2.createSession(socket);
|
||||
|
||||
session.on('stream', async (stream, headers) => {
|
||||
if (headers[':path'] === '/dns-query') {
|
||||
const dnsQuery = await parseDnsQuery(stream);
|
||||
const response = await dnsServer.resolveQuery(dnsQuery);
|
||||
stream.respond({ ':status': 200, 'content-type': 'application/dns-message' });
|
||||
stream.end(response);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Mail Implementation
|
||||
|
||||
#### Email Route Generation with Socket-Handler
|
||||
```typescript
|
||||
private generateEmailRoutes(emailConfig: IUnifiedEmailServerOptions): IRouteConfig[] {
|
||||
const routes: IRouteConfig[] = [];
|
||||
|
||||
// ALL routing in one simple array
|
||||
routes: [
|
||||
// Relay from office
|
||||
{
|
||||
name: 'office-relay',
|
||||
priority: 100,
|
||||
match: { clientIp: '192.168.0.0/16' },
|
||||
action: { type: 'forward', forward: { host: 'internal.mail' } }
|
||||
},
|
||||
for (const port of emailConfig.ports) {
|
||||
const action = emailConfig.useSocketHandler
|
||||
? {
|
||||
type: 'socket-handler',
|
||||
handler: this.createMailSocketHandler(port)
|
||||
}
|
||||
: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: mapPort(port) }
|
||||
};
|
||||
|
||||
// Process local mail
|
||||
{
|
||||
name: 'local-mail',
|
||||
priority: 50,
|
||||
match: { recipients: '*@mycompany.com' },
|
||||
action: {
|
||||
type: 'process',
|
||||
process: { scan: true, dkim: true, queue: 'normal' }
|
||||
}
|
||||
},
|
||||
|
||||
// Reject everything else
|
||||
{
|
||||
name: 'default',
|
||||
match: { recipients: '*' },
|
||||
action: {
|
||||
type: 'reject',
|
||||
reject: { code: 550, message: 'Relay denied' }
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
routes.push({
|
||||
match: { ports: [port] },
|
||||
action
|
||||
});
|
||||
}
|
||||
|
||||
return routes;
|
||||
}
|
||||
```
|
||||
|
||||
## Key Simplifications
|
||||
|
||||
### What We're NOT Building
|
||||
- ❌ Separate RelayManager class - relay is just a route match
|
||||
- ❌ RecipientExpander class - alias expansion is a delivery concern
|
||||
- ❌ ContentAnalyzer with ML - overkill for email routing
|
||||
- ❌ 20+ new classes - keep it simple
|
||||
- ❌ Complex analytics - use existing logging
|
||||
- ❌ API-driven routing - config file is enough
|
||||
- ❌ Compliance routing - not a router concern
|
||||
|
||||
### What We ARE Building
|
||||
- ✅ One router class that does routing well
|
||||
- ✅ Simple match/action pattern
|
||||
- ✅ IP-based relay as a native feature
|
||||
- ✅ Email forwarding (currently missing)
|
||||
- ✅ Clean, modern implementation
|
||||
|
||||
## Clean Implementation
|
||||
|
||||
#### Mail Socket Handler
|
||||
```typescript
|
||||
// Simple, single way to configure:
|
||||
const router = new EmailRouter(routes);
|
||||
|
||||
// No legacy formats, no auto-detection
|
||||
// Just clean route-based configuration
|
||||
const createMailSocketHandler = (port: number) => {
|
||||
return async (socket: net.Socket) => {
|
||||
// Determine protocol based on port
|
||||
const isSecure = port === 465;
|
||||
|
||||
if (isSecure) {
|
||||
// For SMTPS, handle TLS immediately
|
||||
const tlsSocket = new tls.TLSSocket(socket, {
|
||||
isServer: true,
|
||||
cert: tlsCert,
|
||||
key: tlsKey
|
||||
});
|
||||
await this.emailServer.handleSocket(tlsSocket, port);
|
||||
} else {
|
||||
// For SMTP/Submission, pass raw socket
|
||||
await this.emailServer.handleSocket(socket, port);
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Benefits
|
||||
#### UnifiedEmailServer Socket Handling
|
||||
```typescript
|
||||
// In UnifiedEmailServer class
|
||||
public async handleSocket(socket: net.Socket, port: number): Promise<void> {
|
||||
// Create session for this socket
|
||||
const session = this.sessionManager.createSession(socket);
|
||||
|
||||
// Handle connection based on port
|
||||
switch (port) {
|
||||
case 25: // SMTP
|
||||
case 587: // Submission (STARTTLS)
|
||||
await this.connectionManager.handleConnection(socket, session);
|
||||
break;
|
||||
case 465: // SMTPS (already TLS)
|
||||
await this.connectionManager.handleSecureConnection(socket, session);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
1. **Simplicity** - One router, one config array, clear patterns
|
||||
2. **Flexibility** - Any match criteria, any action
|
||||
3. **Performance** - Simple priority-based evaluation
|
||||
4. **Maintainability** - Less code, fewer classes, clearer intent
|
||||
5. **Modern** - Clean slate implementation
|
||||
## Dependencies
|
||||
- @push.rocks/smartproxy (already integrated)
|
||||
- @push.rocks/smartdns (to be added)
|
||||
|
||||
## Detailed Implementation Steps
|
||||
## Implementation Phases
|
||||
|
||||
### Step 1: Create Core Interfaces (30 minutes)
|
||||
- [x] Create `ts/mail/routing/interfaces.ts`
|
||||
- [x] Add `IEmailRoute` interface
|
||||
- [x] Add `IEmailMatch` interface
|
||||
- [x] Add `IEmailAction` interface
|
||||
- [x] Add `IEmailContext` interface: `{ email: Email; session: IExtendedSmtpSession }`
|
||||
- [x] Export all interfaces
|
||||
### Phase 1: DNS Socket-Handler (Priority)
|
||||
1. Add smartdns dependency
|
||||
2. Implement DNS socket-handler
|
||||
3. Add dnsDomain configuration
|
||||
4. Test DoH functionality
|
||||
|
||||
### Step 2: Create EmailRouter Class (2 hours)
|
||||
- [x] Create `ts/mail/routing/classes.email.router.ts`
|
||||
- [x] Import necessary dependencies
|
||||
- [x] Define class with routes property
|
||||
- [x] Implement constructor: `constructor(routes: IEmailRoute[])`
|
||||
- [x] Add `sortRoutesByPriority()` method
|
||||
- [x] Call sort in constructor
|
||||
### Phase 2: Mail Socket-Handler
|
||||
1. Refactor SmtpServer for socket handling
|
||||
2. Update UnifiedEmailServer
|
||||
3. Implement mail socket-handlers
|
||||
4. Add useSocketHandler configuration
|
||||
5. Test all mail protocols
|
||||
|
||||
### Step 3: Implement Route Matching (3 hours)
|
||||
- [x] Add `evaluateRoutes(context: IEmailContext): Promise<IEmailRoute | null>` method
|
||||
- [x] Add `matchesRoute(route: IEmailRoute, context: IEmailContext): boolean` method
|
||||
- [x] Implement `matchesRecipients()` with glob support
|
||||
- [x] Implement `matchesSenders()` with glob support
|
||||
- [x] Implement `matchesClientIp()` with CIDR support
|
||||
- [x] Implement `matchesAuthenticated()` check
|
||||
- [x] Add pattern cache for performance
|
||||
### Phase 3: Integration & Testing
|
||||
1. Full integration testing
|
||||
2. Performance benchmarking
|
||||
3. Documentation updates
|
||||
4. Migration guide for existing users
|
||||
|
||||
### Step 4: Update UnifiedEmailServer Configuration (2 hours)
|
||||
- [x] Update `IUnifiedEmailServerOptions` interface
|
||||
- [x] Replace `domainRules` with `routes: IEmailRoute[]`
|
||||
- [x] Remove `defaultMode`, `defaultServer`, etc.
|
||||
- [x] Update constructor to create EmailRouter with routes
|
||||
- [x] Replace DomainRouter usage with EmailRouter
|
||||
## Notes
|
||||
|
||||
### Step 5: Implement Action Execution (4 hours)
|
||||
- [x] Add `executeAction(action: IEmailAction, email: Email, context: IEmailContext): Promise<void>` to UnifiedEmailServer
|
||||
- [x] Implement 'forward' action:
|
||||
- [x] Get SMTP client via `getSmtpClient()`
|
||||
- [x] Add forwarding headers (X-Forwarded-For, etc.)
|
||||
- [x] Send email using pooled client
|
||||
- [x] Handle errors with bounce manager
|
||||
- [x] Implement 'process' action:
|
||||
- [x] Use existing `handleProcessMode()` logic
|
||||
- [x] Apply scan/dkim options from action
|
||||
- [x] Implement 'deliver' action:
|
||||
- [x] Queue for local delivery
|
||||
- [x] Implement 'reject' action:
|
||||
- [x] Return SMTP rejection with code/message
|
||||
- [x] **IMPROVEMENT**: Moved DKIM signing to delivery system (right before sending) to ensure signature validity
|
||||
### DNS Notes
|
||||
- UDP traffic bypasses proxy entirely (kernel-level routing)
|
||||
- DoH provides encrypted DNS over HTTPS through proxy
|
||||
- Socket handler allows DNS processing without port binding
|
||||
- DNS functionality is entirely optional - only enabled when dnsDomain is configured
|
||||
- Setting dnsDomain triggers automatic setup of all DNS infrastructure
|
||||
- Automatic Let's Encrypt certificate provisioning for configured dnsDomain
|
||||
|
||||
### Step 6: Refactor processEmailByMode (2 hours)
|
||||
- [x] Update `processEmailByMode()` to use EmailRouter
|
||||
- [x] Call `evaluateRoutes()` for each recipient
|
||||
- [x] Call `executeAction()` based on matched route
|
||||
- [x] Handle no-match case (default reject)
|
||||
- [x] Remove old mode-based logic
|
||||
### Mail Notes
|
||||
- Socket-handler mode eliminates internal port binding for mail services
|
||||
- Traditional port forwarding mode remains available for compatibility
|
||||
- STARTTLS negotiation handled within socket-handler for ports 25/587
|
||||
- Port 465 (SMTPS) requires immediate TLS handshake in socket-handler
|
||||
- Connection pooling and session management work in both modes
|
||||
- Socket-handler reduces latency by eliminating internal forwarding
|
||||
|
||||
### Step 7: Testing (4 hours)
|
||||
- [x] Create `test/test.email.router.ts`
|
||||
- [x] Test route priority sorting
|
||||
- [x] Test recipient matching (exact, glob, multiple)
|
||||
- [x] Test IP matching (single, CIDR, arrays)
|
||||
- [x] Test authentication matching
|
||||
- [x] Test action execution (basic)
|
||||
- [x] Test no-match scenarios
|
||||
|
||||
### Step 8: Integration Testing (2 hours)
|
||||
- [x] Create comprehensive integration test suite (`test/test.email.integration.ts`)
|
||||
- [x] Test relay scenario (IP-based forward) with priority routing
|
||||
- [x] Test CIDR IP matching with multiple ranges
|
||||
- [x] Test authentication-based routing scenarios
|
||||
- [x] Test pattern caching performance
|
||||
- [x] Test dynamic route updates
|
||||
- [x] Verify all match/action patterns work end-to-end
|
||||
|
||||
### Step 9: Update Examples and Docs (1 hour)
|
||||
- [x] Update configuration examples in readme
|
||||
- [x] Add route examples for common scenarios
|
||||
- [x] Document glob pattern syntax
|
||||
- [x] Document CIDR notation for IPs
|
||||
- [x] Add troubleshooting section
|
||||
|
||||
### Step 10: Cleanup (1 hour)
|
||||
- [x] Remove DomainRouter imports
|
||||
- [x] Remove old interfaces (IDomainRule, etc.)
|
||||
- [x] Remove legacy configuration code
|
||||
- [x] Update any remaining references
|
||||
- [x] Run full test suite
|
||||
- [x] Commit changes
|
||||
|
||||
## Total Time Estimate
|
||||
- **Day 1**: Steps 1-3 (5.5 hours)
|
||||
- **Day 2**: Steps 4-6 (8 hours)
|
||||
- **Day 3**: Steps 7-8 (6 hours)
|
||||
- **Day 4**: Steps 9-10 (2 hours)
|
||||
|
||||
**Total**: ~21 hours of focused work (2-3 days)
|
||||
|
||||
## Implementation Status
|
||||
|
||||
### ✅ Completed (January 2025)
|
||||
- Created routing interfaces and EmailRouter class
|
||||
- Implemented comprehensive route matching (recipients, senders, IPs, authentication)
|
||||
- Updated UnifiedEmailServer to use new routing system
|
||||
- Implemented all four action types (forward, process, deliver, reject)
|
||||
- Moved DKIM signing to delivery system for proper signature validity
|
||||
- Fixed all compilation errors and updated dependencies
|
||||
- Created basic routing tests with full coverage
|
||||
- **NEW**: Created comprehensive integration test suite with real scenarios
|
||||
- **NEW**: Verified all match/action patterns work end-to-end
|
||||
|
||||
### Key Improvements Made
|
||||
1. **DKIM Signing**: Moved to delivery system right before sending to ensure signatures remain valid
|
||||
2. **Error Handling**: Integrated with BounceManager for proper failure handling
|
||||
3. **Connection Pooling**: Leveraged existing SmtpClient pooling for efficient forwarding
|
||||
4. **Pattern Caching**: Added caching for glob patterns to improve performance
|
||||
5. **Integration Testing**: Comprehensive tests covering IP-based relay, authentication routing, CIDR matching, and dynamic route updates
|
||||
|
||||
### Integration Test Results ✅
|
||||
- Route-based forwarding with priority: 5/5 scenarios passed
|
||||
- CIDR IP matching with multiple ranges: 4/4 IP tests passed
|
||||
- Authentication-based routing: 3/3 auth scenarios passed
|
||||
- Pattern caching performance: Working correctly
|
||||
- Dynamic route updates: Working correctly
|
||||
|
||||
### ✅ Final Completion (January 2025)
|
||||
- **DOCUMENTATION**: Updated readme.md with comprehensive route-based configuration examples
|
||||
- **EXAMPLES**: Added common routing patterns (IP relay, domain routing, auth-based, content filtering)
|
||||
- **PATTERN SYNTAX**: Documented glob patterns and CIDR notation with examples
|
||||
- **TROUBLESHOOTING**: Added routing-specific troubleshooting guide
|
||||
- **CLEANUP**: Removed all legacy DomainRouter and IDomainRule code
|
||||
- **INTERFACES**: Cleaned up interfaces to only include implemented features
|
||||
- **TESTING**: Verified build and core functionality tests pass
|
||||
- **COMMIT**: All changes committed with detailed documentation
|
||||
|
||||
**The match/action pattern implementation is COMPLETE, DOCUMENTED, and PRODUCTION-READY!** 🎉
|
||||
|
||||
### Summary of Achievement
|
||||
✅ **Simple Configuration**: One routes array with match/action pairs
|
||||
✅ **Flexible Matching**: Recipients, senders, IPs (CIDR), authentication, headers, size, subject
|
||||
✅ **Four Actions**: forward, process, deliver, reject
|
||||
✅ **Priority Routing**: Higher priority routes evaluated first
|
||||
✅ **Pattern Caching**: Performance optimization for glob matching
|
||||
✅ **IP Relay Support**: CIDR notation for network-based routing
|
||||
✅ **Complete Documentation**: Examples, patterns, troubleshooting
|
||||
✅ **Clean Implementation**: Legacy code removed, modern interfaces only
|
||||
### General Benefits
|
||||
- Reduced resource usage (no internal port binding)
|
||||
- Better performance (direct socket passing)
|
||||
- Simplified configuration (automatic route creation)
|
||||
- Enhanced security (no exposed internal ports)
|
||||
- Unified approach for all services through smartproxy
|
177
readme.plan2.md
177
readme.plan2.md
@ -1,177 +0,0 @@
|
||||
# DNS Server Configuration Fix Plan
|
||||
|
||||
First command: `cat /home/centraluser/eu.central.ingress-2/CLAUDE.md`
|
||||
|
||||
## Problem Summary
|
||||
The DNS server is logging "Dns Server starting on port undefined" because:
|
||||
1. The `smartdns.DnsServer` expects specific properties (`udpPort`, `httpsPort`, etc.) but something is trying to access `port`
|
||||
2. The `records` array we're passing isn't part of the DnsServer interface
|
||||
3. DnsServer uses a handler registration pattern, not a records array
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### 1. Fix DnsServer Interface Usage in DcRouter
|
||||
- [x] Update `dcrouter/ts/classes.dcrouter.ts` to properly handle DNS configuration
|
||||
- [x] Remove the `records` property from `IDnsServerOptions` type extension
|
||||
- [x] Create a new interface for DNS configuration that includes both DnsServer options and records
|
||||
|
||||
### 2. Implement DNS Record Handler Registration
|
||||
- [x] After creating the DnsServer instance, register handlers for each DNS record
|
||||
- [x] Create a helper method `registerDnsRecords()` that takes the records array and registers appropriate handlers
|
||||
- [x] Use the DnsServer's `registerHandler()` method for each record type
|
||||
|
||||
### 3. Find and Fix the Logging Issue
|
||||
- [x] Search for any logging that references `dnsServerConfig.port`
|
||||
- [x] Update to use `dnsServerConfig.udpPort` instead
|
||||
- [x] Check if the message is coming from within smartdns package or our code
|
||||
|
||||
### 4. Create Proper Type Definitions
|
||||
- [x] Extend the dcrouter options to properly type DNS configuration
|
||||
- [x] Create interface that combines DnsServer options with our custom records array:
|
||||
```typescript
|
||||
interface IDcRouterDnsConfig extends IDnsServerOptions {
|
||||
records?: Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
value: string;
|
||||
ttl?: number;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Update DcRouter DNS Setup Logic
|
||||
- [x] Modify `setupDnsServer()` method (or create if doesn't exist)
|
||||
- [x] Separate DnsServer instantiation from record registration
|
||||
- [x] Add proper error handling for DNS server startup
|
||||
|
||||
### 6. Test the Implementation
|
||||
- [x] Create test file to verify DNS server starts correctly
|
||||
- [x] Test that all DNS records are properly registered
|
||||
- [x] Verify the port logging shows correct port number
|
||||
|
||||
## Code Changes Required
|
||||
|
||||
### File: `dcrouter/ts/classes.dcrouter.ts`
|
||||
|
||||
1. Update the DNS server setup section (around line 117-122):
|
||||
```typescript
|
||||
// Set up DNS server if configured
|
||||
if (this.options.dnsServerConfig) {
|
||||
const { records, ...dnsServerOptions } = this.options.dnsServerConfig;
|
||||
this.dnsServer = new plugins.smartdns.DnsServer(dnsServerOptions);
|
||||
|
||||
// Register DNS record handlers if records provided
|
||||
if (records && records.length > 0) {
|
||||
this.registerDnsRecords(records);
|
||||
}
|
||||
|
||||
await this.dnsServer.start();
|
||||
console.log(`DNS server started on UDP port ${dnsServerOptions.udpPort}`);
|
||||
}
|
||||
```
|
||||
|
||||
2. Add new method for registering DNS records:
|
||||
```typescript
|
||||
private registerDnsRecords(records: Array<{name: string; type: string; value: string; ttl?: number}>) {
|
||||
if (!this.dnsServer) return;
|
||||
|
||||
// Group records by domain pattern
|
||||
const recordsByDomain = new Map<string, typeof records>();
|
||||
|
||||
for (const record of records) {
|
||||
const pattern = record.name.includes('*') ? record.name : `*.${record.name}`;
|
||||
if (!recordsByDomain.has(pattern)) {
|
||||
recordsByDomain.set(pattern, []);
|
||||
}
|
||||
recordsByDomain.get(pattern)!.push(record);
|
||||
}
|
||||
|
||||
// Register handlers for each domain pattern
|
||||
for (const [domainPattern, domainRecords] of recordsByDomain) {
|
||||
const recordTypes = [...new Set(domainRecords.map(r => r.type))];
|
||||
|
||||
this.dnsServer.registerHandler(domainPattern, recordTypes, (question) => {
|
||||
const matchingRecord = domainRecords.find(
|
||||
r => r.name === question.name && r.type === question.type
|
||||
);
|
||||
|
||||
if (matchingRecord) {
|
||||
return {
|
||||
name: matchingRecord.name,
|
||||
type: matchingRecord.type,
|
||||
class: 'IN',
|
||||
ttl: matchingRecord.ttl || 300,
|
||||
data: this.parseDnsRecordData(matchingRecord.type, matchingRecord.value)
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Add helper method to parse DNS record data:
|
||||
```typescript
|
||||
private parseDnsRecordData(type: string, value: string): any {
|
||||
switch (type) {
|
||||
case 'A':
|
||||
return value; // IP address as string
|
||||
case 'MX':
|
||||
const [priority, exchange] = value.split(' ');
|
||||
return { priority: parseInt(priority), exchange };
|
||||
case 'TXT':
|
||||
return value;
|
||||
case 'NS':
|
||||
return value;
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### File: `dcrouter/ts/config/index.ts` (if exists) or create type definition
|
||||
|
||||
Add proper type definition for DNS configuration:
|
||||
```typescript
|
||||
export interface IDcRouterDnsConfig {
|
||||
// Required DnsServer options
|
||||
udpPort: number;
|
||||
httpsPort: number;
|
||||
httpsKey: string;
|
||||
httpsCert: string;
|
||||
dnssecZone: string;
|
||||
|
||||
// Our custom records array
|
||||
records?: Array<{
|
||||
name: string;
|
||||
type: 'A' | 'AAAA' | 'MX' | 'TXT' | 'NS' | 'CNAME' | 'SOA';
|
||||
value: string;
|
||||
ttl?: number;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
1. Create test file `dcrouter/test/test.dns-server-config.ts`
|
||||
2. Test DNS server starts without "undefined" port message
|
||||
3. Test DNS records are queryable after registration
|
||||
4. Test error handling when DNS server fails to start
|
||||
|
||||
## Timeline
|
||||
- Step 1-3: Fix interface and logging (30 min)
|
||||
- Step 4-5: Implement proper record registration (45 min)
|
||||
- Step 6: Testing and verification (30 min)
|
||||
|
||||
Total estimated time: ~2 hours
|
||||
|
||||
## Success Criteria
|
||||
- [x] DNS server starts without "undefined" port message
|
||||
- [x] Shows correct port number in logs (e.g., "DNS server started on UDP port 53")
|
||||
- [x] All DNS records are properly registered and queryable
|
||||
- [x] No type errors or runtime errors
|
||||
- [x] Integration with DKIM auto-registration still works
|
||||
|
||||
## ✅ COMPLETED
|
||||
All tasks have been successfully implemented. The DNS server configuration is now properly handled in dcrouter.
|
@ -52,7 +52,10 @@ export interface IDcRouterOptions {
|
||||
};
|
||||
|
||||
/** DNS server configuration */
|
||||
dnsServerConfig?: plugins.smartdns.IDnsServerOptions;
|
||||
dnsServerConfig?: plugins.smartdns.dnsServerMod.IDnsServerOptions;
|
||||
|
||||
/** DNS domain for automatic DNS server setup with DoH */
|
||||
dnsDomain?: string;
|
||||
|
||||
/** DNS challenge configuration for ACME (optional) */
|
||||
dnsChallenge?: {
|
||||
@ -81,7 +84,7 @@ export class DcRouter {
|
||||
|
||||
// Core services
|
||||
public smartProxy?: plugins.smartproxy.SmartProxy;
|
||||
public dnsServer?: plugins.smartdns.DnsServer;
|
||||
public dnsServer?: plugins.smartdns.dnsServerMod.DnsServer;
|
||||
public emailServer?: UnifiedEmailServer;
|
||||
|
||||
|
||||
@ -114,10 +117,13 @@ export class DcRouter {
|
||||
}
|
||||
}
|
||||
|
||||
// Set up DNS server if configured
|
||||
if (this.options.dnsServerConfig) {
|
||||
// Set up DNS server if configured by dnsDomain
|
||||
if (this.options.dnsDomain) {
|
||||
await this.setupDnsWithSocketHandler();
|
||||
} else if (this.options.dnsServerConfig) {
|
||||
// Legacy DNS server setup
|
||||
const { records, ...dnsServerOptions } = this.options.dnsServerConfig as any;
|
||||
this.dnsServer = new plugins.smartdns.DnsServer(dnsServerOptions);
|
||||
this.dnsServer = new plugins.smartdns.dnsServerMod.DnsServer(dnsServerOptions);
|
||||
|
||||
// Register DNS record handlers if records provided
|
||||
if (records && records.length > 0) {
|
||||
@ -161,6 +167,13 @@ export class DcRouter {
|
||||
routes = [...routes, ...emailRoutes]; // Enable email routing through SmartProxy
|
||||
}
|
||||
|
||||
// If DNS domain is configured, add DNS routes
|
||||
if (this.options.dnsDomain) {
|
||||
const dnsRoutes = this.generateDnsRoutes();
|
||||
console.log(`DNS Routes for domain ${this.options.dnsDomain}:`, dnsRoutes);
|
||||
routes = [...routes, ...dnsRoutes];
|
||||
}
|
||||
|
||||
// Merge TLS/ACME configuration if provided at root level
|
||||
if (this.options.tls && !acmeConfig) {
|
||||
acmeConfig = {
|
||||
@ -247,21 +260,8 @@ export class DcRouter {
|
||||
private generateEmailRoutes(emailConfig: IUnifiedEmailServerOptions): plugins.smartproxy.IRouteConfig[] {
|
||||
const emailRoutes: plugins.smartproxy.IRouteConfig[] = [];
|
||||
|
||||
// Get the custom port mapping if available, otherwise use defaults
|
||||
const defaultPortMapping = {
|
||||
25: 10025, // SMTP
|
||||
587: 10587, // Submission
|
||||
465: 10465 // SMTPS
|
||||
};
|
||||
|
||||
// Use custom port mapping if provided, otherwise fall back to defaults
|
||||
const portMapping = this.options.emailPortConfig?.portMapping || defaultPortMapping;
|
||||
|
||||
// Create routes for each email port
|
||||
for (const port of emailConfig.ports) {
|
||||
// Calculate the internal port using the mapping
|
||||
const internalPort = portMapping[port] || port + 10000;
|
||||
|
||||
// Create a descriptive name for the route based on the port
|
||||
let routeName = 'email-route';
|
||||
let tlsMode = 'passthrough';
|
||||
@ -285,7 +285,6 @@ export class DcRouter {
|
||||
|
||||
default:
|
||||
routeName = `email-port-${port}-route`;
|
||||
// For unknown ports, assume passthrough by default
|
||||
tlsMode = 'passthrough';
|
||||
|
||||
// Check if we have specific settings for this port
|
||||
@ -306,13 +305,27 @@ export class DcRouter {
|
||||
break;
|
||||
}
|
||||
|
||||
// Create the route configuration
|
||||
const routeConfig: plugins.smartproxy.IRouteConfig = {
|
||||
name: routeName,
|
||||
match: {
|
||||
ports: [port]
|
||||
},
|
||||
action: {
|
||||
// Create action based on mode
|
||||
let action: any;
|
||||
|
||||
if (emailConfig.useSocketHandler) {
|
||||
// Socket-handler mode
|
||||
action = {
|
||||
type: 'socket-handler' as any,
|
||||
socketHandler: this.createMailSocketHandler(port)
|
||||
};
|
||||
} else {
|
||||
// Traditional forwarding mode
|
||||
const defaultPortMapping = {
|
||||
25: 10025, // SMTP
|
||||
587: 10587, // Submission
|
||||
465: 10465 // SMTPS
|
||||
};
|
||||
|
||||
const portMapping = this.options.emailPortConfig?.portMapping || defaultPortMapping;
|
||||
const internalPort = portMapping[port] || port + 10000;
|
||||
|
||||
action = {
|
||||
type: 'forward',
|
||||
target: {
|
||||
host: 'localhost', // Forward to internal email server
|
||||
@ -321,19 +334,28 @@ export class DcRouter {
|
||||
tls: {
|
||||
mode: tlsMode as any
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// For TLS terminate mode, add certificate info
|
||||
if (tlsMode === 'terminate') {
|
||||
routeConfig.action.tls.certificate = 'auto';
|
||||
if (tlsMode === 'terminate' && action.tls) {
|
||||
action.tls.certificate = 'auto';
|
||||
}
|
||||
|
||||
// Create the route configuration
|
||||
const routeConfig: plugins.smartproxy.IRouteConfig = {
|
||||
name: routeName,
|
||||
match: {
|
||||
ports: [port]
|
||||
},
|
||||
action: action
|
||||
};
|
||||
|
||||
// Add the route to our list
|
||||
emailRoutes.push(routeConfig);
|
||||
}
|
||||
|
||||
// Add email routes if configured
|
||||
// Add email domain-based routes if configured
|
||||
if (emailConfig.routes) {
|
||||
for (const route of emailConfig.routes) {
|
||||
emailRoutes.push({
|
||||
@ -358,6 +380,39 @@ export class DcRouter {
|
||||
|
||||
return emailRoutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SmartProxy routes for DNS configuration
|
||||
*/
|
||||
private generateDnsRoutes(): plugins.smartproxy.IRouteConfig[] {
|
||||
if (!this.options.dnsDomain) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dnsRoutes: plugins.smartproxy.IRouteConfig[] = [];
|
||||
|
||||
// Create routes for DNS-over-HTTPS paths
|
||||
const dohPaths = ['/dns-query', '/resolve'];
|
||||
|
||||
for (const path of dohPaths) {
|
||||
const dohRoute: plugins.smartproxy.IRouteConfig = {
|
||||
name: `dns-over-https-${path.replace('/', '')}`,
|
||||
match: {
|
||||
ports: [443], // HTTPS port for DoH
|
||||
domains: [this.options.dnsDomain],
|
||||
path: path
|
||||
},
|
||||
action: {
|
||||
type: 'socket-handler' as any,
|
||||
socketHandler: this.createDnsSocketHandler()
|
||||
} as any
|
||||
};
|
||||
|
||||
dnsRoutes.push(dohRoute);
|
||||
}
|
||||
|
||||
return dnsRoutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a domain matches a pattern (including wildcard support)
|
||||
@ -663,6 +718,118 @@ export class DcRouter {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up DNS server with socket handler for DoH
|
||||
*/
|
||||
private async setupDnsWithSocketHandler(): Promise<void> {
|
||||
if (!this.options.dnsDomain) {
|
||||
throw new Error('dnsDomain is required for DNS socket handler setup');
|
||||
}
|
||||
|
||||
logger.log('info', `Setting up DNS server with socket handler for domain: ${this.options.dnsDomain}`);
|
||||
|
||||
// Get VM IP address for UDP binding
|
||||
const networkInterfaces = plugins.os.networkInterfaces();
|
||||
let vmIpAddress = '0.0.0.0'; // Default to all interfaces
|
||||
|
||||
// Try to find the VM's internal IP address
|
||||
for (const [name, interfaces] of Object.entries(networkInterfaces)) {
|
||||
if (interfaces) {
|
||||
for (const iface of interfaces) {
|
||||
if (!iface.internal && iface.family === 'IPv4') {
|
||||
vmIpAddress = iface.address;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create DNS server instance with manual HTTPS mode
|
||||
this.dnsServer = new plugins.smartdns.dnsServerMod.DnsServer({
|
||||
udpPort: 53,
|
||||
udpBindInterface: vmIpAddress,
|
||||
httpsPort: 443, // Required but won't bind due to manual mode
|
||||
manualHttpsMode: true, // Enable manual HTTPS socket handling
|
||||
dnssecZone: this.options.dnsDomain,
|
||||
// For now, use self-signed cert until we integrate with Let's Encrypt
|
||||
httpsKey: '',
|
||||
httpsCert: ''
|
||||
});
|
||||
|
||||
// Start the DNS server (UDP only)
|
||||
await this.dnsServer.start();
|
||||
logger.log('info', `DNS server started on UDP ${vmIpAddress}:53`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DNS socket handler for DoH
|
||||
*/
|
||||
private createDnsSocketHandler(): (socket: plugins.net.Socket) => Promise<void> {
|
||||
return async (socket: plugins.net.Socket) => {
|
||||
if (!this.dnsServer) {
|
||||
logger.log('error', 'DNS socket handler called but DNS server not initialized');
|
||||
socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log('debug', 'DNS socket handler: passing socket to DnsServer');
|
||||
|
||||
try {
|
||||
// Use the built-in socket handler from smartdns
|
||||
// This handles HTTP/2, DoH protocol, etc.
|
||||
await (this.dnsServer as any).handleHttpsSocket(socket);
|
||||
} catch (error) {
|
||||
logger.log('error', `DNS socket handler error: ${error.message}`);
|
||||
socket.destroy();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create mail socket handler for email traffic
|
||||
*/
|
||||
private createMailSocketHandler(port: number): (socket: plugins.net.Socket) => Promise<void> {
|
||||
return async (socket: plugins.net.Socket) => {
|
||||
if (!this.emailServer) {
|
||||
logger.log('error', 'Mail socket handler called but email server not initialized');
|
||||
socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log('debug', `Mail socket handler: handling connection for port ${port}`);
|
||||
|
||||
try {
|
||||
// Port 465 requires immediate TLS
|
||||
if (port === 465) {
|
||||
// Wrap the socket in TLS
|
||||
const tlsOptions = {
|
||||
isServer: true,
|
||||
key: this.options.tls?.keyPath ? plugins.fs.readFileSync(this.options.tls.keyPath, 'utf8') : undefined,
|
||||
cert: this.options.tls?.certPath ? plugins.fs.readFileSync(this.options.tls.certPath, 'utf8') : undefined
|
||||
};
|
||||
|
||||
const tlsSocket = new plugins.tls.TLSSocket(socket, tlsOptions);
|
||||
|
||||
tlsSocket.on('secure', () => {
|
||||
// Pass the secure socket to the email server
|
||||
this.emailServer!.handleSocket(tlsSocket, port);
|
||||
});
|
||||
|
||||
tlsSocket.on('error', (err) => {
|
||||
logger.log('error', `TLS handshake error on port ${port}: ${err.message}`);
|
||||
socket.destroy();
|
||||
});
|
||||
} else {
|
||||
// For ports 25 and 587, pass raw socket (STARTTLS handled by email server)
|
||||
await this.emailServer.handleSocket(socket, port);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log('error', `Mail socket handler error on port ${port}: ${error.message}`);
|
||||
socket.destroy();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Re-export email server types for convenience
|
||||
|
@ -49,6 +49,7 @@ export interface IUnifiedEmailServerOptions {
|
||||
domains: string[]; // Domains to handle email for
|
||||
banner?: string;
|
||||
debug?: boolean;
|
||||
useSocketHandler?: boolean; // Use socket-handler mode instead of port listening
|
||||
|
||||
// Authentication options
|
||||
auth?: {
|
||||
@ -336,6 +337,13 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
logger.log('info', 'Automatic DKIM configuration completed');
|
||||
}
|
||||
|
||||
// Skip server creation in socket-handler mode
|
||||
if (this.options.useSocketHandler) {
|
||||
logger.log('info', 'UnifiedEmailServer started in socket-handler mode (no port listening)');
|
||||
this.emit('started');
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure we have the necessary TLS options
|
||||
const hasTlsConfig = this.options.tls?.keyPath && this.options.tls?.certPath;
|
||||
|
||||
@ -446,6 +454,54 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a socket from smartproxy in socket-handler mode
|
||||
* @param socket The socket to handle
|
||||
* @param port The port this connection is for (25, 587, 465)
|
||||
*/
|
||||
public async handleSocket(socket: plugins.net.Socket | plugins.tls.TLSSocket, port: number): Promise<void> {
|
||||
if (!this.options.useSocketHandler) {
|
||||
logger.log('error', 'handleSocket called but useSocketHandler is not enabled');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log('info', `Handling socket for port ${port}`);
|
||||
|
||||
// Create a temporary SMTP server instance for this connection
|
||||
// We need a full server instance because the SMTP protocol handler needs all components
|
||||
const smtpServerOptions = {
|
||||
port,
|
||||
hostname: this.options.hostname,
|
||||
key: this.options.tls?.keyPath ? plugins.fs.readFileSync(this.options.tls.keyPath, 'utf8') : undefined,
|
||||
cert: this.options.tls?.certPath ? plugins.fs.readFileSync(this.options.tls.certPath, 'utf8') : undefined
|
||||
};
|
||||
|
||||
// Create the SMTP server instance
|
||||
const smtpServer = createSmtpServer(this, smtpServerOptions);
|
||||
|
||||
// Get the connection manager from the server
|
||||
const connectionManager = (smtpServer as any).connectionManager;
|
||||
|
||||
if (!connectionManager) {
|
||||
logger.log('error', 'Could not get connection manager from SMTP server');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine if this is a secure connection
|
||||
// Port 465 uses implicit TLS, so the socket is already secure
|
||||
const isSecure = port === 465 || socket instanceof plugins.tls.TLSSocket;
|
||||
|
||||
// Pass the socket to the connection manager
|
||||
try {
|
||||
await connectionManager.handleConnection(socket, isSecure);
|
||||
} catch (error) {
|
||||
logger.log('error', `Error handling socket connection: ${error.message}`);
|
||||
socket.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the unified email server
|
||||
*/
|
||||
|
@ -4,6 +4,7 @@ import * as fs from 'fs';
|
||||
import * as crypto from 'crypto';
|
||||
import * as http from 'http';
|
||||
import * as net from 'net';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as tls from 'tls';
|
||||
import * as util from 'util';
|
||||
@ -14,6 +15,7 @@ export {
|
||||
crypto,
|
||||
http,
|
||||
net,
|
||||
os,
|
||||
path,
|
||||
tls,
|
||||
util,
|
||||
|
Loading…
x
Reference in New Issue
Block a user