1414
1515import { IpAddressTypes , selectIpAddress } from './ip-addresses' ;
1616import { InstanceConnectionInfo } from './instance-connection-info' ;
17- import { resolveInstanceName } from './parse-instance-connection-name' ;
17+ import { isSameInstance , resolveInstanceName } from './parse-instance-connection-name' ;
1818import { InstanceMetadata } from './sqladmin-fetcher' ;
1919import { generateKeys } from './crypto' ;
2020import { RSAKeys } from './rsa-keys' ;
2121import { SslCert } from './ssl-cert' ;
2222import { getRefreshInterval , isExpirationTimeValid } from './time' ;
2323import { AuthTypes } from './auth-types' ;
24+ import { CloudSQLConnectorError } from './errors' ;
25+
26+ // Private types that describe exactly the methods
27+ // needed from tls.Socket to be able to close
28+ // sockets when the DNS Name changes.
29+ type EventFn = ( ) => void ;
30+ type ClosableSocket = {
31+ destroy : ( error ?: Error ) => void ;
32+ once : ( name : string , handler : EventFn ) => void ;
33+ } ;
2434
2535interface Fetcher {
2636 getInstanceMetadata ( {
@@ -42,6 +52,7 @@ interface CloudSQLInstanceOptions {
4252 ipType : IpAddressTypes ;
4353 limitRateInterval ?: number ;
4454 sqlAdminFetcher : Fetcher ;
55+ checkDomainInterval ?: number ;
4556}
4657
4758interface RefreshResult {
@@ -74,9 +85,13 @@ export class CloudSQLInstance {
7485 // The ongoing refresh promise is referenced by the `next` property
7586 private next ?: Promise < RefreshResult > ;
7687 private scheduledRefreshID ?: ReturnType < typeof setTimeout > | null = undefined ;
88+ private checkDomainID ?: ReturnType < typeof setInterval > | null = undefined ;
7789 /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
7890 private throttle ?: any ;
7991 private closed = false ;
92+ private checkDomainInterval : number ;
93+ private sockets = new Set < ClosableSocket > ( ) ;
94+
8095 public readonly instanceInfo : InstanceConnectionInfo ;
8196 public ephemeralCert ?: SslCert ;
8297 public host ?: string ;
@@ -98,6 +113,7 @@ export class CloudSQLInstance {
98113 this . ipType = options . ipType || IpAddressTypes . PUBLIC ;
99114 this . limitRateInterval = options . limitRateInterval || 30 * 1000 ; // 30 seconds
100115 this . sqlAdminFetcher = options . sqlAdminFetcher ;
116+ this . checkDomainInterval = options . checkDomainInterval || 30 * 1000 ;
101117 }
102118
103119 // p-throttle library has to be initialized in an async scope in order to
@@ -142,7 +158,7 @@ export class CloudSQLInstance {
142158 // Else resolve immediately.
143159 resolve ( ) ;
144160 }
145- } , 0 ) ;
161+ } ) ;
146162 } ) ;
147163 }
148164
@@ -152,6 +168,14 @@ export class CloudSQLInstance {
152168 this . next = undefined ;
153169 return Promise . reject ( 'closed' ) ;
154170 }
171+ if ( this ?. instanceInfo ?. domainName && ! this . checkDomainID ) {
172+ this . checkDomainID = setInterval (
173+ ( ) => {
174+ this . checkDomainChanged ( ) ;
175+ } ,
176+ this . checkDomainInterval || 30 * 1000
177+ ) ;
178+ }
155179
156180 const currentRefreshId = this . scheduledRefreshID ;
157181
@@ -296,8 +320,8 @@ export class CloudSQLInstance {
296320 // If refresh has not yet started, then cancel the setTimeout
297321 if ( this . scheduledRefreshID ) {
298322 clearTimeout ( this . scheduledRefreshID ) ;
323+ this . scheduledRefreshID = null ;
299324 }
300- this . scheduledRefreshID = null ;
301325 }
302326
303327 // Mark this instance as having an active connection. This is important to
@@ -312,9 +336,48 @@ export class CloudSQLInstance {
312336 close ( ) : void {
313337 this . closed = true ;
314338 this . cancelRefresh ( ) ;
339+ if ( this . checkDomainID ) {
340+ clearInterval ( this . checkDomainID ) ;
341+ this . checkDomainID = null ;
342+ }
343+ for ( const socket of this . sockets ) {
344+ socket . destroy (
345+ new CloudSQLConnectorError ( {
346+ code : 'ERRCLOSED' ,
347+ message : 'The connector was closed.' ,
348+ } )
349+ ) ;
350+ }
315351 }
316352
317353 isClosed ( ) : boolean {
318354 return this . closed ;
319355 }
356+ async checkDomainChanged ( ) {
357+ if ( ! this . instanceInfo . domainName ) {
358+ return ;
359+ }
360+
361+ const newInfo = await resolveInstanceName (
362+ undefined ,
363+ this . instanceInfo . domainName
364+ ) ;
365+ if ( ! isSameInstance ( this . instanceInfo , newInfo ) ) {
366+ // Domain name changed. Close and remove, then create a new map entry.
367+ this . close ( ) ;
368+ }
369+ }
370+ addSocket ( socket : ClosableSocket ) {
371+ if ( ! this . instanceInfo . domainName ) {
372+ // This was not connected by domain name. Ignore all sockets.
373+ return ;
374+ }
375+
376+ // Add the socket to the list
377+ this . sockets . add ( socket ) ;
378+ // When the socket is closed, remove it.
379+ socket . once ( 'closed' , ( ) => {
380+ this . sockets . delete ( socket ) ;
381+ } ) ;
382+ }
320383}
0 commit comments