From 9f276d1e64df78d9b4cf60d7055dbeaeb859ec3a Mon Sep 17 00:00:00 2001
From: Marius Tomaschewski <mt@suse.de>
Date: Mon, 4 Apr 2011 18:18:34 +0200
Subject: [PATCH] plain tcp syslog reliability improvement

Backported plain tcp syslog reliability improvement by doing a connection
check before sending. Credits to Martin Schuette for providing the idea.
Details are available at
http://blog.gerhards.net/2008/06/reliable-plain-tcp-syslog-once-again.html
The new "$ActionSendResendLastMsgOnReconnect on" option (default off) can
be used to resend the last message while a connection lost in costs of an
increased probability of message duplication.

Signed-off-by: Marius Tomaschewski <mt@suse.de>
---
 doc/rsyslog_conf.html |    1 +
 omfwd.c               |   31 +++++++++++++++++++++++++++++++
 tcpclt.c              |   36 ++++++++++++++++++++++++++----------
 tcpclt.h              |    4 +++-
 4 files changed, 61 insertions(+), 11 deletions(-)

diff --git a/doc/rsyslog_conf.html b/doc/rsyslog_conf.html
index 330fd51..5cb69f5 100644
--- a/doc/rsyslog_conf.html
+++ b/doc/rsyslog_conf.html
@@ -121,6 +121,7 @@ worker threads, default 1, recommended 1</li>
 <li><a href="rsconf1_actionresumeinterval.html">$ActionResumeInterval</a></li>
 <li>$ActionResumeRetryCount &lt;number&gt; [default 0,
 -1 means eternal]</li>
+<li>$ActionSendResendLastMsgOnReconnect &lt;[on/<b>off</b>]&gt; specifies if the last message is to be resend when a connecition breaks and has been reconnected. May increase reliability, but comes at the risk of message duplication.</li>
 <li><a href="rsconf1_allowedsender.html">$AllowedSender</a></li>
 <li><a href="rsconf1_controlcharacterescapeprefix.html">$ControlCharacterEscapePrefix</a></li>
 <li><a href="rsconf1_debugprintcfsyslinehandlerlist.html">$DebugPrintCFSyslineHandlerList</a></li>
diff --git a/omfwd.c b/omfwd.c
index 1f4d443..31a2782 100644
--- a/omfwd.c
+++ b/omfwd.c
@@ -92,6 +92,7 @@ typedef struct _instanceData {
 
 /* config data */
 static uchar	*pszTplName = NULL; /* name of the default template to use */
+static short bResendLastOnRecon = 0; /* should the last message be re-sent on a successful reconnect? */
 
 
 /* get the syslog forward port from selector_t. The passed in
@@ -206,6 +207,31 @@ static rsRetVal UDPSend(instanceData *pData, char *msg, size_t len)
 
 /* CODE FOR SENDING TCP MESSAGES */
 
+/* This function checks if the connection is still alive - well, kind of... It
+ * is primarily being used for plain TCP syslog and it is quite a hack. However,
+ * as it seems to work, it is worth supporting it. The bottom line is that it
+ * should not be called by anything else but a plain tcp syslog sender.
+ * In order for it to work, it must be called *immediately* *before* the send()
+ * call. For details about what is done, see here:
+ * http://blog.gerhards.net/2008/06/getting-bit-more-reliability-from-plain.html
+ * rgerhards, 2008-06-09
+ */
+static void
+CheckConnection(instanceData *pData)
+{
+	int rc;
+	char msgbuf[1]; /* dummy */
+
+	rc = recv(pData->sock, msgbuf, 1, MSG_DONTWAIT | MSG_PEEK);
+	if(rc == 0) {
+		dbgprintf("CheckConnection detected broken connection - closing it\n");
+		/* in this case, the remote peer had shut down the connection and we
+		 * need to close our side, too.
+		 */
+		close(pData->sock);
+		pData->sock = -1;
+	}
+}
 
 /* Send a frame via plain TCP protocol
  * rgerhards, 2007-12-28
@@ -216,6 +242,7 @@ static rsRetVal TCPSendFrame(void *pvData, char *msg, size_t len)
 	ssize_t lenSend;
 	instanceData *pData = (instanceData *) pvData;
 
+	CheckConnection(pData);
 	lenSend = send(pData->sock, msg, len, 0);
 	dbgprintf("TCP sent %ld bytes, requested %ld\n", (long) lenSend, (long) len);
 
@@ -576,6 +603,7 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1)
 		if(pData->protocol == FORW_TCP) {
 			/* create our tcpclt */
 			CHKiRet(tcpclt.Construct(&pData->pTCPClt));
+			CHKiRet(tcpclt.SetResendLastOnRecon(pData->pTCPClt, bResendLastOnRecon));
 			/* and set callbacks */
 			CHKiRet(tcpclt.SetSendInit(pData->pTCPClt, TCPSendInit));
 			CHKiRet(tcpclt.SetSendFrame(pData->pTCPClt, TCPSendFrame));
@@ -624,6 +652,8 @@ static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __a
 		pszTplName = NULL;
 	}
 
+	bResendLastOnRecon = 0;
+
 	return RS_RET_OK;
 }
 
@@ -637,6 +667,7 @@ CODEmodInit_QueryRegCFSLineHdlr
 	CHKiRet(objUse(tcpclt, LM_TCPCLT_FILENAME));
 
 	CHKiRet(regCfSysLineHdlr((uchar *)"actionforwarddefaulttemplate", 0, eCmdHdlrGetWord, NULL, &pszTplName, NULL));
