fix(client,protocol): EINTR-safe sends, armed abort, keepalive drain grace, TLS WANT_WRITE
This commit is contained in:
+32
-11
@@ -303,6 +303,12 @@ bool protocol_send_n_data(ProtocolSession* session, const void* data, size_t dat
|
||||
wait_events = ssl_err == SSL_ERROR_WANT_WRITE ? POLLOUT : POLLIN;
|
||||
continue;
|
||||
}
|
||||
/* A signal (e.g. Ctrl-C) interrupts the blocking TLS write: retry so
|
||||
the send loop can observe the abort flag at the next checkpoint. */
|
||||
if (ssl_err == SSL_ERROR_SYSCALL && errno == EINTR)
|
||||
continue;
|
||||
} else if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
log_message(LOG_LEVEL_ERROR, "Could not send data");
|
||||
return false;
|
||||
@@ -618,6 +624,7 @@ static bool protocol_read_status_until(ProtocolSession* session, Status* status,
|
||||
const struct timespec* deadline) {
|
||||
Status received = STATUS_ERROR;
|
||||
size_t got = 0;
|
||||
short wait_events = POLLIN;
|
||||
while (got < sizeof(Status)) {
|
||||
if (!session->ssl || SSL_pending(session->ssl) == 0) {
|
||||
int remaining_ms = deadline_remaining_ms(deadline);
|
||||
@@ -625,7 +632,7 @@ static bool protocol_read_status_until(ProtocolSession* session, Status* status,
|
||||
log_message(LOG_LEVEL_ERROR, "Receive timeout while reading status");
|
||||
return false;
|
||||
}
|
||||
struct pollfd pfd = {.fd = session->read_fd, .events = POLLIN};
|
||||
struct pollfd pfd = {.fd = session->read_fd, .events = wait_events};
|
||||
int poll_result = poll(&pfd, 1, remaining_ms);
|
||||
if (poll_result == 0) {
|
||||
log_message(LOG_LEVEL_ERROR, "Receive timeout while reading status");
|
||||
@@ -647,8 +654,10 @@ static bool protocol_read_status_until(ProtocolSession* session, Status* status,
|
||||
if (bytes_received <= 0) {
|
||||
if (session->ssl) {
|
||||
int ssl_err = SSL_get_error(session->ssl, (int)bytes_received);
|
||||
if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE)
|
||||
if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) {
|
||||
wait_events = ssl_err == SSL_ERROR_WANT_WRITE ? POLLOUT : POLLIN;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (bytes_received < 0 && errno == EINTR)
|
||||
continue;
|
||||
@@ -689,7 +698,8 @@ bool protocol_receive_status_keepalive(ProtocolSession* session, Status* status,
|
||||
/* Only interleave a keepalive while waiting for the FIRST byte of a
|
||||
* frame; once part of a frame is buffered a write could race the peer's
|
||||
* reply into the middle of it. */
|
||||
int interval_ms = keepalive_interval_sec * 1000;
|
||||
long long interval_ms_ll = (long long)keepalive_interval_sec * 1000LL;
|
||||
int interval_ms = interval_ms_ll > INT_MAX ? INT_MAX : (int)interval_ms_ll;
|
||||
int wait_ms = interval_ms < remaining_ms ? interval_ms : remaining_ms;
|
||||
struct pollfd pfd = {.fd = session->read_fd, .events = POLLIN};
|
||||
int poll_result = poll(&pfd, 1, wait_ms);
|
||||
@@ -724,15 +734,26 @@ bool protocol_receive_status_keepalive(ProtocolSession* session, Status* status,
|
||||
* was busy. It answers them only after the real status, so leaving them
|
||||
* unread would put stale KEEPALIVE frames ahead of the next exchange and
|
||||
* desynchronize the protocol. */
|
||||
while (replies_seen < keepalives_sent) {
|
||||
Status drained;
|
||||
if (!protocol_read_status_until(session, &drained, &deadline))
|
||||
return false;
|
||||
if (drained != STATUS_KEEPALIVE) {
|
||||
log_message(LOG_LEVEL_ERROR, "Unexpected status while draining keepalive replies");
|
||||
return false;
|
||||
if (replies_seen < keepalives_sent) {
|
||||
/* A short separate grace, not the (possibly exhausted) main deadline: the
|
||||
terminal status already arrived, so a peer that never answers its owed
|
||||
keepalives must not turn a successful ack into a reported failure. */
|
||||
struct timespec drain_deadline;
|
||||
clock_gettime(CLOCK_MONOTONIC, &drain_deadline);
|
||||
drain_deadline.tv_sec += 1;
|
||||
while (replies_seen < keepalives_sent) {
|
||||
Status drained;
|
||||
if (!protocol_read_status_until(session, &drained, &drain_deadline)) {
|
||||
log_message(LOG_LEVEL_WARNING, "peer did not answer %lu keepalive(s); continuing",
|
||||
keepalives_sent - replies_seen);
|
||||
break;
|
||||
}
|
||||
if (drained != STATUS_KEEPALIVE) {
|
||||
log_message(LOG_LEVEL_ERROR, "Unexpected status while draining keepalive replies");
|
||||
return false;
|
||||
}
|
||||
replies_seen++;
|
||||
}
|
||||
replies_seen++;
|
||||
}
|
||||
*status = final;
|
||||
log_debug_message(LOG_DEBUG_PROTO, "Received Status: %s", status_to_string(*status));
|
||||
|
||||
Reference in New Issue
Block a user