# API and build change and fix summaries. Doc correctsions
# and/or changes are not mentioned here; see the commit messages.
+2020 April 17; version 3.8.0
+ Add safe connect to avoid potential connect bug on Linux (RIC-332)
+
+ Change debugging in route table collector to avoid possible
+ segment fault when in level 2 debug (RIC-335)
+
2020 April 15; version 3.7.4
Add missing message type to header file (RIC-334)
cmake_minimum_required( VERSION 3.5 )
set( major_version "3" ) # should be automatically populated from git tag later, but until CI process sets a tag we use this
-set( minor_version "7" )
-set( patch_level "4" )
+set( minor_version "8" )
+set( patch_level "0" )
set( install_root "${CMAKE_INSTALL_PREFIX}" )
set( install_inc "include/rmr" )
============================================================================================
-RMr Library
+RMR Library
============================================================================================
NAME
--------------------------------------------------------------------------------------------
-RMr -- Ric Message Router Library
+RMR -- Ric Message Router Library
DESCRIPTION
--------------------------------------------------------------------------------------------
-RMr is a library which provides a user application with the
-ability to send and receive messages to/from other RMr based
+RMR is a library which provides a user application with the
+ability to send and receive messages to/from other RMR based
applications without having to understand the underlying
messaging transport environment (e.g., SI95) and without
needing to know which other endpoint applications are
-currently available and accepting messages. To do this, RMr
+currently available and accepting messages. To do this, RMR
depends on a routing table generated by an external source.
This table is used to determine the destination endpoint of
each message sent by mapping the message type T (supplied by
the message, and in some cases whether that message was sent
to multiple applications.
-RMr functions do provide for the ability to respond to the
+RMR functions do provide for the ability to respond to the
specific source instance of a message allowing for either a
request response, or call response relationship when needed.
It is the responsibility of the route table generator to know
which endpoints belong to which groups, and which groups
accept which message types. Once understood, the route table
-generator publishes a table that is ingested by RMr and used
+generator publishes a table that is ingested by RMR and used
for mapping messages to end points.
The following is a simple route table which causes message
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To enable configuration of the library behaviour outside of
-direct user application control, RMr supports a number of
+direct user application control, RMR supports a number of
environment variables which provide information to the
library. The following is a list of the various environment
-variables, what they control and the defaults which RMr uses
+variables, what they control and the defaults which RMR uses
if undefined.
--------------------------------------------------------------------------------------------
+2020 April 17; version 3.8.0
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Add safe connect to avoid potential connect bug on Linux
+(RIC-332)
+
+Change debugging in route table collector to avoid possible
+segment fault when in level 2 debug (RIC-335)
+
+
+2020 April 15; version 3.7.4
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Add missing message type to header file (RIC-334)
+
+
+2020 April 14; version 3.7.3
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Fix bug in rmr_call() when using SI95 (RIC-333)
+
+
2020 April 10; version 3.7.2
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**without** any intervening calls to *sleep()* or *usleep().*
The number of retry loops defaults to 1, thus a maximum of
1000 send attempts is performed before returning to the user
-application. This value can be set at any point after RMr
+application. This value can be set at any point after RMR
initialisation using the *rmr_set_stimeout()* function
allowing the user application to completely disable retires
(set to 0), or to increase the number of retry loops.
The rmr_get_src function will copy the *source* information
from the message to a buffer (dest) supplied by the user. In
-an RMr message, the source is the sender's information that
+an RMR message, the source is the sender's information that
is used for return to sender function calls, and is generally
the hostname and port in the form *name*. The source might be
an IP address port combination; the data is populated by the
sending process and the only requirement is that it be
capable of being used to start a TCP session with the sender.
-The maximum size allowed by RMr is 64 bytes (including the
+The maximum size allowed by RMR is 64 bytes (including the
nil string terminator), so the user must ensure that the
destination buffer given is at least 64 bytes.
The rmr_get_srcip function will copy the *source IP address*
from the message to a buffer (dest) supplied by the user. In
-an RMr message, the source IP address is the sender's
+an RMR message, the source IP address is the sender's
information that is used for return to sender function calls;
this function makes it available to the user application. The
address is maintained as IP:port where *IP* could be either
an IPv6 or IPv4 address depending on what was provided by the
sending application.
-The maximum size allowed by RMr is 64 bytes (including the
+The maximum size allowed by RMR is 64 bytes (including the
nil string terminator), so the user must ensure that the
destination buffer given is at least 64 bytes. The user
-application should use the RMr constant RMR_MAX_SRC to ensure
+application should use the RMR constant RMR_MAX_SRC to ensure
that the buffer supplied is large enough, and to protect
-against future RMr enhancements which might increase the
+against future RMR enhancements which might increase the
address buffer size requirement.
RETURN VALUE
specifying the normal buffer size might be a larger memory
footprint.
-*Flags* allows for selection of some RMr options at the time
+*Flags* allows for selection of some RMR options at the time
of initialisation. These are set by ORing RMRFL constants
-from the RMr header file. Currently the following flags are
+from the RMR header file. Currently the following flags are
supported:
The route table collector thread is not to be started.
This should only be used by the route table generator
- application if it is based on RMr.
+ application if it is based on RMR.
RMRFL_MTCALL
and *rmr_mt_rcv()* function calls.
Multi-threaded call support requires the user application to
-specifically enable it when RMr is initialised. This is
+specifically enable it when RMR is initialised. This is
necessary because a second, dedicated, receiver thread must
be started, and requires all messages to be examined and
queued by this thread. The additional overhead is minimal,
-queuing information is all in the RMr message header, but as
+queuing information is all in the RMR message header, but as
an additional process is necessary the user application must
"opt in" to this approach.
--------------------------------------------------------------------------------------------
A value of 1 is returned on success, and 0 on failure. A
-failure indicates that the RMr context (a void pointer passed
+failure indicates that the RMR context (a void pointer passed
to this function was not valid.
SEE ALSO
**without** any intervening calls to *sleep()* or *usleep().*
The number of retry loops defaults to 1, thus a maximum of
1000 send attempts is performed before returning to the user
-application. This value can be set at any point after RMr
+application. This value can be set at any point after RMR
initialisation using the *rmr_set_stimeout()* function
allowing the user application to completely disable retires
(set to 0), or to increase the number of retry loops.
The rmr_mt_rcv function blocks until a message is received,
or the timeout period (milliseconds) has passed. The result
-is an RMr message buffer which references a received message.
+is an RMR message buffer which references a received message.
In the case of a timeout the state will be reflected in an
"empty buffer" (if old_msg was not nil, or simply with the
return of a nil pointer. If a timeout value of zero (0) is
--------------------------------------------------------------------------------------------
When a message is received before the timeout period expires,
-a pointer to the RMr message buffer which describes the
+a pointer to the RMR message buffer which describes the
message is returned. This will, with a high probability, be a
different message buffer than *old_msg;* the user application
should not continue to use *old_msg* after it is passed to
RMR_ERR_NOTSUPP
- The multi-threaded option was not enabled when RMr was
+ The multi-threaded option was not enabled when RMR was
initialised. See the man page for *rmr_init()* for
details.
**without** any intervening calls to *sleep()* or *usleep().*
The number of retry loops defaults to 1, thus a maximum of
1000 send attempts is performed before returning to the user
-application. This value can be set at any point after RMr
+application. This value can be set at any point after RMR
initialisation using the *rmr_set_stimeout()* function
allowing the user application to completely disable retires
(set to 0), or to increase the number of retry loops.
**without** any intervening calls to *sleep()* or *usleep().*
The number of retry loops defaults to 1, thus a maximum of
1000 send attempts is performed before returning to the user
-application. This value can be set at any point after RMr
+application. This value can be set at any point after RMR
initialisation using the *rmr_set_stimeout()* function
allowing the user application to completely disable retires
(set to 0), or to increase the number of retry loops.
--------------------------------------------------------------------------------------------
The rmr_set_stimeout function sets the configuration for how
-RMr will retry message send operations which complete with
+RMR will retry message send operations which complete with
either a *timeout* or *again* completion value. (Send
operations include all of the possible message send
functions: *rmr_send_msg(), rmr_call(), rmr_rts_msg()* and
*rmr_wh_send_msg().* The *rloops* parameter sets the maximum
number of retry loops that will be attempted before giving up
and returning the unsuccessful state to the user application.
-Each retry loop is approximately 1000 attempts, and RMr does
+Each retry loop is approximately 1000 attempts, and RMR does
**not** invoke any sleep function between retries in the
loop; a small, 1 mu-sec, sleep is executed between loop sets
if the *rloops* value is greater than 1.
when the underlying transport mechanism indicates *timeout*
or *again,* the application should invoke this function and
pass a value of 0 (zero) for *rloops.* With this setting, all
-RMr send operations will attempt a send operation only
+RMR send operations will attempt a send operation only
**once,** returning immediately to the caller with the state
of that single attempt.
**without** any intervening calls to *sleep()* or *usleep().*
The number of retry loops defaults to 1, thus a maximum of
1000 send attempts is performed before returning to the user
-application. This value can be set at any point after RMr
+application. This value can be set at any point after RMR
initialisation using the *rmr_set_stimeout()* function
allowing the user application to completely disable retires
(set to 0), or to increase the number of retry loops.
--------------------------------------------------------------------------------------------
The rmr_wh_open function creates a direct link for sending, a
-wormhole, to another RMr based process. Sending messages
+wormhole, to another RMR based process. Sending messages
through a wormhole requires that the connection be
established overtly by the user application (via this
function), and that the ID returned by rmr_wh_open be passed
*Target* is the *name* or *IP-address* combination of the
processess that the wormhole should be connected to. *Vctx*
-is the RMr void context pointer that was returned by the
+is the RMR void context pointer that was returned by the
rmr_init function.
When invoked, this function immediatly attempts to connect to
**without** any intervening calls to *sleep()* or *usleep().*
The number of retry loops defaults to 1, thus a maximum of
1000 send attempts is performed before returning to the user
-application. This value can be set at any point after RMr
+application. This value can be set at any point after RMR
initialisation using the *rmr_set_stimeout()* function
allowing the user application to completely disable retires
(set to 0), or to increase the number of retry loops.
**without** any intervening calls to *sleep()* or *usleep().*
The number of retry loops defaults to 1, thus a maximum of
1000 send attempts is performed before returning to the user
-application. This value can be set at any point after RMr
+application. This value can be set at any point after RMR
initialisation using the *rmr_set_stimeout()* function
allowing the user application to completely disable retires
(set to 0), or to increase the number of retry loops.
The rmr_get_src function will copy the *source* information
from the message to a buffer (dest) supplied by the user. In
-an RMr message, the source is the sender's information that
+an RMR message, the source is the sender's information that
is used for return to sender function calls, and is generally
the hostname and port in the form *name*. The source might be
an IP address port combination; the data is populated by the
sending process and the only requirement is that it be
capable of being used to start a TCP session with the sender.
-The maximum size allowed by RMr is 64 bytes (including the
+The maximum size allowed by RMR is 64 bytes (including the
nil string terminator), so the user must ensure that the
destination buffer given is at least 64 bytes.
The rmr_get_srcip function will copy the *source IP address*
from the message to a buffer (dest) supplied by the user. In
-an RMr message, the source IP address is the sender's
+an RMR message, the source IP address is the sender's
information that is used for return to sender function calls;
this function makes it available to the user application. The
address is maintained as IP:port where *IP* could be either
an IPv6 or IPv4 address depending on what was provided by the
sending application.
-The maximum size allowed by RMr is 64 bytes (including the
+The maximum size allowed by RMR is 64 bytes (including the
nil string terminator), so the user must ensure that the
destination buffer given is at least 64 bytes. The user
-application should use the RMr constant RMR_MAX_SRC to ensure
+application should use the RMR constant RMR_MAX_SRC to ensure
that the buffer supplied is large enough, and to protect
-against future RMr enhancements which might increase the
+against future RMR enhancements which might increase the
address buffer size requirement.
RETURN VALUE
specifying the normal buffer size might be a larger memory
footprint.
-*Flags* allows for selection of some RMr options at the time
+*Flags* allows for selection of some RMR options at the time
of initialisation. These are set by ORing RMRFL constants
-from the RMr header file. Currently the following flags are
+from the RMR header file. Currently the following flags are
supported:
The route table collector thread is not to be started.
This should only be used by the route table generator
- application if it is based on RMr.
+ application if it is based on RMR.
RMRFL_MTCALL
and *rmr_mt_rcv()* function calls.
Multi-threaded call support requires the user application to
-specifically enable it when RMr is initialised. This is
+specifically enable it when RMR is initialised. This is
necessary because a second, dedicated, receiver thread must
be started, and requires all messages to be examined and
queued by this thread. The additional overhead is minimal,
-queuing information is all in the RMr message header, but as
+queuing information is all in the RMR message header, but as
an additional process is necessary the user application must
"opt in" to this approach.
--------------------------------------------------------------------------------------------
A value of 1 is returned on success, and 0 on failure. A
-failure indicates that the RMr context (a void pointer passed
+failure indicates that the RMR context (a void pointer passed
to this function was not valid.
SEE ALSO
**without** any intervening calls to *sleep()* or *usleep().*
The number of retry loops defaults to 1, thus a maximum of
1000 send attempts is performed before returning to the user
-application. This value can be set at any point after RMr
+application. This value can be set at any point after RMR
initialisation using the *rmr_set_stimeout()* function
allowing the user application to completely disable retires
(set to 0), or to increase the number of retry loops.
The rmr_mt_rcv function blocks until a message is received,
or the timeout period (milliseconds) has passed. The result
-is an RMr message buffer which references a received message.
+is an RMR message buffer which references a received message.
In the case of a timeout the state will be reflected in an
"empty buffer" (if old_msg was not nil, or simply with the
return of a nil pointer. If a timeout value of zero (0) is
--------------------------------------------------------------------------------------------
When a message is received before the timeout period expires,
-a pointer to the RMr message buffer which describes the
+a pointer to the RMR message buffer which describes the
message is returned. This will, with a high probability, be a
different message buffer than *old_msg;* the user application
should not continue to use *old_msg* after it is passed to
RMR_ERR_NOTSUPP
- The multi-threaded option was not enabled when RMr was
+ The multi-threaded option was not enabled when RMR was
initialised. See the man page for *rmr_init()* for
details.
**without** any intervening calls to *sleep()* or *usleep().*
The number of retry loops defaults to 1, thus a maximum of
1000 send attempts is performed before returning to the user
-application. This value can be set at any point after RMr
+application. This value can be set at any point after RMR
initialisation using the *rmr_set_stimeout()* function
allowing the user application to completely disable retires
(set to 0), or to increase the number of retry loops.
**without** any intervening calls to *sleep()* or *usleep().*
The number of retry loops defaults to 1, thus a maximum of
1000 send attempts is performed before returning to the user
-application. This value can be set at any point after RMr
+application. This value can be set at any point after RMR
initialisation using the *rmr_set_stimeout()* function
allowing the user application to completely disable retires
(set to 0), or to increase the number of retry loops.
--------------------------------------------------------------------------------------------
The rmr_set_stimeout function sets the configuration for how
-RMr will retry message send operations which complete with
+RMR will retry message send operations which complete with
either a *timeout* or *again* completion value. (Send
operations include all of the possible message send
functions: *rmr_send_msg(), rmr_call(), rmr_rts_msg()* and
*rmr_wh_send_msg().* The *rloops* parameter sets the maximum
number of retry loops that will be attempted before giving up
and returning the unsuccessful state to the user application.
-Each retry loop is approximately 1000 attempts, and RMr does
+Each retry loop is approximately 1000 attempts, and RMR does
**not** invoke any sleep function between retries in the
loop; a small, 1 mu-sec, sleep is executed between loop sets
if the *rloops* value is greater than 1.
when the underlying transport mechanism indicates *timeout*
or *again,* the application should invoke this function and
pass a value of 0 (zero) for *rloops.* With this setting, all
-RMr send operations will attempt a send operation only
+RMR send operations will attempt a send operation only
**once,** returning immediately to the caller with the state
of that single attempt.
**without** any intervening calls to *sleep()* or *usleep().*
The number of retry loops defaults to 1, thus a maximum of
1000 send attempts is performed before returning to the user
-application. This value can be set at any point after RMr
+application. This value can be set at any point after RMR
initialisation using the *rmr_set_stimeout()* function
allowing the user application to completely disable retires
(set to 0), or to increase the number of retry loops.
--------------------------------------------------------------------------------------------
The rmr_wh_open function creates a direct link for sending, a
-wormhole, to another RMr based process. Sending messages
+wormhole, to another RMR based process. Sending messages
through a wormhole requires that the connection be
established overtly by the user application (via this
function), and that the ID returned by rmr_wh_open be passed
*Target* is the *name* or *IP-address* combination of the
processess that the wormhole should be connected to. *Vctx*
-is the RMr void context pointer that was returned by the
+is the RMR void context pointer that was returned by the
rmr_init function.
When invoked, this function immediatly attempts to connect to
**without** any intervening calls to *sleep()* or *usleep().*
The number of retry loops defaults to 1, thus a maximum of
1000 send attempts is performed before returning to the user
-application. This value can be set at any point after RMr
+application. This value can be set at any point after RMR
initialisation using the *rmr_set_stimeout()* function
allowing the user application to completely disable retires
(set to 0), or to increase the number of retry loops.
write_level = RMR_VL_DEBUG;
}
+ memset( msg, 0, sizeof( msg ) ); // logging is slow; this ensures 0 term if msg is too large
hlen = snprintf( msg, sizeof( msg ), "%ld %d/RMR [%s] ", (long) time( NULL ), log_pid, log_situations[write_level] );
body = msg + hlen;
rmr_vlog_init();
}
- if( log_vlevel <= 0 ) { // cant force if off completely to allow for total silience
+ if( log_vlevel <= 0 ) { // can force if off completely to allow for total silience
return;
}
if( msg != NULL && msg->len > 0 ) {
payload = msg->payload;
mlen = msg->len; // usable bytes in the payload
- if( vlevel > 1 ) {
- rmr_vlog_force( RMR_VL_DEBUG, "rmr_rtc: received rt message type=%d len=%d bytes (%s)\n", msg->mtype, (int) mlen, msg->payload );
- } else {
- if( DEBUG > 1 || (vlevel > 0) ) rmr_vlog( RMR_VL_DEBUG, "rmr_rtc: received rt message type=%d len=%d\n", msg->mtype, (int) mlen );
- }
+ if( DEBUG > 1 || (vlevel > 0) ) rmr_vlog( RMR_VL_DEBUG, "rmr_rtc: received rt message type=%d len=%d\n", msg->mtype, (int) mlen );
switch( msg->mtype ) {
case RMRRM_TABLE_DATA:
if( (flags & RTCFL_HAVE_UPDATE) == 0 ) {
}
memcpy( pbuf, payload, mlen );
pbuf[mlen] = 0; // don't depend on sender making this a legit string
+ if( vlevel > 1 ) {
+ rmr_vlog_force( RMR_VL_DEBUG, "rmr_rtc: rt message: (%s)\n", pbuf );
+ }
curr = pbuf;
while( curr ) { // loop over each record in the buffer
***************************************************************************
*
* Mnemonic: SIconnect
-* Abstract: Start a TCP/IP session with another process.
-* Parms:
-* addr - Pointer to a string containing the process' address
-* The address is either ipv4 or ipv6 formmat with the
-* port number separated with a semicolon (::1;4444,
-* localhost;4444 climber;4444 129.168.0.4;4444).
-* Returns: The session number if all goes well, SI_ERROR if not.
+* Abstract: This module contains functions to make the connection using
+* a transport block which has been given a transport (tcp) family
+* address structure.
*
* Date: March 1995
* Author: E. Scott Daniels
*
-* Mod: 08 Mar 2007 - conversion of sorts to support ipv6
+* Mod: 08 Mar 2007 - conversion of sorts to support ipv6
+* 17 Apr 2020 - Add safe connect capabilities
******************************************************************************
*/
#include "sisetup.h"
#include "sitransport.h"
+// ---------------- internal functions ----------------------------------------------
+
+/*
+ Attempts a connection to addr, and ensures that the linux even port
+ connect bug does not establish the connection back to our process.
+ If we detect this, then we abort the connection and return -1. 0
+ returned on a good connection.
+
+ If we are hit with the even port connection bug we have no choice but
+ to abort the connection which will CLOSE the caller's FD. This may
+ not be expected in some situations, and when we do the errno is set to
+ EBADFD to indicate this. We ensure that this state is returned ONLY
+ if the failure results in a closed fd which the caller will need to reopen.
+*/
+int safe_connect( int fd, struct sockaddr* addr, int alen ) {
+ int state = 0;
+ char caddr[255]; // work buffer for get sock name (v6 addr ~28 bytes, so this is plenty)
+ int calen; // len of the connect generated address
+
+ if( (state = CONNECT( fd, addr, alen )) != 0 ) {
+ if( errno == EBADFD ) { // ensure we return bad fd ONLY if we abort later
+ errno = ECONNABORTED;
+ }
+ return state;
+ }
+
+ if( PARANOID_CHECKS ) {
+ if( alen > sizeof( caddr ) ) { // shouldn't happen, but be safe
+ fprintf( stderr, "safe_connect: address buffer for connect exceeds work space %d > %lu\n", alen, sizeof( caddr ) );
+ errno = E2BIG;
+ return -1;
+ }
+ }
+
+ calen = alen; // we assume a bound address will have the same type, and thus len, as the connect to addr
+ if( getsockname( fd, (struct sockaddr *) &caddr, &calen ) == 0 ) {
+ if( calen != alen || memcmp( addr, &caddr, calen ) != 0 ) { // addresses differ, so it's good
+ errno = 0;
+ return 0;
+ }
+ }
+
+ siabort_conn( fd );
+ errno = EBADFD;
+ return -1;
+}
+
/*
Accept a file descriptor and add it to the map.
*/
}
}
+/*
+ Creates a connection to the target endpoint using the address in the
+ buffer provided. The address may be one of three forms:
+ hostname:port
+ IPv4-address:port
+ [IPv6-address]:port
+
+ On success the open file descriptor is returned; else -1 is returned. Errno
+ will be left set by the underlying connect() call.
+
+ To avoid the even port connect bug in the linux connect() systeem call,
+ we will use safe_connect() if indicated during the connection prep
+ process.
+*/
extern int SIconnect( struct ginfo_blk *gptr, char *abuf ) {
int status;
- struct tp_blk *tpptr; // pointer to new block
+ struct tp_blk *tpptr; // pointer to new block
struct sockaddr *taddr; // convenience pointer to addr of target
- int alen = 0; // len of address struct
- int fd = SI_ERROR; // file descriptor to return to caller
+ int alen = 0; // len of address struct
+ int fd = SI_ERROR; // file descriptor to return to caller
if( PARANOID_CHECKS ) {
if( gptr == NULL ) {
if( tpptr != NULL ) {
taddr = tpptr->paddr;
errno = 0;
- if( connect( tpptr->fd, taddr, tpptr->palen ) != 0 ) {
- close( tpptr->fd ); // clean up fd and tp_block
- SItrash( TP_BLK, tpptr ); // free the trasnsport block
- fd = SI_ERROR; // send bad session id num back
- } else { // connect ok
- tpptr->flags |= TPF_SESSION; // indicate we have a session here
- tpptr->next = gptr->tplist; // add block to the list
+ if( tpptr->flags & TPF_SAFEC ) {
+ if( safe_connect( tpptr->fd, taddr, tpptr->palen ) != 0 ) { // fd closed on failure
+ SItrash( TP_BLK, tpptr );
+ tpptr->fd = -1;
+ }
+ } else {
+ if( CONNECT( tpptr->fd, taddr, tpptr->palen ) != 0 ) {
+ CLOSE( tpptr->fd ); // clean up fd and tp_block
+ tpptr->fd = -1;
+ SItrash( TP_BLK, tpptr ); // free the trasnsport block
+ }
+ }
+
+ if( tpptr->fd >= 0 ) { // connect ok
+ tpptr->flags |= TPF_SESSION; // indicate we have a session here
+ tpptr->next = gptr->tplist; // add block to the list
if( tpptr->next != NULL ) {
- tpptr->next->prev = tpptr; // if there - point back at new
+ tpptr->next->prev = tpptr; // if there - point back at new
}
- gptr->tplist = tpptr; // point at new head
- fd = tpptr->fd; // save for return value
+ gptr->tplist = tpptr; // point at new head
+ fd = tpptr->fd; // save for return value
SImap_fd( gptr, fd, tpptr );
}
}
- return fd;
-}
+ return fd;
+}
#define DEBUG 0
#endif
+#ifndef PARANOID_CHECKS
+# define PARANOID_CHECKS 0
+#endif
//#define EOS '\000' // end of string marker
#define TPF_UNBIND 0x04 // SIterm should unbind the fd
#define TPF_DRAIN 0x08 // session is being drained
#define TPF_DELETE 0x10 // block is ready for deletion -- when safe
+#define TPF_SAFEC 0x20 // use safe connect when connecting
+#define TPF_ABORT 0x40 // connection should be aborted at termination
#define MAX_CBS 8 // number of supported callbacks in table
#define MAX_RBUF 8192 // max size of receive buffer
*/
-#include "sisetup.h" // include the necessary setup stuff
+#include "sisetup.h" // include the necessary setup stuff
#include "sitransport.h"
#include <errno.h>
#include <netinet/tcp.h>
Returns a transport struct which is the main context for the listener.
*/
extern struct tp_blk *SIlisten_prep( struct ginfo_blk *gptr, int type, char* abuf, int family ) {
- struct tp_blk *tptr; // pointer at new tp block
- int status = SI_OK; // processing status
- struct sockaddr *addr; // IP address we are requesting
- int protocol; // protocol for socket call
- char buf[256]; // buffer to build request address in
+ struct tp_blk *tptr; // pointer at new tp block
+ int status = SI_OK; // processing status
+ struct sockaddr *addr; // IP address we are requesting
+ int protocol; // protocol for socket call
+ char buf[256]; // buffer to build request address in
int optval = 0;
int alen = 0;
- tptr = (struct tp_blk *) SInew( TP_BLK ); // new transport info block
+ tptr = (struct tp_blk *) SInew( TP_BLK ); // new transport info block
if( tptr != NULL )
{
addr = NULL;
- switch( type ) // things specifc to tcp or udp
+ switch( type ) // things specifc to tcp or udp
{
case UDP_DEVICE:
tptr->type = SOCK_DGRAM;
protocol = IPPROTO_TCP;
}
- alen = SIgenaddr( abuf, protocol, family, tptr->type, &addr ); // family == 0 for type that suits the address passed in
+ alen = SIgenaddr( abuf, protocol, family, tptr->type, &addr ); // family == 0 for type that suits the address passed in
if( alen <= 0 ) {
return NULL;
}
status = BIND( tptr->fd, (struct sockaddr *) addr, alen );
if( status == SI_OK ) {
- tptr->addr = addr; // save address
+ tptr->addr = addr; // save address
} else {
fprintf( stderr, ">>>>> siestablish: bind failed: fam=%d type=%d pro=%d %s\n", tptr->family, tptr->type, protocol, strerror( errno ) );
close( tptr->fd );
}
} else {
- status = ! SI_OK; // force bad return later
+ status = ! SI_OK; // force bad return later
fprintf( stderr, ">>>>> siestablish: socket not esablished: fam=%d type=%d pro=%d %s\n", tptr->family, tptr->type, protocol, strerror( errno ) );
}
- if( status != SI_OK ) { // socket or bind call failed - clean up stuff
+ if( status != SI_OK ) { // socket or bind call failed - clean up stuff
fprintf( stderr, ">>>>> siestablish: bad state -- returning nil pointer\n" );
free( addr );
- SItrash( TP_BLK, tptr ); // free the trasnsport block
- tptr = NULL; // set to return nothing
+ SItrash( TP_BLK, tptr ); // free the trasnsport block
+ tptr = NULL; // set to return nothing
}
}
return tptr;
}
+/*
+ Look at the address and determine if the connect attempt to this address must
+ use safe_connect() rather than the system connect() call. On linux, a smart
+ connect is needed if the target port is >32K and is even. This makes the assumption
+ that the local port rage floor is 32K; we could read something in /proc, but
+ at this point won't bother. Returns true if we determine that it is best to
+ use safe_connect().
+*/
+static int need_smartc( char* abuf ) {
+ char* tok;
+ int state = 1;
+ int v;
+
+ if( (tok = strchr( abuf, ':')) != NULL ) {
+ v = atoi( tok+1 );
+ if( v < 32767 || v % 2 != 0 ) {
+ state = 0;
+ }
+ }
+
+ return state;
+}
+
/*
Prep a socket to use to connect to a listener.
Establish a transport block and target address in prep to connect.
family of 0 (AF_ANY) is usually the best choice.
*/
extern struct tp_blk *SIconn_prep( struct ginfo_blk *gptr, int type, char *abuf, int family ) {
- struct tp_blk *tptr; // pointer at new tp block
- struct sockaddr *addr; // IP address we are requesting
- int protocol; // protocol for socket call
- char buf[256]; // buffer to build request address in
+ struct tp_blk *tptr; // pointer at new tp block
+ struct sockaddr *addr; // IP address we are requesting
+ int protocol; // protocol for socket call
+ char buf[256]; // buffer to build request address in
int optval = 0;
int alen = 0;
- tptr = (struct tp_blk *) SInew( TP_BLK ); // new transport info block
+ tptr = (struct tp_blk *) SInew( TP_BLK ); // new transport info block
if( tptr != NULL )
{
addr = NULL;
- switch( type ) // things specifc to tcp or udp
+ switch( type ) // things specifc to tcp or udp
{
case UDP_DEVICE:
tptr->type = SOCK_DGRAM;
protocol = IPPROTO_TCP;
}
- alen = SIgenaddr( abuf, protocol, family, tptr->type, &addr ); // family == 0 for type that suits the address passed in
+ alen = SIgenaddr( abuf, protocol, family, tptr->type, &addr ); // family == 0 for type that suits the address passed in
if( alen <= 0 )
{
//fprintf( stderr, ">>>>> siconn_prep: error generating an address struct for %s(abuf) %d(proto) %d(type): %s\n",
if( (tptr->fd = SOCKET( tptr->family, tptr->type, protocol )) >= SI_OK ) {
optval = 1;
- if( SO_REUSEPORT ) {
- SETSOCKOPT( tptr->fd, SOL_SOCKET, SO_REUSEPORT, (char *)&optval, sizeof( optval) );
- }
-
if( gptr->tcp_flags & SI_TF_NODELAY ) {
optval = 1;
} else {
optval = 0;
}
- //fprintf( stderr, ">>>>> conn_prep: setting no delay = %d\n", optval );
SETSOCKOPT( tptr->fd, SOL_TCP, TCP_NODELAY, (void *)&optval, sizeof( optval) ) ;
if( gptr->tcp_flags & SI_TF_FASTACK ) {
} else {
optval = 0;
}
- //fprintf( stderr, ">>>>> conn_prep: setting quick ack = %d\n", optval );
SETSOCKOPT( tptr->fd, SOL_TCP, TCP_QUICKACK, (void *)&optval, sizeof( optval) ) ;
tptr->paddr = addr; // tuck the remote peer address away
+ if( need_smartc( abuf ) ) {
+ tptr->flags |= TPF_SAFEC;
+ }
} else {
- //fprintf( stderr, ">>>>> conn_prep: bad socket create: %s\n", strerror( errno ) );
free( addr );
- SItrash( TP_BLK, tptr ); // free the trasnsport block
+ SItrash( TP_BLK, tptr ); // free the trasnsport block
tptr = NULL; // we'll return nil
}
}
newtp = SInew( TP_BLK ); // get a new tp block for the session
if( newtp == NULL ) {
- close( status ); // must disconnect the other side
+ CLOSE( status ); // must disconnect the other side
free( addr );
return SI_ERROR;
}
#ifndef _si_proto_h
#define _si_proto_h
+extern void siabort_conn( int fd ); // use by applications discouraged
+
extern void *SInew( int type );
extern char *sigetname( int sid );
+extern void SIabort( struct ginfo_blk *gptr );
extern int SIaddress( void *src, void **dest, int type );
extern void SIbldpoll( struct ginfo_blk* gptr );
extern struct tp_blk *SIconn_prep( struct ginfo_blk *gptr, int type, char *abuf, int family );
******************************************************************************
*
* Mnemonic: SIshutdown
-* Abstract: This routine will ensure that all tp blocks have been closed
-* with the transport provider and removed from the list. The
-* shutdown flag is set in addition.
-* Parms: gptr - pointer to the ginfo structure (SIHANDLE)
-* Retrns: Nothing.
+* Abstract: Shutdown and abort functions.
+*
* Date: 23 March 1995
* Author: E. Scott Daniels
*
*****************************************************************************
*/
-#include "sisetup.h" // get includes and defines
+#include "sisetup.h" // get includes and defines
+
+/*
+*/
+static void sishutdown( struct ginfo_blk *gptr, int flags ) {
+ struct tp_blk* tpb;
-extern void SIshutdown( struct ginfo_blk *gptr ) {
if( gptr != NULL && gptr->magicnum == MAGICNUM )
{
- gptr->flags |= GIF_SHUTDOWN; // signal shutdown
- while( gptr->tplist != NULL )
+ gptr->flags |= GIF_SHUTDOWN; // signal shutdown
+ for( tpb = gptr->tplist; tpb != NULL; tpb = tpb->next )
{
- gptr->tplist->flags |= TPF_UNBIND; // force unbind on session
- SIterm( gptr, gptr->tplist ); // and drop the session
+ tpb->flags |= (TPF_UNBIND | flags); // force unbind on session and set caller flags
+ SIterm( gptr, tpb ); // term marks ok to delete but does NOT remove it
}
}
-}
+}
+
+/*
+ Run the list of known transport sessions and close them gracefully. This
+ will result in time-waits which might prevent the application from
+ restarting immediately as the listen port(s) might not be usable.
+*/
+extern void SIshutdown( struct ginfo_blk *gptr ) {
+ sishutdown( gptr, 0 );
+}
+
+/*
+ Run the list of known transport sessions and close them by aborting
+ (resetting the connection). This can result in buffered, but untransmitted,
+ data from being lost; the risk should be known by the caller.
+*/
+extern void SIabort( struct ginfo_blk *gptr ) {
+ sishutdown( gptr, TPF_ABORT );
+}
* Mnemonic: SIterm
* Abstract: Manage the transport provider block information relating to
* the need to terminate the session. The block is left in the
-* list; it is unsafe to clean the lsit up outside of the SIwait
+* list; it is unsafe to clean the lsit up outside of the SIwait
* thread. When safe, the SIrm_tpb() function can be called to
* do the rest of the work that was originally done by SIterm.
*
*
**************************************************************************
*/
-#include "sisetup.h" // get the setup stuff
+#include "sisetup.h" // get the setup stuff
#include "sitransport.h"
+
+/*
+ Abort the connection in such a way that there is no resulting time-wait state.
+ This should be used cautiously but is needed for situations like when the Linux
+ connect() system call manages to connect us to ourselves through the even number
+ port bug.
+
+ This needs a real file desc as there may not yet be a transport block when
+ the connection may need to be aborted. For this reason, the function name is
+ lower case indicating that user programmes are discouraged from using this
+ function directly.
+*/
+extern void siabort_conn( int fd ) {
+ struct linger opt_val; // value passed as option to set call
+
+ opt_val.l_onoff = 1; // MUST set linger on with a zero len timeout
+ opt_val.l_linger = 0;
+
+ setsockopt( fd, SOL_SOCKET, SO_LINGER, &opt_val, sizeof( opt_val ) ); // disable linger to prevent time-wait
+ CLOSE( fd ); // close will now abort and not result in time-wait (do NOT use shutdown() first!)
+}
+
/*
Close the FD and mark the transport block as unusable/closed.
Removal of the block from the list is safe only from the siwait
- thread.
+ thread. If the abort flag is set in the transport block, then the
+ connection is aborted (reset).
*/
extern void SIterm( struct ginfo_blk* gptr, struct tp_blk *tpptr ) {
if( tpptr != NULL ) {
if( tpptr->fd >= 0 ) {
- CLOSE( tpptr->fd );
+ if( tpptr->flags & TPF_ABORT ) {
+ siabort_conn( tpptr->fd );
+ } else {
+ CLOSE( tpptr->fd );
+ }
+
if( tpptr->fd < MAX_FDS ) {
gptr->tp_map[tpptr->fd] = NULL; // drop reference
}
/*
It is safe to remove the block from the list; if it was in the list
- in the first place.
+ in the first place.
*/
extern void SIrm_tpb( struct ginfo_blk *gptr, struct tp_blk *tpptr ) {
if( tpptr != NULL ) {
if( tpptr->prev != NULL || tpptr->next != NULL ) { // in the list
- if( tpptr->prev != NULL ) { // remove from the list
- tpptr->prev->next = tpptr->next; // point previous at the next
+ if( tpptr->prev != NULL ) { // remove from the list
+ tpptr->prev->next = tpptr->next; // point previous at the next
} else {
- gptr->tplist = tpptr->next; // this was head, make next new head
+ gptr->tplist = tpptr->next; // this was head, make next new head
}
-
+
if( tpptr->next != NULL ) {
- tpptr->next->prev = tpptr->prev; // point next one back behind this one
+ tpptr->next->prev = tpptr->prev; // point next one back behind this one
}
}
- free( tpptr->addr ); // release the address bufers
+ free( tpptr->addr ); // release the address bufers
free( tpptr->paddr );
- free( tpptr ); // and release the block
+ free( tpptr ); // and release the block
}
}
#ifndef _SOCKET_IF_H
#define _SOCKET_IF_H
-#ifndef PARANOID_CHECKS
-# define PARANOID_CHECKS 0
-#endif
-
#define TCP_DEVICE 0 // device type of socket
#define UDP_DEVICE 1
build=""
errors=0
-si_flag="" # eventually we'll default to -S to run SI tests over NNG tests
+si_flag="-S" # default to -S to prefer to run SI tests over NNG tests
src_root="../.."
if [[ -z $BUILD_PATH ]] # if not explicitly set, assume one of our standard spots
#define NO_EMULATION 1 // no emulation of transport functions
#define NO_PRIVATE_HEADERS 1 // no rmr_si or rmr_nng headers
#define NO_DUMMY_RMR 1 // no msg things
-#include "test_support.c" // things like fail_if()
+#include "test_support.c" // things like fail_if()
+#include "test_transport_em.c" // system/transport emulation (open, close, connect, etc)
/*
#include "rmr.h" // things the users see
//#include <si95/sigetadd.c>
//#include <si95/sigetname.c>
#include <si95/siinit.c>
-//#include <si95/silisten.c>
+#include <si95/silisten.c>
#include <si95/sinew.c>
//#include <si95/sinewses.c>
//#include <si95/sipoll.c>
SIclr_tflags( si_ctx, 0x00 ); // drive for coverage; no return value from these
SIset_tflags( si_ctx, 0x03 );
+ fprintf( stderr, "<INFO> init module finished with %d errors\n", errors );
return errors;
}
return 0;
}
+ SItp_stats( si_ctx ); // drive for coverage only
+ SItp_stats( NULL );
+
SIconnect( si_ctx, "localhost:43086" ); // ensure context has a tp block to free on shutdown
SIshutdown( si_ctx );
+
+ fprintf( stderr, "<INFO> cleanup module finished with %d errors\n", errors );
return errors;
}
dest = NULL;
snprintf( buf1, sizeof( buf1 ), " [ff02::5:4001" ); // invalid address, drive leading space eater too
- l = SIaddress( buf1, &dest, AC_TOADDR6 );
+ l = SIaddress( buf1, (void **) &dest, AC_TOADDR6 );
errors += fail_if_true( l > 0, "to addr6 with bad addr convdersion returned valid len" );
snprintf( buf1, sizeof( buf1 ), "[ff02::5]:4002" ); // v6 might not be supported so failure is OK here
- l=SIaddress( buf1, &dest, AC_TOADDR6 );
+ l=SIaddress( buf1, (void **) &dest, AC_TOADDR6 );
errors += fail_if_true( l < 1, "to addr convdersion failed" );
snprintf( buf1, sizeof( buf1 ), "localhost:43086" );
errors += fail_if_true( l < 1, "to addr convdersion failed" );
snprintf( buf1, sizeof( buf1 ), "localhost:4004" );
- l = SIaddress( buf1, &dest, AC_TODOT );
+ l = SIaddress( buf1, (void **) &dest, AC_TODOT );
errors += fail_if_true( l < 1, "to dot convdersion failed" );
+ fprintf( stderr, "<INFO> addr module finished with %d errors\n", errors );
return errors;
}
+
+/*
+ Connection oriented tests.
+*/
+static int conn( ) {
+ int errors = 0;
+ int state;
+
+ state = SIconnect( si_ctx, "localhost:4567" ); // driver regular connect
+ errors += fail_if_true( state < 0, "connect to low port failed" );
+
+ state = SIconnect( si_ctx, "localhost:43086" ); // drive save connect with good return code
+ errors += fail_if_true( state < 0, "connect to high port failed" );
+
+ tpem_set_addr_dup_state( 1 ); // force get sockket name emulation to return a duplicate address
+ state = SIconnect( si_ctx, "localhost:43086" ); // drive save connect with good return code
+ errors += fail_if_true( state >= 0, "forced dup connect did not return error" );
+
+ tpem_set_addr_dup_state( 0 ); // force get sockket name emulation to return a duplicate address
+ tpem_set_conn_state( 1 );
+ state = SIconnect( si_ctx, "localhost:4567" ); // driver regular connect
+ errors += fail_if_true( state >= 0, "connect to low port successful when failure expected" );
+
+ tpem_set_sock_state( 1 ); // make scoket calls fail
+ state = SIconnect( si_ctx, "localhost:4567" ); // driver regular connect
+ errors += fail_if_true( state >= 0, "connect to low port successful when socket based failure expected" );
+
+ tpem_set_sock_state( 0 );
+
+ state = SIlistener( si_ctx, TCP_DEVICE, "0.0.0.0:4567" );
+ errors += fail_if_true( state < 0, "listen failed" );
+
+ tpem_set_bind_state( 1 );
+ state = SIlistener( si_ctx, TCP_DEVICE, "0.0.0.0:4567" );
+ errors += fail_if_true( state >= 0, "listen successful when bind error set" );
+ tpem_set_bind_state( 0 );
+
+
+ fprintf( stderr, "<INFO> conn module finished with %d errors\n", errors );
+ return errors;
+}
+
/*
Drive tests...
*/
errors += init();
errors += memory();
errors += addr();
-fprintf( stderr, ">>> cleaning\n" );
+ errors += conn();
errors += cleanup();
fprintf( stderr, "<INFO> testing finished\n" );
--- /dev/null
+/*
+==================================================================================
+ Copyright (c) 2020 Nokia
+ Copyright (c) 2020 AT&T Intellectual Property.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================================
+*/
+
+/*
+ Mnemonic: test_transport.c
+ Abstract: This supplies a bunch of dummy system functions which emulate
+ things like connect() bind() etc.
+
+ This module must be directly included to be used.
+ Date: 17 April 2020
+ Author: E. Scott Daniels
+*/
+
+#ifndef _test_transport_c
+#define _sitransport_h // prevent the transport defs when including SI95
+
+
+char tpem_last_addr[1024]; // last address to simulate connection to ourself
+int tpem_last_len = 0;
+
+int tpem_addr_dup = 0; // getsockname duplicates last addr if true
+int tpem_conn_state = 0; // states returned by emulated functions allowing failures to be driven
+int tpem_sock_state = 0;
+int tpem_listen_state = 0;
+int tpem_bind_state = 0;
+
+// ------------ emulation control -------------------------------------------
+
+/*
+ All test prog to set various things
+*/
+static void tpem_set_conn_state( int s ) {
+ tpem_conn_state = s;
+}
+
+static void tpem_set_addr_dup_state( int s ) {
+ tpem_addr_dup = s;
+}
+
+static void tpem_set_sock_state( int s ) {
+ tpem_sock_state = s;
+}
+
+static void tpem_set_bind_state( int s ) {
+ tpem_bind_state = s;
+}
+
+// ---- emulated functions ---------------------------------------------------
+
+static int tpem_bind( int socket, struct sockaddr* addr, socklen_t alen ) {
+ return tpem_bind_state;
+}
+
+static int tpem_connect( int socket, struct sockaddr* addr, socklen_t alen ) {
+ memcpy( tpem_last_addr, addr, alen );
+ tpem_last_len = alen;
+ fprintf( stderr, "<SYSEM> connection simulated rc=%d\n", tpem_conn_state );
+ return tpem_conn_state;
+}
+
+/*
+ This gets the last address connected to if dup is true; else returns 0s
+ which should be enough to test that connection didn't loop back to us.
+*/
+static int tpem_getsockname( int socket, struct sockaddr* address, socklen_t* alen ) {
+ int clen; // copy len
+
+ if( tpem_last_len > 0 ) {
+ clen = tpem_last_len > *alen ? *alen : tpem_last_len;
+ if( tpem_addr_dup ) {
+ memcpy( address, tpem_last_addr, clen );
+ } else {
+ memset( address, 0, clen );
+ }
+ *alen = clen;
+ } else {
+ memset( address, 0, *alen );
+ }
+
+ return 0;
+}
+
+static int tpem_listen( int socket, int backlog ) {
+ return tpem_listen_state;
+}
+
+static int tpem_socket( int domain, int type, int protocol ) {
+ static int fd = 1;
+
+ if( tpem_sock_state == 0 ) {
+ if( ++fd > 10 ) {
+ fd = 1;
+ }
+
+ return fd;
+ }
+
+ return -1;
+}
+
+/*
+ redefine all system calls to reference functions here. There are two defs
+ SI functions should use the capitalised verision so that sliding ff under
+ it is possible. There might be instances wehre the actual system call is
+ needed, so we also define the lowercase value.
+*/
+#define BIND tpem_bind
+#define bind tpem_bind
+#define CONNECT tpem_connect
+#define connect tpem_connect
+#define getsockname tpem_getsockname
+#define SOCKET tpem_socket
+#define socket tpem_socket
+#define LISTEN tpem_listen
+#define listen tpem_listen
+
+/*
+ these are defined in SI so that we can use the system stack or FFstack
+ they must exist and reference system calls if not defined above.
+*/
+#define ACCEPT accept
+#define CLOSE close
+#define SHUTDOWN shutdown
+#define GETSOCKOPT getscokopt
+#define SETSOCKOPT setsockopt
+#define READ read
+#define WRITE write
+#define SEND send
+#define SENDTO sendto
+#define RECV recv
+#define RECVFROM recvfrom
+#define RECVMSG recvmsg
+
+
+
+#endif
+