3232#include <misc_lib.h>
3333#include <cf3.defs.h>
3434#include <protocol.h>
35+ #include <protocol_version.h>
3536
37+ /* Maximum seconds to spend consuming heartbeats before aborting.
38+ * Slightly above the 120s wait gate timeout to allow for the
39+ * legitimate use case. A count-based limit would be wrong here —
40+ * a malicious peer could send 1 heartbeat every 29.9s (just under
41+ * SO_RCVTIMEO) to hold the connection for hours. */
42+ #define MAX_HEARTBEAT_DURATION 150
3643
3744/* TODO remove libpromises dependency. */
3845extern char BINDINTERFACE [CF_MAXVARSIZE ]; /* cf3globals.c, cf3.extern.h */
@@ -131,11 +138,31 @@ int SendTransaction(ConnectionInfo *conn_info,
131138 }
132139}
133140
141+ /**
142+ * Send a heartbeat transaction to keep the connection alive during
143+ * long-running server-side operations.
144+ *
145+ * No-op if the protocol version does not support heartbeats.
146+ * Heartbeats are silently consumed by ReceiveTransaction() on the
147+ * receiving end - callers never see them.
148+ *
149+ * @return 0 on success (or no-op), -1 on error
150+ */
151+ int SendHeartbeat (ConnectionInfo * conn_info )
152+ {
153+ assert (conn_info != NULL );
154+ if (!ProtocolSupportsHeartbeat (conn_info -> protocol ))
155+ {
156+ return 0 ;
157+ }
158+ return SendTransaction (conn_info , "HEARTBEAT" , sizeof ("HEARTBEAT" ), CF_MORE );
159+ }
160+
134161/*************************************************************************/
135162
136163/**
137- * Receive a transaction packet of at most CF_BUFSIZE-1 bytes, and
138- * NULL-terminate it.
164+ * Receive a single transaction packet of at most CF_BUFSIZE-1 bytes,
165+ * and NULL-terminate it.
139166 *
140167 * @param #buffer must be of size at least CF_BUFSIZE.
141168 *
@@ -146,8 +173,10 @@ int SendTransaction(ConnectionInfo *conn_info,
146173 * @TODO shutdown() the connection in all cases were this function returns -1,
147174 * in order to protect against future garbage reads.
148175 */
149- int ReceiveTransaction (ConnectionInfo * conn_info , char * buffer , int * more )
176+ static int ReceiveTransactionInner (ConnectionInfo * conn_info , char * buffer , int * more )
150177{
178+ assert (conn_info != NULL );
179+
151180 char proto [CF_INBAND_OFFSET + 1 ] = { 0 };
152181 int ret ;
153182
@@ -283,6 +312,52 @@ int ReceiveTransaction(ConnectionInfo *conn_info, char *buffer, int *more)
283312 return ret ;
284313}
285314
315+ /**
316+ * Receive a transaction packet, silently consuming any heartbeat
317+ * transactions (ENT-13699). Callers never see heartbeats.
318+ *
319+ * @see ReceiveTransactionInner() for parameter and return value details.
320+ */
321+ int ReceiveTransaction (ConnectionInfo * conn_info , char * buffer , int * more )
322+ {
323+ assert (conn_info != NULL );
324+
325+ time_t heartbeat_start = 0 ;
326+
327+ while (true)
328+ {
329+ int ret = ReceiveTransactionInner (conn_info , buffer , more );
330+ if (ret == -1 )
331+ {
332+ return -1 ;
333+ }
334+
335+ /* Silently consume heartbeat transactions so callers
336+ * never need to handle them. */
337+ if (ProtocolSupportsHeartbeat (conn_info -> protocol )
338+ && ret == (int ) sizeof ("HEARTBEAT" )
339+ && memcmp (buffer , "HEARTBEAT" , sizeof ("HEARTBEAT" )) == 0 )
340+ {
341+ if (heartbeat_start == 0 )
342+ {
343+ heartbeat_start = time (NULL );
344+ }
345+ if (time (NULL ) - heartbeat_start > MAX_HEARTBEAT_DURATION )
346+ {
347+ Log (LOG_LEVEL_WARNING ,
348+ "ReceiveTransaction: heartbeats exceeded %ds, aborting" ,
349+ MAX_HEARTBEAT_DURATION );
350+ conn_info -> status = CONNECTIONINFO_STATUS_BROKEN ;
351+ return -1 ;
352+ }
353+ Log (LOG_LEVEL_DEBUG , "ReceiveTransaction: heartbeat received" );
354+ continue ;
355+ }
356+
357+ return ret ;
358+ }
359+ }
360+
286361/* BWlimit global variables
287362
288363 Throttling happens for all network interfaces, all traffic being sent for
0 commit comments