Compare commits
	
		
			8 Commits
		
	
	
		
			v4.1.5
			...
			46aa7620b0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 46aa7620b0 | |||
| f72db86e37 | |||
| d612df107e | |||
| 1c34578c36 | |||
| 1f9943b5a7 | |||
| 67ddf97547 | |||
| 8a96b45ece | |||
| 2b6464acd5 | 
							
								
								
									
										26
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								changelog.md
									
									
									
									
									
								
							@@ -1,5 +1,31 @@
 | 
			
		||||
# Changelog
 | 
			
		||||
 | 
			
		||||
## 2025-03-16 - 4.1.9 - fix(ConnectionHandler)
 | 
			
		||||
Replace closeNotify alert with handshake failure alert in TLS ClientHello handling to properly signal missing SNI and enforce session ticket restrictions.
 | 
			
		||||
 | 
			
		||||
- Switched alert data sent on missing SNI from closeNotifyAlert to sslHandshakeFailureAlertData.
 | 
			
		||||
- Ensures consistent TLS alert behavior during handshake failure.
 | 
			
		||||
 | 
			
		||||
## 2025-03-16 - 4.1.8 - fix(ConnectionHandler/tls)
 | 
			
		||||
Change the TLS alert sent when a ClientHello lacks SNI: use the close_notify alert instead of handshake_failure to prompt immediate retry with SNI.
 | 
			
		||||
 | 
			
		||||
- Replaced the previously sent handshake_failure alert (code 0x28) with a close_notify alert (code 0x00) in the TLS session resumption handling in ConnectionHandler.
 | 
			
		||||
- This change encourages clients to immediately retry and include SNI when allowSessionTicket is false.
 | 
			
		||||
 | 
			
		||||
## 2025-03-16 - 4.1.7 - fix(classes.pp.connectionhandler)
 | 
			
		||||
Improve TLS alert handling in ClientHello when SNI is missing and session tickets are disallowed
 | 
			
		||||
 | 
			
		||||
- Replace the unrecognized_name alert with a handshake_failure alert to ensure better client behavior.
 | 
			
		||||
- Refactor the alert sending mechanism using cork/uncork and add a safety timeout for the drain event.
 | 
			
		||||
- Enhance logging for debugging TLS handshake failures when SNI is absent.
 | 
			
		||||
 | 
			
		||||
## 2025-03-16 - 4.1.6 - fix(tls)
 | 
			
		||||
Refine TLS ClientHello handling when allowSessionTicket is false by replacing extensive alert timeout logic with a concise warning alert and short delay, encouraging immediate client retry with proper SNI
 | 
			
		||||
 | 
			
		||||
- Update the TLS alert sending mechanism to use cork/uncork and a short, fixed delay instead of long timeouts
 | 
			
		||||
- Remove redundant event listeners and excessive cleanup logic after sending the alert
 | 
			
		||||
- Improve code clarity and encourage clients (e.g., Chrome) to retry handshake with SNI more responsively
 | 
			
		||||
 | 
			
		||||
## 2025-03-16 - 4.1.5 - fix(TLS/ConnectionHandler)
 | 
			
		||||
Improve handling of TLS session resumption without SNI by sending an 'unrecognized_name' alert instead of immediately terminating the connection. This change adds a grace period for the client to retry the handshake with proper SNI and cleans up the connection if no valid response is received.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@push.rocks/smartproxy",
 | 
			
		||||
  "version": "4.1.5",
 | 
			
		||||
  "version": "4.1.9",
 | 
			
		||||
  "private": false,
 | 
			
		||||
  "description": "A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.",
 | 
			
		||||
  "main": "dist_ts/index.js",
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,6 @@
 | 
			
		||||
 */
 | 
			
		||||