+	CHKiRet(regCfSysLineHdlr((uchar *)"actionsendresendlastmsgonreconnect", 0, eCmdHdlrBinary, NULL, &bResendLastOnRecon, NULL));
 	CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID));
 ENDmodInit
 
diff --git a/tcpclt.c b/tcpclt.c
index 3a76e47..d97be43 100644
--- a/tcpclt.c
+++ b/tcpclt.c
@@ -307,16 +307,23 @@ Send(tcpclt_t *pThis, void *pData, char *msg, size_t len)
 			/* we are done, we also use this as indication that the previous
 			 * message was succesfully received (it's not always the case, but its at 
 			 * least our best shot at it -- rgerhards, 2008-03-12
+			 * As of 2008-06-09, we have implemented an algorithm which detects connection
+			 * loss quite good in some (common) scenarios. Thus, the probability of
+			 * message duplication due to the code below has increased. We so now have
+			 * a config setting, default off, that enables the user to request retransmits.
+			 * However, if not requested, we do NOT need to do all the stuff needed for it.
 			 */
-			if(pThis->prevMsg != NULL)
-				free(pThis->prevMsg);
-			/* if we can not alloc a new buffer, we silently ignore it. The worst that
-			 * happens is that we lose our message recovery buffer - anything else would
-			 * be worse, so don't try anything ;) -- rgerhards, 2008-03-12
-			 */
-			if((pThis->prevMsg = malloc(len)) != NULL) {
-				memcpy(pThis->prevMsg, msg, len);
-				pThis->lenPrevMsg = len;
+			if(pThis->bResendLastOnRecon == 1) {
+				if(pThis->prevMsg != NULL)
+					free(pThis->prevMsg);
+				/* if we can not alloc a new buffer, we silently ignore it. The worst that
+				 * happens is that we lose our message recovery buffer - anything else would
+				 * be worse, so don't try anything ;) -- rgerhards, 2008-03-12
+				 */
+				if((pThis->prevMsg = malloc(len)) != NULL) {
+					memcpy(pThis->prevMsg, msg, len);
+					pThis->lenPrevMsg = len;
+				}
 			}
 
 			/* we are done with this record */
@@ -326,7 +333,8 @@ Send(tcpclt_t *pThis, void *pData, char *msg, size_t len)
 				++retry;
 				CHKiRet(pThis->prepRetryFunc(pData)); /* try to recover */
 				/* now try to send our stored previous message (which most probably
-				 * didn't make it
+				 * didn't make it. Note that if bResendLastOnRecon is 0, prevMsg will
+				 * never become non-NULL, so the check below covers all cases.
 				 */
 				if(pThis->prevMsg != NULL) {
 					CHKiRet(pThis->initFunc(pData));
@@ -348,6 +356,13 @@ finalize_it:
 
 /* set functions */
 static rsRetVal
+SetResendLastOnRecon(tcpclt_t *pThis, int bResendLastOnRecon)
+{
+	DEFiRet;
+	pThis->bResendLastOnRecon = (short) bResendLastOnRecon;
+	RETiRet;
+}
+static rsRetVal
 SetSendInit(tcpclt_t *pThis, rsRetVal (*pCB)(void*))
 {
 	DEFiRet;
@@ -427,6 +442,7 @@ CODESTARTobjQueryInterface(tcpclt)
 	pIf->Send = Send;
 
 	/* set functions */
+	pIf->SetResendLastOnRecon = SetResendLastOnRecon;
 	pIf->SetSendInit = SetSendInit;
 	pIf->SetSendFrame = SetSendFrame;
 	pIf->SetSendPrepRetry = SetSendPrepRetry;
diff --git a/tcpclt.h b/tcpclt.h
index d2f1fe0..f61623c 100644
--- a/tcpclt.h
+++ b/tcpclt.h
@@ -34,6 +34,7 @@ typedef struct tcpclt_s {
 	BEGINobjInstance;	/**< Data to implement generic object - MUST be the first data element! */
 	TCPFRAMINGMODE tcp_framing;
 	char *prevMsg;
+	short bResendLastOnRecon; /* should the last message be resent on a successful reconnect? */
 	size_t lenPrevMsg;
 	/* session specific callbacks */
 	rsRetVal (*initFunc)(void*);
@@ -50,12 +51,13 @@ BEGINinterface(tcpclt) /* name must also be changed in ENDinterface macro! */
 	int (*Send)(tcpclt_t *pThis, void*pData, char*msg, size_t len);
 	int (*CreateSocket)(struct addrinfo *addrDest);
 	/* set methods */
+	rsRetVal (*SetResendLastOnRecon)(tcpclt_t*, int);
 	rsRetVal (*SetSendInit)(tcpclt_t*, rsRetVal (*)(void*));
 	rsRetVal (*SetSendFrame)(tcpclt_t*, rsRetVal (*)(void*, char*, size_t));
 	rsRetVal (*SetSendPrepRetry)(tcpclt_t*, rsRetVal (*)(void*));
 	rsRetVal (*SetFraming)(tcpclt_t*, TCPFRAMINGMODE framing);
 ENDinterface(tcpclt)
-#define tcpcltCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+#define tcpcltCURR_IF_VERSION 2 /* increment whenever you change the interface structure! */
 
 
 /* prototypes */
-- 
1.7.3.4

