Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 78 additions & 3 deletions libcfnet/net.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,14 @@
#include <misc_lib.h>
#include <cf3.defs.h>
#include <protocol.h>
#include <protocol_version.h>

/* Maximum seconds to spend consuming heartbeats before aborting.
* Slightly above the 120s wait gate timeout to allow for the
* legitimate use case. A count-based limit would be wrong here —
* a malicious peer could send 1 heartbeat every 29.9s (just under
* SO_RCVTIMEO) to hold the connection for hours. */
#define MAX_HEARTBEAT_DURATION 150

/* TODO remove libpromises dependency. */
extern char BINDINTERFACE[CF_MAXVARSIZE]; /* cf3globals.c, cf3.extern.h */
Expand Down Expand Up @@ -131,11 +138,31 @@ int SendTransaction(ConnectionInfo *conn_info,
}
}

/**
* Send a heartbeat transaction to keep the connection alive during
* long-running server-side operations.
*
* No-op if the protocol version does not support heartbeats.
* Heartbeats are silently consumed by ReceiveTransaction() on the
* receiving end - callers never see them.
*
* @return 0 on success (or no-op), -1 on error
*/
int SendHeartbeat(ConnectionInfo *conn_info)
{
assert(conn_info != NULL);
if (!ProtocolSupportsHeartbeat(conn_info->protocol))
{
return 0;
}
return SendTransaction(conn_info, "HEARTBEAT", sizeof("HEARTBEAT"), CF_MORE);
}

/*************************************************************************/

/**
* Receive a transaction packet of at most CF_BUFSIZE-1 bytes, and
* NULL-terminate it.
* Receive a single transaction packet of at most CF_BUFSIZE-1 bytes,
* and NULL-terminate it.
*
* @param #buffer must be of size at least CF_BUFSIZE.
*
Expand All @@ -146,8 +173,10 @@ int SendTransaction(ConnectionInfo *conn_info,
* @TODO shutdown() the connection in all cases were this function returns -1,
* in order to protect against future garbage reads.
*/
int ReceiveTransaction(ConnectionInfo *conn_info, char *buffer, int *more)
static int ReceiveTransactionInner(ConnectionInfo *conn_info, char *buffer, int *more)
{
assert(conn_info != NULL);

char proto[CF_INBAND_OFFSET + 1] = { 0 };
int ret;

Expand Down Expand Up @@ -283,6 +312,52 @@ int ReceiveTransaction(ConnectionInfo *conn_info, char *buffer, int *more)
return ret;
}

/**
* Receive a transaction packet, silently consuming any heartbeat
* transactions (ENT-13699). Callers never see heartbeats.
*
* @see ReceiveTransactionInner() for parameter and return value details.
*/
int ReceiveTransaction(ConnectionInfo *conn_info, char *buffer, int *more)
{
assert(conn_info != NULL);

time_t heartbeat_start = 0;

while (true)
{
int ret = ReceiveTransactionInner(conn_info, buffer, more);
if (ret == -1)
{
return -1;
}

/* Silently consume heartbeat transactions so callers
* never need to handle them. */
if (ProtocolSupportsHeartbeat(conn_info->protocol)
&& ret == (int) sizeof("HEARTBEAT")
&& memcmp(buffer, "HEARTBEAT", sizeof("HEARTBEAT")) == 0)
{
if (heartbeat_start == 0)
{
heartbeat_start = time(NULL);
}
if (time(NULL) - heartbeat_start > MAX_HEARTBEAT_DURATION)
{
Log(LOG_LEVEL_WARNING,
"ReceiveTransaction: heartbeats exceeded %ds, aborting",
MAX_HEARTBEAT_DURATION);
conn_info->status = CONNECTIONINFO_STATUS_BROKEN;
return -1;
}
Log(LOG_LEVEL_DEBUG, "ReceiveTransaction: heartbeat received");
continue;
}

return ret;
}
}

/* BWlimit global variables

Throttling happens for all network interfaces, all traffic being sent for
Expand Down
1 change: 1 addition & 0 deletions libcfnet/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ extern uint32_t bwlimit_kbytes;

int SendTransaction(ConnectionInfo *conn_info, const char *buffer, int len, char status);
int ReceiveTransaction(ConnectionInfo *conn_info, char *buffer, int *more);
int SendHeartbeat(ConnectionInfo *conn_info);

int SetReceiveTimeout(int fd, unsigned long ms);

Expand Down
4 changes: 4 additions & 0 deletions libcfnet/protocol_version.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ ProtocolVersion ParseProtocolVersionPolicy(const char *const s)
{
return CF_PROTOCOL_FILESTREAM;
}
else if (StringEqual(s, "5") || StringEqual(s, "heartbeat"))
{
return CF_PROTOCOL_HEARTBEAT;
}
else if (StringEqual(s, "latest"))
{
return CF_PROTOCOL_LATEST;
Expand Down
10 changes: 9 additions & 1 deletion libcfnet/protocol_version.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ typedef enum
CF_PROTOCOL_TLS = 2,
CF_PROTOCOL_COOKIE = 3,
CF_PROTOCOL_FILESTREAM = 4,
CF_PROTOCOL_HEARTBEAT = 5,
} ProtocolVersion;

/* We use CF_PROTOCOL_LATEST as the default for new connections. */
#define CF_PROTOCOL_LATEST CF_PROTOCOL_FILESTREAM
#define CF_PROTOCOL_LATEST CF_PROTOCOL_HEARTBEAT

static inline const char *ProtocolVersionString(const ProtocolVersion p)
{
Expand All @@ -57,6 +58,8 @@ static inline const char *ProtocolVersionString(const ProtocolVersion p)
return "classic";
case CF_PROTOCOL_FILESTREAM:
return "filestream";
case CF_PROTOCOL_HEARTBEAT:
return "heartbeat";
default:
return "undefined";
}
Expand Down Expand Up @@ -92,6 +95,11 @@ static inline bool ProtocolSupportsFileStream(const ProtocolVersion p)
return (p >= CF_PROTOCOL_FILESTREAM);
}

static inline bool ProtocolSupportsHeartbeat(const ProtocolVersion p)
{
return (p >= CF_PROTOCOL_HEARTBEAT);
}

static inline bool ProtocolTerminateCSV(const ProtocolVersion p)
{
return (p < CF_PROTOCOL_COOKIE);
Expand Down
Loading