Skip to content

Commit

Permalink
CCT-176: Do not wait until the server closes the TLS connection
Browse files Browse the repository at this point in the history
* Card ID: CCT-176
* Card ID: RHEL-17345

Starting with TLS 1.1, it is not required of the server to send the
`close_notify` alert before closing their the connection. TLS 1.3 uses
half-close policy which extends this behavior.

Candlepin is migrating to Quarkus which does not send these messages by
default. This makes subscription-manager hang during the
`.sock.unwrap()` because it waits for the message until it timeouts.
  • Loading branch information
m-horky committed Dec 19, 2023
1 parent 51f75db commit 01b2277
Showing 1 changed file with 37 additions and 13 deletions.
50 changes: 37 additions & 13 deletions src/rhsm/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
#

import base64
import math

from rhsm import certificate
import datetime
import dateutil.parser
import locale
import logging
import os
import signal
import socket
import sys
import time
Expand Down Expand Up @@ -670,19 +673,37 @@ def close_connection(self) -> None:
Try to close connection to server
:return: None
"""
if self.__conn is not None:
# Do proper TLS shutdown handshake (TLS tear down) first
if self.__conn.sock is not None:
log.debug(f"Closing HTTPS connection {self.__conn.sock}")
try:
self.__conn.sock.unwrap()
except ssl.SSLError as err:
log.debug(f"Unable to close TLS connection properly: {err}")
else:
log.debug("TLS connection closed")
# Then it is possible to close TCP connection
if self.__conn is None:
return

if self.__conn.sock is None:
log.debug(f"Closing TCP connection to {self.__conn.host}")
self.__conn.close()
self.__conn = None
return

# The server may not send the `close_notify` alert when it closes the
# connection on its side.
# Here we set a timeout equal to three times the measured response time
# to prevent the connection from waiting until TCP timeout.
# See RHEL-17345.
log.debug(f"Closing TLS connection {self.__conn.sock}")

response_time: float = self.smoothed_rt or 0.5
timeout_time: int = math.ceil(response_time * 3)

def on_timeout(signum, frame) -> None:
raise TimeoutError(f"Did not get response in {timeout_time}s")

signal.signal(signalnum=signal.SIGALRM, handler=on_timeout)
signal.alarm(timeout_time)

try:
self.__conn.sock.unwrap()
except (ssl.SSLError, TimeoutError) as err:
log.debug(f"TLS connection could not be closed: {err}")

log.debug(f"Closing TCP connection to {self.__conn.host}")
self.__conn.close()

def _get_cert_key_list(self) -> List[Tuple[str, str]]:
"""
Expand Down Expand Up @@ -1227,7 +1248,10 @@ def _update_smoothed_response_time(self, response_time: float):
self.smoothed_rt = response_time
else:
self.smoothed_rt = (self.ALPHA * self.smoothed_rt) + ((1 - self.ALPHA) * response_time)
log.debug("Response time: %s, Smoothed response time: %s" % (response_time, self.smoothed_rt))
log.debug(
f"Latest response time was {response_time:.5f}s, "
f"smoothed response time is {self.smoothed_rt:.5f}s"
)

def validateResult(self, result: dict, request_type: str = None, handler: str = None) -> None:
"""
Expand Down

0 comments on commit 01b2277

Please sign in to comment.