11import EventEmitter from "events" ;
22import { RedisClientOptions } from "." ;
33import RedisCommandsQueue from "./commands-queue" ;
4- import RedisSocket from "./socket" ;
4+ import RedisSocket , { RedisSocketOptions , RedisTcpSocketOptions } from "./socket" ;
5+ import { RedisArgument } from "../.." ;
6+ import { isIP } from "net" ;
7+ import { lookup } from "dns/promises" ;
58
69export const MAINTENANCE_EVENTS = {
710 PAUSE_WRITING : "pause-writing" ,
@@ -18,13 +21,19 @@ const PN = {
1821} ;
1922
2023export interface SocketTimeoutUpdate {
21- inMaintenance : boolean ,
22- timeout ?: number
24+ inMaintenance : boolean ;
25+ timeout ?: number ;
2326}
2427
2528export default class EnterpriseMaintenanceManager extends EventEmitter {
2629 #commandsQueue: RedisCommandsQueue ;
2730 #options: RedisClientOptions ;
31+
32+ static async getHandshakeCommand ( tls : boolean , host : string ) : Promise < Array < RedisArgument > > {
33+ const movingEndpointType = await determineEndpoint ( tls , host ) ;
34+ return [ "CLIENT" , "MAINT_NOTIFICATIONS" , "ON" , "moving-endpoint-type" , movingEndpointType ] ;
35+ }
36+
2837 constructor ( commandsQueue : RedisCommandsQueue , options : RedisClientOptions ) {
2938 super ( ) ;
3039 this . #commandsQueue = commandsQueue ;
@@ -103,7 +112,7 @@ export default class EnterpriseMaintenanceManager extends EventEmitter {
103112
104113 this . emit ( MAINTENANCE_EVENTS . TIMEOUTS_UPDATE , {
105114 inMaintenance : true ,
106- timeout : this . #options. gracefulMaintenance ?. relaxedSocketTimeout
115+ timeout : this . #options. gracefulMaintenance ?. relaxedSocketTimeout ,
107116 } satisfies SocketTimeoutUpdate ) ;
108117 } ;
109118
@@ -113,8 +122,53 @@ export default class EnterpriseMaintenanceManager extends EventEmitter {
113122
114123 this . emit ( MAINTENANCE_EVENTS . TIMEOUTS_UPDATE , {
115124 inMaintenance : false ,
116- timeout : undefined
125+ timeout : undefined ,
117126 } satisfies SocketTimeoutUpdate ) ;
118127 } ;
128+ }
119129
120- } ;
130+ type MovingEndpointType =
131+ | "internal-ip"
132+ | "internal-fqdn"
133+ | "external-ip"
134+ | "external-fqdn"
135+ | "none" ;
136+
137+ function isPrivateIP ( ip : string ) : boolean {
138+ const version = isIP ( ip ) ;
139+ if ( version === 4 ) {
140+ const octets = ip . split ( "." ) . map ( Number ) ;
141+ return (
142+ octets [ 0 ] === 10 ||
143+ ( octets [ 0 ] === 172 && octets [ 1 ] >= 16 && octets [ 1 ] <= 31 ) ||
144+ ( octets [ 0 ] === 192 && octets [ 1 ] === 168 )
145+ ) ;
146+ }
147+ if ( version === 6 ) {
148+ return (
149+ ip . startsWith ( "fc" ) || // Unique local
150+ ip . startsWith ( "fd" ) || // Unique local
151+ ip === "::1" || // Loopback
152+ ip . startsWith ( "fe80" ) // Link-local unicast
153+ ) ;
154+ }
155+ return false ;
156+ }
157+
158+ async function determineEndpoint (
159+ tlsEnabled : boolean ,
160+ host : string ,
161+ ) : Promise < MovingEndpointType > {
162+
163+ const ip = isIP ( host )
164+ ? host
165+ : ( await lookup ( host , { family : 0 } ) ) . address
166+
167+ const isPrivate = isPrivateIP ( ip ) ;
168+
169+ if ( tlsEnabled ) {
170+ return isPrivate ? "internal-fqdn" : "external-fqdn" ;
171+ } else {
172+ return isPrivate ? "internal-ip" : "external-ip" ;
173+ }
174+ }
0 commit comments