|
@@ -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;
|
|
|
}
|
|
|
|