/src/open62541/arch/posix/eventloop_posix_tcp.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
4 | | * |
5 | | * Copyright 2021-2022 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
6 | | * Copyright 2021 (c) Fraunhofer IOSB (Author: Jan Hermes) |
7 | | */ |
8 | | |
9 | | #include "open62541/types.h" |
10 | | #include "eventloop_posix.h" |
11 | | |
12 | | #if defined(UA_ARCHITECTURE_POSIX) && !defined(UA_ARCHITECTURE_LWIP) || defined(UA_ARCHITECTURE_WIN32) |
13 | | |
14 | | /* Configuration parameters */ |
15 | 551 | #define TCP_MANAGERPARAMS 2 |
16 | | |
17 | | static UA_KeyValueRestriction tcpManagerParams[TCP_MANAGERPARAMS] = { |
18 | | {{0, UA_STRING_STATIC("recv-bufsize")}, &UA_TYPES[UA_TYPES_UINT32], false, true, false}, |
19 | | {{0, UA_STRING_STATIC("send-bufsize")}, &UA_TYPES[UA_TYPES_UINT32], false, true, false} |
20 | | }; |
21 | | |
22 | 300 | #define TCP_PARAMETERSSIZE 5 |
23 | 300 | #define TCP_PARAMINDEX_ADDR 0 |
24 | 300 | #define TCP_PARAMINDEX_PORT 1 |
25 | 300 | #define TCP_PARAMINDEX_LISTEN 2 |
26 | 300 | #define TCP_PARAMINDEX_VALIDATE 3 |
27 | 300 | #define TCP_PARAMINDEX_REUSE 4 |
28 | | |
29 | | static UA_KeyValueRestriction tcpConnectionParams[TCP_PARAMETERSSIZE] = { |
30 | | {{0, UA_STRING_STATIC("address")}, &UA_TYPES[UA_TYPES_STRING], false, true, true}, |
31 | | {{0, UA_STRING_STATIC("port")}, &UA_TYPES[UA_TYPES_UINT16], true, true, false}, |
32 | | {{0, UA_STRING_STATIC("listen")}, &UA_TYPES[UA_TYPES_BOOLEAN], false, true, false}, |
33 | | {{0, UA_STRING_STATIC("validate")}, &UA_TYPES[UA_TYPES_BOOLEAN], false, true, false}, |
34 | | {{0, UA_STRING_STATIC("reuse")}, &UA_TYPES[UA_TYPES_BOOLEAN], false, true, false} |
35 | | }; |
36 | | |
37 | | typedef struct { |
38 | | UA_RegisteredFD rfd; |
39 | | |
40 | | UA_ConnectionManager_connectionCallback applicationCB; |
41 | | void *application; |
42 | | void *context; |
43 | | } TCP_FD; |
44 | | |
45 | | static void |
46 | | TCP_shutdown(UA_ConnectionManager *cm, TCP_FD *conn); |
47 | | |
48 | | /* Do not merge packets on the socket (disable Nagle's algorithm) */ |
49 | | static UA_StatusCode |
50 | 83 | TCP_setNoNagle(UA_FD sockfd) { |
51 | 83 | int val = 1; |
52 | 83 | int res = UA_setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)); |
53 | 83 | if(res < 0) |
54 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
55 | 83 | return UA_STATUSCODE_GOOD; |
56 | 83 | } |
57 | | |
58 | | /* Test if the ConnectionManager can be stopped */ |
59 | | static void |
60 | 1.22k | TCP_checkStopped(UA_POSIXConnectionManager *pcm) { |
61 | 1.22k | UA_LOCK_ASSERT(&((UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop)->elMutex); |
62 | | |
63 | 1.22k | if(pcm->fdsSize == 0 && |
64 | 1.22k | pcm->cm.eventSource.state == UA_EVENTSOURCESTATE_STOPPING) { |
65 | 551 | UA_LOG_DEBUG(pcm->cm.eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, |
66 | 551 | "TCP\t| All sockets closed, the EventLoop has stopped"); |
67 | 551 | pcm->cm.eventSource.state = UA_EVENTSOURCESTATE_STOPPED; |
68 | 551 | } |
69 | 1.22k | } |
70 | | |
71 | | static void |
72 | 677 | TCP_delayedClose(void *application, void *context) { |
73 | 677 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)application; |
74 | 677 | UA_ConnectionManager *cm = &pcm->cm; |
75 | 677 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; |
76 | 677 | TCP_FD *conn = (TCP_FD*)context; |
77 | | |
78 | 677 | UA_LOCK(&el->elMutex); |
79 | | |
80 | 677 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, |
81 | 677 | "TCP %u\t| Delayed closing of the connection", |
82 | 677 | (unsigned)conn->rfd.fd); |
83 | | |
84 | | /* Ensure reuse is possible right away. Port-stealing is no longer an issue |
85 | | * as the socket gets closed anyway. And we do not want to wait for the |
86 | | * timeout to open a new socket for the same address and port. */ |
87 | 677 | UA_EventLoopPOSIX_setReusable(conn->rfd.fd); |
88 | | |
89 | | /* Deregister from the EventLoop */ |
90 | 677 | UA_EventLoopPOSIX_deregisterFD(el, &conn->rfd); |
91 | | |
92 | | /* Deregister internally */ |
93 | 677 | ZIP_REMOVE(UA_FDTree, &pcm->fds, &conn->rfd); |
94 | 677 | UA_assert(pcm->fdsSize > 0); |
95 | 677 | pcm->fdsSize--; |
96 | | |
97 | | /* Signal closing to the application */ |
98 | 677 | conn->applicationCB(cm, (uintptr_t)conn->rfd.fd, |
99 | 677 | conn->application, &conn->context, |
100 | 677 | UA_CONNECTIONSTATE_CLOSING, |
101 | 677 | &UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL); |
102 | | |
103 | | /* Close the socket */ |
104 | 677 | UA_RESET_ERRNO; |
105 | 677 | int ret = UA_close(conn->rfd.fd); |
106 | 677 | if(ret == 0) { |
107 | 677 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
108 | 677 | "TCP %u\t| Socket closed", (unsigned)conn->rfd.fd); |
109 | 677 | } else { |
110 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
111 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
112 | 0 | "TCP %u\t| Could not close the socket (%s)", |
113 | 0 | (unsigned)conn->rfd.fd, errno_str)); |
114 | 0 | } |
115 | | |
116 | 677 | UA_free(conn); |
117 | | |
118 | | /* Check if this was the last connection for a closing ConnectionManager */ |
119 | 677 | TCP_checkStopped(pcm); |
120 | | |
121 | 677 | UA_UNLOCK(&el->elMutex); |
122 | 677 | } |
123 | | |
124 | | static int |
125 | 0 | getSockError(TCP_FD *conn) { |
126 | 0 | int error = 0; |
127 | 0 | #ifndef UA_ARCHITECTURE_WIN32 |
128 | 0 | socklen_t errlen = sizeof(int); |
129 | 0 | int err = UA_getsockopt(conn->rfd.fd, SOL_SOCKET, SO_ERROR, &error, &errlen); |
130 | | #else |
131 | | int errlen = (int)sizeof(int); |
132 | | int err = UA_getsockopt((SOCKET)conn->rfd.fd, SOL_SOCKET, SO_ERROR, |
133 | | (char*)&error, &errlen); |
134 | | #endif |
135 | 0 | return (err == 0) ? error : err; |
136 | 0 | } |
137 | | |
138 | | /* Gets called when a connection socket opens, receives data or closes */ |
139 | | static void |
140 | | TCP_connectionSocketCallback(UA_ConnectionManager *cm, TCP_FD *conn, |
141 | 64 | short event) { |
142 | 64 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; |
143 | 64 | UA_LOCK_ASSERT(&el->elMutex); |
144 | | |
145 | 64 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
146 | 64 | "TCP %u\t| Activity on the socket", |
147 | 64 | (unsigned)conn->rfd.fd); |
148 | | |
149 | | /* Error. The connection has closed. */ |
150 | 64 | if(event == UA_FDEVENT_ERR) { |
151 | 0 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
152 | 0 | "TCP %u\t| The connection closes with error %i", |
153 | 0 | (unsigned)conn->rfd.fd, getSockError(conn)); |
154 | 0 | TCP_shutdown(cm, conn); |
155 | 0 | return; |
156 | 0 | } |
157 | | |
158 | | /* Write-Event, a new connection has opened. But some errors come as an |
159 | | * out-event. For example if the remote side could not be reached to |
160 | | * initiate the connection. So we check manually for error conditions on |
161 | | * the socket. */ |
162 | 64 | if(event == UA_FDEVENT_OUT) { |
163 | 0 | int error = getSockError(conn); |
164 | 0 | if(error != 0) { |
165 | 0 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
166 | 0 | "TCP %u\t| The connection closes with error %i", |
167 | 0 | (unsigned)conn->rfd.fd, error); |
168 | 0 | TCP_shutdown(cm, conn); |
169 | 0 | return; |
170 | 0 | } |
171 | | |
172 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
173 | 0 | "TCP %u\t| Opening a new connection", |
174 | 0 | (unsigned)conn->rfd.fd); |
175 | | |
176 | | /* Now we are interested in read-events. */ |
177 | 0 | conn->rfd.listenEvents = UA_FDEVENT_IN; |
178 | 0 | UA_EventLoopPOSIX_modifyFD(el, &conn->rfd); |
179 | | |
180 | | /* A new socket has opened. Signal it to the application. */ |
181 | 0 | conn->applicationCB(cm, (uintptr_t)conn->rfd.fd, |
182 | 0 | conn->application, &conn->context, |
183 | 0 | UA_CONNECTIONSTATE_ESTABLISHED, |
184 | 0 | &UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL); |
185 | 0 | return; |
186 | 0 | } |
187 | | |
188 | 64 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
189 | 64 | "TCP %u\t| Allocate receive buffer", |
190 | 64 | (unsigned)conn->rfd.fd); |
191 | | |
192 | | /* Use the already allocated receive-buffer */ |
193 | 64 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
194 | 64 | UA_ByteString response = pcm->rxBuffer; |
195 | | |
196 | | /* Receive */ |
197 | 64 | UA_RESET_ERRNO; |
198 | 64 | #ifndef UA_ARCHITECTURE_WIN32 |
199 | 64 | ssize_t ret = UA_recv(conn->rfd.fd, (char*)response.data, |
200 | 64 | response.length, MSG_DONTWAIT); |
201 | | #else |
202 | | int ret = UA_recv(conn->rfd.fd, (char*)response.data, |
203 | | response.length, MSG_DONTWAIT); |
204 | | #endif |
205 | | |
206 | | /* Receive has failed */ |
207 | 64 | if(ret <= 0) { |
208 | 0 | if(UA_ERRNO == UA_INTERRUPTED || |
209 | 0 | UA_ERRNO == UA_WOULDBLOCK || |
210 | 0 | UA_ERRNO == UA_AGAIN) |
211 | 0 | return; /* Temporary error on an non-blocking socket */ |
212 | | |
213 | | /* Orderly shutdown of the socket */ |
214 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
215 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
216 | 0 | "TCP %u\t| recv signaled the socket was shutdown (%s)", |
217 | 0 | (unsigned)conn->rfd.fd, errno_str)); |
218 | 0 | TCP_shutdown(cm, conn); |
219 | 0 | return; |
220 | 0 | } |
221 | | |
222 | 64 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
223 | 64 | "TCP %u\t| Received message of size %u", |
224 | 64 | (unsigned)conn->rfd.fd, (unsigned)ret); |
225 | | |
226 | | /* Callback to the application layer */ |
227 | 64 | response.length = (size_t)ret; /* Set the length of the received buffer */ |
228 | 64 | conn->applicationCB(cm, (uintptr_t)conn->rfd.fd, |
229 | 64 | conn->application, &conn->context, |
230 | 64 | UA_CONNECTIONSTATE_ESTABLISHED, |
231 | 64 | &UA_KEYVALUEMAP_NULL, response); |
232 | 64 | } |
233 | | |
234 | | /* Gets called when a new connection opens or if the listenSocket is closed */ |
235 | | static void |
236 | 83 | TCP_listenSocketCallback(UA_ConnectionManager *cm, TCP_FD *conn, short event) { |
237 | 83 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
238 | 83 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; |
239 | 83 | UA_LOCK_ASSERT(&el->elMutex); |
240 | | |
241 | 83 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
242 | 83 | "TCP %u\t| Callback on server socket", |
243 | 83 | (unsigned)conn->rfd.fd); |
244 | | |
245 | | /* Try to accept a new connection */ |
246 | 83 | UA_RESET_ERRNO; |
247 | 83 | struct sockaddr_storage remote; |
248 | 83 | socklen_t remote_size = sizeof(remote); |
249 | 83 | UA_FD newsockfd = UA_accept(conn->rfd.fd, (struct sockaddr*)&remote, &remote_size); |
250 | 83 | if(newsockfd == UA_INVALID_FD) { |
251 | | /* Temporary error -- retry */ |
252 | 0 | if(UA_IS_TEMPORARY_ACCEPT_ERROR(UA_ERRNO)) |
253 | 0 | return; |
254 | | |
255 | | /* Close the listen socket */ |
256 | 0 | if(cm->eventSource.state != UA_EVENTSOURCESTATE_STOPPING) { |
257 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
258 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
259 | 0 | "TCP %u\t| Error %s, closing the server socket", |
260 | 0 | (unsigned)conn->rfd.fd, errno_str)); |
261 | 0 | } |
262 | |
|
263 | 0 | TCP_shutdown(cm, conn); |
264 | 0 | return; |
265 | 0 | } |
266 | | |
267 | | /* Log the name of the remote host */ |
268 | 83 | UA_RESET_ERRNO; |
269 | 83 | char hoststr[UA_MAXHOSTNAME_LENGTH]; |
270 | 83 | int get_res = UA_getnameinfo((struct sockaddr *)&remote, sizeof(remote), |
271 | 83 | hoststr, sizeof(hoststr), |
272 | 83 | NULL, 0, NI_NUMERICHOST); |
273 | 83 | if(get_res != 0) { |
274 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
275 | 0 | UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, |
276 | 0 | "TCP %u\t| getnameinfo(...) could not resolve the " |
277 | 0 | "hostname (%s)", (unsigned)conn->rfd.fd, errno_str)); |
278 | 0 | } |
279 | 83 | UA_LOG_INFO(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, |
280 | 83 | "TCP %u\t| Connection opened from \"%s\" via the server socket %u", |
281 | 83 | (unsigned)newsockfd, hoststr, (unsigned)conn->rfd.fd); |
282 | | |
283 | | /* Configure the new socket */ |
284 | 83 | UA_RESET_ERRNO; |
285 | 83 | UA_StatusCode res = UA_STATUSCODE_GOOD; |
286 | | /* res |= UA_EventLoopPOSIX_setNonBlocking(newsockfd); Inherited from the listen-socket */ |
287 | 83 | res |= UA_EventLoopPOSIX_setNoSigPipe(newsockfd); /* Supress interrupts from the socket */ |
288 | 83 | res |= TCP_setNoNagle(newsockfd); /* Disable Nagle's algorithm */ |
289 | 83 | if(res != UA_STATUSCODE_GOOD) { |
290 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
291 | 0 | UA_LOG_WARNING(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, |
292 | 0 | "TCP %u\t| Error seeting the TCP options (%s)", |
293 | 0 | (unsigned)newsockfd, errno_str)); |
294 | | /* Close the new socket */ |
295 | 0 | UA_close(newsockfd); |
296 | 0 | return; |
297 | 0 | } |
298 | | |
299 | | /* Allocate the UA_RegisteredFD */ |
300 | 83 | TCP_FD *newConn = (TCP_FD*)UA_calloc(1, sizeof(TCP_FD)); |
301 | 83 | if(!newConn) { |
302 | 6 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
303 | 6 | "TCP %u\t| Error allocating memory for the socket", |
304 | 6 | (unsigned)newsockfd); |
305 | 6 | UA_close(newsockfd); |
306 | 6 | return; |
307 | 6 | } |
308 | | |
309 | 77 | newConn->rfd.fd = newsockfd; |
310 | 77 | newConn->rfd.listenEvents = UA_FDEVENT_IN; |
311 | 77 | newConn->rfd.es = &cm->eventSource; |
312 | 77 | newConn->rfd.eventSourceCB = (UA_FDCallback)TCP_connectionSocketCallback; |
313 | 77 | newConn->applicationCB = conn->applicationCB; |
314 | 77 | newConn->application = conn->application; |
315 | 77 | newConn->context = conn->context; |
316 | | |
317 | | /* Register in the EventLoop. Signal to the user if registering failed. */ |
318 | 77 | res = UA_EventLoopPOSIX_registerFD(el, &newConn->rfd); |
319 | 77 | if(res != UA_STATUSCODE_GOOD) { |
320 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
321 | 0 | "TCP %u\t| Error registering the socket", |
322 | 0 | (unsigned)newsockfd); |
323 | 0 | UA_free(newConn); |
324 | 0 | UA_close(newsockfd); |
325 | 0 | return; |
326 | 0 | } |
327 | | |
328 | | /* Register internally in the EventSource */ |
329 | 77 | ZIP_INSERT(UA_FDTree, &pcm->fds, &newConn->rfd); |
330 | 77 | pcm->fdsSize++; |
331 | | |
332 | | /* Forward the remote hostname to the application */ |
333 | 77 | UA_KeyValuePair kvp; |
334 | 77 | kvp.key = UA_QUALIFIEDNAME(0, "remote-address"); |
335 | 77 | UA_String hostName = UA_STRING(hoststr); |
336 | 77 | UA_Variant_setScalar(&kvp.value, &hostName, &UA_TYPES[UA_TYPES_STRING]); |
337 | | |
338 | 77 | UA_KeyValueMap kvm; |
339 | 77 | kvm.mapSize = 1; |
340 | 77 | kvm.map = &kvp; |
341 | | |
342 | | /* The socket has opened. Signal it to the application. */ |
343 | 77 | newConn->applicationCB(cm, (uintptr_t)newsockfd, |
344 | 77 | newConn->application, &newConn->context, |
345 | 77 | UA_CONNECTIONSTATE_ESTABLISHED, |
346 | 77 | &kvm, UA_BYTESTRING_NULL); |
347 | 77 | } |
348 | | |
349 | | static UA_StatusCode |
350 | | TCP_registerListenSocket(UA_POSIXConnectionManager *pcm, struct addrinfo *ai, |
351 | | const char *hostname, UA_UInt16 port, |
352 | | void *application, void *context, |
353 | | UA_ConnectionManager_connectionCallback connectionCallback, |
354 | 600 | UA_Boolean validate, UA_Boolean reuseaddr) { |
355 | 600 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop; |
356 | 600 | UA_LOCK_ASSERT(&el->elMutex); |
357 | | |
358 | | /* Translate INADDR_ANY to IPv4/IPv6 address */ |
359 | 600 | UA_RESET_ERRNO; |
360 | 600 | char addrstr[UA_MAXHOSTNAME_LENGTH]; |
361 | 600 | int get_res = UA_getnameinfo(ai->ai_addr, ai->ai_addrlen, |
362 | 600 | addrstr, sizeof(addrstr), NULL, 0, 0); |
363 | 600 | if(get_res != 0) { |
364 | 0 | get_res = UA_getnameinfo(ai->ai_addr, ai->ai_addrlen, |
365 | 0 | addrstr, sizeof(addrstr), |
366 | 0 | NULL, 0, NI_NUMERICHOST); |
367 | 0 | if(get_res != 0) { |
368 | 0 | addrstr[0] = 0; |
369 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
370 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
371 | 0 | "TCP\t| getnameinfo(...) could not resolve the " |
372 | 0 | "hostname (%s)", errno_str)); |
373 | 0 | } |
374 | 0 | } |
375 | | |
376 | | /* Create the server socket */ |
377 | 600 | UA_RESET_ERRNO; |
378 | 600 | UA_FD listenSocket = UA_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); |
379 | 600 | if(listenSocket == UA_INVALID_FD) { |
380 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
381 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
382 | 0 | "TCP %u\t| Error opening the listen socket for " |
383 | 0 | "\"%s\" on port %u (%s)", |
384 | 0 | (unsigned)listenSocket, addrstr, port, errno_str)); |
385 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
386 | 0 | } |
387 | | |
388 | | /* Some Linux distributions have net.ipv6.bindv6only not activated. So |
389 | | * sockets can double-bind to IPv4 and IPv6. This leads to problems. Use |
390 | | * AF_INET6 sockets only for IPv6. */ |
391 | 600 | #if UA_IPV6 |
392 | 600 | int optval = 1; |
393 | 600 | if(ai->ai_family == AF_INET6 && |
394 | 600 | UA_setsockopt(listenSocket, IPPROTO_IPV6, IPV6_V6ONLY, |
395 | 300 | (const char*)&optval, sizeof(optval)) == -1) { |
396 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
397 | 0 | "TCP %u\t| Could not set an IPv6 socket to IPv6 only", |
398 | 0 | (unsigned)listenSocket); |
399 | 0 | UA_close(listenSocket); |
400 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
401 | 0 | } |
402 | 600 | #endif |
403 | | |
404 | | /* Allow rebinding to the IP/port combination. Eg. to restart the server. */ |
405 | 600 | if(reuseaddr && |
406 | 600 | UA_EventLoopPOSIX_setReusable(listenSocket) != UA_STATUSCODE_GOOD) { |
407 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
408 | 0 | "TCP %u\t| Could not make the socket addr reusable", |
409 | 0 | (unsigned)listenSocket); |
410 | 0 | UA_close(listenSocket); |
411 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
412 | 0 | } |
413 | | |
414 | | /* Set the socket non-blocking */ |
415 | 600 | if(UA_EventLoopPOSIX_setNonBlocking(listenSocket) != UA_STATUSCODE_GOOD) { |
416 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
417 | 0 | "TCP %u\t| Could not set the socket non-blocking", |
418 | 0 | (unsigned)listenSocket); |
419 | 0 | UA_close(listenSocket); |
420 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
421 | 0 | } |
422 | | |
423 | | /* Supress interrupts from the socket */ |
424 | 600 | if(UA_EventLoopPOSIX_setNoSigPipe(listenSocket) != UA_STATUSCODE_GOOD) { |
425 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
426 | 0 | "TCP %u\t| Could not disable SIGPIPE", |
427 | 0 | (unsigned)listenSocket); |
428 | 0 | UA_close(listenSocket); |
429 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
430 | 0 | } |
431 | | |
432 | | /* Bind socket to address */ |
433 | 600 | UA_RESET_ERRNO; |
434 | 600 | int ret = UA_bind(listenSocket, ai->ai_addr, (socklen_t)ai->ai_addrlen); |
435 | | |
436 | | /* Get the port being used if dynamic porting was used */ |
437 | 600 | if(port == 0) { |
438 | 0 | struct sockaddr_in sin; |
439 | 0 | memset(&sin, 0, sizeof(sin)); |
440 | 0 | socklen_t len = sizeof(sin); |
441 | 0 | UA_getsockname(listenSocket, (struct sockaddr *)&sin, &len); |
442 | 0 | port = ntohs(sin.sin_port); |
443 | 0 | } |
444 | | |
445 | | /* If the INADDR_ANY is used, use the local hostname */ |
446 | 600 | char hoststr[UA_MAXHOSTNAME_LENGTH]; |
447 | 600 | if(hostname) { |
448 | 0 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
449 | 0 | "TCP %u\t| Creating listen socket for \"%s\" on port %u", |
450 | 0 | (unsigned)listenSocket, hostname, port); |
451 | 600 | } else { |
452 | 600 | UA_gethostname(hoststr, UA_MAXHOSTNAME_LENGTH); |
453 | 600 | hostname = hoststr; |
454 | 600 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
455 | 600 | "TCP %u\t| Creating listen socket for \"%s\" " |
456 | 600 | "(with local hostname \"%s\") on port %u", |
457 | 600 | (unsigned)listenSocket, addrstr, hostname, port); |
458 | 600 | } |
459 | | |
460 | 600 | if(ret < 0) { |
461 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
462 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
463 | 0 | "TCP %u\t| Error binding the socket to the address %s (%s)", |
464 | 0 | (unsigned)listenSocket, hostname, errno_str)); |
465 | 0 | UA_close(listenSocket); |
466 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
467 | 0 | } |
468 | | |
469 | | /* Only validate, don't actually start listening */ |
470 | 600 | if(validate) { |
471 | 0 | UA_EventLoopPOSIX_setReusable(listenSocket); /* Ensure reuse is possible */ |
472 | 0 | UA_close(listenSocket); |
473 | 0 | return UA_STATUSCODE_GOOD; |
474 | 0 | } |
475 | | |
476 | | /* Start listening */ |
477 | 600 | UA_RESET_ERRNO; |
478 | 600 | if(UA_listen(listenSocket, UA_MAXBACKLOG) < 0) { |
479 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
480 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
481 | 0 | "TCP %u\t| Error listening on the socket (%s)", |
482 | 0 | (unsigned)listenSocket, errno_str)); |
483 | 0 | UA_EventLoopPOSIX_setReusable(listenSocket); /* Ensure reuse is possible */ |
484 | 0 | UA_close(listenSocket); |
485 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
486 | 0 | } |
487 | | |
488 | | /* Allocate the connection */ |
489 | 600 | TCP_FD *newConn = (TCP_FD*)UA_calloc(1, sizeof(TCP_FD)); |
490 | 600 | if(!newConn) { |
491 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
492 | 0 | "TCP %u\t| Error allocating memory for the socket", |
493 | 0 | (unsigned)listenSocket); |
494 | 0 | UA_EventLoopPOSIX_setReusable(listenSocket); /* Ensure reuse is possible */ |
495 | 0 | UA_close(listenSocket); |
496 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
497 | 0 | } |
498 | | |
499 | 600 | newConn->rfd.fd = listenSocket; |
500 | 600 | newConn->rfd.listenEvents = UA_FDEVENT_IN; |
501 | 600 | newConn->rfd.es = &pcm->cm.eventSource; |
502 | 600 | newConn->rfd.eventSourceCB = (UA_FDCallback)TCP_listenSocketCallback; |
503 | 600 | newConn->applicationCB = connectionCallback; |
504 | 600 | newConn->application = application; |
505 | 600 | newConn->context = context; |
506 | | |
507 | | /* Register in the EventLoop */ |
508 | 600 | UA_StatusCode res = UA_EventLoopPOSIX_registerFD(el, &newConn->rfd); |
509 | 600 | if(res != UA_STATUSCODE_GOOD) { |
510 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
511 | 0 | "TCP %u\t| Error registering the socket", |
512 | 0 | (unsigned)listenSocket); |
513 | 0 | UA_free(newConn); |
514 | 0 | UA_EventLoopPOSIX_setReusable(listenSocket); /* Ensure reuse is possible */ |
515 | 0 | UA_close(listenSocket); |
516 | 0 | return res; |
517 | 0 | } |
518 | | |
519 | | /* Register internally */ |
520 | 600 | ZIP_INSERT(UA_FDTree, &pcm->fds, &newConn->rfd); |
521 | 600 | pcm->fdsSize++; |
522 | | |
523 | | /* Set up the callback parameters */ |
524 | 600 | UA_String listenAddress = UA_STRING((char*)(uintptr_t)hostname); |
525 | 600 | UA_KeyValuePair params[2]; |
526 | 600 | params[0].key = UA_QUALIFIEDNAME(0, "listen-address"); |
527 | 600 | UA_Variant_setScalar(¶ms[0].value, &listenAddress, &UA_TYPES[UA_TYPES_STRING]); |
528 | 600 | params[1].key = UA_QUALIFIEDNAME(0, "listen-port"); |
529 | 600 | UA_Variant_setScalar(¶ms[1].value, &port, &UA_TYPES[UA_TYPES_UINT16]); |
530 | 600 | UA_KeyValueMap paramMap = {2, params}; |
531 | | |
532 | | /* Announce the listen-socket in the application */ |
533 | 600 | connectionCallback(&pcm->cm, (uintptr_t)listenSocket, |
534 | 600 | application, &newConn->context, |
535 | 600 | UA_CONNECTIONSTATE_ESTABLISHED, |
536 | 600 | ¶mMap, UA_BYTESTRING_NULL); |
537 | | |
538 | 600 | return UA_STATUSCODE_GOOD; |
539 | 600 | } |
540 | | |
541 | | static UA_StatusCode |
542 | | TCP_registerListenSockets(UA_POSIXConnectionManager *pcm, const char *hostname, |
543 | | UA_UInt16 port, void *application, void *context, |
544 | | UA_ConnectionManager_connectionCallback connectionCallback, |
545 | 300 | UA_Boolean validate, UA_Boolean reuseaddr) { |
546 | 300 | UA_LOCK_ASSERT(&((UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop)->elMutex); |
547 | | |
548 | | /* Create a string for the port */ |
549 | 300 | char portstr[6]; |
550 | 300 | mp_snprintf(portstr, sizeof(portstr), "%d", port); |
551 | | |
552 | | /* Get all the interface and IPv4/6 combinations for the configured hostname */ |
553 | 300 | struct addrinfo hints, *res; |
554 | 300 | memset(&hints, 0, sizeof hints); |
555 | 300 | #if UA_IPV6 |
556 | 300 | hints.ai_family = AF_UNSPEC; /* Allow IPv4 and IPv6 */ |
557 | | #else |
558 | | hints.ai_family = AF_INET; /* IPv4 only */ |
559 | | #endif |
560 | 300 | hints.ai_socktype = SOCK_STREAM; |
561 | 300 | hints.ai_protocol = IPPROTO_TCP; |
562 | 300 | hints.ai_flags = AI_PASSIVE; |
563 | | |
564 | 300 | UA_RESET_ERRNO; |
565 | 300 | int retcode = UA_getaddrinfo(hostname, portstr, &hints, &res); |
566 | 300 | if(retcode != 0) { |
567 | 0 | UA_LOG_SOCKET_ERRNO_GAI_WRAP( |
568 | 0 | UA_LOG_WARNING(pcm->cm.eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, |
569 | 0 | "TCP\t| Lookup for \"%s\" on port %u failed (%s)", |
570 | 0 | hostname, port, errno_str)); |
571 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
572 | 0 | } |
573 | | |
574 | | /* Add listen sockets. Aggregate the results to see if at least one |
575 | | * listen-socket was established. */ |
576 | 300 | UA_StatusCode total_result = UA_INT32_MAX; |
577 | 300 | struct addrinfo *ai = res; |
578 | 900 | while(ai) { |
579 | 600 | total_result &= TCP_registerListenSocket(pcm, ai, hostname, port, application, context, |
580 | 600 | connectionCallback, validate, reuseaddr); |
581 | 600 | ai = ai->ai_next; |
582 | 600 | } |
583 | 300 | UA_freeaddrinfo(res); |
584 | | |
585 | 300 | return total_result; |
586 | 300 | } |
587 | | |
588 | | /* Close the connection via a delayed callback */ |
589 | | static void |
590 | 677 | TCP_shutdown(UA_ConnectionManager *cm, TCP_FD *conn) { |
591 | | /* Already closing - nothing to do */ |
592 | 677 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; |
593 | 677 | UA_LOCK_ASSERT(&el->elMutex); |
594 | | |
595 | 677 | if(conn->rfd.dc.callback) { |
596 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
597 | 0 | "TCP %u\t| Cannot close - already closing", |
598 | 0 | (unsigned)conn->rfd.fd); |
599 | 0 | return; |
600 | 0 | } |
601 | | |
602 | | /* Shutdown the socket to cancel the current select/epoll */ |
603 | 677 | UA_shutdown(conn->rfd.fd, UA_SHUT_RDWR); |
604 | | |
605 | 677 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
606 | 677 | "TCP %u\t| Shutdown triggered", |
607 | 677 | (unsigned)conn->rfd.fd); |
608 | | |
609 | | /* Add to the delayed callback list. Will be cleaned up in the next |
610 | | * iteration. */ |
611 | 677 | UA_DelayedCallback *dc = &conn->rfd.dc; |
612 | 677 | dc->callback = TCP_delayedClose; |
613 | 677 | dc->application = cm; |
614 | 677 | dc->context = conn; |
615 | | |
616 | | /* Adding a delayed callback does not take a lock */ |
617 | 677 | UA_EventLoopPOSIX_addDelayedCallback((UA_EventLoop*)el, dc); |
618 | 677 | } |
619 | | |
620 | | static UA_StatusCode |
621 | 677 | TCP_shutdownConnection(UA_ConnectionManager *cm, uintptr_t connectionId) { |
622 | 677 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
623 | 677 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)cm->eventSource.eventLoop; |
624 | 677 | UA_LOCK(&el->elMutex); |
625 | | |
626 | 677 | UA_FD fd = (UA_FD)connectionId; |
627 | 677 | TCP_FD *conn = (TCP_FD*)ZIP_FIND(UA_FDTree, &pcm->fds, &fd); |
628 | 677 | if(!conn) { |
629 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
630 | 0 | "TCP\t| Cannot close TCP connection %u - not found", |
631 | 0 | (unsigned)connectionId); |
632 | 0 | UA_UNLOCK(&el->elMutex); |
633 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
634 | 0 | } |
635 | | |
636 | 677 | TCP_shutdown(cm, conn); |
637 | | |
638 | 677 | UA_UNLOCK(&el->elMutex); |
639 | 677 | return UA_STATUSCODE_GOOD; |
640 | 677 | } |
641 | | |
642 | | static UA_StatusCode |
643 | | TCP_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId, |
644 | 58 | const UA_KeyValueMap *params, UA_ByteString *buf) { |
645 | | /* We may not have a lock. But we need not take it. As the connectionId is |
646 | | * the fd, no need to do a lookup and access internal data strucures. */ |
647 | | |
648 | | /* Prevent OS signals when sending to a closed socket */ |
649 | 58 | int flags = MSG_NOSIGNAL; |
650 | | |
651 | 58 | struct pollfd tmp_poll_fd; |
652 | 58 | tmp_poll_fd.fd = (UA_FD)connectionId; |
653 | 58 | tmp_poll_fd.events = UA_POLLOUT; |
654 | | |
655 | | /* Send the full buffer. This may require several calls to send */ |
656 | 58 | size_t nWritten = 0; |
657 | 58 | do { |
658 | 58 | ssize_t n = 0; |
659 | 58 | do { |
660 | 58 | UA_RESET_ERRNO; |
661 | 58 | UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, |
662 | 58 | "TCP %u\t| Attempting to send", (unsigned)connectionId); |
663 | 58 | size_t bytes_to_send = buf->length - nWritten; |
664 | 58 | n = UA_send((UA_FD)connectionId, |
665 | 58 | (const char*)buf->data + nWritten, |
666 | 58 | bytes_to_send, flags); |
667 | 58 | if(n < 0) { |
668 | | /* An error we cannot recover from? */ |
669 | 0 | if(UA_ERRNO != UA_INTERRUPTED && UA_ERRNO != UA_WOULDBLOCK && |
670 | 0 | UA_ERRNO != UA_AGAIN) |
671 | 0 | goto shutdown; |
672 | | |
673 | | /* Poll for the socket resources to become available and retry |
674 | | * (blocking) */ |
675 | 0 | int poll_ret; |
676 | 0 | do { |
677 | 0 | UA_RESET_ERRNO; |
678 | 0 | poll_ret = UA_poll(&tmp_poll_fd, 1, 100); |
679 | 0 | if(poll_ret < 0 && UA_ERRNO != UA_INTERRUPTED) |
680 | 0 | goto shutdown; |
681 | 0 | } while(poll_ret <= 0); |
682 | 0 | } |
683 | 58 | } while(n < 0); |
684 | 58 | nWritten += (size_t)n; |
685 | 58 | } while(nWritten < buf->length); |
686 | | |
687 | | /* Clean up and return */ |
688 | 58 | UA_EventLoopPOSIX_freeNetworkBuffer(cm, connectionId, buf); |
689 | 58 | return UA_STATUSCODE_GOOD; |
690 | | |
691 | 0 | shutdown: |
692 | | /* Error -> shutdown the connection */ |
693 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
694 | 0 | UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, |
695 | 0 | "TCP %u\t| Send failed with error %s", |
696 | 0 | (unsigned)connectionId, errno_str)); |
697 | 0 | TCP_shutdownConnection(cm, connectionId); |
698 | 0 | UA_EventLoopPOSIX_freeNetworkBuffer(cm, connectionId, buf); |
699 | 0 | return UA_STATUSCODE_BADCONNECTIONCLOSED; |
700 | 58 | } |
701 | | |
702 | | /* Create a listen-socket that waits for incoming connections */ |
703 | | static UA_StatusCode |
704 | | TCP_openPassiveConnection(UA_POSIXConnectionManager *pcm, const UA_KeyValueMap *params, |
705 | | void *application, void *context, |
706 | | UA_ConnectionManager_connectionCallback connectionCallback, |
707 | 300 | UA_Boolean validate) { |
708 | 300 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop; |
709 | 300 | UA_LOCK_ASSERT(&el->elMutex); |
710 | | |
711 | | /* Get the port parameter */ |
712 | 300 | const UA_UInt16 *port = (const UA_UInt16*) |
713 | 300 | UA_KeyValueMap_getScalar(params, tcpConnectionParams[TCP_PARAMINDEX_PORT].name, |
714 | 300 | &UA_TYPES[UA_TYPES_UINT16]); |
715 | 300 | UA_assert(port); /* existence is checked before */ |
716 | | |
717 | | /* Get the address parameter */ |
718 | 300 | const UA_Variant *addrs = |
719 | 300 | UA_KeyValueMap_get(params, tcpConnectionParams[TCP_PARAMINDEX_ADDR].name); |
720 | 300 | size_t addrsSize = 0; |
721 | 300 | if(addrs) { |
722 | 0 | UA_assert(addrs->type == &UA_TYPES[UA_TYPES_STRING]); |
723 | 0 | if(UA_Variant_isScalar(addrs)) |
724 | 0 | addrsSize = 1; |
725 | 0 | else |
726 | 0 | addrsSize = addrs->arrayLength; |
727 | 0 | } |
728 | | |
729 | | /* Get the reuseaddr parameter */ |
730 | 300 | UA_Boolean reuseaddr = false; |
731 | 300 | const UA_Boolean *reuseaddrTmp = (const UA_Boolean*) |
732 | 300 | UA_KeyValueMap_getScalar(params, tcpConnectionParams[TCP_PARAMINDEX_REUSE].name, |
733 | 300 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
734 | 300 | if(reuseaddrTmp) |
735 | 300 | reuseaddr = *reuseaddrTmp; |
736 | | |
737 | | /* Undefined or empty addresses array -> listen on all interfaces */ |
738 | 300 | if(addrsSize == 0) { |
739 | 300 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
740 | 300 | "TCP\t| Listening on all interfaces"); |
741 | 300 | return TCP_registerListenSockets(pcm, NULL, *port, application, |
742 | 300 | context, connectionCallback, validate, reuseaddr); |
743 | 300 | } |
744 | | |
745 | | /* Iterate over the configured hostnames */ |
746 | 0 | UA_String *hostStrings = (UA_String*)addrs->data; |
747 | 0 | UA_StatusCode retval = UA_STATUSCODE_BADINTERNALERROR; |
748 | 0 | for(size_t i = 0; i < addrsSize; i++) { |
749 | 0 | char hostname[512]; |
750 | 0 | if(hostStrings[i].length >= sizeof(hostname)) |
751 | 0 | continue; |
752 | 0 | memcpy(hostname, hostStrings[i].data, hostStrings->length); |
753 | 0 | hostname[hostStrings->length] = '\0'; |
754 | 0 | if(TCP_registerListenSockets(pcm, hostname, *port, application, |
755 | 0 | context, connectionCallback, validate, reuseaddr) == UA_STATUSCODE_GOOD) |
756 | 0 | retval = UA_STATUSCODE_GOOD; |
757 | 0 | } |
758 | 0 | return retval; |
759 | 300 | } |
760 | | |
761 | | /* Open a TCP connection to a remote host */ |
762 | | static UA_StatusCode |
763 | | TCP_openActiveConnection(UA_POSIXConnectionManager *pcm, const UA_KeyValueMap *params, |
764 | | void *application, void *context, |
765 | | UA_ConnectionManager_connectionCallback connectionCallback, |
766 | 0 | UA_Boolean validate) { |
767 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop; |
768 | 0 | UA_LOCK_ASSERT(&el->elMutex); |
769 | | |
770 | | /* Get the connection parameters */ |
771 | 0 | char hostname[UA_MAXHOSTNAME_LENGTH]; |
772 | 0 | char portStr[UA_MAXPORTSTR_LENGTH]; |
773 | | |
774 | | /* Prepare the port parameter as a string */ |
775 | 0 | const UA_UInt16 *port = (const UA_UInt16*) |
776 | 0 | UA_KeyValueMap_getScalar(params, tcpConnectionParams[TCP_PARAMINDEX_PORT].name, |
777 | 0 | &UA_TYPES[UA_TYPES_UINT16]); |
778 | 0 | UA_assert(port); /* existence is checked before */ |
779 | 0 | mp_snprintf(portStr, UA_MAXPORTSTR_LENGTH, "%d", *port); |
780 | | |
781 | | /* Prepare the hostname string */ |
782 | 0 | const UA_String *addr = (const UA_String*) |
783 | 0 | UA_KeyValueMap_getScalar(params, tcpConnectionParams[TCP_PARAMINDEX_ADDR].name, |
784 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
785 | 0 | if(!addr) { |
786 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
787 | 0 | "TCP\t| Open TCP Connection: No hostname defined, aborting"); |
788 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
789 | 0 | } |
790 | 0 | if(addr->length >= UA_MAXHOSTNAME_LENGTH) { |
791 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, |
792 | 0 | "TCP\t| Open TCP Connection: Hostname too long, aborting"); |
793 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
794 | 0 | } |
795 | 0 | strncpy(hostname, (const char*)addr->data, addr->length); |
796 | 0 | hostname[addr->length] = 0; |
797 | |
|
798 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
799 | 0 | "TCP\t| Open a connection to \"%s\" on port %s", hostname, portStr); |
800 | | |
801 | | /* Create the socket description from the connectString |
802 | | * TODO: Make this non-blocking */ |
803 | 0 | UA_RESET_ERRNO; |
804 | 0 | struct addrinfo hints, *info; |
805 | 0 | memset(&hints, 0, sizeof(struct addrinfo)); |
806 | 0 | hints.ai_family = AF_UNSPEC; |
807 | 0 | hints.ai_socktype = SOCK_STREAM; |
808 | 0 | int error = UA_getaddrinfo(hostname, portStr, &hints, &info); |
809 | 0 | if(error != 0) { |
810 | 0 | UA_LOG_SOCKET_ERRNO_GAI_WRAP( |
811 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
812 | 0 | "TCP\t| Lookup of %s failed (%s)", |
813 | 0 | hostname, errno_str)); |
814 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
815 | 0 | } |
816 | | |
817 | | /* Create a socket */ |
818 | 0 | UA_RESET_ERRNO; |
819 | 0 | UA_FD newSock = UA_socket(info->ai_family, info->ai_socktype, info->ai_protocol); |
820 | 0 | if(newSock == UA_INVALID_FD) { |
821 | 0 | UA_freeaddrinfo(info); |
822 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
823 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
824 | 0 | "TCP\t| Could not create socket to connect to %s (%s)", |
825 | 0 | hostname, errno_str)); |
826 | 0 | return UA_STATUSCODE_BADDISCONNECT; |
827 | 0 | } |
828 | | |
829 | | /* Set the socket options */ |
830 | 0 | UA_RESET_ERRNO; |
831 | 0 | UA_StatusCode res = UA_STATUSCODE_GOOD; |
832 | 0 | res |= UA_EventLoopPOSIX_setNonBlocking(newSock); |
833 | 0 | res |= UA_EventLoopPOSIX_setNoSigPipe(newSock); |
834 | 0 | res |= TCP_setNoNagle(newSock); |
835 | 0 | if(res != UA_STATUSCODE_GOOD) { |
836 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
837 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
838 | 0 | "TCP\t| Could not set socket options: %s", errno_str)); |
839 | 0 | UA_freeaddrinfo(info); |
840 | 0 | UA_close(newSock); |
841 | 0 | return res; |
842 | 0 | } |
843 | | |
844 | | /* Only validate, don't actually open the connection */ |
845 | 0 | if(validate) { |
846 | 0 | UA_freeaddrinfo(info); |
847 | 0 | UA_close(newSock); |
848 | 0 | return UA_STATUSCODE_GOOD; |
849 | 0 | } |
850 | | |
851 | | /* Non-blocking connect */ |
852 | 0 | UA_RESET_ERRNO; |
853 | 0 | error = UA_connect(newSock, info->ai_addr, info->ai_addrlen); |
854 | 0 | UA_freeaddrinfo(info); |
855 | 0 | if(error != 0 && |
856 | 0 | UA_ERRNO != UA_INPROGRESS && |
857 | 0 | UA_ERRNO != UA_WOULDBLOCK) { |
858 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
859 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
860 | 0 | "TCP\t| Connecting the socket to %s failed (%s)", |
861 | 0 | hostname, errno_str)); |
862 | 0 | UA_close(newSock); |
863 | 0 | return UA_STATUSCODE_BADDISCONNECT; |
864 | 0 | } |
865 | | |
866 | | /* Allocate the UA_RegisteredFD */ |
867 | 0 | TCP_FD *newConn = (TCP_FD*)UA_calloc(1, sizeof(TCP_FD)); |
868 | 0 | if(!newConn) { |
869 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
870 | 0 | "TCP %u\t| Error allocating memory for the socket", |
871 | 0 | (unsigned)newSock); |
872 | 0 | UA_close(newSock); |
873 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
874 | 0 | } |
875 | | |
876 | 0 | newConn->rfd.fd = newSock; |
877 | 0 | newConn->rfd.es = &pcm->cm.eventSource; |
878 | 0 | newConn->rfd.eventSourceCB = (UA_FDCallback)TCP_connectionSocketCallback; |
879 | 0 | newConn->rfd.listenEvents = UA_FDEVENT_OUT; /* Switched to _IN once the |
880 | | * connection is open */ |
881 | 0 | newConn->applicationCB = connectionCallback; |
882 | 0 | newConn->application = application; |
883 | 0 | newConn->context = context; |
884 | | |
885 | | /* Register the fd to trigger when output is possible (the connection is open) */ |
886 | 0 | res = UA_EventLoopPOSIX_registerFD(el, &newConn->rfd); |
887 | 0 | if(res != UA_STATUSCODE_GOOD) { |
888 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
889 | 0 | "TCP\t| Registering the socket to connect to %s failed", hostname); |
890 | 0 | UA_close(newSock); |
891 | 0 | UA_free(newConn); |
892 | 0 | return res; |
893 | 0 | } |
894 | | |
895 | | /* Register internally in the EventSource */ |
896 | 0 | ZIP_INSERT(UA_FDTree, &pcm->fds, &newConn->rfd); |
897 | 0 | pcm->fdsSize++; |
898 | |
|
899 | 0 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
900 | 0 | "TCP %u\t| Opening a connection to \"%s\" on port %s", |
901 | 0 | (unsigned)newSock, hostname, portStr); |
902 | | |
903 | | /* Signal the new connection to the application as asynchonously opening */ |
904 | 0 | connectionCallback(&pcm->cm, (uintptr_t)newSock, |
905 | 0 | application, &newConn->context, |
906 | 0 | UA_CONNECTIONSTATE_OPENING, &UA_KEYVALUEMAP_NULL, |
907 | 0 | UA_BYTESTRING_NULL); |
908 | |
|
909 | 0 | return UA_STATUSCODE_GOOD; |
910 | 0 | } |
911 | | |
912 | | static UA_StatusCode |
913 | | TCP_openConnection(UA_ConnectionManager *cm, const UA_KeyValueMap *params, |
914 | | void *application, void *context, |
915 | 300 | UA_ConnectionManager_connectionCallback connectionCallback) { |
916 | 300 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
917 | 300 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; |
918 | 300 | UA_LOCK(&el->elMutex); |
919 | | |
920 | 300 | if(cm->eventSource.state != UA_EVENTSOURCESTATE_STARTED) { |
921 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
922 | 0 | "TCP\t| Cannot open a connection for a " |
923 | 0 | "ConnectionManager that is not started"); |
924 | 0 | UA_UNLOCK(&el->elMutex); |
925 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
926 | 0 | } |
927 | | |
928 | | /* Check the parameters */ |
929 | 300 | UA_StatusCode res = |
930 | 300 | UA_KeyValueRestriction_validate(el->eventLoop.logger, "TCP", |
931 | 300 | tcpConnectionParams, |
932 | 300 | TCP_PARAMETERSSIZE, params); |
933 | 300 | if(res != UA_STATUSCODE_GOOD) { |
934 | 0 | UA_UNLOCK(&el->elMutex); |
935 | 0 | return res; |
936 | 0 | } |
937 | | |
938 | | /* Only validate the parameters? */ |
939 | 300 | UA_Boolean validate = false; |
940 | 300 | const UA_Boolean *validateParam = (const UA_Boolean*) |
941 | 300 | UA_KeyValueMap_getScalar(params, |
942 | 300 | tcpConnectionParams[TCP_PARAMINDEX_VALIDATE].name, |
943 | 300 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
944 | 300 | if(validateParam) |
945 | 0 | validate = *validateParam; |
946 | | |
947 | | /* Listen or active connection? */ |
948 | 300 | UA_Boolean listen = false; |
949 | 300 | const UA_Boolean *listenParam = (const UA_Boolean*) |
950 | 300 | UA_KeyValueMap_getScalar(params, |
951 | 300 | tcpConnectionParams[TCP_PARAMINDEX_LISTEN].name, |
952 | 300 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
953 | 300 | if(listenParam) |
954 | 300 | listen = *listenParam; |
955 | | |
956 | 300 | if(listen) { |
957 | 300 | res = TCP_openPassiveConnection(pcm, params, application, context, |
958 | 300 | connectionCallback, validate); |
959 | 300 | } else { |
960 | 0 | res = TCP_openActiveConnection(pcm, params, application, context, |
961 | 0 | connectionCallback, validate); |
962 | 0 | } |
963 | | |
964 | 300 | UA_UNLOCK(&el->elMutex); |
965 | 300 | return res; |
966 | 300 | } |
967 | | |
968 | | static UA_StatusCode |
969 | 551 | TCP_eventSourceStart(UA_ConnectionManager *cm) { |
970 | 551 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
971 | 551 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; |
972 | 551 | if(!el) |
973 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
974 | | |
975 | 551 | UA_LOCK(&el->elMutex); |
976 | | |
977 | | /* Check the state */ |
978 | 551 | if(cm->eventSource.state != UA_EVENTSOURCESTATE_STOPPED) { |
979 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
980 | 0 | "TCP\t| To start the ConnectionManager, it has to be " |
981 | 0 | "registered in an EventLoop and not started yet"); |
982 | 0 | UA_UNLOCK(&el->elMutex); |
983 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
984 | 0 | } |
985 | | |
986 | | /* Check the parameters */ |
987 | 551 | UA_StatusCode res = |
988 | 551 | UA_KeyValueRestriction_validate(el->eventLoop.logger, "TCP", |
989 | 551 | tcpManagerParams, TCP_MANAGERPARAMS, |
990 | 551 | &cm->eventSource.params); |
991 | 551 | if(res != UA_STATUSCODE_GOOD) |
992 | 0 | goto finish; |
993 | | |
994 | | /* Allocate the rx buffer */ |
995 | 551 | res = UA_EventLoopPOSIX_allocateStaticBuffers(pcm); |
996 | 551 | if(res != UA_STATUSCODE_GOOD) |
997 | 0 | goto finish; |
998 | | |
999 | | /* Set the EventSource to the started state */ |
1000 | 551 | cm->eventSource.state = UA_EVENTSOURCESTATE_STARTED; |
1001 | | |
1002 | 551 | finish: |
1003 | 551 | UA_UNLOCK(&el->elMutex); |
1004 | 551 | return res; |
1005 | 551 | } |
1006 | | |
1007 | | static void * |
1008 | 0 | TCP_shutdownCB(void *application, UA_RegisteredFD *rfd) { |
1009 | 0 | UA_ConnectionManager *cm = (UA_ConnectionManager*)application; |
1010 | 0 | TCP_shutdown(cm, (TCP_FD*)rfd); |
1011 | 0 | return NULL; |
1012 | 0 | } |
1013 | | |
1014 | | static void |
1015 | 551 | TCP_eventSourceStop(UA_ConnectionManager *cm) { |
1016 | 551 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
1017 | 551 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; |
1018 | 551 | (void)el; |
1019 | | |
1020 | 551 | UA_LOCK(&el->elMutex); |
1021 | | |
1022 | 551 | UA_LOG_DEBUG(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, |
1023 | 551 | "TCP\t| Shutting down the ConnectionManager"); |
1024 | | |
1025 | | /* Prevent new connections to open */ |
1026 | 551 | cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPING; |
1027 | | |
1028 | | /* Shutdown all existing connection */ |
1029 | 551 | ZIP_ITER(UA_FDTree, &pcm->fds, TCP_shutdownCB, cm); |
1030 | | |
1031 | | /* All sockets closed? Otherwise iterate some more. */ |
1032 | 551 | TCP_checkStopped(pcm); |
1033 | | |
1034 | 551 | UA_UNLOCK(&el->elMutex); |
1035 | 551 | } |
1036 | | |
1037 | | static UA_StatusCode |
1038 | 551 | TCP_eventSourceDelete(UA_ConnectionManager *cm) { |
1039 | 551 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
1040 | 551 | if(cm->eventSource.state >= UA_EVENTSOURCESTATE_STARTING) { |
1041 | 0 | UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, |
1042 | 0 | "TCP\t| The EventSource must be stopped before it can be deleted"); |
1043 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1044 | 0 | } |
1045 | | |
1046 | 551 | UA_ByteString_clear(&pcm->rxBuffer); |
1047 | 551 | UA_ByteString_clear(&pcm->txBuffer); |
1048 | 551 | UA_KeyValueMap_clear(&cm->eventSource.params); |
1049 | 551 | UA_String_clear(&cm->eventSource.name); |
1050 | 551 | UA_free(cm); |
1051 | | |
1052 | 551 | return UA_STATUSCODE_GOOD; |
1053 | 551 | } |
1054 | | |
1055 | | static const char *tcpName = "tcp"; |
1056 | | |
1057 | | UA_ConnectionManager * |
1058 | 551 | UA_ConnectionManager_new_POSIX_TCP(const UA_String eventSourceName) { |
1059 | 551 | UA_POSIXConnectionManager *cm = (UA_POSIXConnectionManager*) |
1060 | 551 | UA_calloc(1, sizeof(UA_POSIXConnectionManager)); |
1061 | 551 | if(!cm) |
1062 | 0 | return NULL; |
1063 | | |
1064 | 551 | cm->cm.eventSource.eventSourceType = UA_EVENTSOURCETYPE_CONNECTIONMANAGER; |
1065 | 551 | UA_String_copy(&eventSourceName, &cm->cm.eventSource.name); |
1066 | 551 | cm->cm.eventSource.start = (UA_StatusCode (*)(UA_EventSource *))TCP_eventSourceStart; |
1067 | 551 | cm->cm.eventSource.stop = (void (*)(UA_EventSource *))TCP_eventSourceStop; |
1068 | 551 | cm->cm.eventSource.free = (UA_StatusCode (*)(UA_EventSource *))TCP_eventSourceDelete; |
1069 | 551 | cm->cm.protocol = UA_STRING((char*)(uintptr_t)tcpName); |
1070 | 551 | cm->cm.openConnection = TCP_openConnection; |
1071 | 551 | cm->cm.allocNetworkBuffer = UA_EventLoopPOSIX_allocNetworkBuffer; |
1072 | 551 | cm->cm.freeNetworkBuffer = UA_EventLoopPOSIX_freeNetworkBuffer; |
1073 | 551 | cm->cm.sendWithConnection = TCP_sendWithConnection; |
1074 | 551 | cm->cm.closeConnection = TCP_shutdownConnection; |
1075 | 551 | return &cm->cm; |
1076 | 551 | } |
1077 | | |
1078 | | #endif |