export const commitinfo = {
 | 
			
		||||
  name: '@push.rocks/smartproxy',
 | 
			
		||||
  version: '4.1.5',
 | 
			
		||||
  version: '4.1.9',
 | 
			
		||||
  description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.'
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -557,13 +557,13 @@ export class ConnectionHandler {
 | 
			
		||||
            this.tlsManager.isClientHello(chunk) &&
 | 
			
		||||
            !serverName
 | 
			
		||||
          ) {
 | 
			
		||||
            // Always block ClientHello without SNI when allowSessionTicket is false
 | 
			
		||||
            // Block ClientHello without SNI when allowSessionTicket is false
 | 
			
		||||
            console.log(
 | 
			
		||||
              `[${connectionId}] No SNI detected in ClientHello and allowSessionTicket=false. ` +
 | 
			
		||||
                `Sending unrecognized_name alert to encourage client to retry with SNI.`
 | 
			
		||||
                `Sending warning unrecognized_name alert to encourage immediate retry with SNI.`
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Set the termination reason first to avoid races
 | 
			
		||||
            // Set the termination reason first
 | 
			
		||||
            if (record.incomingTerminationReason === null) {
 | 
			
		||||
              record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
 | 
			
		||||
              this.connectionManager.incrementTerminationStat(
 | 
			
		||||
@@ -572,10 +572,9 @@ export class ConnectionHandler {
 | 
			
		||||
              );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Send a proper TLS alert before ending the connection
 | 
			
		||||
            // Using "unrecognized_name" (112) alert which is a warning level alert (1)
 | 
			
		||||
            // that encourages clients to retry with proper SNI
 | 
			
		||||
            const alertData = Buffer.from([
 | 
			
		||||
            // Create a warning-level alert for unrecognized_name
 | 
			
		||||
            // This encourages Chrome to retry immediately with SNI
 | 
			
		||||
            const serverNameUnknownAlertData = Buffer.from([
 | 
			
		||||
              0x15, // Alert record type
 | 
			
		||||
              0x03,
 | 
			
		||||
              0x03, // TLS 1.2 version
 | 
			
		||||
@@ -585,89 +584,64 @@ export class ConnectionHandler {
 | 
			
		||||
              0x70, // unrecognized_name alert (code 112)
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            // Send a handshake_failure alert instead of unrecognized_name
 | 
			
		||||
            const sslHandshakeFailureAlertData = Buffer.from([
 | 
			
		||||
              0x15, // Alert record type
 | 
			
		||||
              0x03,
 | 
			
		||||
              0x03, // TLS 1.2 version
 | 
			
		||||
              0x00,
 | 
			
		||||
              0x02, // Length
 | 
			
		||||
              0x01, // Warning alert level (not fatal)
 | 
			
		||||
              0x28, // handshake_failure alert (40) instead of unrecognized_name (112)
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            const closeNotifyAlert = Buffer.from([
 | 
			
		||||
              0x15, // Alert record type
 | 
			
		||||
              0x03,
 | 
			
		||||
              0x03, // TLS 1.2 version
 | 
			
		||||
              0x00,
 | 
			
		||||
              0x02, // Length
 | 
			
		||||
              0x01, // Warning alert level (1)
 | 
			
		||||
              0x00, // close_notify alert (0)
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
              // Make sure the alert is sent as a single packet
 | 
			
		||||
              // Use cork/uncork to ensure the alert is sent as a single packet
 | 
			
		||||
              socket.cork();
 | 
			
		||||
              socket.write(alertData);
 | 
			
		||||
              const writeSuccessful = socket.write(serverNameUnknownAlertData);
 | 
			
		||||
              socket.uncork();
 | 
			
		||||
 | 
			
		||||
              // Set a longer timeout to allow the client to properly handle the alert
 | 
			
		||||
              // The client might respond by closing the connection or initiating a new handshake
 | 
			
		||||
              const alertTimeout = setTimeout(() => {
 | 
			
		||||
                if (!socket.destroyed) {
 | 
			
		||||
                  console.log(`[${connectionId}] Client didn't respond to TLS alert, closing connection gracefully.`);
 | 
			
		||||
              // Function to handle the clean socket termination
 | 
			
		||||
              const finishConnection = () => {
 | 
			
		||||
                // First call end() to initiate a graceful close (sends FIN)
 | 
			
		||||
                socket.end();
 | 
			
		||||
 | 
			
		||||
                  // Gracefully end the connection
 | 
			
		||||
                  socket.end();
 | 
			
		||||
                  
 | 
			
		||||
                  // Only destroy after a delay if it's still hanging
 | 
			
		||||
                  setTimeout(() => {
 | 
			
		||||
                    if (!socket.destroyed) {
 | 
			
		||||
                      console.log(`[${connectionId}] Forcibly closing connection that didn't terminate properly.`);
 | 
			
		||||
                      socket.destroy();
 | 
			
		||||
                    }
 | 
			
		||||
                    this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
 | 
			
		||||
                  }, 2000);
 | 
			
		||||
                }
 | 
			
		||||
              }, 5000); // Give the client 5 seconds to respond to our alert
 | 
			
		||||
              
 | 
			
		||||
              // Don't let this timeout keep the process alive
 | 
			
		||||
              if (alertTimeout.unref) {
 | 
			
		||||
                alertTimeout.unref();
 | 
			
		||||
              }
 | 
			
		||||
              
 | 
			
		||||
              // Handle a proper close from the client
 | 
			
		||||
              socket.once('close', () => {
 | 
			
		||||
                clearTimeout(alertTimeout);
 | 
			
		||||
                console.log(`[${connectionId}] Client closed connection after receiving TLS alert.`);
 | 
			
		||||
                this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni_client_closed');
 | 
			
		||||
              });
 | 
			
		||||
              
 | 
			
		||||
              // Also handle if the client sends more data (possibly a new handshake with SNI)
 | 
			
		||||
              socket.once('data', (newChunk) => {
 | 
			
		||||
                clearTimeout(alertTimeout);
 | 
			
		||||
                console.log(`[${connectionId}] Client sent new data after TLS alert, checking for SNI...`);
 | 
			
		||||
                
 | 
			
		||||
                // This would normally be handled by our renegotiation handler,
 | 
			
		||||
                // but since we're in a special case, we'll check for SNI again
 | 
			
		||||
                if (this.tlsManager.isTlsHandshake(newChunk) && this.tlsManager.isClientHello(newChunk)) {
 | 
			
		||||
                  const newServerName = this.tlsManager.extractSNI(newChunk, connInfo);
 | 
			
		||||
                  
 | 
			
		||||
                  if (newServerName) {
 | 
			
		||||
                    console.log(`[${connectionId}] Client provided SNI in new handshake: ${newServerName}`);
 | 
			
		||||
                    
 | 
			
		||||
                    // Update the record
 | 
			
		||||
                    record.incomingTerminationReason = null;
 | 
			
		||||
                    
 | 
			
		||||
                    // Remove termination stats increment
 | 
			
		||||
                    // Note: This is a little hacky as we don't have a proper way to decrement stats
 | 
			
		||||
                    
 | 
			
		||||
                    // Process the new handshake with SNI
 | 
			
		||||
                    record.lockedDomain = newServerName;
 | 
			
		||||
                    setupConnection(newServerName, newChunk);
 | 
			
		||||
                    return;
 | 
			
		||||
                  } else {
 | 
			
		||||
                    console.log(`[${connectionId}] Client sent new handshake but still without SNI, closing connection.`);
 | 
			
		||||
                    socket.end();
 | 
			
		||||
                    setTimeout(() => {
 | 
			
		||||
                      if (!socket.destroyed) {
 | 
			
		||||
                        socket.destroy();
 | 
			
		||||
                      }
 | 
			
		||||
                      this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni_retry_failed');
 | 
			
		||||
                    }, 500);
 | 
			
		||||
                // Allow a short delay for the alert and FIN to be transmitted
 | 
			
		||||
                // before we fully close the socket
 | 
			
		||||
                setTimeout(() => {
 | 
			
		||||
                  if (!socket.destroyed) {
 | 
			
		||||
                    socket.destroy();
 | 
			
		||||
                  }
 | 
			
		||||
                } else {
 | 
			
		||||
                  console.log(`[${connectionId}] Client sent non-handshake data after TLS alert, closing connection.`);
 | 
			
		||||
                  socket.end();
 | 
			
		||||
                  setTimeout(() => {
 | 
			
		||||
                    if (!socket.destroyed) {
 | 
			
		||||
                      socket.destroy();
 | 
			
		||||
                    }
 | 
			
		||||
                    this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_invalid_response');
 | 
			
		||||
                  }, 500);
 | 
			
		||||
                }
 | 
			
		||||
              });
 | 
			
		||||
                  this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
 | 
			
		||||
                }, 150); // Short delay, but longer than the standard TCP ACK timeout
 | 
			
		||||
              };
 | 
			
		||||
 | 
			
		||||
              if (writeSuccessful) {
 | 
			
		||||
                // If the data was successfully written to the kernel buffer,
 | 
			
		||||
                // we can finish the connection after a short delay to ensure transmission
 | 
			
		||||
                setTimeout(finishConnection, 50);
 | 
			
		||||
              } else {
 | 
			
		||||
                // If the kernel buffer was full, wait for the drain event
 | 
			
		||||
                socket.once('drain', () => {
 | 
			
		||||
                  setTimeout(finishConnection, 50);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // Set a safety timeout in case drain never happens
 | 
			
		||||
                setTimeout(() => {
 | 
			
		||||
                  socket.removeAllListeners('drain');
 | 
			
		||||
                  finishConnection();
 | 
			
		||||
                }, 250);
 | 
			
		||||
              }
 | 
			
		||||
            } catch (err) {
 | 
			
		||||
              // If we can't send the alert, fall back to immediate termination
 | 
			
		||||
              console.log(`[${connectionId}] Error sending TLS alert: ${err.message}`);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user