Просмотр исходного кода

Fix #1132: use timeout for client connect (#1154)

If the client is connecting to localhost on linux,
the timeout is not recognized and ECONNREFUSED wil be returned
if there's no server running. Therefore we need a loop.
Stefan Profanter лет назад: 7
Родитель
Сommit
ea691ca62f
6 измененных файлов с 152 добавлено и 24 удалено
  1. 1 1
      include/ua_client.h
  2. 6 2
      include/ua_plugin_network.h
  3. 3 0
      include/ua_types.h
  4. 140 19
      plugins/ua_network_tcp.c
  5. 1 1
      plugins/ua_network_tcp.h
  6. 1 1
      src/client/ua_client.c

+ 1 - 1
include/ua_client.h

@@ -36,7 +36,7 @@ extern "C" {
  * -------------------- */
 
 typedef struct UA_ClientConfig {
-    UA_UInt32 timeout;               /* Sync response timeout */
+    UA_UInt32 timeout;               /* Sync response timeout in ms */
     UA_UInt32 secureChannelLifeTime; /* Lifetime in ms (then the channel needs
                                         to be renewed) */
     UA_Logger logger;

+ 6 - 2
include/ua_plugin_network.h

@@ -187,10 +187,14 @@ struct UA_ServerNetworkLayer {
  * Client Network Layer
  * --------------------
  * The client has only a single connection. The connection is used for both
- * sending and receiving binary messages. */
+ * sending and receiving binary messages.
+ * @param localConf the connection config for this client
+ * @param endpointUrl to where to connect
+ * @param timeout in ms until the connection try times out if remote not reachable
+ * */
 
 typedef UA_Connection
-(*UA_ConnectClientConnection)(UA_ConnectionConfig localConf, const char *endpointUrl);
+(*UA_ConnectClientConnection)(UA_ConnectionConfig localConf, const char *endpointUrl, const UA_UInt32 timeout);
 
 /**
  * Endpoint URL Parser

+ 3 - 0
include/ua_types.h

@@ -177,6 +177,9 @@ typedef int64_t UA_DateTime;
 #define UA_USEC_TO_DATETIME 10LL
 #define UA_MSEC_TO_DATETIME (UA_USEC_TO_DATETIME * 1000LL)
 #define UA_SEC_TO_DATETIME (UA_MSEC_TO_DATETIME * 1000LL)
+#define UA_DATETIME_TO_USEC (1/10.0)
+#define UA_DATETIME_TO_MSEC (UA_DATETIME_TO_USEC / 1000.0)
+#define UA_DATETIME_TO_SEC (UA_DATETIME_TO_MSEC / 1000.0)
 
 /* Datetime of 1 Jan 1970 00:00 UTC */
 #define UA_DATETIME_UNIX_EPOCH (11644473600LL * UA_SEC_TO_DATETIME)

+ 140 - 19
plugins/ua_network_tcp.c

@@ -25,10 +25,14 @@
 # define CLOSESOCKET(S) closesocket((SOCKET)S)
 # define ssize_t int
 # define WIN32_INT (int)
+# define OPTVAL_TYPE char
+# define ERR_CONNECTION_PROGRESS WSAEWOULDBLOCK
 #else
 # define CLOSESOCKET(S) close(S)
 # define SOCKET int
 # define WIN32_INT
+# define OPTVAL_TYPE int
+# define ERR_CONNECTION_PROGRESS EINPROGRESS
 # include <arpa/inet.h>
 # include <netinet/in.h>
 # include <sys/select.h>
@@ -73,11 +77,13 @@
 # define INTERRUPTED WSAEINTR
 # define WOULDBLOCK WSAEWOULDBLOCK
 # define AGAIN WSAEWOULDBLOCK
+# define sleepMs(X) Sleep(X)
 #else
 # define errno__ errno
 # define INTERRUPTED EINTR
 # define WOULDBLOCK EWOULDBLOCK
 # define AGAIN EAGAIN
+# define sleepMs(X) usleep(X * 1000)
 #endif
 
 /****************************/
@@ -208,6 +214,20 @@ socket_set_nonblocking(SOCKET sockfd) {
     return UA_STATUSCODE_GOOD;
 }
 
+static UA_StatusCode
+socket_set_blocking(SOCKET sockfd) {
+#ifdef _WIN32
+    u_long iMode = 0;
+    if(ioctlsocket(sockfd, FIONBIO, &iMode) != NO_ERROR)
+        return UA_STATUSCODE_BADINTERNALERROR;
+#else
+    int opts = fcntl(sockfd, F_GETFL);
+    if(opts < 0 || fcntl(sockfd, F_SETFL, opts & (~O_NONBLOCK)) < 0)
+        return UA_STATUSCODE_BADINTERNALERROR;
+#endif
+    return UA_STATUSCODE_GOOD;
+}
+
 /***************************/
 /* Server NetworkLayer TCP */
 /***************************/
@@ -429,7 +449,8 @@ setFDSet(ServerNetworkLayerTCP *layer, fd_set *fdset) {
 }
 
 static UA_StatusCode
-ServerNetworkLayerTCP_listen(UA_ServerNetworkLayer *nl, UA_Server *server,
+ServerNetworkLayerTCP_listen(UA_ServerNetworkLayer *nl,
+                             UA_Server *server,
                              UA_UInt16 timeout) {
     /* Every open socket can generate two jobs */
     ServerNetworkLayerTCP *layer = (ServerNetworkLayerTCP *)nl->handle;
@@ -575,7 +596,7 @@ UA_ServerNetworkLayerTCP(UA_ConnectionConfig conf, UA_UInt16 port) {
 
 UA_Connection
 UA_ClientConnectionTCP(UA_ConnectionConfig conf,
-                       const char *endpointUrl) {
+                       const char *endpointUrl, const UA_UInt32 timeout) {
 #ifdef _WIN32
     WORD wVersionRequested;
     WSADATA wsaData;
@@ -637,32 +658,132 @@ UA_ClientConnectionTCP(UA_ConnectionConfig conf,
         return connection;
     }
 
-    /* Get a socket */
-    SOCKET clientsockfd = socket(server->ai_family,
-                                 server->ai_socktype,
-                                 server->ai_protocol);
+
+    UA_Boolean connected = UA_FALSE;
+
+    UA_DateTime connStart = UA_DateTime_nowMonotonic();
+    SOCKET clientsockfd;
+
+    /* On linux connect may immediately return with ECONNREFUSED but we still want to try to connect */
+    /* Thus use a loop and retry until timeout is reached */
+    do {
+
+        /* Get a socket */
+        clientsockfd = socket(server->ai_family,
+                              server->ai_socktype,
+                              server->ai_protocol);
+    #ifdef _WIN32
+        if(clientsockfd == INVALID_SOCKET) {
+    #else
+        if(clientsockfd < 0) {
+    #endif
+            UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK,
+                           "Could not create client socket: %s", strerror(errno__));
+            freeaddrinfo(server);
+            return connection;
+        }
+
+        /* Connect to the server */
+        connection.sockfd = (UA_Int32) clientsockfd; /* cast for win32 */
+
+        /* Non blocking connect to be able to timeout */
+        if (socket_set_nonblocking(clientsockfd) != UA_STATUSCODE_GOOD) {
+            UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK,
+                           "Could not set the client socket to nonblocking");
+            connection_close(&connection);
+            freeaddrinfo(server);
+            return connection;
+        }
+
+        /* Non blocking connect */
+        error = connect(clientsockfd, server->ai_addr,
+                        WIN32_INT server->ai_addrlen);
+
+        if ((error == -1) && (errno__ != ERR_CONNECTION_PROGRESS)) {
+            connection_close(&connection);
+            UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK,
+                           "Connection to %s failed with error: %s",
+                           endpointUrl, strerror(errno__));
+            freeaddrinfo(server);
+            return connection;
+        }
+
+        /* Use select to wait and check if connected */
+        if (error == -1 && (errno__ == ERR_CONNECTION_PROGRESS)) {
+            /* connection in progress. Wait until connected using select */
+
+
+            UA_UInt32 timeSinceStart = (UA_UInt32) ((UA_Double)(UA_DateTime_nowMonotonic() - connStart) * UA_DATETIME_TO_MSEC);
+            if (timeSinceStart > timeout) {
+                break;
+            }
+
+            fd_set fdset;
+            FD_ZERO(&fdset);
+            UA_fd_set(clientsockfd, &fdset);
+            UA_UInt32 timeout_usec = (timeout - timeSinceStart) * 1000;
+            struct timeval tmptv = {(long int) (timeout_usec / 1000000),
+                                    (long int) (timeout_usec % 1000000)};
+
+            int resultsize = select((UA_Int32)(clientsockfd + 1), NULL, &fdset,
+                                    NULL, &tmptv);
+
+            if (resultsize == 1) {
+                /* Windows does not have any getsockopt equivalent and it is not needed there */
 #ifdef _WIN32
-    if(clientsockfd == INVALID_SOCKET) {
+                connected = true;
+                break;
 #else
-    if(clientsockfd < 0) {
+                OPTVAL_TYPE so_error;
+                socklen_t len = sizeof so_error;
+
+                int ret = getsockopt(clientsockfd, SOL_SOCKET, SO_ERROR, &so_error, &len);
+
+                if (ret != 0 || so_error != 0) {
+                    /* on connection refused we should still try to connect */
+                    /* connection refused happens on localhost or local ip without timeout */
+                    if (so_error != ECONNREFUSED) {
+                        // general error
+                        connection_close(&connection);
+                        UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK,
+                                       "Connection to %s failed with error: %s",
+                                       endpointUrl, strerror(ret == 0 ? so_error : errno__));
+                        freeaddrinfo(server);
+                        return connection;
+                    }
+                    /* wait until we try a again. Do not make this too small, otherwise the
+                     * timeout is somehow wrong */
+                    sleepMs(100);
+                } else {
+                    connected = true;
+                    break;
+                }
 #endif
+            }
+        } else {
+            connected = true;
+            break;
+        }
+        connection_close(&connection);
+
+    } while ((UA_Double)(UA_DateTime_nowMonotonic() - connStart)*UA_DATETIME_TO_MSEC < timeout);
+
+    freeaddrinfo(server);
+    if (!connected) {
+        // connection timeout
+        connection_close(&connection);
         UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK,
-                       "Could not create client socket");
-        freeaddrinfo(server);
+                       "Trying to connect to %s timed out",
+                       endpointUrl);
         return connection;
     }
 
-    /* Connect to the server */
-    connection.sockfd = (UA_Int32)clientsockfd; /* cast for win32 */
-    error = connect(clientsockfd, server->ai_addr,
-                    WIN32_INT server->ai_addrlen);
-    freeaddrinfo(server);
 
-    if(error < 0) {
-        connection_close(&connection);
+    /* we are connected. Reset socket to blocking */
+    if(socket_set_blocking(clientsockfd) != UA_STATUSCODE_GOOD) {
         UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_NETWORK,
-                       "Connection to %s failed with error %d",
-                       endpointUrl, errno__);
+                       "Could not set the client socket to blocking");
+        connection_close(&connection);
         return connection;
     }
 

+ 1 - 1
plugins/ua_network_tcp.h

@@ -15,7 +15,7 @@ UA_ServerNetworkLayer UA_EXPORT
 UA_ServerNetworkLayerTCP(UA_ConnectionConfig conf, UA_UInt16 port);
 
 UA_Connection UA_EXPORT
-UA_ClientConnectionTCP(UA_ConnectionConfig conf, const char *endpointUrl);
+UA_ClientConnectionTCP(UA_ConnectionConfig conf, const char *endpointUrl, const UA_UInt32 timeout);
 
 #ifdef __cplusplus
 } // extern "C"

+ 1 - 1
src/client/ua_client.c

@@ -598,7 +598,7 @@ __UA_Client_connect(UA_Client *client, const char *endpointUrl,
 
     UA_StatusCode retval = UA_STATUSCODE_GOOD;
     client->connection =
-        client->config.connectionFunc(client->config.localConnectionConfig, endpointUrl);
+        client->config.connectionFunc(client->config.localConnectionConfig, endpointUrl, client->config.timeout);
     if(client->connection.state != UA_CONNECTION_OPENING) {
         retval = UA_STATUSCODE_BADCONNECTIONCLOSED;
         goto cleanup;