/src/open62541/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 | 502 | #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 | | #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 */ |
192 | 0 | if(vid > 0 && vid != ETH_P_ALL) { |
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 | 0 | UA_StatusCode res = |
222 | 0 | UA_EventLoopPOSIX_allocNetworkBuffer(cm, connectionId, buf, |
223 | 0 | bufSize + erfd->headerSize); |
224 | 0 | if(UA_LIKELY(res == UA_STATUSCODE_GOOD)) { |
225 | 0 | buf->data += erfd->headerSize; |
226 | 0 | buf->length -= erfd->headerSize; |
227 | 0 | } |
228 | 0 | return res; |
229 | 0 | } |
230 | | |
231 | | static void |
232 | | ETH_freeNetworkBuffer(UA_ConnectionManager *cm, uintptr_t connectionId, |
233 | 0 | UA_ByteString *buf) { |
234 | | /* Get the ETH_FD */ |
235 | 0 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
236 | 0 | UA_FD fd = (UA_FD)connectionId; |
237 | 0 | ETH_FD *erfd = (ETH_FD*)ZIP_FIND(UA_FDTree, &pcm->fds, &fd); |
238 | 0 | if(!erfd) |
239 | 0 | return; |
240 | | |
241 | | /* Unhide the Ethernet header and free */ |
242 | 0 | buf->data -= erfd->headerSize; |
243 | 0 | buf->length += erfd->headerSize; |
244 | 0 | UA_EventLoopPOSIX_freeNetworkBuffer(cm, connectionId, buf); |
245 | 0 | } |
246 | | |
247 | | /* Test if the ConnectionManager can be stopped */ |
248 | | static void |
249 | 502 | ETH_checkStopped(UA_POSIXConnectionManager *pcm) { |
250 | 502 | UA_LOCK_ASSERT(&((UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop)->elMutex); |
251 | | |
252 | 502 | if(pcm->fdsSize == 0 && |
253 | 502 | pcm->cm.eventSource.state == UA_EVENTSOURCESTATE_STOPPING) { |
254 | 502 | UA_LOG_DEBUG(pcm->cm.eventSource.eventLoop->logger, UA_LOGCATEGORY_NETWORK, |
255 | 502 | "ETH\t| All sockets closed, the EventLoop has stopped"); |
256 | 502 | pcm->cm.eventSource.state = UA_EVENTSOURCESTATE_STOPPED; |
257 | 502 | } |
258 | 502 | } |
259 | | |
260 | | /* This method must not be called from the application directly, but from within |
261 | | * the EventLoop. Otherwise we cannot be sure whether the file descriptor is |
262 | | * still used after calling close. */ |
263 | | static void |
264 | 0 | ETH_close(UA_POSIXConnectionManager *pcm, ETH_FD *conn) { |
265 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop; |
266 | 0 | UA_LOCK_ASSERT(&el->elMutex); |
267 | |
|
268 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
269 | 0 | "ETH %u\t| Closing connection", |
270 | 0 | (unsigned)conn->rfd.fd); |
271 | | |
272 | | /* Deregister from the EventLoop */ |
273 | 0 | UA_EventLoopPOSIX_deregisterFD(el, &conn->rfd); |
274 | | |
275 | | /* Deregister internally */ |
276 | 0 | ZIP_REMOVE(UA_FDTree, &pcm->fds, &conn->rfd); |
277 | 0 | UA_assert(pcm->fdsSize > 0); |
278 | 0 | pcm->fdsSize--; |
279 | | |
280 | | /* Signal closing to the application */ |
281 | 0 | conn->applicationCB(&pcm->cm, (uintptr_t)conn->rfd.fd, |
282 | 0 | conn->application, &conn->context, |
283 | 0 | UA_CONNECTIONSTATE_CLOSING, |
284 | 0 | &UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL); |
285 | | |
286 | | /* Close the socket */ |
287 | 0 | UA_RESET_ERRNO; |
288 | 0 | int ret = UA_close(conn->rfd.fd); |
289 | 0 | if(ret == 0) { |
290 | 0 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
291 | 0 | "ETH %u\t| Socket closed", (unsigned)conn->rfd.fd); |
292 | 0 | } else { |
293 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
294 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
295 | 0 | "ETH %u\t| Could not close the socket (%s)", |
296 | 0 | (unsigned)conn->rfd.fd, errno_str)); |
297 | 0 | } |
298 | | |
299 | | /* Don't call free here. This might be done automatically via the delayed |
300 | | * callback that calls ETH_close. */ |
301 | | /* UA_free(rfd); */ |
302 | | |
303 | | /* Stop if the ucm is stopping and this was the last open socket */ |
304 | 0 | ETH_checkStopped(pcm); |
305 | 0 | } |
306 | | |
307 | | static void |
308 | 0 | ETH_delayedClose(void *application, void *context) { |
309 | 0 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)application; |
310 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop; |
311 | 0 | ETH_FD *conn = (ETH_FD*)context; |
312 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP, |
313 | 0 | "ETH %u\t| Delayed closing of the connection", |
314 | 0 | (unsigned)conn->rfd.fd); |
315 | 0 | UA_LOCK(&el->elMutex); |
316 | 0 | ETH_close(pcm, conn); |
317 | 0 | UA_UNLOCK(&el->elMutex); |
318 | 0 | UA_free(conn); |
319 | 0 | } |
320 | | |
321 | | /* Gets called when a socket receives data or closes */ |
322 | | static void |
323 | | ETH_connectionSocketCallback(UA_ConnectionManager *cm, UA_RegisteredFD *rfd, |
324 | 0 | short event) { |
325 | 0 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
326 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; |
327 | 0 | UA_LOCK_ASSERT(&el->elMutex); |
328 | |
|
329 | 0 | ETH_FD *conn = (ETH_FD*)rfd; |
330 | 0 | if(event == UA_FDEVENT_ERR) { |
331 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
332 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
333 | 0 | "ETH %u\t| recv signaled the socket was shutdown (%s)", |
334 | 0 | (unsigned)rfd->fd, errno_str)); |
335 | 0 | ETH_close(pcm, conn); |
336 | 0 | UA_free(rfd); |
337 | 0 | return; |
338 | 0 | } |
339 | | |
340 | | /* Use the already allocated receive-buffer */ |
341 | 0 | UA_ByteString response = pcm->rxBuffer; |
342 | | |
343 | | /* Receive */ |
344 | 0 | UA_RESET_ERRNO; |
345 | 0 | #ifndef UA_ARCHITECTURE_WIN32 |
346 | 0 | ssize_t ret = UA_recv(rfd->fd, (char*)response.data, |
347 | 0 | response.length, MSG_DONTWAIT); |
348 | | #else |
349 | | int ret = UA_recv(rfd->fd, (char*)response.data, |
350 | | response.length, MSG_DONTWAIT); |
351 | | #endif |
352 | | |
353 | | /* Receive has failed */ |
354 | 0 | if(ret <= 0) { |
355 | 0 | if(UA_ERRNO == UA_INTERRUPTED) |
356 | 0 | return; |
357 | | |
358 | | /* Orderly shutdown of the socket. We can immediately close as no method |
359 | | * "below" in the call stack will use the socket in this iteration of |
360 | | * the EventLoop. */ |
361 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
362 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
363 | 0 | "ETH %u\t| recv signaled the socket was shutdown (%s)", |
364 | 0 | (unsigned)rfd->fd, errno_str)); |
365 | 0 | ETH_close(pcm, conn); |
366 | 0 | UA_free(rfd); |
367 | 0 | return; |
368 | 0 | } |
369 | | |
370 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
371 | 0 | "ETH %u\t| Received message of size %u", |
372 | 0 | (unsigned)rfd->fd, (unsigned)ret); |
373 | |
|
374 | 0 | response.length = (size_t)ret; |
375 | | |
376 | | /* Parse the Ethernet header */ |
377 | 0 | unsigned char destAddr[ETHER_ADDR_LEN]; |
378 | 0 | unsigned char sourceAddr[ETHER_ADDR_LEN]; |
379 | 0 | UA_UInt16 etherType = 0; |
380 | 0 | UA_UInt16 vid = 0; |
381 | 0 | UA_Byte pcp = 0; |
382 | 0 | UA_Boolean dei = 0; |
383 | 0 | size_t headerSize = parseETHHeader(&response, destAddr, sourceAddr, |
384 | 0 | ðerType, &vid, &pcp, &dei); |
385 | 0 | if(headerSize == 0) |
386 | 0 | return; |
387 | | |
388 | | /* Set up the parameter arguments passed to the application */ |
389 | 0 | unsigned char destAddrBytes[18]; |
390 | 0 | unsigned char sourceAddrBytes[18]; |
391 | 0 | setAddrString(destAddrBytes, destAddr); |
392 | 0 | setAddrString(sourceAddrBytes, sourceAddr); |
393 | 0 | UA_String destAddrStr = {17, destAddrBytes}; |
394 | 0 | UA_String sourceAddrStr = {17, sourceAddrBytes}; |
395 | |
|
396 | 0 | size_t paramsSize = 2; |
397 | 0 | UA_KeyValuePair params[6]; |
398 | 0 | params[0].key = UA_QUALIFIEDNAME(0, "destination-address"); |
399 | 0 | UA_Variant_setScalar(¶ms[0].value, &destAddrStr, &UA_TYPES[UA_TYPES_STRING]); |
400 | 0 | params[1].key = UA_QUALIFIEDNAME(0, "source-address"); |
401 | 0 | UA_Variant_setScalar(¶ms[1].value, &sourceAddrStr, &UA_TYPES[UA_TYPES_STRING]); |
402 | |
|
403 | 0 | if(etherType > 0) { |
404 | 0 | params[2].key = UA_QUALIFIEDNAME(0, "ethertype"); |
405 | 0 | UA_Variant_setScalar(¶ms[1].value, ðerType, &UA_TYPES[UA_TYPES_UINT16]); |
406 | 0 | paramsSize++; |
407 | 0 | } |
408 | |
|
409 | 0 | if(vid > 0) { |
410 | 0 | params[paramsSize].key = UA_QUALIFIEDNAME(0, "vid"); |
411 | 0 | UA_Variant_setScalar(¶ms[paramsSize].value, &vid, &UA_TYPES[UA_TYPES_UINT16]); |
412 | 0 | params[paramsSize+1].key = UA_QUALIFIEDNAME(0, "pcp"); |
413 | 0 | UA_Variant_setScalar(¶ms[paramsSize+1].value, &pcp, &UA_TYPES[UA_TYPES_BYTE]); |
414 | 0 | params[paramsSize+2].key = UA_QUALIFIEDNAME(0, "dei"); |
415 | 0 | UA_Variant_setScalar(¶ms[paramsSize+2].value, &dei, &UA_TYPES[UA_TYPES_BOOLEAN]); |
416 | 0 | paramsSize += 3; |
417 | 0 | } |
418 | | |
419 | | /* Callback to the application layer with the Ethernet header hidden */ |
420 | 0 | UA_KeyValueMap map = {paramsSize, params}; |
421 | 0 | response.data += headerSize; |
422 | 0 | response.length -= headerSize; |
423 | 0 | conn->applicationCB(cm, (uintptr_t)rfd->fd, conn->application, &conn->context, |
424 | 0 | UA_CONNECTIONSTATE_ESTABLISHED, &map, response); |
425 | 0 | response.data -= headerSize; |
426 | 0 | response.length += headerSize; |
427 | 0 | } |
428 | | |
429 | | static UA_StatusCode |
430 | | ETH_openListenConnection(UA_EventLoopPOSIX *el, ETH_FD *conn, |
431 | | const UA_KeyValueMap *params, |
432 | | int ifindex, UA_UInt16 etherType, |
433 | 0 | UA_Boolean validate) { |
434 | 0 | UA_LOCK_ASSERT(&el->elMutex); |
435 | | |
436 | | /* Bind the socket to interface and EtherType. Don't receive anything else. */ |
437 | 0 | struct sockaddr_ll sll; |
438 | 0 | memset(&sll, 0, sizeof(struct sockaddr_ll)); |
439 | 0 | sll.sll_family = AF_PACKET; |
440 | 0 | sll.sll_protocol = htons(etherType); |
441 | 0 | sll.sll_ifindex = ifindex; |
442 | 0 | if(!validate && bind(conn->rfd.fd, (struct sockaddr*)&sll, sizeof(sll)) < 0) |
443 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
444 | | |
445 | | /* Immediately register for listen events. Don't have to wait for a |
446 | | * connection to open. */ |
447 | 0 | conn->rfd.listenEvents = UA_FDEVENT_IN; |
448 | | |
449 | | /* Set receiving to promiscuous (all target host addresses) */ |
450 | 0 | UA_RESET_ERRNO; |
451 | 0 | const UA_Boolean *promiscuous = (const UA_Boolean*) |
452 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_PROMISCUOUS].name, |
453 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
454 | 0 | if(promiscuous && *promiscuous) { |
455 | 0 | struct packet_mreq mreq; |
456 | 0 | memset(&mreq, 0, sizeof(struct packet_mreq)); |
457 | 0 | mreq.mr_ifindex = ifindex; |
458 | 0 | mreq.mr_type = PACKET_MR_PROMISC; |
459 | 0 | int ret = UA_setsockopt(conn->rfd.fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, |
460 | 0 | &mreq, sizeof(mreq)); |
461 | 0 | if(ret < 0) { |
462 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
463 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
464 | 0 | "ETH %u\t| Could not set raw socket to promiscuous mode %s", |
465 | 0 | (unsigned)conn->rfd.fd, errno_str)); |
466 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
467 | 0 | } else { |
468 | 0 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
469 | 0 | "ETH %u\t| The socket was set to promiscuous mode", |
470 | 0 | (unsigned)conn->rfd.fd); |
471 | 0 | } |
472 | 0 | } |
473 | | |
474 | | /* Register for multicast if an address is defined */ |
475 | 0 | const UA_String *address = (const UA_String*) |
476 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_ADDR].name, |
477 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
478 | 0 | if(address) { |
479 | 0 | UA_Byte addr[ETHER_ADDR_LEN]; |
480 | 0 | UA_StatusCode res = parseEthAddress(address, addr); |
481 | 0 | if(res != UA_STATUSCODE_GOOD) { |
482 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
483 | 0 | "ETH\t| Address for listening cannot be parsed"); |
484 | 0 | return res; |
485 | 0 | } |
486 | | |
487 | 0 | if(!isMulticastEthAddress(addr)) { |
488 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
489 | 0 | "ETH\t| Address for listening is not a multicast address. Ignoring."); |
490 | 0 | return UA_STATUSCODE_GOOD; |
491 | 0 | } |
492 | | |
493 | 0 | UA_RESET_ERRNO; |
494 | 0 | struct packet_mreq mreq; |
495 | 0 | memset(&mreq, 0, sizeof(struct packet_mreq)); |
496 | 0 | mreq.mr_ifindex = ifindex; |
497 | 0 | mreq.mr_type = PACKET_MR_MULTICAST; |
498 | 0 | mreq.mr_alen = ETH_ALEN; |
499 | 0 | memcpy(mreq.mr_address, addr, ETHER_ADDR_LEN); |
500 | 0 | if(!validate && UA_setsockopt(conn->rfd.fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, |
501 | 0 | (char *)&mreq, sizeof(mreq)) < 0) { |
502 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
503 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
504 | 0 | "ETH\t| Registering for multicast failed with error %s", |
505 | 0 | errno_str)); |
506 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
507 | 0 | } |
508 | 0 | } |
509 | | |
510 | 0 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
511 | 0 | "ETH %u\t| Opened an Ethernet listen socket", |
512 | 0 | (unsigned)conn->rfd.fd); |
513 | |
|
514 | 0 | return UA_STATUSCODE_GOOD; |
515 | 0 | } |
516 | | |
517 | | static UA_StatusCode |
518 | | ETH_openSendConnection(UA_EventLoopPOSIX *el, ETH_FD *conn, const UA_KeyValueMap *params, |
519 | 0 | UA_Byte source[ETHER_ADDR_LEN], int ifindex, UA_UInt16 etherType) { |
520 | 0 | UA_LOCK_ASSERT(&el->elMutex); |
521 | | |
522 | | /* Parse the target address (has to exist) */ |
523 | 0 | const UA_String *address = (const UA_String*) |
524 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_ADDR].name, |
525 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
526 | 0 | UA_Byte dest[ETHER_ADDR_LEN]; |
527 | 0 | UA_StatusCode res = parseEthAddress(address, dest); |
528 | 0 | if(res != UA_STATUSCODE_GOOD) { |
529 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
530 | 0 | "ETH\t| Could not parse the Ethernet address \"%.*s\"", |
531 | 0 | (int)address->length, (char*)address->data); |
532 | 0 | return res; |
533 | 0 | } |
534 | | |
535 | | /* Get the VLAN config */ |
536 | 0 | UA_UInt16 vid = 0; |
537 | 0 | UA_Byte pcp = 0; |
538 | 0 | UA_Boolean eid = false; |
539 | |
|
540 | 0 | const UA_UInt16 *vidp = (const UA_UInt16*) |
541 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_VID].name, |
542 | 0 | &UA_TYPES[UA_TYPES_UINT16]); |
543 | 0 | if(vidp) |
544 | 0 | vid = *vidp; |
545 | |
|
546 | 0 | const UA_Byte *pcpp = (const UA_Byte*) |
547 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_PCP].name, |
548 | 0 | &UA_TYPES[UA_TYPES_BYTE]); |
549 | 0 | if(pcpp) |
550 | 0 | pcp = *pcpp; |
551 | |
|
552 | 0 | const UA_Boolean *eidp = (const UA_Boolean*) |
553 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_DEI].name, |
554 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
555 | 0 | if(eidp) |
556 | 0 | eid = *eidp; |
557 | | |
558 | | /* Store the structure for sendto */ |
559 | 0 | conn->sll.sll_ifindex = ifindex; |
560 | 0 | conn->sll.sll_halen = ETH_ALEN; |
561 | 0 | memcpy(conn->sll.sll_addr, dest, ETHER_ADDR_LEN); |
562 | | |
563 | | /* Generate the Ethernet header */ |
564 | 0 | conn->headerSize = setETHHeader(conn->header, dest, source, etherType, |
565 | 0 | vid, pcp, eid, &conn->lengthOffset); |
566 | | |
567 | | /* Set the send priority if defined */ |
568 | 0 | const UA_Int32 *soPriority = (const UA_Int32*) |
569 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_PRIORITY].name, |
570 | 0 | &UA_TYPES[UA_TYPES_INT32]); |
571 | 0 | if(soPriority) { |
572 | 0 | UA_RESET_ERRNO; |
573 | 0 | int prioRes = UA_setsockopt(conn->rfd.fd, SOL_SOCKET, SO_PRIORITY, |
574 | 0 | soPriority, sizeof(int)); |
575 | 0 | if(prioRes != 0) { |
576 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
577 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
578 | 0 | "UA_setsockopt SO_PRIORITY failed with error %s", errno_str)); |
579 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
580 | 0 | } |
581 | 0 | } |
582 | | |
583 | | /* Enable txtime sending */ |
584 | 0 | const UA_Boolean *txtime_enable = (const UA_Boolean*) |
585 | 0 | UA_KeyValueMap_getScalar(params, |
586 | 0 | ethConnectionParams[ETH_PARAMINDEX_TXTIME_ENABLE].name, |
587 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
588 | |
|
589 | 0 | if(txtime_enable && *txtime_enable) { |
590 | | #ifndef SO_TXTIME |
591 | | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
592 | | "ETH %u\t| txtime feature not supported", |
593 | | (unsigned)conn->rfd.fd); |
594 | | #else |
595 | 0 | const UA_UInt32 *txtime_flags = (const UA_UInt32*) |
596 | 0 | UA_KeyValueMap_getScalar(params, |
597 | 0 | ethConnectionParams[ETH_PARAMINDEX_TXTIME_FLAGS].name, |
598 | 0 | &UA_TYPES[UA_TYPES_UINT32]); |
599 | 0 | UA_RESET_ERRNO; |
600 | 0 | struct sock_txtime so_txtime_val; |
601 | 0 | memset(&so_txtime_val, 0, sizeof(struct sock_txtime)); |
602 | 0 | so_txtime_val.clockid = el->clockSourceMonotonic; |
603 | 0 | so_txtime_val.flags = SOF_TXTIME_REPORT_ERRORS; |
604 | 0 | if(txtime_flags) |
605 | 0 | so_txtime_val.flags = *txtime_flags; |
606 | 0 | if(UA_setsockopt(conn->rfd.fd, SOL_SOCKET, SO_TXTIME, |
607 | 0 | &so_txtime_val, sizeof(so_txtime_val)) == 0) { |
608 | 0 | conn->txtimeEnabled = true; |
609 | 0 | } else { |
610 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
611 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
612 | 0 | "ETH %u\t| Could not enable txtime (%s)", |
613 | 0 | (unsigned)conn->rfd.fd, errno_str)); |
614 | 0 | } |
615 | 0 | #endif |
616 | 0 | } |
617 | | |
618 | | /* Done creating the socket */ |
619 | 0 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
620 | 0 | "ETH %u\t| Opened an Ethernet send socket", |
621 | 0 | (unsigned)conn->rfd.fd); |
622 | |
|
623 | 0 | return UA_STATUSCODE_GOOD; |
624 | 0 | } |
625 | | |
626 | | static UA_StatusCode |
627 | | ETH_openConnection(UA_ConnectionManager *cm, const UA_KeyValueMap *params, |
628 | | void *application, void *context, |
629 | 0 | UA_ConnectionManager_connectionCallback connectionCallback) { |
630 | 0 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
631 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX *)cm->eventSource.eventLoop; |
632 | |
|
633 | 0 | UA_LOCK(&el->elMutex); |
634 | | |
635 | | /* Listen or send connection? */ |
636 | 0 | const UA_Boolean *listen = (const UA_Boolean*) |
637 | 0 | UA_KeyValueMap_getScalar(params, |
638 | 0 | ethConnectionParams[ETH_PARAMINDEX_LISTEN].name, |
639 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
640 | 0 | size_t ethParamRestrictions = ETH_PARAMETERSSIZE; |
641 | 0 | if(!listen || !*listen) |
642 | 0 | ethParamRestrictions++; /* Use the last restriction only for send connections */ |
643 | | |
644 | | /* Validate the parameters */ |
645 | 0 | UA_StatusCode res = |
646 | 0 | UA_KeyValueRestriction_validate(el->eventLoop.logger, "ETH", ethConnectionParams, |
647 | 0 | ethParamRestrictions, params); |
648 | 0 | if(res != UA_STATUSCODE_GOOD) { |
649 | 0 | UA_UNLOCK(&el->elMutex); |
650 | 0 | return res; |
651 | 0 | } |
652 | | |
653 | | /* Only validate the parameters? */ |
654 | 0 | UA_Boolean validate = false; |
655 | 0 | const UA_Boolean *validateParam = (const UA_Boolean*) |
656 | 0 | UA_KeyValueMap_getScalar(params, |
657 | 0 | ethConnectionParams[ETH_PARAMINDEX_VALIDATE].name, |
658 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
659 | 0 | if(validateParam) |
660 | 0 | validate = *validateParam; |
661 | | |
662 | | /* Get the EtherType parameter */ |
663 | 0 | UA_UInt16 etherType = ETH_P_ALL; |
664 | 0 | const UA_UInt16 *etParam = (const UA_UInt16*) |
665 | 0 | UA_KeyValueMap_getScalar(params, |
666 | 0 | ethConnectionParams[ETH_PARAMINDEX_ETHERTYPE].name, |
667 | 0 | &UA_TYPES[UA_TYPES_UINT16]); |
668 | 0 | if(etParam) |
669 | 0 | etherType = *etParam; |
670 | | |
671 | | /* Get the interface index */ |
672 | 0 | const UA_String *interface = (const UA_String*) |
673 | 0 | UA_KeyValueMap_getScalar(params, |
674 | 0 | ethConnectionParams[ETH_PARAMINDEX_IFACE].name, |
675 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
676 | 0 | if(interface->length >= 128) { |
677 | 0 | UA_UNLOCK(&el->elMutex); |
678 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
679 | 0 | } |
680 | 0 | char ifname[128]; |
681 | 0 | memcpy(ifname, interface->data, interface->length); |
682 | 0 | ifname[interface->length] = 0; |
683 | 0 | int ifindex = (int)if_nametoindex(ifname); |
684 | 0 | if(ifindex == 0) { |
685 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
686 | 0 | "ETH\t| Could not find the interface %s", ifname); |
687 | 0 | UA_UNLOCK(&el->elMutex); |
688 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
689 | 0 | } |
690 | | |
691 | | /* Create the socket and add the basic configuration */ |
692 | 0 | ETH_FD *conn = NULL; |
693 | 0 | UA_FD sockfd; |
694 | 0 | if(listen && *listen) |
695 | 0 | sockfd = UA_socket(PF_PACKET, SOCK_RAW, htons(etherType)); |
696 | 0 | else |
697 | 0 | sockfd = UA_socket(PF_PACKET, SOCK_RAW, 0); /* Don't receive */ |
698 | 0 | if(sockfd == -1) { |
699 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
700 | 0 | "ETH\t| Could not create a raw Ethernet socket (are you root?)"); |
701 | 0 | UA_UNLOCK(&el->elMutex); |
702 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
703 | 0 | } |
704 | | |
705 | | /* SO_REUSEADDR is unnecessary (and unsupported on some Linux systems): |
706 | | * res |= UA_EventLoopPOSIX_setReusable(sockfd); */ |
707 | 0 | res |= UA_EventLoopPOSIX_setNonBlocking(sockfd); |
708 | 0 | res |= UA_EventLoopPOSIX_setNoSigPipe(sockfd); |
709 | 0 | if(res != UA_STATUSCODE_GOOD) |
710 | 0 | goto cleanup; |
711 | | |
712 | | /* Create the FD object */ |
713 | 0 | conn = (ETH_FD*)UA_calloc(1, sizeof(ETH_FD)); |
714 | 0 | if(!conn) { |
715 | 0 | res = UA_STATUSCODE_BADOUTOFMEMORY; |
716 | 0 | goto cleanup; |
717 | 0 | } |
718 | | |
719 | 0 | conn->rfd.fd = sockfd; |
720 | 0 | conn->rfd.es = &pcm->cm.eventSource; |
721 | 0 | conn->rfd.eventSourceCB = (UA_FDCallback)ETH_connectionSocketCallback; |
722 | 0 | conn->context = context; |
723 | 0 | conn->application = application; |
724 | 0 | conn->applicationCB = connectionCallback; |
725 | | |
726 | | /* Configure a listen or a send connection */ |
727 | 0 | if(!listen || !*listen) { |
728 | | /* Get the source address for the interface */ |
729 | 0 | UA_RESET_ERRNO; |
730 | 0 | struct ifreq ifr; |
731 | 0 | memcpy(ifr.ifr_name, ifname, interface->length); |
732 | 0 | ifr.ifr_name[interface->length] = 0; |
733 | 0 | int result = ioctl(conn->rfd.fd, SIOCGIFHWADDR, &ifr); |
734 | 0 | if(result == -1) { |
735 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
736 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
737 | 0 | "ETH %u\t| Cannot get the source address, %s", |
738 | 0 | (unsigned)conn->rfd.fd, errno_str)); |
739 | 0 | res = UA_STATUSCODE_BADCONNECTIONREJECTED; |
740 | 0 | goto cleanup; |
741 | 0 | } |
742 | 0 | res = ETH_openSendConnection(el, conn, params, |
743 | 0 | (unsigned char*)ifr.ifr_hwaddr.sa_data, |
744 | 0 | ifindex, etherType); |
745 | 0 | } else { |
746 | 0 | res = ETH_openListenConnection(el, conn, params, ifindex, etherType, validate); |
747 | 0 | } |
748 | | |
749 | | /* Don't actually open or shut down */ |
750 | 0 | if(validate || res != UA_STATUSCODE_GOOD) |
751 | 0 | goto cleanup; |
752 | | |
753 | | /* Register in the EventLoop */ |
754 | 0 | res = UA_EventLoopPOSIX_registerFD(el, &conn->rfd); |
755 | 0 | if(res != UA_STATUSCODE_GOOD) |
756 | 0 | goto cleanup; |
757 | | |
758 | | /* Register locally */ |
759 | 0 | ZIP_INSERT(UA_FDTree, &pcm->fds, &conn->rfd); |
760 | 0 | pcm->fdsSize++; |
761 | | |
762 | | /* Register the listen socket in the application */ |
763 | 0 | connectionCallback(cm, (uintptr_t)sockfd, application, &conn->context, |
764 | 0 | UA_CONNECTIONSTATE_ESTABLISHED, &UA_KEYVALUEMAP_NULL, |
765 | 0 | UA_BYTESTRING_NULL); |
766 | 0 | UA_UNLOCK(&el->elMutex); |
767 | 0 | return UA_STATUSCODE_GOOD; |
768 | | |
769 | 0 | cleanup: |
770 | 0 | UA_close(sockfd); |
771 | 0 | UA_free(conn); |
772 | 0 | UA_UNLOCK(&el->elMutex); |
773 | 0 | return res; |
774 | 0 | } |
775 | | |
776 | | static void |
777 | 0 | ETH_shutdown(UA_POSIXConnectionManager *pcm, ETH_FD *conn) { |
778 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop; |
779 | 0 | UA_LOCK_ASSERT(&((UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop)->elMutex); |
780 | |
|
781 | 0 | UA_DelayedCallback *dc = &conn->rfd.dc; |
782 | 0 | if(dc->callback) { |
783 | 0 | UA_LOG_INFO(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
784 | 0 | "ETH %u\t| Cannot close - already closing", |
785 | 0 | (unsigned)conn->rfd.fd); |
786 | 0 | return; |
787 | 0 | } |
788 | | |
789 | | /* Shutdown the socket to cancel the current select/epoll */ |
790 | 0 | UA_shutdown(conn->rfd.fd, UA_SHUT_RDWR); |
791 | |
|
792 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
793 | 0 | "ETH %u\t| Shutdown called", (unsigned)conn->rfd.fd); |
794 | |
|
795 | 0 | dc->callback = ETH_delayedClose; |
796 | 0 | dc->application = pcm; |
797 | 0 | dc->context = conn; |
798 | | |
799 | | /* Adding a delayed callback does not take a lock */ |
800 | 0 | UA_EventLoopPOSIX_addDelayedCallback((UA_EventLoop*)el, dc); |
801 | 0 | } |
802 | | |
803 | | static UA_StatusCode |
804 | 0 | ETH_shutdownConnection(UA_ConnectionManager *cm, uintptr_t connectionId) { |
805 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; |
806 | 0 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
807 | 0 | UA_LOCK(&el->elMutex); |
808 | | |
809 | | /* Get the ETH_FD */ |
810 | 0 | UA_FD fd = (UA_FD)connectionId; |
811 | 0 | UA_RegisteredFD *rfd = ZIP_FIND(UA_FDTree, &pcm->fds, &fd); |
812 | 0 | if(!rfd) { |
813 | 0 | UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
814 | 0 | "ETH\t| Cannot close Ethernet connection %u - not found", |
815 | 0 | (unsigned)connectionId); |
816 | 0 | UA_UNLOCK(&el->elMutex); |
817 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
818 | 0 | } |
819 | | |
820 | 0 | ETH_shutdown(pcm, (ETH_FD*)rfd); |
821 | 0 | UA_UNLOCK(&el->elMutex); |
822 | 0 | return UA_STATUSCODE_GOOD; |
823 | 0 | } |
824 | | |
825 | | #ifdef SO_TXTIME |
826 | | static ssize_t |
827 | | send_txtime(UA_EventLoopPOSIX *el, ETH_FD *conn, const UA_KeyValueMap *params, |
828 | 0 | UA_DateTime txtime, const char *bytes, size_t bytesSize) { |
829 | | /* Get additiona parameters */ |
830 | 0 | const UA_UInt16 *txtime_pico = (const UA_UInt16*) |
831 | 0 | UA_KeyValueMap_getScalar(params, |
832 | 0 | ethConnectionParams[ETH_PARAMINDEX_TXTIME_PICO].name, |
833 | 0 | &UA_TYPES[UA_TYPES_UINT16]); |
834 | 0 | const UA_Boolean *txtime_drop = (const UA_Boolean*) |
835 | 0 | UA_KeyValueMap_getScalar(params, |
836 | 0 | ethConnectionParams[ETH_PARAMINDEX_TXTIME_DROP].name, |
837 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
838 | 0 | #ifndef SCM_DROP_IF_LATE |
839 | 0 | if(txtime_drop) { |
840 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
841 | 0 | "ETH %u\t| txtime drop_if_late not supported on the current system", |
842 | 0 | (unsigned)conn->rfd.fd); |
843 | 0 | return 0; |
844 | 0 | } |
845 | 0 | #endif |
846 | | |
847 | | |
848 | | /* Transform from 100ns since 1601 to ns since the Unix Epoch */ |
849 | 0 | UA_UInt64 transmission_time = (UA_UInt64) |
850 | 0 | (txtime - UA_DATETIME_UNIX_EPOCH) * 100; |
851 | 0 | if(txtime_pico) |
852 | 0 | transmission_time += (*txtime_pico) / 1000; |
853 | | |
854 | | /* Structure for scattering or gathering of input/output */ |
855 | 0 | struct iovec inputOutputVec; |
856 | 0 | inputOutputVec.iov_base = (void*)(uintptr_t)bytes; |
857 | 0 | inputOutputVec.iov_len = bytesSize; |
858 | | |
859 | | /* Specify the transmission time in the CMSG. */ |
860 | 0 | char dataPacket[CMSG_SPACE(sizeof(uint64_t)) |
861 | | #ifdef SCM_DROP_IF_LATE |
862 | | + CMSG_SPACE(sizeof(uint8_t)) |
863 | | #endif |
864 | 0 | ]; |
865 | 0 | struct msghdr message; |
866 | 0 | memset(&message, 0, sizeof(struct msghdr)); |
867 | 0 | message.msg_control = dataPacket; |
868 | 0 | message.msg_controllen = sizeof(dataPacket); |
869 | 0 | message.msg_name = (struct sockaddr*)&conn->sll; |
870 | 0 | message.msg_namelen = sizeof(conn->sll); |
871 | 0 | message.msg_iov = &inputOutputVec; |
872 | 0 | message.msg_iovlen = 1; |
873 | |
|
874 | 0 | struct cmsghdr *cmsg = CMSG_FIRSTHDR(&message); |
875 | 0 | cmsg->cmsg_level = SOL_SOCKET; |
876 | 0 | cmsg->cmsg_type = SCM_TXTIME; |
877 | 0 | cmsg->cmsg_len = CMSG_LEN(sizeof(__u64)); |
878 | 0 | *((__u64*)CMSG_DATA(cmsg)) = transmission_time; |
879 | |
|
880 | | #ifdef SCM_DROP_IF_LATE |
881 | | cmsg = CMSG_NXTHDR(&message, cmsg); |
882 | | cmsg->cmsg_level = SOL_SOCKET; |
883 | | cmsg->cmsg_type = SCM_DROP_IF_LATE; |
884 | | cmsg->cmsg_len = CMSG_LEN(sizeof(uint8_t)); |
885 | | *((uint8_t*)CMSG_DATA(cmsg)) = (!txtime_drop || *txtime_drop) ? 1: 0; |
886 | | #endif |
887 | | |
888 | | /* Send */ |
889 | 0 | return sendmsg(conn->rfd.fd, &message, 0); |
890 | 0 | } |
891 | | #endif |
892 | | |
893 | | static UA_StatusCode |
894 | | ETH_sendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId, |
895 | 0 | const UA_KeyValueMap *params, UA_ByteString *buf) { |
896 | 0 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; |
897 | 0 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
898 | |
|
899 | 0 | UA_LOCK(&el->elMutex); |
900 | | |
901 | | /* Get the ETH_FD */ |
902 | 0 | UA_FD fd = (UA_FD)connectionId; |
903 | 0 | ETH_FD *conn = (ETH_FD*)ZIP_FIND(UA_FDTree, &pcm->fds, &fd); |
904 | 0 | if(!conn) { |
905 | 0 | UA_UNLOCK(&el->elMutex); |
906 | 0 | UA_EventLoopPOSIX_freeNetworkBuffer(cm, connectionId, buf); |
907 | 0 | return UA_STATUSCODE_BADCONNECTIONREJECTED; |
908 | 0 | } |
909 | | |
910 | | /* Uncover and set the Ethernet header */ |
911 | 0 | buf->data -= conn->headerSize; |
912 | 0 | buf->length += conn->headerSize; |
913 | 0 | memcpy(buf->data, conn->header, conn->headerSize); |
914 | 0 | if(conn->lengthOffset) { |
915 | 0 | UA_UInt16 *ethLength = (UA_UInt16*)&buf->data[conn->lengthOffset]; |
916 | 0 | *ethLength = htons((UA_UInt16)(buf->length - conn->headerSize)); |
917 | 0 | } |
918 | | |
919 | | /* Was a txtime configured? */ |
920 | 0 | const UA_DateTime *txtime = (const UA_DateTime*) |
921 | 0 | UA_KeyValueMap_getScalar(params, ethConnectionParams[ETH_PARAMINDEX_TXTIME].name, |
922 | 0 | &UA_TYPES[UA_TYPES_DATETIME]); |
923 | 0 | if(txtime && !conn->txtimeEnabled) { |
924 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
925 | 0 | "ETH %u\t| txtime was not configured for the connection", |
926 | 0 | (unsigned)connectionId); |
927 | 0 | UA_UNLOCK(&el->elMutex); |
928 | 0 | UA_EventLoopPOSIX_freeNetworkBuffer(cm, connectionId, buf); |
929 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
930 | 0 | } |
931 | | |
932 | | /* Prevent OS signals when sending to a closed socket */ |
933 | 0 | int flags = MSG_NOSIGNAL; |
934 | |
|
935 | 0 | struct pollfd tmp_poll_fd; |
936 | 0 | tmp_poll_fd.fd = (UA_FD)connectionId; |
937 | 0 | tmp_poll_fd.events = UA_POLLOUT; |
938 | | |
939 | | /* Send the full buffer. This may require several calls to send */ |
940 | 0 | size_t nWritten = 0; |
941 | 0 | do { |
942 | 0 | ssize_t n = 0; |
943 | 0 | do { |
944 | 0 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
945 | 0 | "ETH %u\t| Attempting to send", (unsigned)connectionId); |
946 | 0 | UA_RESET_ERRNO; |
947 | 0 | size_t bytes_to_send = buf->length - nWritten; |
948 | 0 | #ifdef SO_TXTIME |
949 | 0 | if(txtime) { |
950 | 0 | n = send_txtime(el, conn, params, *txtime, |
951 | 0 | (const char*)buf->data + nWritten, bytes_to_send); |
952 | 0 | } else |
953 | 0 | #endif |
954 | 0 | { |
955 | 0 | n = UA_sendto(conn->rfd.fd, |
956 | 0 | (const char*)buf->data + nWritten, bytes_to_send, |
957 | 0 | flags, (struct sockaddr*)&conn->sll, sizeof(conn->sll)); |
958 | 0 | } |
959 | 0 | if(n < 0) { |
960 | | /* An error we cannot recover from? */ |
961 | 0 | if(UA_ERRNO != UA_INTERRUPTED && |
962 | 0 | UA_ERRNO != UA_WOULDBLOCK && |
963 | 0 | UA_ERRNO != UA_AGAIN) { |
964 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
965 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
966 | 0 | "ETH %u\t| Send failed with error %s", |
967 | 0 | (unsigned)connectionId, errno_str)); |
968 | 0 | ETH_shutdown(pcm, conn); |
969 | 0 | UA_UNLOCK(&el->elMutex); |
970 | 0 | UA_EventLoopPOSIX_freeNetworkBuffer(cm, connectionId, buf); |
971 | 0 | return UA_STATUSCODE_BADCONNECTIONCLOSED; |
972 | 0 | } |
973 | | |
974 | | /* Poll for the socket resources to become available and retry |
975 | | * (blocking) */ |
976 | 0 | int poll_ret; |
977 | 0 | do { |
978 | 0 | UA_RESET_ERRNO; |
979 | 0 | poll_ret = UA_poll(&tmp_poll_fd, 1, 100); |
980 | 0 | if(poll_ret < 0 && UA_ERRNO != UA_INTERRUPTED) { |
981 | 0 | UA_LOG_SOCKET_ERRNO_WRAP( |
982 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
983 | 0 | "ETH %u\t| Send failed with error %s", |
984 | 0 | (unsigned)connectionId, errno_str)); |
985 | 0 | ETH_shutdown(pcm, conn); |
986 | 0 | UA_UNLOCK(&el->elMutex); |
987 | 0 | UA_EventLoopPOSIX_freeNetworkBuffer(cm, connectionId, buf); |
988 | 0 | return UA_STATUSCODE_BADCONNECTIONCLOSED; |
989 | 0 | } |
990 | 0 | } while(poll_ret <= 0); |
991 | 0 | } |
992 | 0 | } while(n < 0); |
993 | 0 | nWritten += (size_t)n; |
994 | 0 | } while(nWritten < buf->length); |
995 | | |
996 | | /* Free the buffer */ |
997 | 0 | UA_UNLOCK(&el->elMutex); |
998 | 0 | UA_EventLoopPOSIX_freeNetworkBuffer(cm, connectionId, buf); |
999 | 0 | return UA_STATUSCODE_GOOD; |
1000 | 0 | } |
1001 | | |
1002 | | static UA_StatusCode |
1003 | 502 | ETH_eventSourceStart(UA_ConnectionManager *cm) { |
1004 | 502 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
1005 | 502 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop; |
1006 | 502 | UA_LOCK(&el->elMutex); |
1007 | | |
1008 | | /* Check the state */ |
1009 | 502 | if(cm->eventSource.state != UA_EVENTSOURCESTATE_STOPPED) { |
1010 | 0 | UA_LOG_ERROR(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
1011 | 0 | "To start the Ethernet ConnectionManager, " |
1012 | 0 | "it has to be registered in an EventLoop and not started"); |
1013 | 0 | UA_UNLOCK(&el->elMutex); |
1014 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1015 | 0 | } |
1016 | | |
1017 | | /* Check the parameters */ |
1018 | 502 | UA_StatusCode res = |
1019 | 502 | UA_KeyValueRestriction_validate(el->eventLoop.logger, "ETH", |
1020 | 502 | ethManagerParams, ETH_MANAGERPARAMS, |
1021 | 502 | &cm->eventSource.params); |
1022 | 502 | if(res != UA_STATUSCODE_GOOD) |
1023 | 0 | goto finish; |
1024 | | |
1025 | | /* Allocate the rx buffer */ |
1026 | 502 | res = UA_EventLoopPOSIX_allocateStaticBuffers(pcm); |
1027 | 502 | if(res != UA_STATUSCODE_GOOD) |
1028 | 0 | goto finish; |
1029 | | |
1030 | | /* Set the EventSource to the started state */ |
1031 | 502 | cm->eventSource.state = UA_EVENTSOURCESTATE_STARTED; |
1032 | | |
1033 | 502 | finish: |
1034 | 502 | UA_UNLOCK(&el->elMutex); |
1035 | 502 | return res; |
1036 | 502 | } |
1037 | | |
1038 | | static void * |
1039 | 0 | ETH_shutdownCB(void *application, UA_RegisteredFD *rfd) { |
1040 | 0 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)application; |
1041 | 0 | ETH_shutdown(pcm, (ETH_FD*)rfd); |
1042 | 0 | return NULL; |
1043 | 0 | } |
1044 | | |
1045 | | static void |
1046 | 502 | ETH_eventSourceStop(UA_ConnectionManager *cm) { |
1047 | 502 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
1048 | 502 | UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop; |
1049 | 502 | UA_LOCK(&el->elMutex); |
1050 | | |
1051 | 502 | UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_NETWORK, |
1052 | 502 | "ETH\t| Shutting down the ConnectionManager"); |
1053 | | |
1054 | | /* Prevent new connections to open */ |
1055 | 502 | cm->eventSource.state = UA_EVENTSOURCESTATE_STOPPING; |
1056 | | |
1057 | | /* Shutdown all existing connection */ |
1058 | 502 | ZIP_ITER(UA_FDTree, &pcm->fds, ETH_shutdownCB, cm); |
1059 | | |
1060 | | /* Check if stopped once more (also checking inside ETH_close, but there we |
1061 | | * don't check if there is no rfd at all) */ |
1062 | 502 | ETH_checkStopped(pcm); |
1063 | | |
1064 | 502 | UA_UNLOCK(&el->elMutex); |
1065 | 502 | } |
1066 | | |
1067 | | static UA_StatusCode |
1068 | 502 | ETH_eventSourceDelete(UA_ConnectionManager *cm) { |
1069 | 502 | UA_POSIXConnectionManager *pcm = (UA_POSIXConnectionManager*)cm; |
1070 | 502 | if(cm->eventSource.state >= UA_EVENTSOURCESTATE_STARTING) { |
1071 | 0 | UA_LOG_ERROR(cm->eventSource.eventLoop->logger, UA_LOGCATEGORY_EVENTLOOP, |
1072 | 0 | "ETH\t| The EventSource must be stopped before it can be deleted"); |
1073 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1074 | 0 | } |
1075 | | |
1076 | 502 | UA_KeyValueMap_clear(&cm->eventSource.params); |
1077 | 502 | UA_ByteString_clear(&pcm->rxBuffer); |
1078 | 502 | UA_ByteString_clear(&pcm->txBuffer); |
1079 | 502 | UA_String_clear(&cm->eventSource.name); |
1080 | 502 | UA_free(cm); |
1081 | 502 | return UA_STATUSCODE_GOOD; |
1082 | 502 | } |
1083 | | |
1084 | | static const char *ethName = "eth"; |
1085 | | |
1086 | | UA_ConnectionManager * |
1087 | 502 | UA_ConnectionManager_new_POSIX_Ethernet(const UA_String eventSourceName) { |
1088 | 502 | UA_POSIXConnectionManager *cm = (UA_POSIXConnectionManager*) |
1089 | 502 | UA_calloc(1, sizeof(UA_POSIXConnectionManager)); |
1090 | 502 | if(!cm) |
1091 | 0 | return NULL; |
1092 | | |
1093 | 502 | cm->cm.eventSource.eventSourceType = UA_EVENTSOURCETYPE_CONNECTIONMANAGER; |
1094 | 502 | UA_String_copy(&eventSourceName, &cm->cm.eventSource.name); |
1095 | 502 | cm->cm.eventSource.start = (UA_StatusCode (*)(UA_EventSource *))ETH_eventSourceStart; |
1096 | 502 | cm->cm.eventSource.stop = (void (*)(UA_EventSource *))ETH_eventSourceStop; |
1097 | 502 | cm->cm.eventSource.free = (UA_StatusCode (*)(UA_EventSource *))ETH_eventSourceDelete; |
1098 | 502 | cm->cm.protocol = UA_STRING((char*)(uintptr_t)ethName); |
1099 | 502 | cm->cm.openConnection = ETH_openConnection; |
1100 | 502 | cm->cm.allocNetworkBuffer = ETH_allocNetworkBuffer; |
1101 | 502 | cm->cm.freeNetworkBuffer = ETH_freeNetworkBuffer; |
1102 | 502 | cm->cm.sendWithConnection = ETH_sendWithConnection; |
1103 | 502 | cm->cm.closeConnection = ETH_shutdownConnection; |
1104 | 502 | return &cm->cm; |
1105 | 502 | } |
1106 | | |
1107 | | #endif /* defined(UA_ARCHITECTURE_POSIX) && defined(__linux__) */ |
1108 | | |