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