diff --git a/doc/api/quic.md b/doc/api/quic.md index d90b1b938bd2..80390b0d7d0e 100644 --- a/doc/api/quic.md +++ b/doc/api/quic.md @@ -1414,7 +1414,9 @@ added: v23.8.0 * `local` {net.SocketAddress} * `remote` {net.SocketAddress} -The local and remote socket addresses associated with the session. Read only. +The local and remote socket addresses associated with the session. The object is +cached, and its read-only `local` and `remote` properties are refreshed from the +current path whenever `session.path` is accessed. ### `session.remoteTransportParams` diff --git a/lib/internal/quic/quic.js b/lib/internal/quic/quic.js index 11152f070add..57e601eb35c4 100644 --- a/lib/internal/quic/quic.js +++ b/lib/internal/quic/quic.js @@ -2786,6 +2786,8 @@ class QuicSession { handshakeInfo: undefined, /** @type {QuicSessionPath|undefined} */ path: undefined, + /** @type {QuicSessionPath|undefined} */ + pathState: undefined, certificate: undefined, peerCertificate: undefined, ephemeralKeyInfo: undefined, @@ -3272,11 +3274,36 @@ class QuicSession { get path() { assertIsQuicSession(this); if (this.destroyed) return undefined; - return this.#inner.path ??= { - __proto__: null, - local: new InternalSocketAddress(this.#handle.getLocalAddress()), - remote: new InternalSocketAddress(this.#handle.getRemoteAddress()), - }; + const local = new InternalSocketAddress(this.#handle.getLocalAddress()); + const remote = new InternalSocketAddress(this.#handle.getRemoteAddress()); + const inner = this.#inner; + let state = inner.pathState; + if (state === undefined) { + state = { + __proto__: null, + local, + remote, + }; + inner.pathState = state; + inner.path = ObjectDefineProperties({ __proto__: null }, { + local: { + __proto__: null, + configurable: false, + enumerable: true, + get: () => state.local, + }, + remote: { + __proto__: null, + configurable: false, + enumerable: true, + get: () => state.remote, + }, + }); + return inner.path; + } + state.local = local; + state.remote = remote; + return inner.path; } /** @@ -3722,6 +3749,7 @@ class QuicSession { inner.onorigin = undefined; inner.ongoaway = undefined; inner.path = undefined; + inner.pathState = undefined; inner.certificate = undefined; inner.peerCertificate = undefined; inner.ephemeralKeyInfo = undefined; diff --git a/src/quic/session.cc b/src/quic/session.cc index 2266f74a1277..1d034b316669 100644 --- a/src/quic/session.cc +++ b/src/quic/session.cc @@ -1051,7 +1051,8 @@ struct Session::Impl final : public MemoryRetainer { return THROW_ERR_INVALID_STATE(env, "Session is destroyed"); } - auto address = session->remote_address(); + const auto* path = ngtcp2_conn_get_path2(*session); + auto address = SocketAddress(path->remote.addr); args.GetReturnValue().Set( SocketAddressBase::Create(env, std::make_shared(address)) ->object()); @@ -1066,7 +1067,8 @@ struct Session::Impl final : public MemoryRetainer { return THROW_ERR_INVALID_STATE(env, "Session is destroyed"); } - auto address = session->local_address(); + const auto* path = ngtcp2_conn_get_path2(*session); + auto address = SocketAddress(path->local.addr); args.GetReturnValue().Set( SocketAddressBase::Create(env, std::make_shared(address)) ->object()); diff --git a/test/parallel/test-quic-session-preferred-address.mjs b/test/parallel/test-quic-session-preferred-address.mjs index 92194cf42a87..0483143b0f6d 100644 --- a/test/parallel/test-quic-session-preferred-address.mjs +++ b/test/parallel/test-quic-session-preferred-address.mjs @@ -19,6 +19,7 @@ const { listen, connect } = await import('../common/quic.mjs'); const allStatusDone = Promise.withResolvers(); const serverGot = Promise.withResolvers(); const serverPathValidated = Promise.withResolvers(); +const clientPathValidated = Promise.withResolvers(); let statusCount = 0; const handleSession = mustCall(async (serverSession) => { @@ -75,18 +76,32 @@ const clientSession = await connect(serverEndpoint.address, { assert.strictEqual(oldLocal, null); assert.strictEqual(oldRemote, null); assert.strictEqual(preferred, true); + clientPathValidated.resolve(); }), maxDatagramSendAttempts: 100, // While the connection is restablished, // all the acknowledgement packets of ngtcp2 are counted as send attempts // so either this or a delay, or a change in ngtcp2 interfaces }); +const initialPath = clientSession.path; +assertEqualAddress(initialPath.local, clientSession.endpoint.address); +assertEqualAddress(initialPath.remote, serverEndpoint.address); +// Freezing the cached object must not prevent internal address refreshes. +Object.freeze(initialPath); await clientSession.opened; // Send two datagrams. await clientSession.sendDatagram(new Uint8Array([1])); await clientSession.sendDatagram(new Uint8Array([2])); -await serverPathValidated.promise; +await Promise.all([ + serverPathValidated.promise, + clientPathValidated.promise, +]); + +const migratedPath = clientSession.path; +assert.strictEqual(migratedPath, initialPath); +assertEqualAddress(migratedPath.local, clientSession.endpoint.address); +assertEqualAddress(migratedPath.remote, preferredEndpoint.address); // Send more datagrams after the preferred address migration completes // To show that data is still flowing after we close the original