Coverage Report

Created: 2026-11-05 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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(&params[0].value, &port, &UA_TYPES[UA_TYPES_UINT16]);
1061
0
    params[1].key = UA_QUALIFIEDNAME(0, "address");
1062
0
    UA_Variant_setScalar(&params[1].value, &address, &UA_TYPES[UA_TYPES_STRING]);
1063
0
    params[2].key = UA_QUALIFIEDNAME(0, "listen");
1064
0
    UA_Variant_setScalar(&params[2].value, &listen, &UA_TYPES[UA_TYPES_BOOLEAN]);
1065
0
    params[3].key = UA_QUALIFIEDNAME(0, "reuse");
1066
0
    UA_Variant_setScalar(&params[3].value, &reuse, &UA_TYPES[UA_TYPES_BOOLEAN]);
1067
0
    params[4].key = UA_QUALIFIEDNAME(0, "ttl");
1068
0
    UA_Variant_setScalar(&params[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(&params[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(&params, &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 */