1082 lines
42 KiB
C
1082 lines
42 KiB
C
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* Copyright 2021-2022 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
|
|
* Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes)
|
|
*/
|
|
|
|
#include "open62541/types.h"
|
|
#include "eventloop_posix.h"
|
|
#include "eventloop_common.h"
|
|
|
|
/* Configuration parameters */
|
|
#define TCP_PARAMETERSSIZE 6
|
|
#define TCP_PARAMINDEX_RECVBUF 0
|
|
#define TCP_PARAMINDEX_ADDR 1
|
|
#define TCP_PARAMINDEX_PORT 2
|
|
#define TCP_PARAMINDEX_LISTEN 3
|
|
#define TCP_PARAMINDEX_VALIDATE 4
|
|
#define TCP_PARAMINDEX_REUSE 5
|
|
|
|
static UA_KeyValueRestriction TCPConfigParameters[TCP_PARAMETERSSIZE] = {
|
|
{{0, UA_STRING_STATIC("recv-bufsize")}, &UA_TYPES[UA_TYPES_UINT32], false, true, false},
|
|
{{0, UA_STRING_STATIC("address")}, &UA_TYPES[UA_TYPES_STRING], false, true, true},
|
|
{{0, UA_STRING_STATIC("port")}, &UA_TYPES[UA_TYPES_UINT16], true, true, false},
|
|
{{0, UA_STRING_STATIC("listen")}, &UA_TYPES[UA_TYPES_BOOLEAN], false, true, false},
|
|
{{0, UA_STRING_STATIC("validate")}, &UA_TYPES[UA_TYPES_BOOLEAN], false, true, false},
|
|
{{0, UA_STRING_STATIC("reuse")}, &UA_TYPES[UA_TYPES_BOOLEAN], false, true, false}
|
|
};
|
|
|
|
typedef struct {
|
|
UA_RegisteredFD rfd;
|
|
|
|
UA_ConnectionManager_connectionCallback applicationCB;
|
|
void *application;
|
|
void *context;
|
|
} TCP_FD;
|
|
|
|
static void
|
|
TCP_shutdown(UA_ConnectionManager *cm, TCP_FD *conn);
|
|
|
|
/* Do not merge packets on the socket (disable Nagle's algorithm) */
|
|
static UA_StatusCode
|
|
TCP_setNoNagle(UA_FD sockfd) {
|
|
int val = 1;
|
|
int res = UA_setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
|
|
if(res < 0)
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
/* Test if the ConnectionManager can be stopped */
|
|
static void
|
|
TCP_checkStopped(UA_POSIXConnectionManager *pcm) {
|
|
UA_LOCK_ASSERT(&((UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop)->elMutex, 1);
|
|
|
|
if(pcm->fdsSize == 0 &&
|
|
pcm->cm.eventSource.state == UA_EVENTSOURCESTATE_STOPPING) {
|
|
UA_LOG_DEBUG(pcm->cm.eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| All sockets closed, the EventLoop has stopped");
|
|
pcm->cm.eventSource.state = UA_EVENTSOURCESTATE_STOPPED;
|
|
}
|
|
}
|
|
|
|
static void
|
|
TCP_delayedClose(void *application, void *context) {
|
|
UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)application;
|
|
UA_ConnectionManager *cm = &pcm->cm;
|
|
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop;
|
|
TCP_FD *conn = (TCP_FD*)context;
|
|
|
|
UA_LOCK(&el->elMutex);
|
|
|
|
UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP,
|
|
"TCP %u\t| Delayed closing of the connection",
|
|
(unsigned)conn->rfd.fd);
|
|
|
|
/* Ensure reuse is possible right away. Port-stealing is no longer an issue
|
|
* as the socket gets closed anyway. And we do not want to wait for the
|
|
* timeout to open a new socket for the same address and port. */
|
|
UA_EventLoopPOSIX_setReusable(conn->rfd.fd);
|
|
|
|
/* Deregister from the EventLoop */
|
|
UA_EventLoopPOSIX_deregisterFD(el, &conn->rfd);
|
|
|
|
/* Deregister internally */
|
|
ZIP_REMOVE(UA_FDTree, &pcm->fds, &conn->rfd);
|
|
UA_assert(pcm->fdsSize > 0);
|
|
pcm->fdsSize--;
|
|
|
|
/* Signal closing to the application */
|
|
UA_UNLOCK(&el->elMutex);
|
|
conn->applicationCB(cm, (uintptr_t)conn->rfd.fd,
|
|
conn->application, &conn->context,
|
|
UA_CONNECTIONSTATE_CLOSING,
|
|
&UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
|
|
UA_LOCK(&el->elMutex);
|
|
|
|
/* Close the socket */
|
|
int ret = UA_close(conn->rfd.fd);
|
|
if(ret == 0) {
|
|
UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Socket closed", (unsigned)conn->rfd.fd);
|
|
} else {
|
|
UA_LOG_SOCKET_ERRNO_WRAP(
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Could not close the socket (%s)",
|
|
(unsigned)conn->rfd.fd, errno_str));
|
|
}
|
|
|
|
UA_free(conn);
|
|
|
|
/* Check if this was the last connection for a closing ConnectionManager */
|
|
TCP_checkStopped(pcm);
|
|
|
|
UA_UNLOCK(&el->elMutex);
|
|
}
|
|
|
|
static int
|
|
getSockError(TCP_FD *conn) {
|
|
int error = 0;
|
|
#ifndef _WIN32
|
|
socklen_t errlen = sizeof(int);
|
|
int err = getsockopt(conn->rfd.fd, SOL_SOCKET, SO_ERROR, &error, &errlen);
|
|
#else
|
|
int errlen = (int)sizeof(int);
|
|
int err = getsockopt((SOCKET)conn->rfd.fd, SOL_SOCKET, SO_ERROR,
|
|
(char*)&error, &errlen);
|
|
#endif
|
|
return (err == 0) ? error : err;
|
|
}
|
|
|
|
/* Gets called when a connection socket opens, receives data or closes */
|
|
static void
|
|
TCP_connectionSocketCallback(UA_ConnectionManager *cm, TCP_FD *conn,
|
|
short event) {
|
|
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop;
|
|
UA_LOCK_ASSERT(&el->elMutex, 1);
|
|
|
|
UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Activity on the socket",
|
|
(unsigned)conn->rfd.fd);
|
|
|
|
/* Error. The connection has closed. */
|
|
if(event == UA_FDEVENT_ERR) {
|
|
UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| The connection closes with error %i",
|
|
(unsigned)conn->rfd.fd, getSockError(conn));
|
|
TCP_shutdown(cm, conn);
|
|
return;
|
|
}
|
|
|
|
/* Write-Event, a new connection has opened. But some errors come as an
|
|
* out-event. For example if the remote side could not be reached to
|
|
* initiate the connection. So we check manually for error conditions on
|
|
* the socket. */
|
|
if(event == UA_FDEVENT_OUT) {
|
|
int error = getSockError(conn);
|
|
if(error != 0) {
|
|
UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| The connection closes with error %i",
|
|
(unsigned)conn->rfd.fd, error);
|
|
TCP_shutdown(cm, conn);
|
|
return;
|
|
}
|
|
|
|
UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Opening a new connection",
|
|
(unsigned)conn->rfd.fd);
|
|
|
|
/* Now we are interested in read-events. */
|
|
conn->rfd.listenEvents = UA_FDEVENT_IN;
|
|
UA_EventLoopPOSIX_modifyFD(el, &conn->rfd);
|
|
|
|
/* A new socket has opened. Signal it to the application. */
|
|
UA_UNLOCK(&el->elMutex);
|
|
conn->applicationCB(cm, (uintptr_t)conn->rfd.fd,
|
|
conn->application, &conn->context,
|
|
UA_CONNECTIONSTATE_ESTABLISHED,
|
|
&UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
|
|
UA_LOCK(&el->elMutex);
|
|
return;
|
|
}
|
|
|
|
UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Allocate receive buffer",
|
|
(unsigned)conn->rfd.fd);
|
|
|
|
/* Use the already allocated receive-buffer */
|
|
UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm;
|
|
UA_ByteString response = pcm->rxBuffer;
|
|
|
|
/* Receive */
|
|
#ifndef _WIN32
|
|
ssize_t ret = UA_recv(conn->rfd.fd, (char*)response.data,
|
|
response.length, MSG_DONTWAIT);
|
|
#else
|
|
int ret = UA_recv(conn->rfd.fd, (char*)response.data,
|
|
response.length, MSG_DONTWAIT);
|
|
#endif
|
|
|
|
/* Receive has failed */
|
|
if(ret <= 0) {
|
|
if(UA_ERRNO == UA_INTERRUPTED ||
|
|
UA_ERRNO == UA_WOULDBLOCK ||
|
|
UA_ERRNO == UA_AGAIN)
|
|
return; /* Temporary error on an non-blocking socket */
|
|
|
|
/* Orderly shutdown of the socket */
|
|
UA_LOG_SOCKET_ERRNO_WRAP(
|
|
UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| recv signaled the socket was shutdown (%s)",
|
|
(unsigned)conn->rfd.fd, errno_str));
|
|
TCP_shutdown(cm, conn);
|
|
return;
|
|
}
|
|
|
|
UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Received message of size %u",
|
|
(unsigned)conn->rfd.fd, (unsigned)ret);
|
|
|
|
/* Callback to the application layer */
|
|
response.length = (size_t)ret; /* Set the length of the received buffer */
|
|
UA_UNLOCK(&el->elMutex);
|
|
conn->applicationCB(cm, (uintptr_t)conn->rfd.fd,
|
|
conn->application, &conn->context,
|
|
UA_CONNECTIONSTATE_ESTABLISHED,
|
|
&UA_KEYVALUEMAP_NULL, response);
|
|
UA_LOCK(&el->elMutex);
|
|
}
|
|
|
|
/* Gets called when a new connection opens or if the listenSocket is closed */
|
|
static void
|
|
TCP_listenSocketCallback(UA_ConnectionManager *cm, TCP_FD *conn, short event) {
|
|
UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm;
|
|
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop;
|
|
UA_LOCK_ASSERT(&el->elMutex, 1);
|
|
|
|
UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Callback on server socket",
|
|
(unsigned)conn->rfd.fd);
|
|
|
|
/* Try to accept a new connection */
|
|
struct sockaddr_storage remote;
|
|
socklen_t remote_size = sizeof(remote);
|
|
UA_FD newsockfd = accept(conn->rfd.fd, (struct sockaddr*)&remote, &remote_size);
|
|
if(newsockfd == UA_INVALID_FD) {
|
|
/* Temporary error -- retry */
|
|
if(UA_ERRNO == UA_INTERRUPTED)
|
|
return;
|
|
|
|
/* Close the listen socket */
|
|
if(cm->eventSource.state != UA_EVENTSOURCESTATE_STOPPING) {
|
|
UA_LOG_SOCKET_ERRNO_WRAP(
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Error %s, closing the server socket",
|
|
(unsigned)conn->rfd.fd, errno_str));
|
|
}
|
|
|
|
TCP_shutdown(cm, conn);
|
|
return;
|
|
}
|
|
|
|
/* Log the name of the remote host */
|
|
char hoststr[UA_MAXHOSTNAME_LENGTH];
|
|
int get_res = UA_getnameinfo((struct sockaddr *)&remote, sizeof(remote),
|
|
hoststr, sizeof(hoststr),
|
|
NULL, 0, NI_NUMERICHOST);
|
|
if(get_res != 0) {
|
|
UA_LOG_SOCKET_ERRNO_WRAP(
|
|
UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| getnameinfo(...) could not resolve the "
|
|
"hostname (%s)", (unsigned)conn->rfd.fd, errno_str));
|
|
}
|
|
UA_LOG_INFO(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Connection opened from \"%s\" via the server socket %u",
|
|
(unsigned)newsockfd, hoststr, (unsigned)conn->rfd.fd);
|
|
|
|
/* Configure the new socket */
|
|
UA_StatusCode res = UA_STATUSCODE_GOOD;
|
|
/* res |= UA_EventLoopPOSIX_setNonBlocking(newsockfd); Inherited from the listen-socket */
|
|
res |= UA_EventLoopPOSIX_setNoSigPipe(newsockfd); /* Supress interrupts from the socket */
|
|
res |= TCP_setNoNagle(newsockfd); /* Disable Nagle's algorithm */
|
|
if(res != UA_STATUSCODE_GOOD) {
|
|
UA_LOG_SOCKET_ERRNO_WRAP(
|
|
UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Error seeting the TCP options (%s)",
|
|
(unsigned)newsockfd, errno_str));
|
|
/* Close the new socket */
|
|
UA_close(newsockfd);
|
|
return;
|
|
}
|
|
|
|
/* Allocate the UA_RegisteredFD */
|
|
TCP_FD *newConn = (TCP_FD*)UA_calloc(1, sizeof(TCP_FD));
|
|
if(!newConn) {
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Error allocating memory for the socket",
|
|
(unsigned)newsockfd);
|
|
UA_close(newsockfd);
|
|
return;
|
|
}
|
|
|
|
newConn->rfd.fd = newsockfd;
|
|
newConn->rfd.listenEvents = UA_FDEVENT_IN;
|
|
newConn->rfd.es = &cm->eventSource;
|
|
newConn->rfd.eventSourceCB = (UA_FDCallback)TCP_connectionSocketCallback;
|
|
newConn->applicationCB = conn->applicationCB;
|
|
newConn->application = conn->application;
|
|
newConn->context = conn->context;
|
|
|
|
/* Register in the EventLoop. Signal to the user if registering failed. */
|
|
res = UA_EventLoopPOSIX_registerFD(el, &newConn->rfd);
|
|
if(res != UA_STATUSCODE_GOOD) {
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Error registering the socket",
|
|
(unsigned)newsockfd);
|
|
UA_free(newConn);
|
|
UA_close(newsockfd);
|
|
return;
|
|
}
|
|
|
|
/* Register internally in the EventSource */
|
|
ZIP_INSERT(UA_FDTree, &pcm->fds, &newConn->rfd);
|
|
pcm->fdsSize++;
|
|
|
|
/* Forward the remote hostname to the application */
|
|
UA_KeyValuePair kvp;
|
|
kvp.key = UA_QUALIFIEDNAME(0, "remote-address");
|
|
UA_String hostName = UA_STRING(hoststr);
|
|
UA_Variant_setScalar(&kvp.value, &hostName, &UA_TYPES[UA_TYPES_STRING]);
|
|
|
|
UA_KeyValueMap kvm;
|
|
kvm.mapSize = 1;
|
|
kvm.map = &kvp;
|
|
|
|
/* The socket has opened. Signal it to the application. */
|
|
UA_UNLOCK(&el->elMutex);
|
|
newConn->applicationCB(cm, (uintptr_t)newsockfd,
|
|
newConn->application, &newConn->context,
|
|
UA_CONNECTIONSTATE_ESTABLISHED,
|
|
&kvm, UA_BYTESTRING_NULL);
|
|
UA_LOCK(&el->elMutex);
|
|
}
|
|
|
|
static UA_StatusCode
|
|
TCP_registerListenSocket(UA_POSIXConnectionManager *pcm, struct addrinfo *ai,
|
|
const char *hostname, UA_UInt16 port,
|
|
void *application, void *context,
|
|
UA_ConnectionManager_connectionCallback connectionCallback,
|
|
UA_Boolean validate, UA_Boolean reuseaddr) {
|
|
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop;
|
|
UA_LOCK_ASSERT(&el->elMutex, 1);
|
|
|
|
/* Translate INADDR_ANY to IPv4/IPv6 address */
|
|
char addrstr[UA_MAXHOSTNAME_LENGTH];
|
|
int get_res = UA_getnameinfo(ai->ai_addr, ai->ai_addrlen,
|
|
addrstr, sizeof(addrstr), NULL, 0, 0);
|
|
if(get_res != 0) {
|
|
get_res = UA_getnameinfo(ai->ai_addr, ai->ai_addrlen,
|
|
addrstr, sizeof(addrstr),
|
|
NULL, 0, NI_NUMERICHOST);
|
|
if(get_res != 0) {
|
|
addrstr[0] = 0;
|
|
UA_LOG_SOCKET_ERRNO_WRAP(
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| getnameinfo(...) could not resolve the "
|
|
"hostname (%s)", errno_str));
|
|
}
|
|
}
|
|
|
|
/* Create the server socket */
|
|
UA_FD listenSocket = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
|
|
if(listenSocket == UA_INVALID_FD) {
|
|
UA_LOG_SOCKET_ERRNO_WRAP(
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Error opening the listen socket for "
|
|
"\"%s\" on port %u (%s)",
|
|
(unsigned)listenSocket, addrstr, port, errno_str));
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
|
|
/* Some Linux distributions have net.ipv6.bindv6only not activated. So
|
|
* sockets can double-bind to IPv4 and IPv6. This leads to problems. Use
|
|
* AF_INET6 sockets only for IPv6. */
|
|
#if UA_IPV6
|
|
int optval = 1;
|
|
if(ai->ai_family == AF_INET6 &&
|
|
UA_setsockopt(listenSocket, IPPROTO_IPV6, IPV6_V6ONLY,
|
|
(const char*)&optval, sizeof(optval)) == -1) {
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Could not set an IPv6 socket to IPv6 only",
|
|
(unsigned)listenSocket);
|
|
UA_close(listenSocket);
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
#endif
|
|
|
|
/* Allow rebinding to the IP/port combination. Eg. to restart the server. */
|
|
if(reuseaddr &&
|
|
UA_EventLoopPOSIX_setReusable(listenSocket) != UA_STATUSCODE_GOOD) {
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Could not make the socket addr reusable",
|
|
(unsigned)listenSocket);
|
|
UA_close(listenSocket);
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
|
|
/* Set the socket non-blocking */
|
|
if(UA_EventLoopPOSIX_setNonBlocking(listenSocket) != UA_STATUSCODE_GOOD) {
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Could not set the socket non-blocking",
|
|
(unsigned)listenSocket);
|
|
UA_close(listenSocket);
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
|
|
/* Supress interrupts from the socket */
|
|
if(UA_EventLoopPOSIX_setNoSigPipe(listenSocket) != UA_STATUSCODE_GOOD) {
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Could not disable SIGPIPE",
|
|
(unsigned)listenSocket);
|
|
UA_close(listenSocket);
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
|
|
/* Bind socket to address */
|
|
int ret = bind(listenSocket, ai->ai_addr, (socklen_t)ai->ai_addrlen);
|
|
|
|
/* Get the port being used if dynamic porting was used */
|
|
if(port == 0) {
|
|
struct sockaddr_in sin;
|
|
memset(&sin, 0, sizeof(sin));
|
|
socklen_t len = sizeof(sin);
|
|
getsockname(listenSocket, (struct sockaddr *)&sin, &len);
|
|
port = ntohs(sin.sin_port);
|
|
}
|
|
|
|
/* If the INADDR_ANY is used, use the local hostname */
|
|
char hoststr[UA_MAXHOSTNAME_LENGTH];
|
|
if(hostname) {
|
|
UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Creating listen socket for \"%s\" on port %u",
|
|
(unsigned)listenSocket, hostname, port);
|
|
} else {
|
|
gethostname(hoststr, UA_MAXHOSTNAME_LENGTH);
|
|
hostname = hoststr;
|
|
UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Creating listen socket for \"%s\" "
|
|
"(with local hostname \"%s\") on port %u",
|
|
(unsigned)listenSocket, addrstr, hostname, port);
|
|
}
|
|
|
|
if(ret < 0) {
|
|
UA_LOG_SOCKET_ERRNO_WRAP(
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Error binding the socket to the address %s (%s)",
|
|
(unsigned)listenSocket, hostname, errno_str));
|
|
UA_close(listenSocket);
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
|
|
/* Only validate, don't actually start listening */
|
|
if(validate) {
|
|
UA_EventLoopPOSIX_setReusable(listenSocket); /* Ensure reuse is possible */
|
|
UA_close(listenSocket);
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
/* Start listening */
|
|
if(listen(listenSocket, UA_MAXBACKLOG) < 0) {
|
|
UA_LOG_SOCKET_ERRNO_WRAP(
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Error listening on the socket (%s)",
|
|
(unsigned)listenSocket, errno_str));
|
|
UA_EventLoopPOSIX_setReusable(listenSocket); /* Ensure reuse is possible */
|
|
UA_close(listenSocket);
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
|
|
/* Allocate the connection */
|
|
TCP_FD *newConn = (TCP_FD*)UA_calloc(1, sizeof(TCP_FD));
|
|
if(!newConn) {
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Error allocating memory for the socket",
|
|
(unsigned)listenSocket);
|
|
UA_EventLoopPOSIX_setReusable(listenSocket); /* Ensure reuse is possible */
|
|
UA_close(listenSocket);
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
|
|
newConn->rfd.fd = listenSocket;
|
|
newConn->rfd.listenEvents = UA_FDEVENT_IN;
|
|
newConn->rfd.es = &pcm->cm.eventSource;
|
|
newConn->rfd.eventSourceCB = (UA_FDCallback)TCP_listenSocketCallback;
|
|
newConn->applicationCB = connectionCallback;
|
|
newConn->application = application;
|
|
newConn->context = context;
|
|
|
|
/* Register in the EventLoop */
|
|
UA_StatusCode res = UA_EventLoopPOSIX_registerFD(el, &newConn->rfd);
|
|
if(res != UA_STATUSCODE_GOOD) {
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Error registering the socket",
|
|
(unsigned)listenSocket);
|
|
UA_free(newConn);
|
|
UA_EventLoopPOSIX_setReusable(listenSocket); /* Ensure reuse is possible */
|
|
UA_close(listenSocket);
|
|
return res;
|
|
}
|
|
|
|
/* Register internally */
|
|
ZIP_INSERT(UA_FDTree, &pcm->fds, &newConn->rfd);
|
|
pcm->fdsSize++;
|
|
|
|
/* Set up the callback parameters */
|
|
UA_String listenAddress = UA_STRING((char*)(uintptr_t)hostname);
|
|
UA_KeyValuePair params[2];
|
|
params[0].key = UA_QUALIFIEDNAME(0, "listen-address");
|
|
UA_Variant_setScalar(¶ms[0].value, &listenAddress, &UA_TYPES[UA_TYPES_STRING]);
|
|
params[1].key = UA_QUALIFIEDNAME(0, "listen-port");
|
|
UA_Variant_setScalar(¶ms[1].value, &port, &UA_TYPES[UA_TYPES_UINT16]);
|
|
UA_KeyValueMap paramMap = {2, params};
|
|
|
|
/* Announce the listen-socket in the application */
|
|
UA_UNLOCK(&el->elMutex);
|
|
connectionCallback(&pcm->cm, (uintptr_t)listenSocket,
|
|
application, &newConn->context,
|
|
UA_CONNECTIONSTATE_ESTABLISHED,
|
|
¶mMap, UA_BYTESTRING_NULL);
|
|
UA_LOCK(&el->elMutex);
|
|
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
static UA_StatusCode
|
|
TCP_registerListenSockets(UA_POSIXConnectionManager *pcm, const char *hostname,
|
|
UA_UInt16 port, void *application, void *context,
|
|
UA_ConnectionManager_connectionCallback connectionCallback,
|
|
UA_Boolean validate, UA_Boolean reuseaddr) {
|
|
UA_LOCK_ASSERT(&((UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop)->elMutex, 1);
|
|
|
|
/* Create a string for the port */
|
|
char portstr[6];
|
|
mp_snprintf(portstr, sizeof(portstr), "%d", port);
|
|
|
|
/* Get all the interface and IPv4/6 combinations for the configured hostname */
|
|
struct addrinfo hints, *res;
|
|
memset(&hints, 0, sizeof hints);
|
|
#if UA_IPV6
|
|
hints.ai_family = AF_UNSPEC; /* Allow IPv4 and IPv6 */
|
|
#else
|
|
hints.ai_family = AF_INET; /* IPv4 only */
|
|
#endif
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_protocol = IPPROTO_TCP;
|
|
hints.ai_flags = AI_PASSIVE;
|
|
|
|
int retcode = getaddrinfo(hostname, portstr, &hints, &res);
|
|
if(retcode != 0) {
|
|
#ifdef _WIN32
|
|
UA_LOG_SOCKET_ERRNO_WRAP(
|
|
UA_LOG_WARNING(pcm->cm.eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| Lookup for \"%s\" on port %u failed (%s)",
|
|
hostname, port, errno_str));
|
|
#else
|
|
UA_LOG_WARNING(pcm->cm.eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| Lookup for \"%s\" on port %u failed (%s)",
|
|
hostname, port, gai_strerror(retcode));
|
|
#endif
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
|
|
/* Add listen sockets. Aggregate the results to see if at least one
|
|
* listen-socket was established. */
|
|
UA_StatusCode total_result = UA_INT32_MAX;
|
|
struct addrinfo *ai = res;
|
|
while(ai) {
|
|
total_result &= TCP_registerListenSocket(pcm, ai, hostname, port, application, context,
|
|
connectionCallback, validate, reuseaddr);
|
|
ai = ai->ai_next;
|
|
}
|
|
freeaddrinfo(res);
|
|
|
|
return total_result;
|
|
}
|
|
|
|
/* Close the connection via a delayed callback */
|
|
static void
|
|
TCP_shutdown(UA_ConnectionManager *cm, TCP_FD *conn) {
|
|
/* Already closing - nothing to do */
|
|
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop;
|
|
UA_LOCK_ASSERT(&el->elMutex, 1);
|
|
|
|
if(conn->rfd.dc.callback) {
|
|
UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Cannot shutdown - already triggered",
|
|
(unsigned)conn->rfd.fd);
|
|
return;
|
|
}
|
|
|
|
/* Shutdown the socket to cancel the current select/epoll */
|
|
shutdown(conn->rfd.fd, UA_SHUT_RDWR);
|
|
|
|
UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Shutdown triggered",
|
|
(unsigned)conn->rfd.fd);
|
|
|
|
/* Add to the delayed callback list. Will be cleaned up in the next
|
|
* iteration. */
|
|
UA_DelayedCallback *dc = &conn->rfd.dc;
|
|
dc->callback = TCP_delayedClose;
|
|
dc->application = cm;
|
|
dc->context = conn;
|
|
|
|
/* Don't use the "public" el->addDelayedCallback. It takes a lock. */
|
|
dc->next = el->delayedCallbacks;
|
|
el->delayedCallbacks = dc;
|
|
}
|
|
|
|
static UA_StatusCode
|
|
TCP_shutdownConnection(UA_ConnectionManager *cm, uintptr_t connectionId) {
|
|
UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm;
|
|
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)cm->eventSource.eventLoop;
|
|
UA_LOCK(&el->elMutex);
|
|
|
|
UA_FD fd = (UA_FD)connectionId;
|
|
TCP_FD *conn = (TCP_FD*)ZIP_FIND(UA_FDTree, &pcm->fds, &fd);
|
|
if(!conn) {
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| Cannot close TCP connection %u - not found",
|
|
(unsigned)connectionId);
|
|
UA_UNLOCK(&el->elMutex);
|
|
return UA_STATUSCODE_BADNOTFOUND;
|
|
}
|
|
|
|
TCP_shutdown(cm, conn);
|
|
|
|
UA_UNLOCK(&el->elMutex);
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
static UA_StatusCode
|
|
TCP_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId,
|
|
const UA_KeyValueMap *params, UA_ByteString *buf) {
|
|
/* We may not have a lock. But we need not take it. As the connectionId is
|
|
* the fd, no need to do a lookup and access internal data strucures. */
|
|
|
|
/* Prevent OS signals when sending to a closed socket */
|
|
int flags = MSG_NOSIGNAL;
|
|
|
|
struct pollfd tmp_poll_fd;
|
|
tmp_poll_fd.fd = (UA_FD)connectionId;
|
|
tmp_poll_fd.events = UA_POLLOUT;
|
|
|
|
/* Send the full buffer. This may require several calls to send */
|
|
size_t nWritten = 0;
|
|
do {
|
|
ssize_t n = 0;
|
|
do {
|
|
UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Attempting to send", (unsigned)connectionId);
|
|
size_t bytes_to_send = buf->length - nWritten;
|
|
n = UA_send((UA_FD)connectionId,
|
|
(const char*)buf->data + nWritten,
|
|
bytes_to_send, flags);
|
|
if(n < 0) {
|
|
/* An error we cannot recover from? */
|
|
if(UA_ERRNO != UA_INTERRUPTED && UA_ERRNO != UA_WOULDBLOCK &&
|
|
UA_ERRNO != UA_AGAIN)
|
|
goto shutdown;
|
|
|
|
/* Poll for the socket resources to become available and retry
|
|
* (blocking) */
|
|
int poll_ret;
|
|
do {
|
|
poll_ret = UA_poll(&tmp_poll_fd, 1, 100);
|
|
if(poll_ret < 0 && UA_ERRNO != UA_INTERRUPTED)
|
|
goto shutdown;
|
|
} while(poll_ret <= 0);
|
|
}
|
|
} while(n < 0);
|
|
nWritten += (size_t)n;
|
|
} while(nWritten < buf->length);
|
|
|
|
/* Free the buffer */
|
|
UA_ByteString_clear(buf);
|
|
return UA_STATUSCODE_GOOD;
|
|
|
|
shutdown:
|
|
UA_LOG_SOCKET_ERRNO_WRAP(
|
|
UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Send failed with error %s",
|
|
(unsigned)connectionId, errno_str));
|
|
TCP_shutdownConnection(cm, connectionId);
|
|
UA_ByteString_clear(buf);
|
|
return UA_STATUSCODE_BADCONNECTIONCLOSED;
|
|
}
|
|
|
|
/* Create a listen-socket that waits for incoming connections */
|
|
static UA_StatusCode
|
|
TCP_openPassiveConnection(UA_POSIXConnectionManager *pcm, const UA_KeyValueMap *params,
|
|
void *application, void *context,
|
|
UA_ConnectionManager_connectionCallback connectionCallback,
|
|
UA_Boolean validate) {
|
|
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop;
|
|
UA_LOCK_ASSERT(&el->elMutex, 1);
|
|
|
|
/* Get the port parameter */
|
|
const UA_UInt16 *port = (const UA_UInt16*)
|
|
UA_KeyValueMap_getScalar(params, TCPConfigParameters[TCP_PARAMINDEX_PORT].name,
|
|
&UA_TYPES[UA_TYPES_UINT16]);
|
|
UA_assert(port); /* existence is checked before */
|
|
|
|
/* Get the address parameter */
|
|
const UA_Variant *addrs =
|
|
UA_KeyValueMap_get(params, TCPConfigParameters[TCP_PARAMINDEX_ADDR].name);
|
|
size_t addrsSize = 0;
|
|
if(addrs) {
|
|
UA_assert(addrs->type == &UA_TYPES[UA_TYPES_STRING]);
|
|
if(UA_Variant_isScalar(addrs))
|
|
addrsSize = 1;
|
|
else
|
|
addrsSize = addrs->arrayLength;
|
|
}
|
|
|
|
/* Get the reuseaddr parameter */
|
|
UA_Boolean reuseaddr = false;
|
|
const UA_Boolean *reuseaddrTmp = (const UA_Boolean*)
|
|
UA_KeyValueMap_getScalar(params, TCPConfigParameters[TCP_PARAMINDEX_REUSE].name,
|
|
&UA_TYPES[UA_TYPES_BOOLEAN]);
|
|
if(reuseaddrTmp)
|
|
reuseaddr = *reuseaddrTmp;
|
|
|
|
#ifdef UA_ENABLE_ALLOW_REUSEADDR
|
|
reuseaddr = true;
|
|
#endif
|
|
|
|
/* Undefined or empty addresses array -> listen on all interfaces */
|
|
if(addrsSize == 0) {
|
|
UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| Listening on all interfaces");
|
|
return TCP_registerListenSockets(pcm, NULL, *port, application,
|
|
context, connectionCallback, validate, reuseaddr);
|
|
}
|
|
|
|
/* Iterate over the configured hostnames */
|
|
UA_String *hostStrings = (UA_String*)addrs->data;
|
|
UA_StatusCode retval = UA_STATUSCODE_BADINTERNALERROR;
|
|
for(size_t i = 0; i < addrsSize; i++) {
|
|
char hostname[512];
|
|
if(hostStrings[i].length >= sizeof(hostname))
|
|
continue;
|
|
memcpy(hostname, hostStrings[i].data, hostStrings->length);
|
|
hostname[hostStrings->length] = '\0';
|
|
if(TCP_registerListenSockets(pcm, hostname, *port, application,
|
|
context, connectionCallback, validate, reuseaddr) == UA_STATUSCODE_GOOD)
|
|
retval = UA_STATUSCODE_GOOD;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/* Open a TCP connection to a remote host */
|
|
static UA_StatusCode
|
|
TCP_openActiveConnection(UA_POSIXConnectionManager *pcm, const UA_KeyValueMap *params,
|
|
void *application, void *context,
|
|
UA_ConnectionManager_connectionCallback connectionCallback,
|
|
UA_Boolean validate) {
|
|
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop;
|
|
UA_LOCK_ASSERT(&el->elMutex, 1);
|
|
|
|
/* Get the connection parameters */
|
|
char hostname[UA_MAXHOSTNAME_LENGTH];
|
|
char portStr[UA_MAXPORTSTR_LENGTH];
|
|
|
|
/* Prepare the port parameter as a string */
|
|
const UA_UInt16 *port = (const UA_UInt16*)
|
|
UA_KeyValueMap_getScalar(params, TCPConfigParameters[TCP_PARAMINDEX_PORT].name,
|
|
&UA_TYPES[UA_TYPES_UINT16]);
|
|
UA_assert(port); /* existence is checked before */
|
|
mp_snprintf(portStr, UA_MAXPORTSTR_LENGTH, "%d", *port);
|
|
|
|
/* Prepare the hostname string */
|
|
const UA_String *addr = (const UA_String*)
|
|
UA_KeyValueMap_getScalar(params, TCPConfigParameters[TCP_PARAMINDEX_ADDR].name,
|
|
&UA_TYPES[UA_TYPES_STRING]);
|
|
if(!addr) {
|
|
UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| Open TCP Connection: No hostname defined, aborting");
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
if(addr->length >= UA_MAXHOSTNAME_LENGTH) {
|
|
UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP,
|
|
"TCP\t| Open TCP Connection: Hostname too long, aborting");
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
strncpy(hostname, (const char*)addr->data, addr->length);
|
|
hostname[addr->length] = 0;
|
|
|
|
UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| Open a connection to \"%s\" on port %s", hostname, portStr);
|
|
|
|
/* Create the socket description from the connectString
|
|
* TODO: Make this non-blocking */
|
|
struct addrinfo hints, *info;
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
int error = getaddrinfo(hostname, portStr, &hints, &info);
|
|
if(error != 0) {
|
|
#ifdef _WIN32
|
|
UA_LOG_SOCKET_ERRNO_WRAP(
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| Lookup of %s failed (%s)",
|
|
hostname, errno_str));
|
|
#else
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| Lookup of %s failed (%s)",
|
|
hostname, gai_strerror(error));
|
|
#endif
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
|
|
/* Create a socket */
|
|
UA_FD newSock = socket(info->ai_family, info->ai_socktype, info->ai_protocol);
|
|
if(newSock == UA_INVALID_FD) {
|
|
freeaddrinfo(info);
|
|
UA_LOG_SOCKET_ERRNO_WRAP(
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| Could not create socket to connect to %s (%s)",
|
|
hostname, errno_str));
|
|
return UA_STATUSCODE_BADDISCONNECT;
|
|
}
|
|
|
|
/* Set the socket options */
|
|
UA_StatusCode res = UA_STATUSCODE_GOOD;
|
|
res |= UA_EventLoopPOSIX_setNonBlocking(newSock);
|
|
res |= UA_EventLoopPOSIX_setNoSigPipe(newSock);
|
|
res |= TCP_setNoNagle(newSock);
|
|
if(res != UA_STATUSCODE_GOOD) {
|
|
UA_LOG_SOCKET_ERRNO_WRAP(
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| Could not set socket options: %s", errno_str));
|
|
freeaddrinfo(info);
|
|
UA_close(newSock);
|
|
return res;
|
|
}
|
|
|
|
/* Only validate, don't actually open the connection */
|
|
if(validate) {
|
|
freeaddrinfo(info);
|
|
UA_close(newSock);
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
/* Non-blocking connect */
|
|
error = UA_connect(newSock, info->ai_addr, info->ai_addrlen);
|
|
freeaddrinfo(info);
|
|
if(error != 0 &&
|
|
UA_ERRNO != UA_INPROGRESS &&
|
|
UA_ERRNO != UA_WOULDBLOCK) {
|
|
UA_LOG_SOCKET_ERRNO_WRAP(
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| Connecting the socket to %s failed (%s)",
|
|
hostname, errno_str));
|
|
UA_close(newSock);
|
|
return UA_STATUSCODE_BADDISCONNECT;
|
|
}
|
|
|
|
/* Allocate the UA_RegisteredFD */
|
|
TCP_FD *newConn = (TCP_FD*)UA_calloc(1, sizeof(TCP_FD));
|
|
if(!newConn) {
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Error allocating memory for the socket",
|
|
(unsigned)newSock);
|
|
UA_close(newSock);
|
|
return UA_STATUSCODE_BADOUTOFMEMORY;
|
|
}
|
|
|
|
newConn->rfd.fd = newSock;
|
|
newConn->rfd.es = &pcm->cm.eventSource;
|
|
newConn->rfd.eventSourceCB = (UA_FDCallback)TCP_connectionSocketCallback;
|
|
newConn->rfd.listenEvents = UA_FDEVENT_OUT; /* Switched to _IN once the
|
|
* connection is open */
|
|
newConn->applicationCB = connectionCallback;
|
|
newConn->application = application;
|
|
newConn->context = context;
|
|
|
|
/* Register the fd to trigger when output is possible (the connection is open) */
|
|
res = UA_EventLoopPOSIX_registerFD(el, &newConn->rfd);
|
|
if(res != UA_STATUSCODE_GOOD) {
|
|
UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| Registering the socket to connect to %s failed", hostname);
|
|
UA_close(newSock);
|
|
UA_free(newConn);
|
|
return res;
|
|
}
|
|
|
|
/* Register internally in the EventSource */
|
|
ZIP_INSERT(UA_FDTree, &pcm->fds, &newConn->rfd);
|
|
pcm->fdsSize++;
|
|
|
|
UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP %u\t| Opening a connection to \"%s\" on port %s",
|
|
(unsigned)newSock, hostname, portStr);
|
|
|
|
/* Signal the new connection to the application as asynchonously opening */
|
|
UA_UNLOCK(&el->elMutex);
|
|
connectionCallback(&pcm->cm, (uintptr_t)newSock,
|
|
application, &newConn->context,
|
|
UA_CONNECTIONSTATE_OPENING, &UA_KEYVALUEMAP_NULL,
|
|
UA_BYTESTRING_NULL);
|
|
UA_LOCK(&el->elMutex);
|
|
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
static UA_StatusCode
|
|
TCP_openConnection(UA_ConnectionManager *cm, const UA_KeyValueMap *params,
|
|
void *application, void *context,
|
|
UA_ConnectionManager_connectionCallback connectionCallback) {
|
|
UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm;
|
|
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop;
|
|
UA_LOCK(&el->elMutex);
|
|
|
|
if(cm->eventSource.state != UA_EVENTSOURCESTATE_STARTED) {
|
|
UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| Cannot open a connection for a "
|
|
"ConnectionManager that is not started");
|
|
UA_UNLOCK(&el->elMutex);
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
|
|
/* Check the parameters */
|
|
UA_StatusCode res =
|
|
UA_KeyValueRestriction_validate(el->eventLoop.logger, "TCP",
|
|
&TCPConfigParameters[1],
|
|
TCP_PARAMETERSSIZE-1, params);
|
|
if(res != UA_STATUSCODE_GOOD) {
|
|
UA_UNLOCK(&el->elMutex);
|
|
return res;
|
|
}
|
|
|
|
/* Only validate the parameters? */
|
|
UA_Boolean validate = false;
|
|
const UA_Boolean *validateParam = (const UA_Boolean*)
|
|
UA_KeyValueMap_getScalar(params,
|
|
TCPConfigParameters[TCP_PARAMINDEX_VALIDATE].name,
|
|
&UA_TYPES[UA_TYPES_BOOLEAN]);
|
|
if(validateParam)
|
|
validate = *validateParam;
|
|
|
|
/* Listen or active connection? */
|
|
UA_Boolean listen = false;
|
|
const UA_Boolean *listenParam = (const UA_Boolean*)
|
|
UA_KeyValueMap_getScalar(params,
|
|
TCPConfigParameters[TCP_PARAMINDEX_LISTEN].name,
|
|
&UA_TYPES[UA_TYPES_BOOLEAN]);
|
|
if(listenParam)
|
|
listen = *listenParam;
|
|
|
|
if(listen) {
|
|
res = TCP_openPassiveConnection(pcm, params, application, context,
|
|
connectionCallback, validate);
|
|
} else {
|
|
res = TCP_openActiveConnection(pcm, params, application, context,
|
|
connectionCallback, validate);
|
|
}
|
|
|
|
UA_UNLOCK(&el->elMutex);
|
|
return res;
|
|
}
|
|
|
|
static UA_StatusCode
|
|
TCP_eventSourceStart(UA_ConnectionManager *cm) {
|
|
UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm;
|
|
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop;
|
|
if(!el)
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
|
|
UA_LOCK(&el->elMutex);
|
|
|
|
/* Check the state */
|
|
if(cm->eventSource.state != UA_EVENTSOURCESTATE_STOPPED) {
|
|
UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| To start the ConnectionManager, it has to be "
|
|
"registered in an EventLoop and not started yet");
|
|
UA_UNLOCK(&el->elMutex);
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
|
|
/* Check the parameters */
|
|
UA_StatusCode res =
|
|
UA_KeyValueRestriction_validate(el->eventLoop.logger, "TCP",
|
|
TCPConfigParameters, 1,
|
|
&cm->eventSource.params);
|
|
if(res != UA_STATUSCODE_GOOD)
|
|
goto finish;
|
|
|
|
/* Allocate the rx buffer */
|
|
res = UA_EventLoopPOSIX_allocateRXBuffer(pcm);
|
|
if(res != UA_STATUSCODE_GOOD)
|
|
goto finish;
|
|
|
|
/* Set the EventSource to the started state */
|
|
cm->eventSource.state = UA_EVENTSOURCESTATE_STARTED;
|
|
|
|
finish:
|
|
UA_UNLOCK(&el->elMutex);
|
|
return res;
|
|
}
|
|
|
|
static void *
|
|
TCP_shutdownCB(void *application, UA_RegisteredFD *rfd) {
|
|
UA_ConnectionManager *cm = (UA_ConnectionManager*)application;
|
|
TCP_shutdown(cm, (TCP_FD*)rfd);
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
TCP_eventSourceStop(UA_ConnectionManager *cm) {
|
|
UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm;
|
|
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop;
|
|
(void)el;
|
|
|
|
UA_LOCK(&el->elMutex);
|
|
|
|
UA_LOG_INFO(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK,
|
|
"TCP\t| Shutting down the ConnectionManager");
|
|
|
|
/* Prevent new connections to open */
|
|
cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPING;
|
|
|
|
/* Shutdown all existing connection */
|
|
ZIP_ITER(UA_FDTree, &pcm->fds, TCP_shutdownCB, cm);
|
|
|
|
/* All sockets closed? Otherwise iterate some more. */
|
|
TCP_checkStopped(pcm);
|
|
|
|
UA_UNLOCK(&el->elMutex);
|
|
}
|
|
|
|
static UA_StatusCode
|
|
TCP_eventSourceDelete(UA_ConnectionManager *cm) {
|
|
UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm;
|
|
if(cm->eventSource.state >= UA_EVENTSOURCESTATE_STARTING) {
|
|
UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP,
|
|
"TCP\t| The EventSource must be stopped before it can be deleted");
|
|
return UA_STATUSCODE_BADINTERNALERROR;
|
|
}
|
|
|
|
UA_ByteString_clear(&pcm->rxBuffer);
|
|
UA_KeyValueMap_clear(&cm->eventSource.params);
|
|
UA_String_clear(&cm->eventSource.name);
|
|
UA_free(cm);
|
|
|
|
return UA_STATUSCODE_GOOD;
|
|
}
|
|
|
|
static const char *tcpName = "tcp";
|
|
|
|
UA_ConnectionManager *
|
|
UA_ConnectionManager_new_POSIX_TCP(const UA_String eventSourceName) {
|
|
UA_POSIXConnectionManager *cm = (UA_POSIXConnectionManager*)
|
|
UA_calloc(1, sizeof(UA_POSIXConnectionManager));
|
|
if(!cm)
|
|
return NULL;
|
|
|
|
cm->cm.eventSource.eventSourceType = UA_EVENTSOURCETYPE_CONNECTIONMANAGER;
|
|
UA_String_copy(&eventSourceName, &cm->cm.eventSource.name);
|
|
cm->cm.eventSource.start = (UA_StatusCode (*)(UA_EventSource *))TCP_eventSourceStart;
|
|
cm->cm.eventSource.stop = (void (*)(UA_EventSource *))TCP_eventSourceStop;
|
|
cm->cm.eventSource.free = (UA_StatusCode (*)(UA_EventSource *))TCP_eventSourceDelete;
|
|
cm->cm.protocol = UA_STRING((char*)(uintptr_t)tcpName);
|
|
cm->cm.openConnection = TCP_openConnection;
|
|
cm->cm.allocNetworkBuffer = UA_EventLoopPOSIX_allocNetworkBuffer;
|
|
cm->cm.freeNetworkBuffer = UA_EventLoopPOSIX_freeNetworkBuffer;
|
|
cm->cm.sendWithConnection = TCP_sendWithConnection;
|
|
cm->cm.closeConnection = TCP_shutdownConnection;
|
|
return &cm->cm;
|
|
}
|