Skip to content

Commit fe8d007

Browse files
authored
tls(Server) fix connectionListener and make alpnProtocol more compatible with node.js (#14695)
Co-authored-by: cirospaciari <cirospaciari@users.noreply.github.com>
1 parent 8063e9d commit fe8d007

File tree

4 files changed

+70
-22
lines changed

4 files changed

+70
-22
lines changed

src/bun.js/api/bun/socket.zig

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,17 +1295,13 @@ fn selectALPNCallback(
12951295
if (protos.len == 0) {
12961296
return BoringSSL.SSL_TLSEXT_ERR_NOACK;
12971297
}
1298-
12991298
const status = BoringSSL.SSL_select_next_proto(bun.cast([*c][*c]u8, out), outlen, protos.ptr, @as(c_uint, @intCast(protos.len)), in, inlen);
1300-
13011299
// Previous versions of Node.js returned SSL_TLSEXT_ERR_NOACK if no protocol
13021300
// match was found. This would neither cause a fatal alert nor would it result
13031301
// in a useful ALPN response as part of the Server Hello message.
13041302
// We now return SSL_TLSEXT_ERR_ALERT_FATAL in that case as per Section 3.2
13051303
// of RFC 7301, which causes a fatal no_application_protocol alert.
1306-
const expected = if (comptime BoringSSL.OPENSSL_NPN_NEGOTIATED == 1) BoringSSL.SSL_TLSEXT_ERR_OK else BoringSSL.SSL_TLSEXT_ERR_ALERT_FATAL;
1307-
1308-
return if (status == expected) 1 else 0;
1304+
return if (status == BoringSSL.OPENSSL_NPN_NEGOTIATED) BoringSSL.SSL_TLSEXT_ERR_OK else BoringSSL.SSL_TLSEXT_ERR_ALERT_FATAL;
13091305
} else {
13101306
return BoringSSL.SSL_TLSEXT_ERR_NOACK;
13111307
}

src/js/node/net.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ function closeNT(callback, err) {
8484
callback(err);
8585
}
8686

87+
function detachAfterFinish() {
88+
this[bunSocketInternal] = null;
89+
}
90+
8791
var SocketClass;
8892
const Socket = (function (InternalSocket) {
8993
SocketClass = InternalSocket;
@@ -164,7 +168,7 @@ const Socket = (function (InternalSocket) {
164168
self._secureEstablished = !!success;
165169

166170
self.emit("secure", self);
167-
171+
self.alpnProtocol = socket.alpnProtocol;
168172
const { checkServerIdentity } = self[bunTLSConnectOptions];
169173
if (!verifyError && typeof checkServerIdentity === "function" && self.servername) {
170174
const cert = self.getPeerCertificate(true);
@@ -291,10 +295,7 @@ const Socket = (function (InternalSocket) {
291295

292296
if (typeof connectionListener == "function") {
293297
this.pauseOnConnect = pauseOnConnect;
294-
if (isTLS) {
295-
// add secureConnection event handler
296-
self.once("secureConnection", () => connectionListener.$call(self, _socket));
297-
} else {
298+
if (!isTLS) {
298299
connectionListener.$call(self, _socket);
299300
}
300301
}
@@ -312,6 +313,7 @@ const Socket = (function (InternalSocket) {
312313
self._secureEstablished = !!success;
313314
self.servername = socket.getServername();
314315
const server = self.server;
316+
self.alpnProtocol = socket.alpnProtocol;
315317
if (self._requestCert || self._rejectUnauthorized) {
316318
if (verifyError) {
317319
self.authorized = false;
@@ -329,7 +331,11 @@ const Socket = (function (InternalSocket) {
329331
} else {
330332
self.authorized = true;
331333
}
332-
self.server.emit("secureConnection", self);
334+
const connectionListener = server[bunSocketServerOptions]?.connectionListener;
335+
if (typeof connectionListener == "function") {
336+
connectionListener.$call(server, self);
337+
}
338+
server.emit("secureConnection", self);
333339
// after secureConnection event we emmit secure and secureConnect
334340
self.emit("secure", self);
335341
self.emit("secureConnect", verifyError);
@@ -685,14 +691,23 @@ const Socket = (function (InternalSocket) {
685691
}
686692

687693
_destroy(err, callback) {
688-
const socket = this[bunSocketInternal];
689-
if (socket) {
694+
const { ending } = this._writableState;
695+
// lets make sure that the writable side is closed
696+
if (!ending) {
697+
// at this state destroyed will be true but we need to close the writable side
698+
this._writableState.destroyed = false;
699+
this.end();
700+
// we now restore the destroyed flag
701+
this._writableState.destroyed = true;
702+
}
703+
704+
if (this.writableFinished) {
705+
// closed we can detach the socket
690706
this[bunSocketInternal] = null;
691-
// we still have a socket, call end before destroy
692-
process.nextTick(endNT, socket, callback, err);
693-
return;
707+
} else {
708+
// lets wait for the finish event before detaching the socket
709+
this.once("finish", detachAfterFinish);
694710
}
695-
// no socket, just destroy
696711
process.nextTick(closeNT, callback, err);
697712
}
698713

@@ -706,7 +721,6 @@ const Socket = (function (InternalSocket) {
706721
this.#final_callback = callback;
707722
} else {
708723
// emit FIN not allowing half open
709-
this[bunSocketInternal] = null;
710724
process.nextTick(endNT, socket, callback);
711725
}
712726
}

src/js/node/tls.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ const TLSSocket = (function (InternalTLSSocket) {
330330
#socket;
331331
#checkServerIdentity;
332332
#session;
333+
alpnProtocol = null;
333334

334335
constructor(socket, options) {
335336
super(socket instanceof InternalTCPSocket ? options : options || socket);
@@ -503,10 +504,6 @@ const TLSSocket = (function (InternalTLSSocket) {
503504
throw Error("Not implented in Bun yet");
504505
}
505506

506-
get alpnProtocol() {
507-
return this[bunSocketInternal]?.alpnProtocol;
508-
}
509-
510507
[buntls](port, host) {
511508
return {
512509
socket: this.#socket,

test/js/node/tls/node-tls-server.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { tmpdir } from "os";
66
import { join } from "path";
77
import type { PeerCertificate } from "tls";
88
import tls, { connect, createServer, rootCertificates, Server, TLSSocket } from "tls";
9+
import { once } from "node:events";
910

1011
const { describe, expect, it, createCallCheckCtx } = createTest(import.meta.path);
1112

@@ -662,3 +663,43 @@ it("tls.rootCertificates should exists", () => {
662663
expect(rootCertificates.length).toBeGreaterThan(0);
663664
expect(typeof rootCertificates[0]).toBe("string");
664665
});
666+
667+
it("connectionListener should emit the right amount of times, and with alpnProtocol available", async () => {
668+
let count = 0;
669+
const promises = [];
670+
const server: Server = createServer(
671+
{
672+
...COMMON_CERT,
673+
ALPNProtocols: ["bun"],
674+
},
675+
socket => {
676+
count++;
677+
expect(socket.alpnProtocol).toBe("bun");
678+
socket.end();
679+
},
680+
);
681+
server.setMaxListeners(100);
682+
683+
server.listen(0);
684+
await once(server, "listening");
685+
for (let i = 0; i < 50; i++) {
686+
const { promise, resolve } = Promise.withResolvers();
687+
promises.push(promise);
688+
const socket = connect(
689+
{
690+
ca: COMMON_CERT.cert,
691+
rejectUnauthorized: false,
692+
port: server.address().port,
693+
ALPNProtocols: ["bun"],
694+
},
695+
() => {
696+
socket.on("close", resolve);
697+
socket.resume();
698+
socket.end();
699+
},
700+
);
701+
}
702+
703+
await Promise.all(promises);
704+
expect(count).toBe(50);
705+
});

0 commit comments

Comments
 (0)