Coverage Report

Created: 2026-07-16 06:23

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