/src/open62541/drivers/discovery_mdns_mdnsd.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 2017 (c) Stefan Profanter, fortiss GmbH |
6 | | * Copyright 2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
7 | | * Copyright 2017 (c) Thomas Stalder, Blue Time Concept SA |
8 | | * Copyright 2026 (c) o6 Automation GmbH (Author: Julius Pfrommer) |
9 | | */ |
10 | | |
11 | | #include <open62541/server.h> |
12 | | #include <open62541/driver/mdns.h> |
13 | | #include "open62541_queue.h" |
14 | | |
15 | | #ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD |
16 | | |
17 | | #include "mdnsd/mdnsd.h" |
18 | | #include "mdnsd/xht.h" |
19 | | #include "mdnsd/sdtxt.h" |
20 | | |
21 | | #ifdef UA_ARCHITECTURE_WIN32 |
22 | | /* inet_ntoa is deprecated on MSVC but used for compatibility */ |
23 | | # define _WINSOCK_DEPRECATED_NO_WARNINGS |
24 | | # include <winsock2.h> |
25 | | # include <iphlpapi.h> |
26 | | # include <ws2tcpip.h> |
27 | | #else |
28 | | # include <unistd.h> |
29 | | # include <sys/types.h> |
30 | | # include <sys/socket.h> |
31 | | # include <sys/time.h> // for struct timeval |
32 | | # include <arpa/inet.h> |
33 | | # include <netinet/in.h> // for struct ip_mreq |
34 | | # if defined(UA_HAS_GETIFADDR) |
35 | | # include <ifaddrs.h> |
36 | | # endif /* UA_HAS_GETIFADDR */ |
37 | | # include <net/if.h> /* for IFF_RUNNING */ |
38 | | # include <netdb.h> // for recvfrom in cygwin |
39 | | #endif |
40 | | |
41 | 0 | #define UA_MAXMDNSRECVSOCKETS 8 |
42 | 0 | #define UA_MDNS_POLL_INTERVAL_MS 1000 |
43 | | #define UA_MDNS_MAX_RECORD_NAME_LENGTH 256 |
44 | 0 | #define UA_MDNS_OPCUA_TCP_LOCAL "_opcua-tcp._tcp.local." |
45 | | |
46 | | /***************************/ |
47 | | /* Multicast DNS Discovery */ |
48 | | /***************************/ |
49 | | |
50 | | /* open62541 splits mDNS into two layers: |
51 | | * |
52 | | * 1) The mDNS driver handles the wire protocol. It sends and receives multicast |
53 | | * DNS packets, maps OPC UA discovery information to DNS records and parses |
54 | | * incoming records from other servers on the network. |
55 | | * |
56 | | * 2) The implementation here manages the discovery state in the server. The |
57 | | * plugin reports discovered servers via UA_Server_registerServerOnNetwork() |
58 | | * and UA_Server_deregisterServerOnNetwork(). The resulting |
59 | | * UA_ServerOnNetwork entries back FindServersOnNetwork and the discovery |
60 | | * notification callbacks. |
61 | | * |
62 | | * A typical OPC UA mDNS announcement consists of: |
63 | | * |
64 | | * - PTR: |
65 | | * _opcua-tcp._tcp.local. -> [servername]-[hostname]._opcua-tcp._tcp.local. |
66 | | * This advertises that an OPC UA TCP service instance exists. |
67 | | * |
68 | | * - SRV: |
69 | | * [servername]-[hostname]._opcua-tcp._tcp.local. -> host + port |
70 | | * This gives the target host and port for the discovery URL. |
71 | | * |
72 | | * - TXT: |
73 | | * [servername]-[hostname]._opcua-tcp._tcp.local. -> path=..., caps=... |
74 | | * This carries the discovery path and server capabilities. |
75 | | * |
76 | | * Lifecycle: |
77 | | * |
78 | | * - Announcement: |
79 | | * The local server (or a registered remote server) is announced by the mDNS |
80 | | * plugin. The plugin publishes PTR/SRV/TXT records and stores the normalized |
81 | | * result here as a UA_ServerOnNetwork entry. The UDP event loop owns the |
82 | | * multicast socket mechanics such as interface selection, group membership, |
83 | | * send-interface selection and packet TTL. |
84 | | * |
85 | | * - Update: |
86 | | * A repeated announce or a changed SRV/TXT payload refreshes the existing |
87 | | * UA_ServerOnNetwork entry. Depending on the packet order, TXT may arrive |
88 | | * before SRV or vice versa; the plugin combines both into one normalized |
89 | | * discovery URL plus capabilities and then reports the added/updated entry |
90 | | * here. |
91 | | * |
92 | | * - Removal: |
93 | | * A server can be retracted locally, or it can disappear remotely via a |
94 | | * "goodbye" packet with TTL=0. The plugin translates that into |
95 | | * UA_Server_deregisterServerOnNetwork(), which removes the normalized entry |
96 | | * and emits a removal notification. |
97 | | */ |
98 | | |
99 | | typedef enum { |
100 | | SERVER_ON_NETWORK_RECORD_REMOTE_RECEIVED, |
101 | | SERVER_ON_NETWORK_RECORD_LOCAL_OWNED |
102 | | } ServerOnNetworkRecordOrigin; |
103 | | |
104 | | typedef struct ServerOnNetworkRecord { |
105 | | LIST_ENTRY(ServerOnNetworkRecord) listPointers; |
106 | | UA_ServerOnNetwork serverOnNetwork; |
107 | | UA_Boolean txtSet; |
108 | | UA_Boolean srvSet; |
109 | | char *pathTmp; |
110 | | ServerOnNetworkRecordOrigin origin; |
111 | | UA_DateTime nextAction; /* Remote: Remove after TTL |
112 | | * Local: Announce again */ |
113 | | UA_UInt32 ttl; /* Local: the interval before sending again */ |
114 | | } ServerOnNetworkRecord; |
115 | | |
116 | | typedef struct { |
117 | | UA_MdnsDriver mdns; |
118 | | |
119 | | UA_Logger *logging; /* Shortcut from md->server->logging */ |
120 | | |
121 | | mdns_daemon_t *mdnsDaemon; |
122 | | |
123 | | UA_ConnectionManager *cm; |
124 | | uintptr_t mdnsSendConnection; |
125 | | uintptr_t mdnsRecvConnections[UA_MAXMDNSRECVSOCKETS]; |
126 | | size_t mdnsRecvConnectionsSize; |
127 | | UA_UInt64 sendCallbackId; |
128 | | UA_UInt64 presenceQueryCallbackId; |
129 | | |
130 | | LIST_HEAD(, ServerOnNetworkRecord) serverList; |
131 | | |
132 | | /* Configuration parameters */ |
133 | | UA_Boolean listen; |
134 | | UA_Boolean announce; |
135 | | UA_Boolean queryPresence; |
136 | | UA_Boolean queryDetails; |
137 | | UA_UInt32 queryInterval; |
138 | | UA_UInt32 announceTTL; |
139 | | UA_String interfaceName; |
140 | | } MdnsdDriver; |
141 | | |
142 | | static void |
143 | | flushMulticastMessages(MdnsdDriver *md); |
144 | | |
145 | | static void |
146 | | stopRecordDetailsQueryForEntry(MdnsdDriver *md, |
147 | | const ServerOnNetworkRecord *entry); |
148 | | |
149 | | static void |
150 | 0 | ServerOnNetworkRecord_delete(ServerOnNetworkRecord *son) { |
151 | 0 | UA_ServerOnNetwork_clear(&son->serverOnNetwork); |
152 | 0 | if(son->pathTmp) |
153 | 0 | UA_free(son->pathTmp); |
154 | 0 | UA_free(son); |
155 | 0 | } |
156 | | |
157 | | static ServerOnNetworkRecord * |
158 | 0 | findSON(MdnsdDriver *md, UA_String serverName) { |
159 | 0 | ServerOnNetworkRecord *son; |
160 | 0 | LIST_FOREACH(son, &md->serverList, listPointers) { |
161 | 0 | if(UA_String_equal(&serverName, &son->serverOnNetwork.serverName)) |
162 | 0 | return son; |
163 | 0 | } |
164 | 0 | return NULL; |
165 | 0 | } |
166 | | |
167 | | static UA_StatusCode |
168 | | addSON(MdnsdDriver *md, const UA_ServerOnNetwork *son, |
169 | 0 | ServerOnNetworkRecord **addedEntry) { |
170 | 0 | UA_LOG_INFO(md->logging, UA_LOGCATEGORY_DISCOVERY, |
171 | 0 | "mDNS: Add record for %S", son->serverName); |
172 | | |
173 | | /* Allocate new */ |
174 | 0 | ServerOnNetworkRecord *entry = (ServerOnNetworkRecord*) |
175 | 0 | UA_calloc(1, sizeof(ServerOnNetworkRecord)); |
176 | 0 | if(!entry) |
177 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
178 | | |
179 | | /* Copy the SON */ |
180 | 0 | UA_StatusCode res = |
181 | 0 | UA_ServerOnNetwork_copy(son, &entry->serverOnNetwork); |
182 | 0 | if(res != UA_STATUSCODE_GOOD) { |
183 | 0 | UA_free(entry); |
184 | 0 | return res; |
185 | 0 | } |
186 | | |
187 | | /* Add to the linked list */ |
188 | 0 | LIST_INSERT_HEAD(&md->serverList, entry, listPointers); |
189 | | |
190 | | /* Return pointer if requested */ |
191 | 0 | if(addedEntry != NULL) |
192 | 0 | *addedEntry = entry; |
193 | 0 | return UA_STATUSCODE_GOOD; |
194 | 0 | } |
195 | | |
196 | | static void |
197 | | removeSONEntry(MdnsdDriver *md, ServerOnNetworkRecord *entry, |
198 | 0 | UA_Boolean deregisterFromServer) { |
199 | 0 | UA_LOG_INFO(md->logging, UA_LOGCATEGORY_DISCOVERY, |
200 | 0 | "mDNS: Remove record for %S", entry->serverOnNetwork.serverName); |
201 | | |
202 | | /* Remove from linked list first to break the recursion. The driver's |
203 | | * notification callback can be triggered by UA_Server_deregisterServerOnNetwork |
204 | | * and would otherwise re-enter with the same entry. */ |
205 | 0 | LIST_REMOVE(entry, listPointers); |
206 | |
|
207 | 0 | if(deregisterFromServer) |
208 | 0 | UA_Server_deregisterServerOnNetwork(md->mdns.drv.server, |
209 | 0 | entry->serverOnNetwork.serverName); |
210 | |
|
211 | 0 | ServerOnNetworkRecord_delete(entry); |
212 | 0 | } |
213 | | |
214 | | static void |
215 | 0 | removeReceivedSON(MdnsdDriver *md, ServerOnNetworkRecord *entry) { |
216 | | /* This record came from the network. Removing it is a cache update and must |
217 | | * not emit a multicast goodbye for somebody else's service. Deregistering |
218 | | * from the server updates FindServersOnNetwork and notifies the application. */ |
219 | 0 | stopRecordDetailsQueryForEntry(md, entry); |
220 | 0 | removeSONEntry(md, entry, true); |
221 | 0 | } |
222 | | |
223 | | static void |
224 | 0 | removeLocalSON(MdnsdDriver *md, ServerOnNetworkRecord *entry) { |
225 | | /* The server notification already removed the locally-owned record from the |
226 | | * server's public discovery table. Only remove the driver's bookkeeping. */ |
227 | 0 | removeSONEntry(md, entry, false); |
228 | 0 | } |
229 | | |
230 | | static UA_Boolean |
231 | 0 | isRemoteReceivedSON(const ServerOnNetworkRecord *entry) { |
232 | 0 | return entry->origin == SERVER_ON_NETWORK_RECORD_REMOTE_RECEIVED; |
233 | 0 | } |
234 | | |
235 | | static UA_Boolean |
236 | 0 | isLocalOwnedSON(const ServerOnNetworkRecord *entry) { |
237 | 0 | return entry->origin == SERVER_ON_NETWORK_RECORD_LOCAL_OWNED; |
238 | 0 | } |
239 | | |
240 | | /****************************/ |
241 | | /* Process Received Records */ |
242 | | /****************************/ |
243 | | |
244 | | static UA_StatusCode |
245 | 0 | processTxt(ServerOnNetworkRecord *entry, const struct resource *r) { |
246 | | /* Parse the record */ |
247 | 0 | xht_t *x = txt2sd(r->rdata, r->rdlength); |
248 | 0 | if(!x) |
249 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
250 | | |
251 | | /* Process the path */ |
252 | 0 | char *path = (char *) xht_get(x, "path"); |
253 | 0 | size_t pathLen = path ? strlen(path) : 0; |
254 | 0 | if(pathLen > 1) { |
255 | 0 | if(!entry->srvSet) { |
256 | | /* TXT arrived before SRV. Cache path entry for assembly of the full |
257 | | * DiscoveryUrl */ |
258 | 0 | if(!entry->pathTmp) { |
259 | 0 | entry->pathTmp = (char*)UA_malloc(pathLen+1); |
260 | 0 | if(!entry->pathTmp) { |
261 | 0 | xht_free(x); |
262 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY;; |
263 | 0 | } |
264 | 0 | memcpy(entry->pathTmp, path, pathLen); |
265 | 0 | entry->pathTmp[pathLen] = '\0'; |
266 | 0 | } |
267 | 0 | } else { |
268 | | /* SRV already there and discovery URL set. Add path to discovery URL */ |
269 | 0 | UA_String_append(&entry->serverOnNetwork.discoveryUrl, UA_STRING(path)); |
270 | 0 | } |
271 | 0 | } |
272 | | |
273 | | /* Process the capabilities */ |
274 | 0 | char *caps = (char*)xht_get(x, "caps"); |
275 | 0 | if(caps && strlen(caps) > 0) { |
276 | | /* Count comma in caps */ |
277 | 0 | size_t capsCount = 1; |
278 | 0 | for(size_t i = 0; caps[i]; i++) { |
279 | 0 | if(caps[i] == ',') |
280 | 0 | capsCount++; |
281 | 0 | } |
282 | | |
283 | | /* Delete the old capabilities array */ |
284 | 0 | UA_Array_delete(entry->serverOnNetwork.serverCapabilities, |
285 | 0 | entry->serverOnNetwork.serverCapabilitiesSize, |
286 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
287 | | |
288 | | /* Allocate capabilities array */ |
289 | 0 | entry->serverOnNetwork.serverCapabilities = (UA_String *) |
290 | 0 | UA_Array_new(capsCount, &UA_TYPES[UA_TYPES_STRING]); |
291 | 0 | if(!entry->serverOnNetwork.serverCapabilities) { |
292 | 0 | entry->serverOnNetwork.serverCapabilitiesSize = 0; |
293 | 0 | xht_free(x); |
294 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
295 | 0 | } |
296 | 0 | entry->serverOnNetwork.serverCapabilitiesSize = capsCount; |
297 | | |
298 | | /* Add capabilities to the array */ |
299 | 0 | for(size_t i = 0; i < capsCount; i++) { |
300 | 0 | char *nextStr = strchr(caps, ','); |
301 | 0 | if(nextStr) |
302 | 0 | *nextStr = '\0'; |
303 | 0 | entry->serverOnNetwork.serverCapabilities[i] = |
304 | 0 | UA_STRING_ALLOC(caps); |
305 | 0 | if(nextStr) |
306 | 0 | caps = nextStr + 1; |
307 | 0 | } |
308 | 0 | } |
309 | | |
310 | 0 | xht_free(x); |
311 | 0 | entry->txtSet = true; |
312 | 0 | return UA_STATUSCODE_GOOD; |
313 | 0 | } |
314 | | |
315 | | /* Example: _opcua-tcp._tcp.[servername]. 86400 IN SRV 0 5 4840 [hostname]. */ |
316 | | static void |
317 | 0 | processSrv(ServerOnNetworkRecord *entry, const struct resource *r) { |
318 | | /* The specification Part 12 says: The hostname maps onto the SRV record |
319 | | * target field. If the hostname is an IP-address then it must be converted |
320 | | * to a domain name. If this cannot be done then LDS shall report an |
321 | | * error. */ |
322 | | |
323 | | /* Cut off the last dot from the hostname */ |
324 | 0 | UA_String hostname = {strlen(r->known.srv.name), (UA_Byte*)r->known.srv.name}; |
325 | 0 | if(hostname.length > 0 && hostname.data[hostname.length - 1] == '.') |
326 | 0 | hostname.length--; |
327 | | |
328 | | /* Prepare the DiscoveryUrl as opc.tcp://[servername]:[port][path] */ |
329 | 0 | UA_String_format(&entry->serverOnNetwork.discoveryUrl, |
330 | 0 | "opc.tcp://%S:%d", hostname, r->known.srv.port); |
331 | 0 | if(entry->pathTmp) { |
332 | 0 | UA_String_append(&entry->serverOnNetwork.discoveryUrl, |
333 | 0 | UA_STRING(entry->pathTmp)); |
334 | 0 | UA_free(entry->pathTmp); |
335 | 0 | entry->pathTmp = NULL; |
336 | 0 | } |
337 | |
|
338 | 0 | entry->srvSet = true; |
339 | 0 | } |
340 | | |
341 | | static int |
342 | 0 | multicastQueryAnswer(mdns_answer_t *a, void *arg) { |
343 | 0 | (void)a; |
344 | 0 | (void)arg; |
345 | 0 | return 0; |
346 | 0 | } |
347 | | |
348 | | static UA_Boolean |
349 | 0 | serviceInstanceToServerName(char *serviceInstance, UA_String *serverName) { |
350 | 0 | char *opcStr = strstr(serviceInstance, "." UA_MDNS_OPCUA_TCP_LOCAL); |
351 | 0 | if(!opcStr) |
352 | 0 | return false; |
353 | | |
354 | 0 | serverName->length = (size_t)(opcStr - serviceInstance); |
355 | 0 | serverName->data = (UA_Byte*)serviceInstance; |
356 | 0 | return serverName->length > 0; |
357 | 0 | } |
358 | | |
359 | | static void |
360 | | queryRecordDetails(MdnsdDriver *md, const char *serviceInstance, |
361 | 0 | UA_Boolean querySrv, UA_Boolean queryTxt) { |
362 | 0 | if(!md->queryDetails || !serviceInstance || !md->mdnsDaemon) |
363 | 0 | return; |
364 | | |
365 | 0 | if(querySrv) |
366 | 0 | mdnsd_query(md->mdnsDaemon, serviceInstance, QTYPE_SRV, |
367 | 0 | multicastQueryAnswer, md); |
368 | 0 | if(queryTxt) |
369 | 0 | mdnsd_query(md->mdnsDaemon, serviceInstance, QTYPE_TXT, |
370 | 0 | multicastQueryAnswer, md); |
371 | 0 | if(querySrv || queryTxt) |
372 | 0 | flushMulticastMessages(md); |
373 | 0 | } |
374 | | |
375 | | static void |
376 | 0 | stopRecordDetailsQuery(MdnsdDriver *md, const char *serviceInstance) { |
377 | 0 | if(!md->queryDetails || !serviceInstance || !md->mdnsDaemon) |
378 | 0 | return; |
379 | | |
380 | 0 | mdnsd_query(md->mdnsDaemon, serviceInstance, QTYPE_SRV, NULL, NULL); |
381 | 0 | mdnsd_query(md->mdnsDaemon, serviceInstance, QTYPE_TXT, NULL, NULL); |
382 | 0 | } |
383 | | |
384 | | static void |
385 | | stopRecordDetailsQueryForEntry(MdnsdDriver *md, |
386 | 0 | const ServerOnNetworkRecord *entry) { |
387 | 0 | const UA_String *serverName = &entry->serverOnNetwork.serverName; |
388 | 0 | const char suffix[] = "." UA_MDNS_OPCUA_TCP_LOCAL; |
389 | 0 | char serviceInstance[UA_MDNS_MAX_RECORD_NAME_LENGTH]; |
390 | 0 | if(serverName->length + sizeof(suffix) > sizeof(serviceInstance)) |
391 | 0 | return; |
392 | | |
393 | 0 | memcpy(serviceInstance, serverName->data, serverName->length); |
394 | 0 | memcpy(serviceInstance + serverName->length, suffix, sizeof(suffix)); |
395 | 0 | stopRecordDetailsQuery(md, serviceInstance); |
396 | 0 | } |
397 | | |
398 | | static void |
399 | 0 | queryPresence(MdnsdDriver *md) { |
400 | 0 | if(!md->queryPresence || !md->mdnsDaemon) |
401 | 0 | return; |
402 | | |
403 | 0 | mdnsd_query(md->mdnsDaemon, UA_MDNS_OPCUA_TCP_LOCAL, QTYPE_PTR, |
404 | 0 | multicastQueryAnswer, md); |
405 | 0 | flushMulticastMessages(md); |
406 | 0 | mdnsd_query(md->mdnsDaemon, UA_MDNS_OPCUA_TCP_LOCAL, QTYPE_PTR, |
407 | 0 | NULL, NULL); |
408 | 0 | } |
409 | | |
410 | | static void |
411 | 0 | MdnsdPresenceQueryCallback(UA_Server *server, void *drv) { |
412 | 0 | (void)server; |
413 | 0 | queryPresence((MdnsdDriver*)drv); |
414 | 0 | } |
415 | | |
416 | | /* Called by the mDNS library on every received record */ |
417 | | static void |
418 | 0 | processRecord(const struct resource *r, void *data) { |
419 | 0 | MdnsdDriver *md = (MdnsdDriver*)data; |
420 | | |
421 | | /* Are we even listening? */ |
422 | 0 | if(!md->listen) |
423 | 0 | return; |
424 | | |
425 | | /* We only need PTR, SRV and TXT records */ |
426 | | /* TODO: remove magic number */ |
427 | 0 | if((r->clazz != QCLASS_IN && r->clazz != QCLASS_IN + 32768) || |
428 | 0 | (r->type != QTYPE_PTR && r->type != QTYPE_SRV && r->type != QTYPE_TXT)) |
429 | 0 | return; |
430 | | |
431 | 0 | char *serviceInstance = r->name; |
432 | 0 | if(r->type == QTYPE_PTR) { |
433 | 0 | if(strcmp(r->name, UA_MDNS_OPCUA_TCP_LOCAL) != 0) |
434 | 0 | return; |
435 | 0 | serviceInstance = r->known.ptr.name; |
436 | 0 | if(!serviceInstance) |
437 | 0 | return; |
438 | 0 | } |
439 | | |
440 | | /* Only handle '._opcua-tcp._tcp.' records */ |
441 | 0 | UA_String serverName; |
442 | 0 | if(!serviceInstanceToServerName(serviceInstance, &serverName)) |
443 | 0 | return; |
444 | | |
445 | | /* Find an existing entry or create a new one */ |
446 | 0 | ServerOnNetworkRecord *entry = findSON(md, serverName); |
447 | 0 | if(!entry) { |
448 | 0 | if(r->ttl == 0) |
449 | 0 | return; |
450 | 0 | UA_ServerOnNetwork son; |
451 | 0 | UA_ServerOnNetwork_init(&son); |
452 | 0 | son.serverName = serverName; |
453 | 0 | UA_StatusCode res = addSON(md, &son, &entry); |
454 | 0 | if(res != UA_STATUSCODE_GOOD) |
455 | 0 | return; |
456 | 0 | entry->origin = SERVER_ON_NETWORK_RECORD_REMOTE_RECEIVED; |
457 | 0 | } else { |
458 | | /* Local entries are already owned by this driver. Ignore matching |
459 | | * network traffic so our own announcements are not mirrored back into |
460 | | * the local discovery table. */ |
461 | 0 | if(isLocalOwnedSON(entry)) |
462 | 0 | return; |
463 | 0 | } |
464 | | |
465 | | /* If TTL == 0, the entry is retracted. */ |
466 | 0 | if(r->ttl == 0) { |
467 | 0 | removeReceivedSON(md, entry); |
468 | 0 | return; |
469 | 0 | } |
470 | | |
471 | | /* Process the record. These can arrive in any order. */ |
472 | 0 | if(r->type == QTYPE_PTR) |
473 | 0 | queryRecordDetails(md, serviceInstance, !entry->srvSet, !entry->txtSet); |
474 | 0 | else if(r->type == QTYPE_TXT) |
475 | 0 | processTxt(entry, r); |
476 | 0 | else if(r->type == QTYPE_SRV) |
477 | 0 | processSrv(entry, r); |
478 | |
|
479 | 0 | if(r->type == QTYPE_TXT && !entry->srvSet) |
480 | 0 | queryRecordDetails(md, serviceInstance, true, false); |
481 | 0 | else if(r->type == QTYPE_SRV && !entry->txtSet) |
482 | 0 | queryRecordDetails(md, serviceInstance, false, true); |
483 | | |
484 | | /* Set TTL until we require the next received value */ |
485 | 0 | if(r->ttl > entry->ttl) |
486 | 0 | entry->ttl = r->ttl; |
487 | | |
488 | | /* Update nextAction. If no update is received until then the record is |
489 | | * deleted */ |
490 | 0 | UA_EventLoop *el = UA_Server_getConfig(md->mdns.drv.server)->eventLoop; |
491 | 0 | entry->nextAction = |
492 | 0 | el->dateTime_nowMonotonic(el) + (UA_DATETIME_SEC * entry->ttl); |
493 | | |
494 | | /* Received over mDNS (instead of locally created) */ |
495 | 0 | entry->origin = SERVER_ON_NETWORK_RECORD_REMOTE_RECEIVED; |
496 | | |
497 | | /* If the entry is complete, forward it to the server */ |
498 | 0 | if(entry->srvSet && entry->txtSet) { |
499 | 0 | stopRecordDetailsQuery(md, serviceInstance); |
500 | 0 | UA_Server_registerServerOnNetwork(md->mdns.drv.server, |
501 | 0 | &entry->serverOnNetwork, |
502 | 0 | UA_KEYVALUEMAP_NULL); |
503 | 0 | } |
504 | 0 | } |
505 | | |
506 | | /*******************/ |
507 | | /* Announce Record */ |
508 | | /*******************/ |
509 | | |
510 | | static void |
511 | 0 | flushMulticastMessages(MdnsdDriver *md) { |
512 | 0 | UA_ConnectionManager *cm = md->cm; |
513 | 0 | if(!cm || md->mdnsSendConnection == 0) |
514 | 0 | return; |
515 | | |
516 | 0 | struct message mm; |
517 | 0 | memset(&mm, 0, sizeof(struct message)); |
518 | |
|
519 | 0 | inet_addr_t to; |
520 | 0 | memset(&to, 0, sizeof(inet_addr_t)); |
521 | 0 | while(mdnsd_out(md->mdnsDaemon, &mm, &to) > 0) { |
522 | 0 | int len = message_packet_len(&mm); |
523 | 0 | char* buf = (char*)message_packet(&mm); |
524 | 0 | if(len <= 0) |
525 | 0 | continue; |
526 | 0 | UA_ByteString sendBuf = UA_BYTESTRING_NULL; |
527 | 0 | UA_StatusCode rv = cm->allocNetworkBuffer(cm, md->mdnsSendConnection, |
528 | 0 | &sendBuf, (size_t)len); |
529 | 0 | if(rv != UA_STATUSCODE_GOOD) |
530 | 0 | continue; |
531 | 0 | memcpy(sendBuf.data, buf, sendBuf.length); |
532 | 0 | cm->sendWithConnection(cm, md->mdnsSendConnection, |
533 | 0 | &UA_KEYVALUEMAP_NULL, &sendBuf); |
534 | 0 | } |
535 | 0 | } |
536 | | |
537 | | static UA_UInt32 |
538 | 0 | announceTTL(const MdnsdDriver *md) { |
539 | 0 | return (md->announceTTL > 0) ? md->announceTTL : 600u; |
540 | 0 | } |
541 | | |
542 | | static mdns_record_t * |
543 | | findRecord(mdns_daemon_t *mdnsDaemon, unsigned short type, |
544 | 0 | const char *host, const char *rdname) { |
545 | 0 | for(mdns_record_t *r = mdnsd_get_published(mdnsDaemon, host); |
546 | 0 | r != NULL; r = mdnsd_record_next(r)) { |
547 | 0 | const mdns_answer_t *data = mdnsd_record_data(r); |
548 | 0 | if(data->type == type && strcmp(data->rdname, rdname) == 0) |
549 | 0 | return r; |
550 | 0 | } |
551 | 0 | return NULL; |
552 | 0 | } |
553 | | |
554 | | /* PTR record: _opcua-tcp._tcp.local. PTR [servername]-[hostname]._opcua-tcp._tcp.local. */ |
555 | | static UA_StatusCode |
556 | | announcePTR(MdnsdDriver *md, const char *serviceDomain, |
557 | 0 | const UA_ServerOnNetwork *son, UA_UInt32 ttl) { |
558 | 0 | mdns_record_t *r = findRecord(md->mdnsDaemon, QTYPE_PTR, |
559 | 0 | "_opcua-tcp._tcp.local.", serviceDomain); |
560 | 0 | if(!r) |
561 | 0 | r = mdnsd_shared(md->mdnsDaemon, "_opcua-tcp._tcp.local.", |
562 | 0 | QTYPE_PTR, ttl); |
563 | 0 | if(!r) |
564 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
565 | 0 | mdnsd_set_host(md->mdnsDaemon, r, serviceDomain); |
566 | 0 | return UA_STATUSCODE_GOOD; |
567 | 0 | } |
568 | | |
569 | | /* SRV record: [servername]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname].local. |
570 | | * The first 63 characters of the hostname (or less) and ".local." */ |
571 | | static UA_StatusCode |
572 | | announceSVR(MdnsdDriver *md, UA_String hostname, UA_UInt16 port, |
573 | | const char *serviceDomain, const UA_ServerOnNetwork *son, |
574 | 0 | UA_UInt32 ttl) { |
575 | 0 | size_t maxHostnameLen = UA_MIN(hostname.length, 63); |
576 | 0 | char localDomain[71]; |
577 | 0 | memcpy(localDomain, hostname.data, maxHostnameLen); |
578 | 0 | strcpy(localDomain + maxHostnameLen, ".local."); |
579 | 0 | mdns_record_t *r = mdnsd_find(md->mdnsDaemon, serviceDomain, QTYPE_SRV); |
580 | 0 | if(!r) |
581 | 0 | r = mdnsd_shared(md->mdnsDaemon, serviceDomain, QTYPE_SRV, ttl); |
582 | |
|
583 | 0 | if(!r) |
584 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
585 | 0 | mdnsd_set_srv(md->mdnsDaemon, r, 0, 0, port, localDomain); |
586 | 0 | return UA_STATUSCODE_GOOD; |
587 | 0 | } |
588 | | |
589 | | /* TXT record: [servername]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,... */ |
590 | | static UA_StatusCode |
591 | | announceTXT(MdnsdDriver *md, UA_String path, const char *serviceDomain, |
592 | 0 | const UA_ServerOnNetwork *son, UA_UInt32 ttl) { |
593 | | /* Create the table for the TXT payload */ |
594 | 0 | xht_t *h = xht_new(11); |
595 | 0 | if(!h) |
596 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
597 | | |
598 | | /* Buffers for the path and caps values. They are referenced by xht and |
599 | | * have to live until sd2txt has copied their contents out (and ideally |
600 | | * stay alive in the function frame to keep ASan happy). */ |
601 | 0 | char pathBuf[256]; |
602 | 0 | char caps[256]; |
603 | | |
604 | | /* Add the path */ |
605 | 0 | if(path.length >= 254) { |
606 | 0 | xht_free(h); |
607 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
608 | 0 | } |
609 | 0 | if(path.length == 0) { |
610 | 0 | xht_set(h, "path", "/"); |
611 | 0 | } else { |
612 | 0 | size_t pathLen = path.length; |
613 | 0 | if(path.data[0] != '/') { |
614 | | /* Prepend missing slash */ |
615 | 0 | pathBuf[0] = '/'; |
616 | 0 | memcpy(&pathBuf[1], path.data, path.length); |
617 | 0 | pathLen++; |
618 | 0 | } else { |
619 | 0 | memcpy(pathBuf, path.data, path.length); |
620 | 0 | } |
621 | |
|
622 | 0 | pathBuf[pathLen] = '\0'; |
623 | 0 | xht_set(h, "path", pathBuf); |
624 | 0 | } |
625 | | |
626 | | /* Calculate string length for the capabilities */ |
627 | 0 | size_t capsLen = 0; |
628 | 0 | for(size_t i = 0; i < son->serverCapabilitiesSize; i++) { |
629 | 0 | capsLen += son->serverCapabilities[i].length + 1; |
630 | 0 | } |
631 | 0 | if(capsLen >= 256) { |
632 | 0 | xht_free(h); |
633 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
634 | 0 | } |
635 | | |
636 | | /* Add the capabilities */ |
637 | 0 | if(son->serverCapabilitiesSize > 0) { |
638 | 0 | size_t idx = 0; |
639 | 0 | for(size_t i = 0; i < son->serverCapabilitiesSize; i++) { |
640 | 0 | if(i > 0) |
641 | 0 | caps[idx++] = ','; |
642 | 0 | memcpy(caps + idx, son->serverCapabilities[i].data, |
643 | 0 | son->serverCapabilities[i].length); |
644 | 0 | idx += son->serverCapabilities[i].length; |
645 | 0 | } |
646 | 0 | caps[idx] = '\0'; |
647 | 0 | xht_set(h, "caps", caps); |
648 | 0 | } else { |
649 | 0 | xht_set(h, "caps", "NA"); /* NA - Not Available */ |
650 | 0 | } |
651 | | |
652 | | /* Encode the packet and set in the MDNSD daemon */ |
653 | 0 | int len; |
654 | 0 | unsigned char *packet = sd2txt(h, &len); |
655 | 0 | xht_free(h); |
656 | 0 | if(!packet) |
657 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
658 | | |
659 | | /* Create the record and set the payload */ |
660 | 0 | mdns_record_t *r = mdnsd_find(md->mdnsDaemon, serviceDomain, QTYPE_TXT); |
661 | 0 | if(!r) |
662 | 0 | r = mdnsd_shared(md->mdnsDaemon, serviceDomain, QTYPE_TXT, ttl); |
663 | 0 | if(!r) { |
664 | 0 | UA_free(packet); |
665 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
666 | 0 | } |
667 | 0 | mdnsd_set_raw(md->mdnsDaemon, r, (char*)packet, (unsigned short)len); |
668 | 0 | UA_free(packet); |
669 | |
|
670 | 0 | return UA_STATUSCODE_GOOD; |
671 | 0 | } |
672 | | |
673 | | /* Create a mDNS Record for the given server info and adds it to the mDNS output |
674 | | * queue. |
675 | | * |
676 | | * We assume that the hostname is not an IP address, but a valid domain name. It |
677 | | * is required by the OPC UA spec (see Part 12, DiscoveryURL to DNS SRV mapping) |
678 | | * to always use the hostname instead of the IP address. */ |
679 | | static UA_StatusCode |
680 | 0 | announceRecord(MdnsdDriver *md, const ServerOnNetworkRecord *entry) { |
681 | 0 | if(!md->announce) |
682 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
683 | | |
684 | 0 | const UA_ServerOnNetwork *son = &entry->serverOnNetwork; |
685 | | |
686 | | /* Use a limit for the ServerName to make sure it fits into 63 chars |
687 | | * (limited by DNS spec) */ |
688 | 0 | if(son->serverName.length > 63) { |
689 | 0 | UA_LOG_WARNING(md->logging, UA_LOGCATEGORY_DISCOVERY, |
690 | 0 | "mDNS: ServerName of %S exceeds maximum of 63 chars", |
691 | 0 | son->serverName); |
692 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
693 | 0 | } |
694 | | |
695 | | /* Extract hostname, port and path form the DiscoveryUrl */ |
696 | 0 | UA_String hostname = UA_STRING_NULL; |
697 | 0 | UA_String path = UA_STRING_NULL; |
698 | 0 | UA_UInt16 port = 4840; |
699 | 0 | UA_StatusCode res = |
700 | 0 | UA_parseEndpointUrl(&son->discoveryUrl, &hostname, &port, &path); |
701 | 0 | if(res != UA_STATUSCODE_GOOD) |
702 | 0 | return res; |
703 | | |
704 | | /* Create the service name [servername]._opcua-tcp._tcp.local. */ |
705 | 0 | char serviceDomainBuf[63+25]; |
706 | 0 | UA_String serviceDomain = {63+24, (UA_Byte*)serviceDomainBuf}; |
707 | 0 | UA_String_format(&serviceDomain, "%S._opcua-tcp._tcp.local.", son->serverName); |
708 | 0 | serviceDomain.data[serviceDomain.length] = '\0'; |
709 | |
|
710 | 0 | UA_LOG_INFO(md->logging, UA_LOGCATEGORY_DISCOVERY, |
711 | 0 | "mDNS: Announcing record for %S", serviceDomain); |
712 | | |
713 | | /* Announce the different mDNS records */ |
714 | 0 | res |= announcePTR(md, serviceDomainBuf, son, entry->ttl); |
715 | 0 | res |= announceSVR(md, hostname, port, serviceDomainBuf, son, entry->ttl); |
716 | 0 | res |= announceTXT(md, path, serviceDomainBuf, son, entry->ttl); |
717 | 0 | if(res != UA_STATUSCODE_GOOD) { |
718 | 0 | UA_LOG_INFO(md->logging, UA_LOGCATEGORY_DISCOVERY, |
719 | 0 | "mDNS: Could not create record for %S with StatusCode %s", |
720 | 0 | serviceDomain, UA_StatusCode_name(res)); |
721 | 0 | } |
722 | | |
723 | | /* Send out the queued messages */ |
724 | 0 | flushMulticastMessages(md); |
725 | |
|
726 | 0 | return UA_STATUSCODE_GOOD; |
727 | 0 | } |
728 | | |
729 | | /******************/ |
730 | | /* Retract Record */ |
731 | | /******************/ |
732 | | |
733 | | static UA_StatusCode |
734 | 0 | retractRecord(MdnsdDriver *md, const UA_ServerOnNetwork *son) { |
735 | 0 | if(!md->announce) |
736 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
737 | | |
738 | | /* Use a limit for the ServerName to make sure it fits into 63 chars |
739 | | * (limited by DNS spec) */ |
740 | 0 | if(son->serverName.length > 63) { |
741 | 0 | UA_LOG_WARNING(md->logging, UA_LOGCATEGORY_DISCOVERY, |
742 | 0 | "mDNS: ServerName of %S exceeds maximum of 63 chars", |
743 | 0 | son->serverName); |
744 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
745 | 0 | } |
746 | | |
747 | | /* Extract hostname, port and path form the DiscoveryUrl */ |
748 | 0 | UA_String hostname = UA_STRING_NULL; |
749 | 0 | UA_String path = UA_STRING_NULL; |
750 | 0 | UA_UInt16 port = 4840; |
751 | 0 | UA_StatusCode res = |
752 | 0 | UA_parseEndpointUrl(&son->discoveryUrl, &hostname, &port, &path); |
753 | 0 | if(res != UA_STATUSCODE_GOOD) |
754 | 0 | return res; |
755 | | |
756 | | /* Create the service name [servername]._opcua-tcp._tcp.local. */ |
757 | 0 | char serviceDomainBuf[63+25]; |
758 | 0 | UA_String serviceDomain = {63+24, (UA_Byte*)serviceDomainBuf}; |
759 | 0 | UA_String_format(&serviceDomain, "%S._opcua-tcp._tcp.local.", son->serverName); |
760 | 0 | serviceDomain.data[serviceDomain.length] = '\0'; |
761 | |
|
762 | 0 | UA_LOG_INFO(md->logging, UA_LOGCATEGORY_DISCOVERY, |
763 | 0 | "mDNS: Retracting record for %S", serviceDomain); |
764 | | |
765 | | /* _opcua-tcp._tcp.local. PTR [servername]._opcua-tcp._tcp.local. */ |
766 | 0 | mdns_record_t *r = |
767 | 0 | findRecord(md->mdnsDaemon, QTYPE_PTR, "_opcua-tcp._tcp.local.", serviceDomainBuf); |
768 | 0 | if(r) |
769 | 0 | mdnsd_done(md->mdnsDaemon, r); |
770 | | |
771 | | /* SRV record: [servername]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port hostname.local. |
772 | | * TXT record: [servername]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,... */ |
773 | 0 | mdns_record_t *r2 = mdnsd_get_published(md->mdnsDaemon, serviceDomainBuf); |
774 | 0 | while(r2) { |
775 | 0 | const mdns_answer_t *data = mdnsd_record_data(r2); |
776 | 0 | mdns_record_t *next = mdnsd_record_next(r2); |
777 | 0 | if(data->type == QTYPE_TXT || |
778 | 0 | (data->type == QTYPE_SRV && data->srv.port == port)) { |
779 | 0 | mdnsd_done(md->mdnsDaemon, r2); |
780 | 0 | } |
781 | 0 | r2 = next; |
782 | 0 | } |
783 | |
|
784 | 0 | return UA_STATUSCODE_GOOD; |
785 | 0 | } |
786 | | |
787 | | /**************************/ |
788 | | /* Manual Address Records */ |
789 | | /**************************/ |
790 | | |
791 | | static UA_StatusCode |
792 | | MdnsdDriver_addARecord(UA_MdnsDriver *drv, UA_String hostname, |
793 | 0 | UA_String address, UA_UInt32 ttl) { |
794 | 0 | MdnsdDriver *md = (MdnsdDriver*)drv; |
795 | 0 | if(!md->mdnsDaemon) |
796 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
797 | | |
798 | 0 | char host[UA_MDNS_MAX_RECORD_NAME_LENGTH]; |
799 | 0 | if(hostname.length >= sizeof(host)) |
800 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
801 | 0 | memcpy(host, hostname.data, hostname.length); |
802 | 0 | host[hostname.length] = '\0'; |
803 | |
|
804 | 0 | char addr[INET6_ADDRSTRLEN]; |
805 | 0 | if(address.length >= sizeof(addr)) |
806 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
807 | 0 | memcpy(addr, address.data, address.length); |
808 | 0 | addr[address.length] = '\0'; |
809 | |
|
810 | 0 | struct in_addr ip; |
811 | 0 | int rv = inet_pton(AF_INET, addr, &ip); |
812 | 0 | if(rv != 1) |
813 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
814 | | |
815 | 0 | mdns_record_t *r = mdnsd_shared(md->mdnsDaemon, host, QTYPE_A, ttl); |
816 | 0 | if(!r) |
817 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
818 | 0 | mdnsd_set_ip(md->mdnsDaemon, r, ip); |
819 | |
|
820 | 0 | flushMulticastMessages(md); |
821 | 0 | return UA_STATUSCODE_GOOD; |
822 | 0 | } |
823 | | |
824 | | static UA_StatusCode |
825 | | MdnsdDriver_addAAAARecord(UA_MdnsDriver *drv, UA_String hostname, |
826 | 0 | UA_String address, UA_UInt32 ttl) { |
827 | 0 | MdnsdDriver *md = (MdnsdDriver*)drv; |
828 | 0 | if(!md->mdnsDaemon) |
829 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
830 | | |
831 | 0 | char host[UA_MDNS_MAX_RECORD_NAME_LENGTH]; |
832 | 0 | if(hostname.length >= sizeof(host)) |
833 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
834 | 0 | memcpy(host, hostname.data, hostname.length); |
835 | 0 | host[hostname.length] = '\0'; |
836 | |
|
837 | 0 | char addr[INET6_ADDRSTRLEN]; |
838 | 0 | if(address.length >= sizeof(addr)) |
839 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
840 | 0 | memcpy(addr, address.data, address.length); |
841 | 0 | addr[address.length] = '\0'; |
842 | |
|
843 | 0 | struct in6_addr ip6; |
844 | 0 | int rv = inet_pton(AF_INET6, addr, &ip6); |
845 | 0 | if(rv != 1) |
846 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
847 | | |
848 | 0 | mdns_record_t *r = mdnsd_shared(md->mdnsDaemon, host, QTYPE_AAAA, ttl); |
849 | 0 | if(!r) |
850 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
851 | 0 | mdnsd_set_ipv6(md->mdnsDaemon, r, ip6); |
852 | |
|
853 | 0 | flushMulticastMessages(md); |
854 | 0 | return UA_STATUSCODE_GOOD; |
855 | 0 | } |
856 | | |
857 | | static UA_StatusCode |
858 | | MdnsdDriver_removeAddressRecord(UA_MdnsDriver *drv, UA_String hostname, |
859 | 0 | UA_String address, unsigned short type) { |
860 | 0 | MdnsdDriver *md = (MdnsdDriver*)drv; |
861 | 0 | if(!md->mdnsDaemon) |
862 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
863 | | |
864 | 0 | char host[UA_MDNS_MAX_RECORD_NAME_LENGTH]; |
865 | 0 | if(hostname.length >= sizeof(host)) |
866 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
867 | 0 | memcpy(host, hostname.data, hostname.length); |
868 | 0 | host[hostname.length] = '\0'; |
869 | |
|
870 | 0 | char addr[INET6_ADDRSTRLEN]; |
871 | 0 | if(address.length >= sizeof(addr)) |
872 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
873 | 0 | memcpy(addr, address.data, address.length); |
874 | 0 | addr[address.length] = '\0'; |
875 | |
|
876 | 0 | struct in_addr ip; |
877 | 0 | struct in6_addr ip6; |
878 | 0 | int rv = (type == QTYPE_A) ? inet_pton(AF_INET, addr, &ip) : |
879 | 0 | inet_pton(AF_INET6, addr, &ip6); |
880 | 0 | if(rv != 1) |
881 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
882 | | |
883 | 0 | mdns_record_t *r = mdnsd_get_published(md->mdnsDaemon, host); |
884 | 0 | while(r) { |
885 | 0 | const mdns_answer_t *data = mdnsd_record_data(r); |
886 | 0 | mdns_record_t *next = mdnsd_record_next(r); |
887 | 0 | if(data->type == type) { |
888 | 0 | UA_Boolean match = (type == QTYPE_A) ? |
889 | 0 | (memcmp(&data->ip, &ip, sizeof(ip)) == 0) : |
890 | 0 | (memcmp(&data->ip6, &ip6, sizeof(ip6)) == 0); |
891 | 0 | if(match) |
892 | 0 | mdnsd_done(md->mdnsDaemon, r); |
893 | 0 | } |
894 | 0 | r = next; |
895 | 0 | } |
896 | |
|
897 | 0 | flushMulticastMessages(md); |
898 | 0 | return UA_STATUSCODE_GOOD; |
899 | 0 | } |
900 | | |
901 | | static UA_StatusCode |
902 | | MdnsdDriver_removeARecord(UA_MdnsDriver *drv, UA_String hostname, |
903 | 0 | UA_String address) { |
904 | 0 | return MdnsdDriver_removeAddressRecord(drv, hostname, address, QTYPE_A); |
905 | 0 | } |
906 | | |
907 | | static UA_StatusCode |
908 | | MdnsdDriver_removeAAAARecord(UA_MdnsDriver *drv, UA_String hostname, |
909 | 0 | UA_String address) { |
910 | 0 | return MdnsdDriver_removeAddressRecord(drv, hostname, address, QTYPE_AAAA); |
911 | 0 | } |
912 | | |
913 | | static void |
914 | | addConnection(MdnsdDriver *md, uintptr_t connectionId, |
915 | 0 | UA_Boolean recv) { |
916 | 0 | if(!recv) { |
917 | 0 | md->mdnsSendConnection = connectionId; |
918 | 0 | return; |
919 | 0 | } |
920 | 0 | size_t freeIdx = UA_MAXMDNSRECVSOCKETS; |
921 | 0 | for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { |
922 | 0 | uintptr_t recvConn = md->mdnsRecvConnections[i]; |
923 | 0 | if(recvConn == connectionId) |
924 | 0 | return; |
925 | 0 | if(recvConn == 0 && freeIdx == UA_MAXMDNSRECVSOCKETS) |
926 | 0 | freeIdx = i; |
927 | 0 | } |
928 | | |
929 | 0 | if(freeIdx == UA_MAXMDNSRECVSOCKETS) |
930 | 0 | return; |
931 | 0 | md->mdnsRecvConnections[freeIdx] = connectionId; |
932 | 0 | md->mdnsRecvConnectionsSize++; |
933 | 0 | } |
934 | | |
935 | | static void |
936 | 0 | removeConnection(MdnsdDriver *md, uintptr_t connectionId) { |
937 | 0 | if(md->mdnsSendConnection == connectionId) { |
938 | 0 | md->mdnsSendConnection = 0; |
939 | 0 | return; |
940 | 0 | } |
941 | 0 | for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { |
942 | 0 | if(md->mdnsRecvConnections[i] != connectionId) |
943 | 0 | continue; |
944 | 0 | md->mdnsRecvConnections[i] = 0; |
945 | 0 | md->mdnsRecvConnectionsSize--; |
946 | 0 | break; |
947 | 0 | } |
948 | 0 | } |
949 | | |
950 | | static UA_Boolean |
951 | 0 | allConnectionsClosed(MdnsdDriver *md) { |
952 | 0 | if(md->mdnsSendConnection != 0) |
953 | 0 | return false; |
954 | 0 | for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { |
955 | 0 | if(md->mdnsRecvConnections[i] != 0) |
956 | 0 | return false; |
957 | 0 | } |
958 | 0 | return true; |
959 | 0 | } |
960 | | |
961 | | static void |
962 | | MulticastDiscoveryCallback(UA_ConnectionManager *cm, uintptr_t connectionId, |
963 | | void *_, void **connectionContext, |
964 | | UA_ConnectionState state, const UA_KeyValueMap *params, |
965 | | UA_ByteString msg, UA_Boolean recv); |
966 | | |
967 | | static void |
968 | | MulticastDiscoveryRecvCallback(UA_ConnectionManager *cm, uintptr_t connectionId, |
969 | | void *application, void **connectionContext, |
970 | | UA_ConnectionState state, const UA_KeyValueMap *params, |
971 | 0 | UA_ByteString msg) { |
972 | 0 | MulticastDiscoveryCallback(cm, connectionId, application, connectionContext, |
973 | 0 | state, params, msg, true); |
974 | 0 | } |
975 | | |
976 | | static void |
977 | | MulticastDiscoverySendCallback(UA_ConnectionManager *cm, uintptr_t connectionId, |
978 | | void *application, void **connectionContext, |
979 | | UA_ConnectionState state, const UA_KeyValueMap *params, |
980 | 0 | UA_ByteString msg) { |
981 | 0 | MulticastDiscoveryCallback(cm, connectionId, application, connectionContext, |
982 | 0 | state, params, msg, false); |
983 | 0 | } |
984 | | |
985 | | /* Create multicast 224.0.0.251:5353 socket */ |
986 | | static void |
987 | 0 | createMulticastSocket(MdnsdDriver *md) { |
988 | 0 | UA_Server *server = md->mdns.drv.server; |
989 | 0 | UA_ServerConfig *config = UA_Server_getConfig(server); |
990 | | |
991 | | /* Find the UDP connection manager */ |
992 | 0 | if(!md->cm) { |
993 | 0 | UA_String udpString = UA_STRING("udp"); |
994 | 0 | UA_EventSource *es = config->eventLoop->eventSources; |
995 | 0 | for(; es != NULL; es = es->next) { |
996 | | /* Is this a usable connection manager? */ |
997 | 0 | if(es->eventSourceType != UA_EVENTSOURCETYPE_CONNECTIONMANAGER) |
998 | 0 | continue; |
999 | 0 | UA_ConnectionManager *cm = (UA_ConnectionManager*)es; |
1000 | 0 | if(UA_String_equal(&udpString, &cm->protocol)) { |
1001 | 0 | md->cm = cm; |
1002 | 0 | break; |
1003 | 0 | } |
1004 | 0 | } |
1005 | 0 | } |
1006 | |
|
1007 | 0 | if(!md->cm) { |
1008 | 0 | UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY, |
1009 | 0 | "mDNS: No UDP ConnectionManager found"); |
1010 | 0 | return; |
1011 | 0 | } |
1012 | | |
1013 | | /* Set up the parameters */ |
1014 | 0 | UA_KeyValuePair params[6]; |
1015 | 0 | size_t paramsSize = 5; |
1016 | |
|
1017 | 0 | UA_UInt16 port = 5353; |
1018 | 0 | UA_String address = UA_STRING("224.0.0.251"); |
1019 | 0 | UA_UInt32 ttl = 255; |
1020 | 0 | UA_Boolean reuse = true; |
1021 | 0 | UA_Boolean listen = true; |
1022 | |
|
1023 | 0 | params[0].key = UA_QUALIFIEDNAME(0, "port"); |
1024 | 0 | UA_Variant_setScalar(¶ms[0].value, &port, &UA_TYPES[UA_TYPES_UINT16]); |
1025 | 0 | params[1].key = UA_QUALIFIEDNAME(0, "address"); |
1026 | 0 | UA_Variant_setScalar(¶ms[1].value, &address, &UA_TYPES[UA_TYPES_STRING]); |
1027 | 0 | params[2].key = UA_QUALIFIEDNAME(0, "listen"); |
1028 | 0 | UA_Variant_setScalar(¶ms[2].value, &listen, &UA_TYPES[UA_TYPES_BOOLEAN]); |
1029 | 0 | params[3].key = UA_QUALIFIEDNAME(0, "reuse"); |
1030 | 0 | UA_Variant_setScalar(¶ms[3].value, &reuse, &UA_TYPES[UA_TYPES_BOOLEAN]); |
1031 | 0 | params[4].key = UA_QUALIFIEDNAME(0, "ttl"); |
1032 | 0 | UA_Variant_setScalar(¶ms[4].value, &ttl, &UA_TYPES[UA_TYPES_UINT32]); |
1033 | 0 | if(md->interfaceName.length > 0) { |
1034 | 0 | params[5].key = UA_QUALIFIEDNAME(0, "interface"); |
1035 | 0 | UA_Variant_setScalar(¶ms[5].value, &md->interfaceName, |
1036 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
1037 | 0 | paramsSize++; |
1038 | 0 | } |
1039 | | |
1040 | | /* Open the listen connection */ |
1041 | 0 | UA_KeyValueMap kvm = {paramsSize, params}; |
1042 | 0 | UA_StatusCode res = UA_STATUSCODE_GOOD; |
1043 | |
|
1044 | 0 | if(md->listen && md->mdnsRecvConnectionsSize == 0) { |
1045 | 0 | res = md->cm->openConnection(md->cm, &kvm, md->mdns.drv.server, md, |
1046 | 0 | MulticastDiscoveryRecvCallback); |
1047 | 0 | if(res != UA_STATUSCODE_GOOD) |
1048 | 0 | UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY, |
1049 | 0 | "mDNS: Could not create the UDP multicast listen connection"); |
1050 | 0 | } |
1051 | | |
1052 | | /* Open the send connection */ |
1053 | 0 | if((md->announce || md->queryPresence || md->queryDetails) && |
1054 | 0 | md->mdnsSendConnection == 0) { |
1055 | 0 | res = md->cm->openConnection(md->cm, &kvm, md->mdns.drv.server, md, |
1056 | 0 | MulticastDiscoverySendCallback); |
1057 | 0 | if(res != UA_STATUSCODE_GOOD) |
1058 | 0 | UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY, |
1059 | 0 | "mDNS: Could not create the UDP multicast send connection"); |
1060 | 0 | } |
1061 | 0 | } |
1062 | | |
1063 | | static void |
1064 | | MulticastDiscoveryCallback(UA_ConnectionManager *cm, uintptr_t connectionId, |
1065 | | void *_, void **connectionContext, |
1066 | | UA_ConnectionState state, const UA_KeyValueMap *params, |
1067 | 0 | UA_ByteString msg, UA_Boolean recv) { |
1068 | 0 | MdnsdDriver *md = *(MdnsdDriver**)connectionContext; |
1069 | 0 | if(!md) |
1070 | 0 | return; |
1071 | | |
1072 | 0 | if(state == UA_CONNECTIONSTATE_CLOSING) { |
1073 | | /* Remove closed connection */ |
1074 | 0 | removeConnection(md, connectionId); |
1075 | 0 | UA_Boolean allClosed = allConnectionsClosed(md); |
1076 | |
|
1077 | 0 | if(md->mdns.drv.state == UA_LIFECYCLESTATE_STOPPING) { |
1078 | 0 | if(allClosed) |
1079 | 0 | md->mdns.drv.state = UA_LIFECYCLESTATE_STOPPED; |
1080 | 0 | } else if(md->mdns.drv.state == UA_LIFECYCLESTATE_STARTED) { |
1081 | | /* Restart mdns sockets if not shutting down */ |
1082 | 0 | createMulticastSocket(md); |
1083 | 0 | if(md->mdnsSendConnection == 0) |
1084 | 0 | UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY, |
1085 | 0 | "mDNS: Could not create UDP multicast socket"); |
1086 | 0 | } |
1087 | 0 | return; |
1088 | 0 | } |
1089 | | |
1090 | 0 | addConnection(md, connectionId, recv); |
1091 | | |
1092 | | /* Expect packet between 0 and 512 bytes */ |
1093 | 0 | if(msg.length == 0) |
1094 | 0 | return; |
1095 | 0 | if(msg.length >= 512) |
1096 | 0 | return; |
1097 | | |
1098 | | /* Prepare the sockaddrinfo */ |
1099 | 0 | const UA_UInt16 *port = (const UA_UInt16*) |
1100 | 0 | UA_KeyValueMap_getScalar(params, UA_QUALIFIEDNAME(0, "remote-port"), |
1101 | 0 | &UA_TYPES[UA_TYPES_UINT16]); |
1102 | 0 | const UA_String *address = (const UA_String*) |
1103 | 0 | UA_KeyValueMap_getScalar(params, UA_QUALIFIEDNAME(0, "remote-address"), |
1104 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
1105 | 0 | if(!port || !address) |
1106 | 0 | return; |
1107 | | |
1108 | 0 | char portStr[16]; |
1109 | 0 | snprintf(portStr, sizeof(portStr), "%u", (unsigned)*port); |
1110 | |
|
1111 | 0 | struct addrinfo *infoptr; |
1112 | 0 | int res = getaddrinfo((const char*)address->data, portStr, NULL, &infoptr); |
1113 | 0 | if(res != 0) |
1114 | 0 | return; |
1115 | | |
1116 | | /* Zero-terminated buffer */ |
1117 | 0 | unsigned char buf[512]; |
1118 | 0 | memcpy(buf, msg.data, msg.length); |
1119 | 0 | buf[msg.length] = 0; |
1120 | | |
1121 | | /* Parse and process the message */ |
1122 | 0 | struct message mm; |
1123 | 0 | memset(&mm, 0, sizeof(struct message)); |
1124 | 0 | int rr = message_parse(&mm, buf); |
1125 | 0 | if(rr == 0) { |
1126 | 0 | inet_addr_t from; |
1127 | 0 | memset(&from, 0, sizeof(inet_addr_t)); |
1128 | 0 | memcpy(&from, infoptr->ai_addr, infoptr->ai_addrlen); |
1129 | 0 | mdnsd_in(md->mdnsDaemon, &mm, &from); |
1130 | 0 | } |
1131 | 0 | freeaddrinfo(infoptr); |
1132 | 0 | } |
1133 | | |
1134 | | /* Loop over all SON and perform required actions */ |
1135 | | static void |
1136 | 0 | MdnsdHouseKeeping(UA_Server *server, void *drv) { |
1137 | 0 | (void)server; |
1138 | 0 | MdnsdDriver *md = (MdnsdDriver*)drv; |
1139 | |
|
1140 | 0 | UA_EventLoop *el = UA_Server_getConfig(md->mdns.drv.server)->eventLoop; |
1141 | 0 | UA_DateTime now = el->dateTime_nowMonotonic(el); |
1142 | |
|
1143 | 0 | ServerOnNetworkRecord *son, *son_tmp; |
1144 | 0 | LIST_FOREACH_SAFE(son, &md->serverList, listPointers, son_tmp) { |
1145 | | /* Remove if stale */ |
1146 | 0 | if(son->nextAction > now) |
1147 | 0 | continue; |
1148 | 0 | if(isRemoteReceivedSON(son)) { |
1149 | 0 | removeReceivedSON(md, son); |
1150 | 0 | continue; |
1151 | 0 | } |
1152 | | /* Announce the record again. |
1153 | | * Update nextAction to a timestamp before the TTL runs out. */ |
1154 | 0 | announceRecord(md, son); |
1155 | 0 | son->nextAction = now + ((son->ttl * 9 * UA_DATETIME_SEC) / 10); |
1156 | 0 | } |
1157 | | |
1158 | | /* Send out queued messages */ |
1159 | 0 | flushMulticastMessages(md); |
1160 | 0 | } |
1161 | | |
1162 | | /* Notifications from the server to the driver */ |
1163 | | static void |
1164 | | MdnsdDriverNotificationCallback(UA_Driver *drv, |
1165 | | UA_ApplicationNotificationType type, |
1166 | 0 | const UA_KeyValueMap payload) { |
1167 | 0 | if(type != UA_APPLICATIONNOTIFICATIONTYPE_DISCOVERY_SERVERONNETWORK) |
1168 | 0 | return; |
1169 | | |
1170 | 0 | if(drv->state != UA_LIFECYCLESTATE_STARTED) |
1171 | 0 | return; |
1172 | | |
1173 | 0 | MdnsdDriver *md = (MdnsdDriver*)drv; |
1174 | 0 | UA_StatusCode res = UA_STATUSCODE_GOOD; |
1175 | | |
1176 | | /* Extract the notification parameters */ |
1177 | 0 | const UA_ServerOnNetwork *son = (const UA_ServerOnNetwork*) |
1178 | 0 | UA_KeyValueMap_getScalar(&payload, UA_QUALIFIEDNAME(0, "server-on-network"), |
1179 | 0 | &UA_TYPES[UA_TYPES_SERVERONNETWORK]); |
1180 | 0 | if(!son) |
1181 | 0 | return; |
1182 | | |
1183 | 0 | const UA_UInt32 *_ttl = (const UA_UInt32*) |
1184 | 0 | UA_KeyValueMap_getScalar(&payload, UA_QUALIFIEDNAME(0, "ttl"), |
1185 | 0 | &UA_TYPES[UA_TYPES_UINT32]); |
1186 | | /* A TTL of zero means "unspecified"; fall back to the driver's default |
1187 | | * (e.g. configured announce-ttl, or 600s). The notification always |
1188 | | * carries a ttl key (possibly zero), so the only way to detect |
1189 | | * "unspecified" is to check for zero explicitly. */ |
1190 | 0 | UA_UInt32 ttl = (_ttl && *_ttl > 0) ? *_ttl : announceTTL(md); |
1191 | |
|
1192 | 0 | const UA_Boolean *_added = (const UA_Boolean*) |
1193 | 0 | UA_KeyValueMap_getScalar(&payload, UA_QUALIFIEDNAME(0, "server-added"), |
1194 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
1195 | 0 | UA_Boolean added = (_added) ? *_added : false; |
1196 | |
|
1197 | 0 | const UA_Boolean *_updated = (const UA_Boolean*) |
1198 | 0 | UA_KeyValueMap_getScalar(&payload, UA_QUALIFIEDNAME(0, "server-updated"), |
1199 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
1200 | 0 | UA_Boolean updated = (_updated) ? *_updated : false; |
1201 | |
|
1202 | 0 | const UA_Boolean *_removed = (const UA_Boolean*) |
1203 | 0 | UA_KeyValueMap_getScalar(&payload, UA_QUALIFIEDNAME(0, "server-removed"), |
1204 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
1205 | 0 | UA_Boolean removed = (_removed) ? *_removed : false; |
1206 | | |
1207 | | /* Find existing entry for the ServerName */ |
1208 | 0 | ServerOnNetworkRecord *entry = findSON(md, son->serverName); |
1209 | | |
1210 | | /* Add or update record */ |
1211 | 0 | if(added || updated) { |
1212 | 0 | if(!entry) { |
1213 | | /* Create a new entry if none exists */ |
1214 | 0 | res = addSON(md, son, &entry); |
1215 | 0 | if(res != UA_STATUSCODE_GOOD) |
1216 | 0 | return; |
1217 | 0 | } else { |
1218 | | /* Abort if the mDNS-relevant fields are identical. The recordId is |
1219 | | * bumped server-side (e.g. by resetDiscoveryResetIds) and must |
1220 | | * not be part of the equality check. Use a shallow copy on the |
1221 | | * stack so the string pointers of `son` stay valid. */ |
1222 | 0 | UA_ServerOnNetwork sonForCmp = *son; |
1223 | 0 | sonForCmp.recordId = entry->serverOnNetwork.recordId; |
1224 | 0 | UA_Order same = |
1225 | 0 | UA_order(&sonForCmp, &entry->serverOnNetwork, |
1226 | 0 | &UA_TYPES[UA_TYPES_SERVERONNETWORK]); |
1227 | 0 | if(same == UA_ORDER_EQ) { |
1228 | | /* If this is an entry received from mDNS, this notification is |
1229 | | * just the server table echo of our cache update. Keep the |
1230 | | * remote origin and do not announce it back out. */ |
1231 | 0 | return; |
1232 | 0 | } |
1233 | 0 | UA_ServerOnNetwork tmp; |
1234 | 0 | res = UA_ServerOnNetwork_copy(son, &tmp); |
1235 | 0 | if(res != UA_STATUSCODE_GOOD) |
1236 | 0 | return; |
1237 | 0 | UA_ServerOnNetwork_clear(&entry->serverOnNetwork); |
1238 | 0 | entry->serverOnNetwork = tmp; |
1239 | 0 | } |
1240 | | |
1241 | | /* Locally created and not received over the network */ |
1242 | 0 | entry->origin = SERVER_ON_NETWORK_RECORD_LOCAL_OWNED; |
1243 | 0 | entry->ttl = ttl; |
1244 | | |
1245 | | /* Announce right away */ |
1246 | 0 | announceRecord(md, entry); |
1247 | | |
1248 | | /* Next time the entry is announced */ |
1249 | 0 | UA_EventLoop *el = UA_Server_getConfig(md->mdns.drv.server)->eventLoop; |
1250 | 0 | UA_DateTime now = el->dateTime_nowMonotonic(el); |
1251 | 0 | entry->nextAction = now + ((entry->ttl * 9 * UA_DATETIME_SEC) / 10); |
1252 | 0 | } |
1253 | | |
1254 | | /* Remove record */ |
1255 | 0 | if(removed) { |
1256 | 0 | if(!entry) |
1257 | 0 | return; |
1258 | | |
1259 | 0 | if(isLocalOwnedSON(entry)) { |
1260 | | /* Locally owned record: the API removal means we own the goodbye. */ |
1261 | 0 | retractRecord(md, son); |
1262 | | /* Send the goodbye message immediately so the |
1263 | | * public-api tests observe the deregister packet. */ |
1264 | 0 | flushMulticastMessages(md); |
1265 | 0 | removeLocalSON(md, entry); |
1266 | 0 | } else { |
1267 | | /* Received record: the server notification was caused by a remote |
1268 | | * goodbye or TTL expiry. Do not mirror a goodbye back out. */ |
1269 | 0 | removeSONEntry(md, entry, false); |
1270 | 0 | } |
1271 | 0 | } |
1272 | 0 | } |
1273 | | |
1274 | | static void |
1275 | 0 | MdnsdDriver_stop(UA_Driver *drv) { |
1276 | 0 | UA_Server *server = drv->server; |
1277 | 0 | MdnsdDriver *md = (MdnsdDriver*)drv; |
1278 | | |
1279 | | /* Already stopped */ |
1280 | 0 | if(drv->state == UA_LIFECYCLESTATE_STOPPED) |
1281 | 0 | return; |
1282 | | |
1283 | | /* Commence async stopping */ |
1284 | 0 | drv->state = UA_LIFECYCLESTATE_STOPPING; |
1285 | | |
1286 | | /* Remove repeated callback to send out multicast messages */ |
1287 | 0 | UA_Server_removeRepeatedCallback(server, md->sendCallbackId); |
1288 | 0 | md->sendCallbackId = 0; |
1289 | 0 | if(md->presenceQueryCallbackId) { |
1290 | 0 | UA_Server_removeRepeatedCallback(server, md->presenceQueryCallbackId); |
1291 | 0 | md->presenceQueryCallbackId = 0; |
1292 | 0 | } |
1293 | | |
1294 | | /* Retract all locally-announced records so that goodbyes (TTL=0) are |
1295 | | * flushed before the UDP sockets are closed. Only retract entries we |
1296 | | * created locally; received entries will be cleaned up by the server |
1297 | | * via the normal deregister path. */ |
1298 | 0 | ServerOnNetworkRecord *son, *son_tmp; |
1299 | 0 | LIST_FOREACH_SAFE(son, &md->serverList, listPointers, son_tmp) { |
1300 | 0 | if(isLocalOwnedSON(son)) |
1301 | 0 | retractRecord(md, &son->serverOnNetwork); |
1302 | 0 | } |
1303 | | |
1304 | | /* Flush queued goodbye packets before the UDP sockets are closed. This is |
1305 | | * the last point where the component still owns live connections but no |
1306 | | * further periodic queries will be scheduled. */ |
1307 | 0 | flushMulticastMessages(md); |
1308 | | |
1309 | | /* Close the sockets (async) */ |
1310 | 0 | if(md->cm) { |
1311 | 0 | if(md->mdnsSendConnection) |
1312 | 0 | md->cm->closeConnection(md->cm, md->mdnsSendConnection); |
1313 | 0 | for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) |
1314 | 0 | if(md->mdnsRecvConnections[i] != 0) |
1315 | 0 | md->cm->closeConnection(md->cm, md->mdnsRecvConnections[i]); |
1316 | 0 | } |
1317 | | |
1318 | | /* Check if already fully closed */ |
1319 | 0 | if(allConnectionsClosed(md)) |
1320 | 0 | drv->state = UA_LIFECYCLESTATE_STOPPED; |
1321 | 0 | } |
1322 | | |
1323 | | static UA_StatusCode |
1324 | 0 | MdnsdDriver_start(UA_Driver *drv) { |
1325 | | /* Check that the server has been set */ |
1326 | 0 | MdnsdDriver *md = (MdnsdDriver*)drv; |
1327 | 0 | if(!drv->server) |
1328 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1329 | | |
1330 | | /* Initialize networking on win32 */ |
1331 | | #if defined(UA_ARCHITECTURE_WIN32) || defined(UA_ARCHITECTURE_WEC7) |
1332 | | WSADATA wsaData; |
1333 | | WSAStartup(MAKEWORD(2, 2), &wsaData); |
1334 | | #endif |
1335 | | |
1336 | | /* Set the logging shortcut */ |
1337 | 0 | UA_ServerConfig *config = UA_Server_getConfig(drv->server); |
1338 | 0 | md->logging = config->logging; |
1339 | | |
1340 | | /* Check the state */ |
1341 | 0 | if(drv->state != UA_LIFECYCLESTATE_STOPPED) { |
1342 | 0 | UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY, |
1343 | 0 | "mDNS: Cannot start driver that is already running"); |
1344 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1345 | 0 | } |
1346 | | |
1347 | 0 | drv->state = UA_LIFECYCLESTATE_STARTED; |
1348 | | |
1349 | | /* Initialize the list of server-on-network records. The list head is |
1350 | | * embedded in the driver struct (zeroed by calloc) so the initial state |
1351 | | * is an empty list. Re-initialize in case the driver is restarted. */ |
1352 | 0 | LIST_INIT(&md->serverList); |
1353 | | |
1354 | | /* Extract the configuration parameters from the key-value map */ |
1355 | 0 | const UA_Boolean *listen = (const UA_Boolean*) |
1356 | 0 | UA_KeyValueMap_getScalar(&drv->params, |
1357 | 0 | UA_QUALIFIEDNAME(0, "listen"), |
1358 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
1359 | 0 | md->listen = listen ? *listen : false; |
1360 | |
|
1361 | 0 | const UA_Boolean *announce = (const UA_Boolean*) |
1362 | 0 | UA_KeyValueMap_getScalar(&drv->params, |
1363 | 0 | UA_QUALIFIEDNAME(0, "announce"), |
1364 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
1365 | 0 | md->announce = announce ? *announce : false; |
1366 | |
|
1367 | 0 | const UA_Boolean *queryPresenceParam = (const UA_Boolean*) |
1368 | 0 | UA_KeyValueMap_getScalar(&drv->params, |
1369 | 0 | UA_QUALIFIEDNAME(0, "query-presence"), |
1370 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
1371 | 0 | md->queryPresence = queryPresenceParam ? *queryPresenceParam : false; |
1372 | |
|
1373 | 0 | const UA_Boolean *queryDetailsParam = (const UA_Boolean*) |
1374 | 0 | UA_KeyValueMap_getScalar(&drv->params, |
1375 | 0 | UA_QUALIFIEDNAME(0, "query-details"), |
1376 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
1377 | 0 | md->queryDetails = queryDetailsParam ? *queryDetailsParam : false; |
1378 | |
|
1379 | 0 | const UA_UInt32 *queryIntervalParam = (const UA_UInt32*) |
1380 | 0 | UA_KeyValueMap_getScalar(&drv->params, |
1381 | 0 | UA_QUALIFIEDNAME(0, "query-interval"), |
1382 | 0 | &UA_TYPES[UA_TYPES_UINT32]); |
1383 | 0 | md->queryInterval = queryIntervalParam ? *queryIntervalParam : 0; |
1384 | |
|
1385 | 0 | if(!md->listen && (md->queryPresence || md->queryDetails)) { |
1386 | 0 | UA_LOG_WARNING(md->logging, UA_LOGCATEGORY_DISCOVERY, |
1387 | 0 | "mDNS: Querying requires listen=true; disabling queries"); |
1388 | 0 | md->queryPresence = false; |
1389 | 0 | md->queryDetails = false; |
1390 | 0 | md->queryInterval = 0; |
1391 | 0 | } |
1392 | |
|
1393 | 0 | const UA_UInt32 *announceTTL = (const UA_UInt32*) |
1394 | 0 | UA_KeyValueMap_getScalar(&drv->params, |
1395 | 0 | UA_QUALIFIEDNAME(0, "announce-ttl"), |
1396 | 0 | &UA_TYPES[UA_TYPES_UINT32]); |
1397 | 0 | md->announceTTL = announceTTL ? *announceTTL : 0; |
1398 | |
|
1399 | 0 | UA_String_clear(&md->interfaceName); |
1400 | 0 | const UA_String *interfaceName = (const UA_String*) |
1401 | 0 | UA_KeyValueMap_getScalar(&drv->params, |
1402 | 0 | UA_QUALIFIEDNAME(0, "interface"), |
1403 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
1404 | 0 | if(interfaceName) |
1405 | 0 | UA_String_copy(interfaceName, &md->interfaceName); |
1406 | | |
1407 | | /* Create mDNS daemon */ |
1408 | 0 | if(!md->mdnsDaemon) { |
1409 | 0 | md->mdnsDaemon = mdnsd_new(QCLASS_IN, 1000); |
1410 | 0 | if(!md->mdnsDaemon) { |
1411 | 0 | UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY, |
1412 | 0 | "mDNS: Could not create mDNS daemon"); |
1413 | 0 | MdnsdDriver_stop(&md->mdns.drv); |
1414 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
1415 | 0 | } |
1416 | 0 | mdnsd_register_receive_callback(md->mdnsDaemon, processRecord, md); |
1417 | 0 | } |
1418 | | |
1419 | | /* Open the UDP sockets if needed */ |
1420 | 0 | if(md->listen || md->announce || md->queryPresence || md->queryDetails) { |
1421 | 0 | if(md->mdnsSendConnection == 0) |
1422 | 0 | createMulticastSocket(md); |
1423 | 0 | if(md->mdnsSendConnection == 0) { |
1424 | 0 | UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY, |
1425 | 0 | "mDNS: Could not create UDP multicast socket"); |
1426 | 0 | MdnsdDriver_stop(&md->mdns.drv); |
1427 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1428 | 0 | } |
1429 | 0 | } |
1430 | | |
1431 | | /* Add a repeated callback to process actions and send out multicast |
1432 | | * messages */ |
1433 | 0 | UA_Server_addRepeatedCallback(md->mdns.drv.server, MdnsdHouseKeeping, |
1434 | 0 | md, UA_MDNS_POLL_INTERVAL_MS, |
1435 | 0 | &md->sendCallbackId); |
1436 | |
|
1437 | 0 | if(md->queryPresence) { |
1438 | 0 | queryPresence(md); |
1439 | 0 | if(md->queryInterval > 0) { |
1440 | 0 | UA_Server_addRepeatedCallback(md->mdns.drv.server, |
1441 | 0 | MdnsdPresenceQueryCallback, md, |
1442 | 0 | md->queryInterval * 1000, |
1443 | 0 | &md->presenceQueryCallbackId); |
1444 | 0 | } |
1445 | 0 | } |
1446 | |
|
1447 | 0 | return UA_STATUSCODE_GOOD; |
1448 | 0 | } |
1449 | | |
1450 | | static UA_StatusCode |
1451 | 0 | MdnsdDriver_free(UA_Driver *drv) { |
1452 | 0 | MdnsdDriver *md = (MdnsdDriver*)drv; |
1453 | |
|
1454 | 0 | if(drv->state != UA_LIFECYCLESTATE_STOPPED) { |
1455 | 0 | UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY, |
1456 | 0 | "Cannot free multicast discovery before it is fully stopped"); |
1457 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1458 | 0 | } |
1459 | | |
1460 | | /* Clean up the serverOnNetwork list */ |
1461 | 0 | ServerOnNetworkRecord *son, *son_tmp; |
1462 | 0 | LIST_FOREACH_SAFE(son, &md->serverList, listPointers, son_tmp) { |
1463 | 0 | LIST_REMOVE(son, listPointers); |
1464 | 0 | ServerOnNetworkRecord_delete(son); |
1465 | 0 | } |
1466 | | |
1467 | | /* Clean up mdns daemon */ |
1468 | 0 | if(md->mdnsDaemon) { |
1469 | 0 | mdnsd_shutdown(md->mdnsDaemon); |
1470 | 0 | mdnsd_free(md->mdnsDaemon); |
1471 | 0 | md->mdnsDaemon = NULL; |
1472 | 0 | } |
1473 | | |
1474 | | /* Clean up the configuration */ |
1475 | 0 | UA_String_clear(&md->interfaceName); |
1476 | 0 | UA_KeyValueMap_clear(&md->mdns.drv.params); |
1477 | |
|
1478 | 0 | UA_free(md); |
1479 | |
|
1480 | 0 | return UA_STATUSCODE_GOOD; |
1481 | 0 | } |
1482 | | |
1483 | | UA_MdnsDriver * |
1484 | 0 | UA_MdnsDriver_Mdnsd(const UA_KeyValueMap params) { |
1485 | | /* Allocate the memory */ |
1486 | 0 | MdnsdDriver *md = (MdnsdDriver*) |
1487 | 0 | UA_calloc(1, sizeof(MdnsdDriver)); |
1488 | 0 | if(!md) |
1489 | 0 | return NULL; |
1490 | | |
1491 | | /* Copy over the parameters */ |
1492 | 0 | UA_StatusCode res = |
1493 | 0 | UA_KeyValueMap_copy(¶ms, &md->mdns.drv.params); |
1494 | 0 | if(res != UA_STATUSCODE_GOOD) { |
1495 | 0 | UA_free(md); |
1496 | 0 | return NULL; |
1497 | 0 | } |
1498 | | |
1499 | | /* Set the name and function pointers */ |
1500 | 0 | md->mdns.drv.name = UA_STRING("discovery-mdns"); |
1501 | 0 | md->mdns.drv.start = MdnsdDriver_start; |
1502 | 0 | md->mdns.drv.stop = MdnsdDriver_stop; |
1503 | 0 | md->mdns.drv.free = MdnsdDriver_free; |
1504 | | |
1505 | | /* Callback for server notifications */ |
1506 | 0 | md->mdns.drv.notificationCallback = MdnsdDriverNotificationCallback; |
1507 | 0 | md->mdns.drv.notificationFilter = UA_APPLICATIONNOTIFICATIONTYPE_DISCOVERY; |
1508 | | |
1509 | | /* Manual address records */ |
1510 | 0 | md->mdns.addARecord = MdnsdDriver_addARecord; |
1511 | 0 | md->mdns.removeARecord = MdnsdDriver_removeARecord; |
1512 | 0 | md->mdns.addAAAARecord = MdnsdDriver_addAAAARecord; |
1513 | 0 | md->mdns.removeAAAARecord = MdnsdDriver_removeAAAARecord; |
1514 | |
|
1515 | 0 | return &md->mdns; |
1516 | 0 | } |
1517 | | |
1518 | | #endif /* UA_ENABLE_DISCOVERY_MULTICAST */ |