Commit 135b70e6707 for nodejs

commit 135b70e6707fe2eef715bc56c7a338edc621d24e
Author: Tim Perry <pimterry@gmail.com>
Date:   Mon Oct 19 14:35:59 2026 +0200

    tls: add tlsSocket.socket, to access the raw stream beneath TLS

    Signed-off-by: Tim Perry <pimterry@gmail.com>
    PR-URL: https://github.com/nodejs/node/pull/66598
    Reviewed-By: Luigi Pinca <luigipinca@gmail.com>

diff --git a/doc/api/tls.md b/doc/api/tls.md
index 7e4f77f3ced..678513846ba 100644
--- a/doc/api/tls.md
+++ b/doc/api/tls.md
@@ -1670,6 +1670,27 @@ and their processing can be delayed due to packet loss or reordering. However,
 smaller fragments add extra TLS framing bytes and CPU overhead, which may
 decrease overall server throughput.

+### `tlsSocket.socket`
+
+<!-- YAML
+added: REPLACEME
+-->
+
+* Type: {net.Socket|stream.Duplex|null}
+
+The underlying socket or stream that this socket reads and writes TLS data
+through. This is the `socket` passed to [`new tls.TLSSocket()`][] or
+[`tls.connect()`][], or the connection given to a [`tls.Server`][] by its
+`'connection'` event.
+
+This is only `null` for client sockets that manage their own connection,
+created by [`tls.connect()`][] with a `port` or `path` rather than a `socket`.
+Connection details such as `remoteAddress` are then available directly on this
+socket. Server sockets always have an underlying socket.
+
+This remains available after the socket has been destroyed, for example to
+identify the underlying connection from a [`'tlsClientError'`][] listener.
+
 ## `tls.checkServerIdentity(hostname, cert)`

 <!-- YAML
@@ -2596,6 +2617,7 @@ added: v0.11.3
 [`'secureConnect'`]: #event-secureconnect
 [`'secureConnection'`]: #event-secureconnection
 [`'session'`]: #event-session
+[`'tlsClientError'`]: #event-tlsclienterror
 [`--tls-cipher-list`]: cli.md#--tls-cipher-listlist
 [`--use-bundled-ca`]: cli.md#--use-bundled-ca---use-openssl-ca
 [`--use-openssl-ca`]: cli.md#--use-bundled-ca---use-openssl-ca
@@ -2611,6 +2633,7 @@ added: v0.11.3
 [`net.Server`]: net.md#class-netserver
 [`net.Socket`]: net.md#class-netsocket
 [`net.createServer()`]: net.md#netcreateserveroptions-connectionlistener
+[`new tls.TLSSocket()`]: #new-tlstlssocketsocket-options
 [`server.addContext()`]: #serveraddcontexthostname-context
 [`server.getTicketKeys()`]: #servergetticketkeys
 [`server.listen()`]: net.md#serverlisten
diff --git a/lib/internal/tls/wrap.js b/lib/internal/tls/wrap.js
index 7a47f10bb04..65611de32d8 100644
--- a/lib/internal/tls/wrap.js
+++ b/lib/internal/tls/wrap.js
@@ -700,6 +700,8 @@ function TLSSocket(socket, opts) {
   // Proxy for API compatibility
   this.ssl = this._handle;  // C++ TLSWrap object

+  this.socket = socket ?? null;
+
   this.on('error', this._tlsError);

   this._init(socket, wrap);
diff --git a/test/parallel/test-tls-socket-underlying-socket.js b/test/parallel/test-tls-socket-underlying-socket.js
new file mode 100644
index 00000000000..de689443914
--- /dev/null
+++ b/test/parallel/test-tls-socket-underlying-socket.js
@@ -0,0 +1,57 @@
+'use strict';
+const common = require('../common');
+if (!common.hasCrypto)
+  common.skip('missing crypto');
+
+const assert = require('assert');
+const fixtures = require('../common/fixtures');
+const net = require('net');
+const tls = require('tls');
+const { duplexPair } = require('stream');
+
+const key = fixtures.readKey('agent1-key.pem');
+const cert = fixtures.readKey('agent1-cert.pem');
+
+// Server sockets over a plain connection expose that net.Socket.
+{
+  const server = tls.createServer({ key, cert });
+  let rawSocket;
+  server.prependListener('connection', common.mustCall((socket) => {
+    rawSocket = socket;
+  }));
+  server.on('secureConnection', common.mustCall((tlsSocket) => {
+    assert.ok(rawSocket instanceof net.Socket);
+    assert.strictEqual(tlsSocket.socket, rawSocket);
+    tlsSocket.end();
+    server.close();
+  }));
+  server.listen(0, common.mustCall(() => {
+    const client = tls.connect({
+      port: server.address().port,
+      rejectUnauthorized: false,
+    }, common.mustCall(() => {
+      assert.strictEqual(client.socket, null);
+      client.end();
+    }));
+  }));
+}
+
+// Server sockets over any other stream expose that stream, and still do after
+// a failed handshake has destroyed them.
+{
+  const server = tls.createServer({ key, cert, minVersion: 'TLSv1.3' });
+  const [serverSide, clientSide] = duplexPair();
+  server.on('secureConnection', common.mustNotCall());
+  server.on('tlsClientError', common.mustCall((err, tlsSocket) => {
+    assert.strictEqual(tlsSocket.socket, serverSide);
+    setImmediate(common.mustCall(() => {
+      assert.strictEqual(tlsSocket.destroyed, true);
+      assert.strictEqual(tlsSocket.socket, serverSide);
+    }));
+  }));
+  server.emit('connection', serverSide);
+
+  const client = tls.connect({ socket: clientSide, maxVersion: 'TLSv1.2' });
+  assert.strictEqual(client.socket, clientSide);
+  client.on('error', common.mustCall());
+}