/src/open62541_15/arch/posix/eventloop_posix_eth.c
Line | Count | Source |
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 2018 (c) Kontron Europe GmbH (Author: Rudolf Hoyler) |
6 | | * Copyright 2019-2020 (c) Kalycito Infotech Private Limited |
7 | | * Copyright 2019-2020 (c) Wind River Systems, Inc. |
8 | | * Copyright 2022 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
9 | | * Copyright 2025 (c) SICK AG (Author: Joerg Fischer) |
10 | | */ |
11 | | |
12 | | #include "eventloop_posix.h" |
13 | | |
14 | | #if defined(UA_ARCHITECTURE_POSIX) && !defined(UA_ARCHITECTURE_LWIP) && defined(__linux__) |
15 | | |
16 | | #include <arpa/inet.h> /* htons */ |
17 | | #include <net/ethernet.h> /* ETH_P_*/ |
18 | | #include <linux/if_packet.h> |
19 | | #include <linux/net_tstamp.h> /* txtime */ |
20 | | |
21 | | /* Configuration parameters */ |
22 | | |
23 | 20.6k | #define ETH_MANAGERPARAMS 2 |
24 | | |
25 | | static UA_KeyValueRestriction ethManagerParams[ETH_MANAGERPARAMS] = { |
26 | | {{0, UA_STRING_STATIC("recv-bufsize")}, &UA_TYPES[UA_TYPES_UINT32], false, true, false}, |
27 | | {{0, UA_STRING_STATIC("send-bufsize")}, &UA_TYPES[UA_TYPES_UINT32], false, true, false} |
28 | | }; |
29 | | |
30 | 0 | #define ETH_PARAMETERSSIZE 15 |
31 | 0 | #define ETH_PARAMINDEX_ADDR 0 |
32 | 0 | #define ETH_PARAMINDEX_LISTEN 1 |
33 | 0 | #define ETH_PARAMINDEX_IFACE 2 |
34 | 0 | #define ETH_PARAMINDEX_ETHERTYPE 3 |
35 | 0 | #define ETH_PARAMINDEX_VID 4 |
36 | 0 | #define ETH_PARAMINDEX_PCP 5 |
37 | 0 | #define ETH_PARAMINDEX_DEI 6 |
38 | 0 | #define ETH_PARAMINDEX_PROMISCUOUS 7 |
39 | 0 | #define ETH_PARAMINDEX_PRIORITY 8 |
40 | 0 | #define ETH_PARAMINDEX_TXTIME_ENABLE 9 |
41 | 0 | #define ETH_PARAMINDEX_TXTIME_FLAGS 10 |
42 | 0 | #define ETH_PARAMINDEX_TXTIME 11 |
43 | 0 | #define ETH_PARAMINDEX_TXTIME_PICO 12 |
44 | 0 | #define ETH_PARAMINDEX_TXTIME_DROP 13 |
45 | 0 | #define ETH_PARAMINDEX_VALIDATE 14 |
46 | | |
47 | | static UA_KeyValueRestriction ethConnectionParams[ETH_PARAMETERSSIZE+1] = { |
48 | | {{0, UA_STRING_STATIC("address")}, &UA_TYPES[UA_TYPES_STRING], false, true, false}, |
49 | | {{0, UA_STRING_STATIC("listen")}, &UA_TYPES[UA_TYPES_BOOLEAN], false, true, false}, |
50 | | {{0, UA_STRING_STATIC("interface")}, &UA_TYPES[UA_TYPES_STRING], true, true, false}, |
51 | | {{0, UA_STRING_STATIC("ethertype")}, &UA_TYPES[UA_TYPES_UINT16], false, true, false}, |
52 | | {{0, UA_STRING_STATIC("vid")}, &UA_TYPES[UA_TYPES_UINT16], false, true, false}, |
53 | | {{0, UA_STRING_STATIC("pcp")}, &UA_TYPES[UA_TYPES_BYTE], false, true, false}, |
54 | | {{0, UA_STRING_STATIC("dei")}, &UA_TYPES[UA_TYPES_BOOLEAN], false, true, false}, |
55 | | {{0, UA_STRING_STATIC("promiscuous")}, &UA_TYPES[UA_TYPES_BOOLEAN], false, true, false}, |
56 | | {{0, UA_STRING_STATIC("priority")}, &UA_TYPES[UA_TYPES_UINT32], false, true, false}, |
57 | | {{0, UA_STRING_STATIC("txtime-enable")}, &UA_TYPES[UA_TYPES_BOOLEAN], false, true, false}, |
58 | | {{0, UA_STRING_STATIC("txtime-flags")}, &UA_TYPES[UA_TYPES_UINT32], false, true, false}, |
59 | | {{0, UA_STRING_STATIC("txtime")}, &UA_TYPES[UA_TYPES_DATETIME], false, true, false}, |
60 | | {{0, UA_STRING_STATIC("txtime-pico")}, &UA_TYPES[UA_TYPES_UINT16], false, true, false}, |
61 | | {{0, UA_STRING_STATIC("txtime-drop-late")}, &UA_TYPES[UA_TYPES_BOOLEAN], false, true, false}, |
62 | | {{0, UA_STRING_STATIC("validate")}, &UA_TYPES[UA_TYPES_BOOLEAN], false, true, false}, |
63 | | /* Duplicated address parameter with a scalar value required. For the send-socket case. */ |
64 | | {{0, UA_STRING_STATIC("address")}, &UA_TYPES[UA_TYPES_STRING], true, true, false}, |
65 | | }; |
66 | | |
67 | 0 | #define UA_ETH_MAXHEADERLENGTH (2*ETHER_ADDR_LEN)+4+2+2 |
68 | | |
69 | | typedef struct { |
70 | | UA_RegisteredFD rfd; |
71 | | |
72 | | UA_ConnectionManager_connectionCallback applicationCB; |
73 | | void *application; |
74 | | void *context; |
75 | | |
76 | | struct sockaddr_ll sll; |
77 | | /* The Ethernet header to prepend for sending frames is precomputed and reused. |
78 | | * The length field (the last 2 byte) is adjusted. |
79 | | * - 2 * ETHER_ADDR_LEN: destination and source |
80 | | * - 4 byte: VLAN tagging (optional) |
81 | | * - 2 byte: EtherType (optional) |
82 | | * - 2 byte: length */ |
83 | | unsigned char header[UA_ETH_MAXHEADERLENGTH]; |
84 | | unsigned char headerSize; |
85 | | unsigned char lengthOffset; /* No length field if zero */ |
86 | | |
87 | | UA_Boolean txtimeEnabled; |
88 | | } ETH_FD; |
89 | | |
90 | | /* The format of a Ethernet address is six groups of hexadecimal digits, |
91 | | * separated by hyphens (e.g. 01-23-45-67-89-ab). */ |
92 | | static UA_StatusCode |
93 | 0 | parseEthAddress(const UA_String *buf, UA_Byte *addr) { |
94 | 0 | size_t curr = 0, idx = 0; |
95 | 0 | for(; idx < ETHER_ADDR_LEN; idx++) { |
96 | 0 | UA_UInt32 value; |
97 | 0 | size_t progress = UA_readNumberWithBase(&buf->data[curr], |
98 | 0 | buf->length - curr, &value, 16); |
99 | 0 | if(progress == 0 || value > (long)0xff) |
100 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
101 | | |
102 | 0 | addr[idx] = (UA_Byte) value; |
103 | |
|
104 | 0 | curr += progress; |
105 | 0 | if(curr == buf->length) |
106 | 0 | break; |
107 | | |
108 | 0 | if(buf->data[curr] != '-') |
109 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
110 | | |
111 | 0 | curr++; /* skip '-' */ |
112 | 0 | } |
113 | | |
114 | 0 | if(idx != (ETH_ALEN-1)) |
115 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
116 | | |
117 | 0 | return UA_STATUSCODE_GOOD; |
118 | 0 | } |
119 | | |
120 | | static UA_Boolean |
121 | 0 | isMulticastEthAddress(const UA_Byte *address) { |
122 | 0 | if((address[0] & 1) == 0) |
123 | 0 | return false; /* Unicast address */ |
124 | 0 | for(size_t i = 0; i < ETHER_ADDR_LEN; i++) { |
125 | 0 | if(address[i] != 0xff) |
126 | 0 | return true; /* Not broadcast address ff-ff-ff-ff-ff-ff */ |
127 | 0 | } |
128 | 0 | return false; |
129 | 0 | } |
130 | | |
131 | | static void |
132 | 0 | setAddrString(unsigned char addrStr[18], unsigned char addr[ETHER_ADDR_LEN]) { |
133 | 0 | mp_snprintf((char*)addrStr, 18, "%02x-%02x-%02x-%02x-%02x-%02x", |
134 | 0 | addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); |
135 | 0 | } |
136 | | |
137 | | /* Return zero if parsing failed */ |
138 | | static size_t |
139 | | parseETHHeader(const UA_ByteString *buf, |
140 | | unsigned char destAddr[ETHER_ADDR_LEN], |
141 | | unsigned char sourceAddr[ETHER_ADDR_LEN], |
142 | | UA_UInt16 *etherType, UA_UInt16 *vid, |
143 | 0 | UA_Byte *pcp, UA_Boolean *dei) { |
144 | 0 | if(buf->length < (2 * ETHER_ADDR_LEN)+2) |
145 | 0 | return 0; |
146 | | |
147 | | /* Parse "normal" Ethernet header */ |
148 | 0 | memcpy(destAddr, buf->data, ETHER_ADDR_LEN); |
149 | 0 | memcpy(sourceAddr, &buf->data[ETHER_ADDR_LEN], ETHER_ADDR_LEN); |
150 | 0 | size_t pos = 2 * ETHER_ADDR_LEN; |
151 | 0 | UA_UInt16 length = ntohs(*(UA_UInt16*)&buf->data[pos]); |
152 | 0 | pos += 2; |
153 | | |
154 | | /* No EtherType and no VLAN */ |
155 | 0 | if(length <= 1500) |
156 | 0 | return pos; |
157 | | |
158 | | /* Parse 802.1Q VLAN header */ |
159 | 0 | if(length == 0x8100) { |
160 | 0 | if(buf->length < (2 * ETHER_ADDR_LEN)+2+4) |
161 | 0 | return 0; |
162 | 0 | pos += 2; |
163 | 0 | UA_UInt16 tci = ntohs(*(UA_UInt16*)&buf->data[pos]); |
164 | 0 | *pcp = (tci >> 13) & 0x07; |
165 | 0 | *dei = (tci >> 12) & 0x01; |
166 | 0 | *vid = tci & 0x0FFF; |
167 | 0 | pos += 2; |
168 | 0 | length = ntohs(*(UA_UInt16*)&buf->data[pos]); |
169 | 0 | } |
170 | | |
171 | | /* Set the EtherType if it is set */ |
172 | 0 | if(length > 1500) |
173 | 0 | *etherType = length; |
174 | |
|
175 | 0 | return pos; |
176 | 0 | } |
177 | | |
178 | | static unsigned char |
179 | | setETHHeader(unsigned char *buf, |
180 | | unsigned char destAddr[ETHER_ADDR_LEN], |
181 | | unsigned char sourceAddr[ETHER_ADDR_LEN], |
182 | | UA_UInt16 etherType, UA_UInt16 vid, |
183 | 0 | UA_Byte pcp, UA_Boolean dei, unsigned char *lengthOffset) { |
184 | | /* Set dest and source address */ |
185 | 0 | size_t pos = 0; |
186 | 0 | memcpy(buf, destAddr, ETHER_ADDR_LEN); |
187 | 0 | pos += ETHER_ADDR_LEN; |
188 | 0 | memcpy(&buf[pos], sourceAddr, ETHER_ADDR_LEN); |
189 | 0 | pos += ETHER_ADDR_LEN; |
190 | | |
191 | | /* Set the 802.1Q VLAN header for VIDs in the valid range 1-4094. */ |
192 | 0 | if(vid > 0 && vid <= 4094) { |
193 | 0 | *(UA_UInt16*)&buf[pos] = htons(0x8100); |
194 | 0 | pos += 2; |
195 | 0 | UA_UInt16 tci = (UA_UInt16)(((UA_UInt16)pcp << 13) | ((UA_UInt16)dei << 12) | ((UA_UInt16)vid)); |
196 | 0 | *(UA_UInt16*)&buf[pos] = htons(tci); |
197 | 0 | pos += 2; |
198 | 0 | } |
199 | | |
200 | | /* Set the Ethertype or store the offset for the length field */ |
201 | 0 | if(etherType == 0 || etherType == ETH_P_ALL) { |
202 | 0 | *lengthOffset = (unsigned char)pos; |
203 | 0 | } else { |
204 | 0 | *(UA_UInt16*)&buf[pos] = htons(etherType); |
205 | 0 | } |
206 | 0 | pos += 2; |
207 | 0 | return (unsigned char)pos; |
208 | 0 | } |
209 | | |
210 | | static UA_StatusCode |
211 | | ETH_allocNetworkBuffer(UA_ConnectionManager *cm, uintptr_t connectionId, |
212 | 0 | UA_ByteString *buf, size_t bufSize) { |
213 | | /* Get the ETH_FD */ |
214 | 0 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
215 | 0 | UA_FD fd = (UA_FD)connectionId; |
216 | 0 | ETH_FD *erfd = (ETH_FD*)ZIP_FIND(UA_FDTree, &pcm->fds, &fd); |
217 | 0 | if(!erfd) |
218 | 0 | return UA_STATUSCODE_BADCONNECTIONREJECTED; |
219 | | |
220 | | /* Allocate the buffer with the hidden Ethernet header in front. |
221 | | * Spec 7.3.3: "MaxNetworkMessageSize shall be limited to an Ethernet |
222 | | * frame size of 1522 Byte." Reject oversized frames. */ |
223 | 0 | if(bufSize > (size_t)1522 - erfd->headerSize) { |
224 | 0 | return UA_STATUSCODE_BADENCODINGERROR; |
225 | 0 | } |
226 | | /* Reserve a fixed prefix. This lets the buffer be recovered even if the |
227 | | * connection disappears between allocation and send. */ |
228 | 0 | UA_StatusCode res = |
229 | 0 | UA_EventLoopPOSIX_allocNetworkBuffer(cm, connectionId, buf, |
230 | 0 | bufSize + UA_ETH_MAXHEADERLENGTH); |
231 | 0 | if(UA_LIKELY(res == UA_STATUSCODE_GOOD)) { |
232 | 0 | buf->data += UA_ETH_MAXHEADERLENGTH; |
233 | 0 | buf->length -= UA_ETH_MAXHEADERLENGTH; |
234 | 0 | } |
235 | 0 | return res; |
236 | 0 | } |
237 | | |
238 | | static void |
239 | | ETH_freeNetworkBuffer(UA_ConnectionManager *cm, uintptr_t connectionId, |
240 | 0 | UA_ByteString *buf) { |
241 | | /* Allocation uses a fixed prefix, so freeing does not depend on the |
242 | | * connection still being registered. */ |
243 | 0 | buf->data -= UA_ETH_MAXHEADERLENGTH; |
244 | 0 | buf->length += UA_ETH_MAXHEADERLENGTH; |
245 | 0 | UA_EventLoopPOSIX_freeNetworkBuffer(cm, connectionId, buf); |
246 | 0 | } |
247 | | |
248 | | static void |
249 | | ETH_freeSendBuffer(UA_ConnectionManager *cm, uintptr_t connectionId, |
250 | 0 | UA_ByteString *buf, size_t headerSize) { |
251 | | /* Restore the application payload view expected by |
252 | | * ETH_freeNetworkBuffer after sendWithConnection exposed the header. */ |
253 | 0 | buf->data += headerSize; |
254 | 0 | buf->length -= headerSize; |
255 | 0 | ETH_freeNetworkBuffer(cm, connectionId, buf); |
256 | 0 | } |
257 | | |
258 | | /* Test if the ConnectionManager can be stopped */ |
259 | | static void |
260 | 20.6k | ETH_checkStopped(UA_POSIXConnectionManager *pcm) { |
261 | 20.6k | UA_LOCK_ASSERT(&((UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop)->elMutex); |
262 | | |
263 | 20.6k | if(pcm->fdsSize == 0 && |
264 | 20.6k | pcm->cm.eventSource.state == UA_EVENTSOURCESTATE_STOPPING) { |
265 | 20.6k | UA_LOG_DEBUG(pcm->cm.eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, |
266 | 20.6k | "ETH\t| All sockets closed, the EventLoop has stopped"); |
267 | 20.6k | pcm->cm.eventSource.state = UA_EVENTSOURCESTATE_STOPPED; |
268 | 20.6k | } |
269 | 20.6k | } |
270 | | |
271 | | /* This method must not be called from the application directly, but from within |
272 | | * the EventLoop. Otherwise we cannot be sure whether the file descriptor is |
273 | | * still used after calling close. */ |
274 | | static void |
275 | 0 | ETH_close(UA_POSIXConnectionManager *pcm, ETH_FD *conn) { |
276 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop; |
277 | 0 | UA_LOCK_ASSERT(&el->elMutex); |
278 | |
|
279 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
280 | 0 | "ETH %u\t| Closing connection", |
281 | 0 | (unsigned)conn->rfd.fd); |
282 | | |
283 | | /* Deregister from the EventLoop */ |
284 | 0 | UA_EventLoopPOSIX_deregisterFD(el, &conn->rfd); |
285 | | |
286 | | /* Deregister internally */ |
287 | 0 | ZIP_REMOVE(UA_FDTree, &pcm->fds, &conn->rfd); |
288 | 0 | UA_assert(pcm->fdsSize > 0); |
289 | 0 | pcm->fdsSize--; |
290 | | |
291 | | /* Signal closing to the application */ |
292 | 0 | conn->applicationCB(&pcm->cm, (uintptr_t)conn->rfd.fd, |
293 | 0 | conn->application, &conn->context, |
294 | 0 | UA_CONNECTIONSTATE_CLOSING, |
295 | 0 | &UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL); |
296 | | |
297 | | /* Close the socket */ |
298 | 0 | UA_RESET_ERRNO; |
299 | 0 | int ret = UA_close(conn->rfd.fd); |
300 | 0 | if(ret == 0) { |
301 | 0 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
302 | 0 | "ETH %u\t| Socket closed", (unsigned)conn->rfd.fd); |
303 | 0 | } else { |
304 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
305 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
306 | 0 | "ETH %u\t| Could not close the socket (%s)", |
307 | 0 | (unsigned)conn->rfd.fd, errno_str)); |
308 | 0 | } |
309 | | |
310 | | /* Don't call free here. This might be done automatically via the delayed |
311 | | * callback that calls ETH_close. */ |
312 | | /* UA_free(rfd); */ |
313 | | |
314 | | /* Stop if the ucm is stopping and this was the last open socket */ |
315 | 0 | ETH_checkStopped(pcm); |
316 | 0 | } |
317 | | |
318 | | static void |
319 | 0 | ETH_delayedClose(void *application, void *context) { |
320 | 0 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)application; |
321 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop; |
322 | 0 | ETH_FD *conn = (ETH_FD*)context; |
323 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, |
324 | 0 | "ETH %u\t| Delayed closing of the connection", |
325 | 0 | (unsigned)conn->rfd.fd); |
326 | 0 | UA_LOCK(&el->elMutex); |
327 | 0 | ETH_close(pcm, conn); |
328 | 0 | UA_UNLOCK(&el->elMutex); |
329 | 0 | UA_free(conn); |
330 | 0 | } |
331 | | |
332 | | /* Gets called when a socket receives data or closes */ |
333 | | static void |
334 | | ETH_connectionSocketCallback(UA_ConnectionManager *cm, UA_RegisteredFD *rfd, |
335 | 0 | short event) { |
336 | 0 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
337 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; |
338 | 0 | UA_LOCK_ASSERT(&el->elMutex); |
339 | |
|
340 | 0 | ETH_FD *conn = (ETH_FD*)rfd; |
341 | 0 | if(event == UA_FDEVENT_ERR) { |
342 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
343 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
344 | 0 | "ETH %u\t| recv signaled the socket was shutdown (%s)", |
345 | 0 | (unsigned)rfd->fd, errno_str)); |
346 | 0 | ETH_close(pcm, conn); |
347 | 0 | UA_free(rfd); |
348 | 0 | return; |
349 | 0 | } |
350 | | |
351 | | /* Use the already allocated receive-buffer */ |
352 | 0 | UA_ByteString response = pcm->rxBuffer; |
353 | | |
354 | | /* Receive */ |
355 | 0 | UA_RESET_ERRNO; |
356 | 0 | #ifndef UA_ARCHITECTURE_WIN32 |
357 | 0 | ssize_t ret = UA_recv(rfd->fd, (char*)response.data, |
358 | 0 | response.length, MSG_DONTWAIT); |
359 | | #else |
360 | | int ret = UA_recv(rfd->fd, (char*)response.data, |
361 | | response.length, MSG_DONTWAIT); |
362 | | #endif |
363 | | |
364 | | /* Receive has failed */ |
365 | 0 | if(ret <= 0) { |
366 | 0 | if(ret < 0 && UA_ERRNO == UA_INTERRUPTED) |
367 | 0 | return; |
368 | | |
369 | | /* Orderly shutdown of the socket. We can immediately close as no method |
370 | | * "below" in the call stack will use the socket in this iteration of |
371 | | * the EventLoop. */ |
372 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
373 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
374 | 0 | "ETH %u\t| recv signaled the socket was shutdown (%s)", |
375 | 0 | (unsigned)rfd->fd, errno_str)); |
376 | 0 | ETH_close(pcm, conn); |
377 | 0 | UA_free(rfd); |
378 | 0 | return; |
379 | 0 | } |
380 | | |
381 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
382 | 0 | "ETH %u\t| Received message of size %u", |
383 | 0 | (unsigned)rfd->fd, (unsigned)ret); |
384 | |
|
385 | 0 | response.length = (size_t)ret; |
386 | | |
387 | | /* Parse the Ethernet header */ |
388 | 0 | unsigned char destAddr[ETHER_ADDR_LEN]; |
389 | 0 | unsigned char sourceAddr[ETHER_ADDR_LEN]; |
390 | 0 | UA_UInt16 etherType = 0; |
391 | 0 | UA_UInt16 vid = 0; |
392 | 0 | UA_Byte pcp = 0; |
393 | 0 | UA_Boolean dei = 0; |
394 | 0 | size_t headerSize = parseETHHeader(&response, destAddr, sourceAddr, |
395 | 0 | ðerType, &vid, &pcp, &dei); |
396 | 0 | if(headerSize == 0) |
397 | 0 | return; |
398 | | |
399 | | /* Set up the parameter arguments passed to the application */ |
400 | 0 | unsigned char destAddrBytes[18]; |
401 | 0 | unsigned char sourceAddrBytes[18]; |
402 | 0 | setAddrString(destAddrBytes, destAddr); |
403 | 0 | setAddrString(sourceAddrBytes, sourceAddr); |
404 | 0 | UA_String destAddrStr = {17, destAddrBytes}; |
405 | 0 | UA_String sourceAddrStr = {17, sourceAddrBytes}; |
406 | |
|
407 | 0 | size_t paramsSize = 2; |
408 | 0 | UA_KeyValuePair params[6]; |
409 | 0 | params[0].key = UA_QUALIFIEDNAME(0, "destination-address"); |
410 | 0 | UA_Variant_setScalar(¶ms[0].value, &destAddrStr, &UA_TYPES[UA_TYPES_STRING]); |
411 | 0 | params[1].key = UA_QUALIFIEDNAME(0, "source-address"); |
412 | 0 | UA_Variant_setScalar(¶ms[1].value, &sourceAddrStr, &UA_TYPES[UA_TYPES_STRING]); |
413 | |
|
414 | 0 | if(etherType > 0) { |
415 | 0 | params[2].key = UA_QUALIFIEDNAME(0, "ethertype"); |
416 | 0 | UA_Variant_setScalar(¶ms[2].value, ðerType, &UA_TYPES[UA_TYPES_UINT16]); |
417 | 0 | paramsSize++; |
418 | 0 | } |
419 | |
|
420 | 0 | if(vid > 0) { |
421 | 0 | params[paramsSize].key = UA_QUALIFIEDNAME(0, "vid"); |
422 | 0 | UA_Variant_setScalar(¶ms[paramsSize].value, &vid, &UA_TYPES[UA_TYPES_UINT16]); |
423 | 0 | params[paramsSize+1].key = UA_QUALIFIEDNAME(0, "pcp"); |
424 | 0 | UA_Variant_setScalar(¶ms[paramsSize+1].value, &pcp, &UA_TYPES[UA_TYPES_BYTE]); |
425 | 0 | params[paramsSize+2].key = UA_QUALIFIEDNAME(0, "dei"); |
426 | 0 | UA_Variant_setScalar(¶ms[paramsSize+2].value, &dei, &UA_TYPES[UA_TYPES_BOOLEAN]); |
427 | 0 | paramsSize += 3; |
428 | 0 | } |
429 | | |
430 | | /* Callback to the application layer with the Ethernet header hidden */ |
431 | 0 | UA_KeyValueMap map = {paramsSize, params}; |
432 | 0 | response.data += headerSize; |
433 | 0 | response.length -= headerSize; |
434 | 0 | conn->applicationCB(cm, (uintptr_t)rfd->fd, conn->application, &conn->context, |
435 | 0 | UA_CONNECTIONSTATE_ESTABLISHED, &map, response); |
436 | 0 | response.data -= headerSize; |
437 | 0 | response.length += headerSize; |
438 | 0 | } |
439 | | |
440 | | static UA_StatusCode |
441 | | ETH_openListenConnection(UA_EventLoopPOSIX *el, ETH_FD *conn, |
442 | | const UA_KeyValueMap *params, |
443 | | int ifindex, UA_UInt16 etherType, |
444 | 0 | UA_Boolean validate) { |
445 | 0 | UA_LOCK_ASSERT(&el->elMutex); |
446 | | |
447 | | /* Bind the socket to interface and EtherType. Don't receive anything else. */ |
448 | 0 | struct sockaddr_ll sll; |
449 | 0 | memset(&sll, 0, sizeof(struct sockaddr_ll)); |
450 | 0 | sll.sll_family = AF_PACKET; |
451 | 0 | sll.sll_protocol = htons(etherType); |
452 | 0 | sll.sll_ifindex = ifindex; |
453 | 0 | if(!validate && bind(conn->rfd.fd, (struct sockaddr*)&sll, sizeof(sll)) < 0) |
454 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
455 | | |
456 | | /* Immediately register for listen events. Don't have to wait for a |
457 | | * connection to open. */ |
458 | 0 | conn->rfd.listenEvents = UA_FDEVENT_IN; |
459 | | |
460 | | /* Set receiving to promiscuous (all target host addresses) */ |
461 | 0 | UA_RESET_ERRNO; |
462 | 0 | const UA_Boolean *promiscuous = (const UA_Boolean*) |
463 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_PROMISCUOUS].name, |
464 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
465 | 0 | if(promiscuous && *promiscuous) { |
466 | 0 | struct packet_mreq mreq; |
467 | 0 | memset(&mreq, 0, sizeof(struct packet_mreq)); |
468 | 0 | mreq.mr_ifindex = ifindex; |
469 | 0 | mreq.mr_type = PACKET_MR_PROMISC; |
470 | 0 | int ret = UA_setsockopt(conn->rfd.fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, |
471 | 0 | &mreq, sizeof(mreq)); |
472 | 0 | if(ret < 0) { |
473 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
474 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
475 | 0 | "ETH %u\t| Could not set raw socket to promiscuous mode %s", |
476 | 0 | (unsigned)conn->rfd.fd, errno_str)); |
477 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
478 | 0 | } else { |
479 | 0 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
480 | 0 | "ETH %u\t| The socket was set to promiscuous mode", |
481 | 0 | (unsigned)conn->rfd.fd); |
482 | 0 | } |
483 | 0 | } |
484 | | |
485 | | /* Register for multicast if an address is defined */ |
486 | 0 | const UA_String *address = (const UA_String*) |
487 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_ADDR].name, |
488 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
489 | 0 | if(address) { |
490 | 0 | UA_Byte addr[ETHER_ADDR_LEN]; |
491 | 0 | UA_StatusCode res = parseEthAddress(address, addr); |
492 | 0 | if(res != UA_STATUSCODE_GOOD) { |
493 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
494 | 0 | "ETH\t| Address for listening cannot be parsed"); |
495 | 0 | return res; |
496 | 0 | } |
497 | | |
498 | 0 | if(!isMulticastEthAddress(addr)) { |
499 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
500 | 0 | "ETH\t| Address for listening is not a multicast address. Ignoring."); |
501 | 0 | return UA_STATUSCODE_GOOD; |
502 | 0 | } |
503 | | |
504 | 0 | UA_RESET_ERRNO; |
505 | 0 | struct packet_mreq mreq; |
506 | 0 | memset(&mreq, 0, sizeof(struct packet_mreq)); |
507 | 0 | mreq.mr_ifindex = ifindex; |
508 | 0 | mreq.mr_type = PACKET_MR_MULTICAST; |
509 | 0 | mreq.mr_alen = ETH_ALEN; |
510 | 0 | memcpy(mreq.mr_address, addr, ETHER_ADDR_LEN); |
511 | 0 | if(!validate && UA_setsockopt(conn->rfd.fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, |
512 | 0 | (char *)&mreq, sizeof(mreq)) < 0) { |
513 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
514 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
515 | 0 | "ETH\t| Registering for multicast failed with error %s", |
516 | 0 | errno_str)); |
517 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
518 | 0 | } |
519 | 0 | } |
520 | | |
521 | 0 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
522 | 0 | "ETH %u\t| Opened an Ethernet listen socket", |
523 | 0 | (unsigned)conn->rfd.fd); |
524 | |
|
525 | 0 | return UA_STATUSCODE_GOOD; |
526 | 0 | } |
527 | | |
528 | | static UA_StatusCode |
529 | | ETH_openSendConnection(UA_EventLoopPOSIX *el, ETH_FD *conn, const UA_KeyValueMap *params, |
530 | 0 | UA_Byte source[ETHER_ADDR_LEN], int ifindex, UA_UInt16 etherType) { |
531 | 0 | UA_LOCK_ASSERT(&el->elMutex); |
532 | | |
533 | | /* Parse the target address (has to exist) */ |
534 | 0 | const UA_String *address = (const UA_String*) |
535 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_ADDR].name, |
536 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
537 | 0 | UA_Byte dest[ETHER_ADDR_LEN]; |
538 | 0 | UA_StatusCode res = parseEthAddress(address, dest); |
539 | 0 | if(res != UA_STATUSCODE_GOOD) { |
540 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
541 | 0 | "ETH\t| Could not parse the Ethernet address \"%.*s\"", |
542 | 0 | (int)address->length, (char*)address->data); |
543 | 0 | return res; |
544 | 0 | } |
545 | | |
546 | | /* Get the VLAN config */ |
547 | 0 | UA_UInt16 vid = 0; |
548 | 0 | UA_Byte pcp = 0; |
549 | 0 | UA_Boolean eid = false; |
550 | |
|
551 | 0 | const UA_UInt16 *vidp = (const UA_UInt16*) |
552 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_VID].name, |
553 | 0 | &UA_TYPES[UA_TYPES_UINT16]); |
554 | 0 | if(vidp) |
555 | 0 | vid = *vidp; |
556 | |
|
557 | 0 | const UA_Byte *pcpp = (const UA_Byte*) |
558 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_PCP].name, |
559 | 0 | &UA_TYPES[UA_TYPES_BYTE]); |
560 | 0 | if(pcpp) |
561 | 0 | pcp = *pcpp; |
562 | |
|
563 | 0 | const UA_Boolean *eidp = (const UA_Boolean*) |
564 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_DEI].name, |
565 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
566 | 0 | if(eidp) |
567 | 0 | eid = *eidp; |
568 | | |
569 | | /* Store the structure for sendto */ |
570 | 0 | conn->sll.sll_ifindex = ifindex; |
571 | 0 | conn->sll.sll_halen = ETH_ALEN; |
572 | 0 | memcpy(conn->sll.sll_addr, dest, ETHER_ADDR_LEN); |
573 | | |
574 | | /* Generate the Ethernet header */ |
575 | 0 | conn->headerSize = setETHHeader(conn->header, dest, source, etherType, |
576 | 0 | vid, pcp, eid, &conn->lengthOffset); |
577 | | |
578 | | /* Set the send priority if defined */ |
579 | 0 | const UA_Int32 *soPriority = (const UA_Int32*) |
580 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_PRIORITY].name, |
581 | 0 | &UA_TYPES[UA_TYPES_INT32]); |
582 | 0 | if(soPriority) { |
583 | 0 | UA_RESET_ERRNO; |
584 | 0 | int prioRes = UA_setsockopt(conn->rfd.fd, SOL_SOCKET, SO_PRIORITY, |
585 | 0 | soPriority, sizeof(int)); |
586 | 0 | if(prioRes != 0) { |
587 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
588 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
589 | 0 | "UA_setsockopt SO_PRIORITY failed with error %s", errno_str)); |
590 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
591 | 0 | } |
592 | 0 | } |
593 | | |
594 | | /* Enable txtime sending */ |
595 | 0 | const UA_Boolean *txtime_enable = (const UA_Boolean*) |
596 | 0 | UA_KeyValueMap_getScalar(params, |
597 | 0 | ethConnectionParams[ETH_PARAMINDEX_TXTIME_ENABLE].name, |
598 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
599 | |
|
600 | 0 | if(txtime_enable && *txtime_enable) { |
601 | | #ifndef SO_TXTIME |
602 | | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
603 | | "ETH %u\t| txtime feature not supported", |
604 | | (unsigned)conn->rfd.fd); |
605 | | #else |
606 | 0 | const UA_UInt32 *txtime_flags = (const UA_UInt32*) |
607 | 0 | UA_KeyValueMap_getScalar(params, |
608 | 0 | ethConnectionParams[ETH_PARAMINDEX_TXTIME_FLAGS].name, |
609 | 0 | &UA_TYPES[UA_TYPES_UINT32]); |
610 | 0 | UA_RESET_ERRNO; |
611 | 0 | struct sock_txtime so_txtime_val; |
612 | 0 | memset(&so_txtime_val, 0, sizeof(struct sock_txtime)); |
613 | 0 | so_txtime_val.clockid = el->clockSourceMonotonic; |
614 | 0 | so_txtime_val.flags = SOF_TXTIME_REPORT_ERRORS; |
615 | 0 | if(txtime_flags) |
616 | 0 | so_txtime_val.flags = *txtime_flags; |
617 | 0 | if(UA_setsockopt(conn->rfd.fd, SOL_SOCKET, SO_TXTIME, |
618 | 0 | &so_txtime_val, sizeof(so_txtime_val)) == 0) { |
619 | 0 | conn->txtimeEnabled = true; |
620 | 0 | } else { |
621 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
622 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
623 | 0 | "ETH %u\t| Could not enable txtime (%s)", |
624 | 0 | (unsigned)conn->rfd.fd, errno_str)); |
625 | 0 | } |
626 | 0 | #endif |
627 | 0 | } |
628 | | |
629 | | /* Done creating the socket */ |
630 | 0 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
631 | 0 | "ETH %u\t| Opened an Ethernet send socket", |
632 | 0 | (unsigned)conn->rfd.fd); |
633 | |
|
634 | 0 | return UA_STATUSCODE_GOOD; |
635 | 0 | } |
636 | | |
637 | | static UA_StatusCode |
638 | | ETH_openConnection(UA_ConnectionManager *cm, const UA_KeyValueMap *params, |
639 | | void *application, void *context, |
640 | 0 | UA_ConnectionManager_connectionCallback connectionCallback) { |
641 | 0 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
642 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)cm->eventSource.eventLoop; |
643 | |
|
644 | 0 | UA_LOCK(&el->elMutex); |
645 | | |
646 | | /* Listen or send connection? */ |
647 | 0 | const UA_Boolean *listen = (const UA_Boolean*) |
648 | 0 | UA_KeyValueMap_getScalar(params, |
649 | 0 | ethConnectionParams[ETH_PARAMINDEX_LISTEN].name, |
650 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
651 | 0 | size_t ethParamRestrictions = ETH_PARAMETERSSIZE; |
652 | 0 | if(!listen || !*listen) |
653 | 0 | ethParamRestrictions++; /* Use the last restriction only for send connections */ |
654 | | |
655 | | /* Validate the parameters */ |
656 | 0 | UA_StatusCode res = |
657 | 0 | UA_KeyValueRestriction_validate(el->eventLoop.logger, "ETH", ethConnectionParams, |
658 | 0 | ethParamRestrictions, params); |
659 | 0 | if(res != UA_STATUSCODE_GOOD) { |
660 | 0 | UA_UNLOCK(&el->elMutex); |
661 | 0 | return res; |
662 | 0 | } |
663 | | |
664 | | /* Only validate the parameters? */ |
665 | 0 | UA_Boolean validate = false; |
666 | 0 | const UA_Boolean *validateParam = (const UA_Boolean*) |
667 | 0 | UA_KeyValueMap_getScalar(params, |
668 | 0 | ethConnectionParams[ETH_PARAMINDEX_VALIDATE].name, |
669 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
670 | 0 | if(validateParam) |
671 | 0 | validate = *validateParam; |
672 | | |
673 | | /* Get the EtherType parameter */ |
674 | 0 | UA_UInt16 etherType = ETH_P_ALL; |
675 | 0 | const UA_UInt16 *etParam = (const UA_UInt16*) |
676 | 0 | UA_KeyValueMap_getScalar(params, |
677 | 0 | ethConnectionParams[ETH_PARAMINDEX_ETHERTYPE].name, |
678 | 0 | &UA_TYPES[UA_TYPES_UINT16]); |
679 | 0 | if(etParam) |
680 | 0 | etherType = *etParam; |
681 | | |
682 | | /* Get the interface index */ |
683 | 0 | const UA_String *interface = (const UA_String*) |
684 | 0 | UA_KeyValueMap_getScalar(params, |
685 | 0 | ethConnectionParams[ETH_PARAMINDEX_IFACE].name, |
686 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
687 | 0 | if (interface == NULL) { |
688 | 0 | UA_UNLOCK(&el->elMutex); |
689 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
690 | 0 | } |
691 | 0 | if(interface->length >= 128) { |
692 | 0 | UA_UNLOCK(&el->elMutex); |
693 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
694 | 0 | } |
695 | 0 | char ifname[128]; |
696 | 0 | if(interface->length) |
697 | 0 | memcpy(ifname, interface->data, interface->length); |
698 | 0 | ifname[interface->length] = 0; |
699 | 0 | int ifindex = (int)if_nametoindex(ifname); |
700 | 0 | if(ifindex == 0) { |
701 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
702 | 0 | "ETH\t| Could not find the interface %s", ifname); |
703 | 0 | UA_UNLOCK(&el->elMutex); |
704 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
705 | 0 | } |
706 | | |
707 | | /* Create the socket and add the basic configuration */ |
708 | 0 | ETH_FD *conn = NULL; |
709 | 0 | UA_FD sockfd; |
710 | 0 | if(listen && *listen) |
711 | 0 | sockfd = UA_socket(PF_PACKET, SOCK_RAW, htons(etherType)); |
712 | 0 | else |
713 | 0 | sockfd = UA_socket(PF_PACKET, SOCK_RAW, 0); /* Don't receive */ |
714 | 0 | if(sockfd == -1) { |
715 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
716 | 0 | "ETH\t| Could not create a raw Ethernet socket (are you root?)"); |
717 | 0 | UA_UNLOCK(&el->elMutex); |
718 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
719 | 0 | } |
720 | | |
721 | | /* SO_REUSEADDR is unnecessary (and unsupported on some Linux systems): |
722 | | * res |= UA_EventLoopPOSIX_setReusable(sockfd); */ |
723 | 0 | res |= UA_EventLoopPOSIX_setNonBlocking(sockfd); |
724 | 0 | res |= UA_EventLoopPOSIX_setNoSigPipe(sockfd); |
725 | 0 | if(res != UA_STATUSCODE_GOOD) |
726 | 0 | goto cleanup; |
727 | | |
728 | | /* Create the FD object */ |
729 | 0 | conn = (ETH_FD*)UA_calloc(1, sizeof(ETH_FD)); |
730 | 0 | if(!conn) { |
731 | 0 | res = UA_STATUSCODE_BADOUTOFMEMORY; |
732 | 0 | goto cleanup; |
733 | 0 | } |
734 | | |
735 | 0 | conn->rfd.fd = sockfd; |
736 | 0 | conn->rfd.es = &pcm->cm.eventSource; |
737 | 0 | conn->rfd.eventSourceCB = (UA_FDCallback)ETH_connectionSocketCallback; |
738 | 0 | conn->context = context; |
739 | 0 | conn->application = application; |
740 | 0 | conn->applicationCB = connectionCallback; |
741 | | |
742 | | /* Configure a listen or a send connection */ |
743 | 0 | if(!listen || !*listen) { |
744 | | /* Get the source address for the interface */ |
745 | 0 | UA_RESET_ERRNO; |
746 | 0 | struct ifreq ifr; |
747 | 0 | memcpy(ifr.ifr_name, ifname, interface->length); |
748 | 0 | ifr.ifr_name[interface->length] = 0; |
749 | 0 | int result = ioctl(conn->rfd.fd, SIOCGIFHWADDR, &ifr); |
750 | 0 | if(result == -1) { |
751 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
752 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
753 | 0 | "ETH %u\t| Cannot get the source address, %s", |
754 | 0 | (unsigned)conn->rfd.fd, errno_str)); |
755 | 0 | res = UA_STATUSCODE_BADCONNECTIONREJECTED; |
756 | 0 | goto cleanup; |
757 | 0 | } |
758 | 0 | res = ETH_openSendConnection(el, conn, params, |
759 | 0 | (unsigned char*)ifr.ifr_hwaddr.sa_data, |
760 | 0 | ifindex, etherType); |
761 | 0 | } else { |
762 | 0 | res = ETH_openListenConnection(el, conn, params, ifindex, etherType, validate); |
763 | 0 | } |
764 | | |
765 | | /* Don't actually open or shut down */ |
766 | 0 | if(validate || res != UA_STATUSCODE_GOOD) |
767 | 0 | goto cleanup; |
768 | | |
769 | | /* Register in the EventLoop */ |
770 | 0 | res = UA_EventLoopPOSIX_registerFD(el, &conn->rfd); |
771 | 0 | if(res != UA_STATUSCODE_GOOD) |
772 | 0 | goto cleanup; |
773 | | |
774 | | /* Register locally */ |
775 | 0 | ZIP_INSERT(UA_FDTree, &pcm->fds, &conn->rfd); |
776 | 0 | pcm->fdsSize++; |
777 | | |
778 | | /* Register the listen socket in the application */ |
779 | 0 | connectionCallback(cm, (uintptr_t)sockfd, application, &conn->context, |
780 | 0 | UA_CONNECTIONSTATE_ESTABLISHED, &UA_KEYVALUEMAP_NULL, |
781 | 0 | UA_BYTESTRING_NULL); |
782 | 0 | UA_UNLOCK(&el->elMutex); |
783 | 0 | return UA_STATUSCODE_GOOD; |
784 | | |
785 | 0 | cleanup: |
786 | 0 | UA_close(sockfd); |
787 | 0 | UA_free(conn); |
788 | 0 | UA_UNLOCK(&el->elMutex); |
789 | 0 | return res; |
790 | 0 | } |
791 | | |
792 | | static void |
793 | 0 | ETH_shutdown(UA_POSIXConnectionManager *pcm, ETH_FD *conn) { |
794 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop; |
795 | 0 | UA_LOCK_ASSERT(&((UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop)->elMutex); |
796 | |
|
797 | 0 | UA_DelayedCallback *dc = &conn->rfd.dc; |
798 | 0 | if(dc->callback) { |
799 | 0 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
800 | 0 | "ETH %u\t| Cannot close - already closing", |
801 | 0 | (unsigned)conn->rfd.fd); |
802 | 0 | return; |
803 | 0 | } |
804 | | |
805 | | /* Shutdown the socket to cancel the current select/epoll */ |
806 | 0 | UA_shutdown(conn->rfd.fd, UA_SHUT_RDWR); |
807 | |
|
808 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
809 | 0 | "ETH %u\t| Shutdown called", (unsigned)conn->rfd.fd); |
810 | |
|
811 | 0 | dc->callback = ETH_delayedClose; |
812 | 0 | dc->application = pcm; |
813 | 0 | dc->context = conn; |
814 | | |
815 | | /* Adding a delayed callback does not take a lock */ |
816 | 0 | UA_EventLoopPOSIX_addDelayedCallback((UA_EventLoop*)el, dc); |
817 | 0 | } |
818 | | |
819 | | static UA_StatusCode |
820 | 0 | ETH_shutdownConnection(UA_ConnectionManager *cm, uintptr_t connectionId) { |
821 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; |
822 | 0 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
823 | 0 | UA_LOCK(&el->elMutex); |
824 | | |
825 | | /* Get the ETH_FD */ |
826 | 0 | UA_FD fd = (UA_FD)connectionId; |
827 | 0 | UA_RegisteredFD *rfd = ZIP_FIND(UA_FDTree, &pcm->fds, &fd); |
828 | 0 | if(!rfd) { |
829 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
830 | 0 | "ETH\t| Cannot close Ethernet connection %u - not found", |
831 | 0 | (unsigned)connectionId); |
832 | 0 | UA_UNLOCK(&el->elMutex); |
833 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
834 | 0 | } |
835 | | |
836 | 0 | ETH_shutdown(pcm, (ETH_FD*)rfd); |
837 | 0 | UA_UNLOCK(&el->elMutex); |
838 | 0 | return UA_STATUSCODE_GOOD; |
839 | 0 | } |
840 | | |
841 | | #ifdef SO_TXTIME |
842 | | static ssize_t |
843 | | send_txtime(UA_EventLoopPOSIX *el, ETH_FD *conn, const UA_KeyValueMap *params, |
844 | 0 | UA_DateTime txtime, const char *bytes, size_t bytesSize) { |
845 | | /* Get additiona parameters */ |
846 | 0 | const UA_UInt16 *txtime_pico = (const UA_UInt16*) |
847 | 0 | UA_KeyValueMap_getScalar(params, |
848 | 0 | ethConnectionParams[ETH_PARAMINDEX_TXTIME_PICO].name, |
849 | 0 | &UA_TYPES[UA_TYPES_UINT16]); |
850 | 0 | const UA_Boolean *txtime_drop = (const UA_Boolean*) |
851 | 0 | UA_KeyValueMap_getScalar(params, |
852 | 0 | ethConnectionParams[ETH_PARAMINDEX_TXTIME_DROP].name, |
853 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
854 | 0 | #ifndef SCM_DROP_IF_LATE |
855 | 0 | if(txtime_drop) { |
856 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
857 | 0 | "ETH %u\t| txtime drop_if_late not supported on the current system", |
858 | 0 | (unsigned)conn->rfd.fd); |
859 | 0 | return 0; |
860 | 0 | } |
861 | 0 | #endif |
862 | | |
863 | | |
864 | | /* Transform from 100ns since 1601 to ns since the Unix Epoch */ |
865 | 0 | UA_UInt64 transmission_time = (UA_UInt64) |
866 | 0 | (txtime - UA_DATETIME_UNIX_EPOCH) * 100; |
867 | 0 | if(txtime_pico) |
868 | 0 | transmission_time += (*txtime_pico) / 1000; |
869 | | |
870 | | /* Structure for scattering or gathering of input/output */ |
871 | 0 | struct iovec inputOutputVec; |
872 | 0 | inputOutputVec.iov_base = (void*)(uintptr_t)bytes; |
873 | 0 | inputOutputVec.iov_len = bytesSize; |
874 | | |
875 | | /* Specify the transmission time in the CMSG. */ |
876 | 0 | char dataPacket[CMSG_SPACE(sizeof(uint64_t)) |
877 | | #ifdef SCM_DROP_IF_LATE |
878 | | + CMSG_SPACE(sizeof(uint8_t)) |
879 | | #endif |
880 | 0 | ]; |
881 | 0 | struct msghdr message; |
882 | 0 | memset(&message, 0, sizeof(struct msghdr)); |
883 | 0 | message.msg_control = dataPacket; |
884 | 0 | message.msg_controllen = sizeof(dataPacket); |
885 | 0 | message.msg_name = (struct sockaddr*)&conn->sll; |
886 | 0 | message.msg_namelen = sizeof(conn->sll); |
887 | 0 | message.msg_iov = &inputOutputVec; |
888 | 0 | message.msg_iovlen = 1; |
889 | |
|
890 | 0 | struct cmsghdr *cmsg = CMSG_FIRSTHDR(&message); |
891 | 0 | cmsg->cmsg_level = SOL_SOCKET; |
892 | 0 | cmsg->cmsg_type = SCM_TXTIME; |
893 | 0 | cmsg->cmsg_len = CMSG_LEN(sizeof(__u64)); |
894 | 0 | *((__u64*)CMSG_DATA(cmsg)) = transmission_time; |
895 | |
|
896 | | #ifdef SCM_DROP_IF_LATE |
897 | | cmsg = CMSG_NXTHDR(&message, cmsg); |
898 | | cmsg->cmsg_level = SOL_SOCKET; |
899 | | cmsg->cmsg_type = SCM_DROP_IF_LATE; |
900 | | cmsg->cmsg_len = CMSG_LEN(sizeof(uint8_t)); |
901 | | *((uint8_t*)CMSG_DATA(cmsg)) = (!txtime_drop || *txtime_drop) ? 1: 0; |
902 | | #endif |
903 | | |
904 | | /* Send */ |
905 | 0 | return sendmsg(conn->rfd.fd, &message, 0); |
906 | 0 | } |
907 | | #endif |
908 | | |
909 | | static UA_StatusCode |
910 | | ETH_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId, |
911 | 0 | const UA_KeyValueMap *params, UA_ByteString *buf) { |
912 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; |
913 | 0 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
914 | |
|
915 | 0 | UA_LOCK(&el->elMutex); |
916 | | |
917 | | /* Get the ETH_FD */ |
918 | 0 | UA_FD fd = (UA_FD)connectionId; |
919 | 0 | ETH_FD *conn = (ETH_FD*)ZIP_FIND(UA_FDTree, &pcm->fds, &fd); |
920 | 0 | if(!conn) { |
921 | 0 | UA_UNLOCK(&el->elMutex); |
922 | | /* The fixed allocation prefix can be recovered without the vanished |
923 | | * connection. */ |
924 | 0 | ETH_freeNetworkBuffer(cm, connectionId, buf); |
925 | 0 | return UA_STATUSCODE_BADCONNECTIONREJECTED; |
926 | 0 | } |
927 | | /* The connection may be finalized after the EventLoop lock is released. |
928 | | * Retain the only connection-specific value needed to restore the buffer. */ |
929 | 0 | const size_t headerSize = conn->headerSize; |
930 | | |
931 | | /* Uncover and set the Ethernet header */ |
932 | 0 | buf->data -= headerSize; |
933 | 0 | buf->length += headerSize; |
934 | 0 | memcpy(buf->data, conn->header, headerSize); |
935 | 0 | if(conn->lengthOffset) { |
936 | 0 | UA_UInt16 *ethLength = (UA_UInt16*)&buf->data[conn->lengthOffset]; |
937 | 0 | *ethLength = htons((UA_UInt16)(buf->length - headerSize)); |
938 | 0 | } |
939 | | |
940 | | /* Was a txtime configured? */ |
941 | 0 | const UA_DateTime *txtime = (const UA_DateTime*) |
942 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_TXTIME].name, |
943 | 0 | &UA_TYPES[UA_TYPES_DATETIME]); |
944 | 0 | if(txtime && !conn->txtimeEnabled) { |
945 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
946 | 0 | "ETH %u\t| txtime was not configured for the connection", |
947 | 0 | (unsigned)connectionId); |
948 | 0 | UA_UNLOCK(&el->elMutex); |
949 | 0 | ETH_freeSendBuffer(cm, connectionId, buf, headerSize); |
950 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
951 | 0 | } |
952 | | |
953 | | /* Prevent OS signals when sending to a closed socket */ |
954 | 0 | int flags = MSG_NOSIGNAL; |
955 | |
|
956 | 0 | struct pollfd tmp_poll_fd; |
957 | 0 | tmp_poll_fd.fd = (UA_FD)connectionId; |
958 | 0 | tmp_poll_fd.events = UA_POLLOUT; |
959 | | |
960 | | /* Send the full buffer. This may require several calls to send */ |
961 | 0 | size_t nWritten = 0; |
962 | 0 | do { |
963 | 0 | ssize_t n = 0; |
964 | 0 | do { |
965 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
966 | 0 | "ETH %u\t| Attempting to send", (unsigned)connectionId); |
967 | 0 | UA_RESET_ERRNO; |
968 | 0 | size_t bytes_to_send = buf->length - nWritten; |
969 | 0 | #ifdef SO_TXTIME |
970 | 0 | if(txtime) { |
971 | 0 | n = send_txtime(el, conn, params, *txtime, |
972 | 0 | (const char*)buf->data + nWritten, bytes_to_send); |
973 | 0 | } else |
974 | 0 | #endif |
975 | 0 | { |
976 | 0 | n = UA_sendto(conn->rfd.fd, |
977 | 0 | (const char*)buf->data + nWritten, bytes_to_send, |
978 | 0 | flags, (struct sockaddr*)&conn->sll, sizeof(conn->sll)); |
979 | 0 | } |
980 | 0 | if(n < 0) { |
981 | | /* An error we cannot recover from? */ |
982 | 0 | if(UA_ERRNO != UA_INTERRUPTED && |
983 | 0 | UA_ERRNO != UA_WOULDBLOCK && |
984 | 0 | UA_ERRNO != UA_AGAIN) { |
985 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
986 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
987 | 0 | "ETH %u\t| Send failed with error %s", |
988 | 0 | (unsigned)connectionId, errno_str)); |
989 | 0 | ETH_shutdown(pcm, conn); |
990 | 0 | UA_UNLOCK(&el->elMutex); |
991 | 0 | ETH_freeSendBuffer(cm, connectionId, buf, headerSize); |
992 | 0 | return UA_STATUSCODE_BADCONNECTIONCLOSED; |
993 | 0 | } |
994 | | |
995 | | /* Poll for the socket resources to become available and retry |
996 | | * (blocking) */ |
997 | 0 | int poll_ret; |
998 | 0 | do { |
999 | 0 | UA_RESET_ERRNO; |
1000 | 0 | poll_ret = UA_poll(&tmp_poll_fd, 1, 100); |
1001 | 0 | if(poll_ret < 0 && UA_ERRNO != UA_INTERRUPTED) { |
1002 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
1003 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
1004 | 0 | "ETH %u\t| Send failed with error %s", |
1005 | 0 | (unsigned)connectionId, errno_str)); |
1006 | 0 | ETH_shutdown(pcm, conn); |
1007 | 0 | UA_UNLOCK(&el->elMutex); |
1008 | 0 | ETH_freeSendBuffer(cm, connectionId, buf, headerSize); |
1009 | 0 | return UA_STATUSCODE_BADCONNECTIONCLOSED; |
1010 | 0 | } |
1011 | 0 | } while(poll_ret <= 0); |
1012 | 0 | } |
1013 | 0 | } while(n < 0); |
1014 | 0 | nWritten += (size_t)n; |
1015 | 0 | } while(nWritten < buf->length); |
1016 | | |
1017 | | /* Free the buffer */ |
1018 | 0 | UA_UNLOCK(&el->elMutex); |
1019 | 0 | ETH_freeSendBuffer(cm, connectionId, buf, headerSize); |
1020 | 0 | return UA_STATUSCODE_GOOD; |
1021 | 0 | } |
1022 | | |
1023 | | static UA_StatusCode |
1024 | 20.6k | ETH_eventSourceStart(UA_ConnectionManager *cm) { |
1025 | 20.6k | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
1026 | 20.6k | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; |
1027 | 20.6k | UA_LOCK(&el->elMutex); |
1028 | | |
1029 | | /* Check the state */ |
1030 | 20.6k | if(cm->eventSource.state != UA_EVENTSOURCESTATE_STOPPED) { |
1031 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
1032 | 0 | "To start the Ethernet ConnectionManager, " |
1033 | 0 | "it has to be registered in an EventLoop and not started"); |
1034 | 0 | UA_UNLOCK(&el->elMutex); |
1035 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1036 | 0 | } |
1037 | | |
1038 | | /* Check the parameters */ |
1039 | 20.6k | UA_StatusCode res = |
1040 | 20.6k | UA_KeyValueRestriction_validate(el->eventLoop.logger, "ETH", |
1041 | 20.6k | ethManagerParams, ETH_MANAGERPARAMS, |
1042 | 20.6k | &cm->eventSource.params); |
1043 | 20.6k | if(res != UA_STATUSCODE_GOOD) |
1044 | 0 | goto finish; |
1045 | | |
1046 | | /* Allocate the rx buffer */ |
1047 | 20.6k | res = UA_EventLoopPOSIX_allocateStaticBuffers(pcm); |
1048 | 20.6k | if(res != UA_STATUSCODE_GOOD) |
1049 | 0 | goto finish; |
1050 | | |
1051 | | /* Set the EventSource to the started state */ |
1052 | 20.6k | cm->eventSource.state = UA_EVENTSOURCESTATE_STARTED; |
1053 | | |
1054 | 20.6k | finish: |
1055 | 20.6k | UA_UNLOCK(&el->elMutex); |
1056 | 20.6k | return res; |
1057 | 20.6k | } |
1058 | | |
1059 | | static void * |
1060 | 0 | ETH_shutdownCB(void *application, UA_RegisteredFD *rfd) { |
1061 | 0 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)application; |
1062 | 0 | ETH_shutdown(pcm, (ETH_FD*)rfd); |
1063 | 0 | return NULL; |
1064 | 0 | } |
1065 | | |
1066 | | static void |
1067 | 20.6k | ETH_eventSourceStop(UA_ConnectionManager *cm) { |
1068 | 20.6k | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
1069 | 20.6k | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop; |
1070 | 20.6k | UA_LOCK(&el->elMutex); |
1071 | | |
1072 | 20.6k | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
1073 | 20.6k | "ETH\t| Shutting down the ConnectionManager"); |
1074 | | |
1075 | | /* Prevent new connections to open */ |
1076 | 20.6k | cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPING; |
1077 | | |
1078 | | /* Shutdown all existing connection */ |
1079 | 20.6k | ZIP_ITER(UA_FDTree, &pcm->fds, ETH_shutdownCB, cm); |
1080 | | |
1081 | | /* Check if stopped once more (also checking inside ETH_close, but there we |
1082 | | * don't check if there is no rfd at all) */ |
1083 | 20.6k | ETH_checkStopped(pcm); |
1084 | | |
1085 | 20.6k | UA_UNLOCK(&el->elMutex); |
1086 | 20.6k | } |
1087 | | |
1088 | | static UA_StatusCode |
1089 | 20.6k | ETH_eventSourceDelete(UA_ConnectionManager *cm) { |
1090 | 20.6k | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
1091 | 20.6k | if(cm->eventSource.state >= UA_EVENTSOURCESTATE_STARTING) { |
1092 | 0 | UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, |
1093 | 0 | "ETH\t| The EventSource must be stopped before it can be deleted"); |
1094 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1095 | 0 | } |
1096 | | |
1097 | 20.6k | UA_KeyValueMap_clear(&cm->eventSource.params); |
1098 | 20.6k | UA_ByteString_clear(&pcm->rxBuffer); |
1099 | 20.6k | UA_ByteString_clear(&pcm->txBuffer); |
1100 | 20.6k | UA_String_clear(&cm->eventSource.name); |
1101 | 20.6k | UA_free(cm); |
1102 | 20.6k | return UA_STATUSCODE_GOOD; |
1103 | 20.6k | } |
1104 | | |
1105 | | static const char *ethName = "eth"; |
1106 | | |
1107 | | UA_ConnectionManager * |
1108 | 20.6k | UA_ConnectionManager_new_POSIX_Ethernet(const UA_String eventSourceName) { |
1109 | 20.6k | UA_POSIXConnectionManager *cm = (UA_POSIXConnectionManager*) |
1110 | 20.6k | UA_calloc(1, sizeof(UA_POSIXConnectionManager)); |
1111 | 20.6k | if(!cm) |
1112 | 0 | return NULL; |
1113 | | |
1114 | 20.6k | cm->cm.eventSource.eventSourceType = UA_EVENTSOURCETYPE_CONNECTIONMANAGER; |
1115 | 20.6k | UA_String_copy(&eventSourceName, &cm->cm.eventSource.name); |
1116 | 20.6k | cm->cm.eventSource.start = (UA_StatusCode (*)(UA_EventSource *))ETH_eventSourceStart; |
1117 | 20.6k | cm->cm.eventSource.stop = (void (*)(UA_EventSource *))ETH_eventSourceStop; |
1118 | 20.6k | cm->cm.eventSource.free = (UA_StatusCode (*)(UA_EventSource *))ETH_eventSourceDelete; |
1119 | 20.6k | cm->cm.protocol = UA_STRING((char*)(uintptr_t)ethName); |
1120 | 20.6k | cm->cm.openConnection = ETH_openConnection; |
1121 | 20.6k | cm->cm.allocNetworkBuffer = ETH_allocNetworkBuffer; |
1122 | 20.6k | cm->cm.freeNetworkBuffer = ETH_freeNetworkBuffer; |
1123 | 20.6k | cm->cm.sendWithConnection = ETH_sendWithConnection; |
1124 | 20.6k | cm->cm.closeConnection = ETH_shutdownConnection; |
1125 | 20.6k | return &cm->cm; |
1126 | 20.6k | } |
1127 | | |
1128 | | #endif /* defined(UA_ARCHITECTURE_POSIX) && defined(__linux__) */ |