refactor(drivers): Rework the mDNS plugins into drivers with their own lifecycle

This commit is contained in:
Julius Pfrommer
2026-06-09 17:37:02 +02:00
committed by Julius Pfrommer
parent 30cad06c4e
commit 316093c01b
18 changed files with 3681 additions and 2828 deletions
+5 -4
View File
@@ -567,9 +567,10 @@ endif()
if(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI) if(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_search_module(AVAHI REQUIRED avahi-client avahi-common) pkg_search_module(AVAHI REQUIRED avahi-client)
include_directories(${AVAHI_INCLUDE_DIRS}) include_directories(${AVAHI_INCLUDE_DIRS})
list(APPEND open62541_PUBLIC_LIBRARIES "${AVAHI_LIBRARIES}") list(APPEND open62541_PUBLIC_LIBRARIES "${AVAHI_LIBRARIES}")
list(APPEND open62541_LIBRARIES ${AVAHI_LIBRARIES})
endif() endif()
if(UA_ARCHITECTURE_LWIP) if(UA_ARCHITECTURE_LWIP)
@@ -1055,7 +1056,7 @@ if(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD)
${MDNSD_GENERATED_DIR}/inet.h ${MDNSD_GENERATED_DIR}/inet.h
${MDNSD_GENERATED_DIR}/mdnsd.h) ${MDNSD_GENERATED_DIR}/mdnsd.h)
list(APPEND lib_sources list(APPEND lib_sources
#${PROJECT_SOURCE_DIR}/plugins/mdns/discovery_mdnsd.c ${PROJECT_SOURCE_DIR}/drivers/discovery_mdns_mdnsd.c
${MDNSD_GENERATED_DIR}/1035.c ${MDNSD_GENERATED_DIR}/1035.c
${MDNSD_GENERATED_DIR}/xht.c ${MDNSD_GENERATED_DIR}/xht.c
${MDNSD_GENERATED_DIR}/sdtxt.c ${MDNSD_GENERATED_DIR}/sdtxt.c
@@ -1065,8 +1066,8 @@ if(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD)
endif() endif()
if(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI) if(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI)
#list(APPEND lib_sources list(APPEND lib_sources
# ${PROJECT_SOURCE_DIR}/plugins/mdns/discovery_avahi.c) ${PROJECT_SOURCE_DIR}/drivers/discovery_mdns_avahi.c)
endif() endif()
if(UA_BUILD_FUZZING OR UA_BUILD_OSS_FUZZ) if(UA_BUILD_FUZZING OR UA_BUILD_OSS_FUZZ)
+864
View File
@@ -0,0 +1,864 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2017 (c) Stefan Profanter, fortiss GmbH
* Copyright 2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
* Copyright 2017 (c) Thomas Stalder, Blue Time Concept SA
* Copyright 2024 (c) Linutronix GmbH (Author: Vasilij Strassheim)
* Copyright 2026 (c) o6 Automation GmbH (Author: Julius Pfrommer)
*/
#include <open62541/server.h>
#include <open62541/driver/mdns.h>
#ifdef UA_ENABLE_DISCOVERY_MULTICAST_AVAHI
#include <avahi-client/client.h>
#include <avahi-client/lookup.h>
#include <avahi-client/publish.h>
#include <avahi-common/address.h>
#include <avahi-common/error.h>
#include <avahi-common/malloc.h>
#include <avahi-common/simple-watch.h>
#include <avahi-common/strlst.h>
#include <net/if.h>
#include <stdio.h>
#define UA_AVAHI_POLL_INTERVAL_MS 100
#define UA_AVAHI_MAX_NAME_LENGTH 256
typedef enum {
AVAHI_RECORD_REMOTE_RECEIVED,
AVAHI_RECORD_LOCAL_OWNED
} AvahiRecordOrigin;
typedef struct ServerOnNetworkRecord {
struct ServerOnNetworkRecord *next;
UA_ServerOnNetwork serverOnNetwork;
AvahiEntryGroup *group;
AvahiRecordOrigin origin;
UA_Boolean txtSet;
UA_Boolean srvSet;
UA_UInt32 ttl;
} ServerOnNetworkRecord;
typedef struct AddressRecord {
struct AddressRecord *next;
UA_String hostname;
UA_String address;
unsigned short type;
AvahiEntryGroup *group;
} AddressRecord;
typedef struct {
UA_MdnsDriver mdns;
UA_Logger *logging;
AvahiClient *client;
AvahiSimplePoll *simplePoll;
AvahiServiceBrowser *browser;
UA_UInt64 pollCallbackId;
ServerOnNetworkRecord *serverList;
AddressRecord *addressList;
UA_Boolean listen;
UA_Boolean announce;
UA_Boolean queryPresence;
UA_Boolean queryDetails;
UA_UInt32 queryInterval;
UA_UInt32 announceTTL;
UA_String interface;
AvahiIfIndex ifIndex;
} AvahiDriver;
static UA_StatusCode publishLocalRecord(AvahiDriver *md,
ServerOnNetworkRecord *entry);
static UA_UInt32
announceTTL(const AvahiDriver *md) {
return (md->announceTTL > 0) ? md->announceTTL : 600u;
}
static UA_Boolean
isRemoteReceivedRecord(const ServerOnNetworkRecord *entry) {
return entry->origin == AVAHI_RECORD_REMOTE_RECEIVED;
}
static UA_Boolean
isLocalOwnedRecord(const ServerOnNetworkRecord *entry) {
return entry->origin == AVAHI_RECORD_LOCAL_OWNED;
}
static ServerOnNetworkRecord *
findSON(AvahiDriver *md, UA_String serverName) {
for(ServerOnNetworkRecord *entry = md->serverList; entry; entry = entry->next) {
if(UA_String_equal(&serverName, &entry->serverOnNetwork.serverName))
return entry;
}
return NULL;
}
static UA_StatusCode
addSON(AvahiDriver *md, const UA_ServerOnNetwork *son,
AvahiRecordOrigin origin, ServerOnNetworkRecord **out) {
ServerOnNetworkRecord *entry = (ServerOnNetworkRecord*)
UA_calloc(1, sizeof(ServerOnNetworkRecord));
if(!entry)
return UA_STATUSCODE_BADOUTOFMEMORY;
UA_StatusCode res = UA_ServerOnNetwork_copy(son, &entry->serverOnNetwork);
if(res != UA_STATUSCODE_GOOD) {
UA_free(entry);
return res;
}
entry->origin = origin;
entry->next = md->serverList;
md->serverList = entry;
if(out)
*out = entry;
return UA_STATUSCODE_GOOD;
}
static void
clearEntryGroup(AvahiEntryGroup **group) {
if(!group || !*group)
return;
avahi_entry_group_reset(*group);
avahi_entry_group_free(*group);
*group = NULL;
}
static void
removeSONEntry(AvahiDriver *md, ServerOnNetworkRecord *entry,
UA_Boolean deregisterFromServer, UA_Boolean clearGroup) {
ServerOnNetworkRecord **prev = &md->serverList;
while(*prev && *prev != entry)
prev = &(*prev)->next;
if(!*prev)
return;
*prev = entry->next;
if(clearGroup)
clearEntryGroup(&entry->group);
if(deregisterFromServer)
UA_Server_deregisterServerOnNetwork(md->mdns.drv.server,
entry->serverOnNetwork.serverName);
UA_ServerOnNetwork_clear(&entry->serverOnNetwork);
UA_free(entry);
}
static UA_StatusCode
stringToBuffer(UA_String src, char *dst, size_t dstSize) {
if(src.length >= dstSize)
return UA_STATUSCODE_BADOUTOFRANGE;
memcpy(dst, src.data, src.length);
dst[src.length] = '\0';
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
appendLocalSuffix(UA_String hostname, char *dst, size_t dstSize) {
if(hostname.length >= dstSize)
return UA_STATUSCODE_BADOUTOFRANGE;
memcpy(dst, hostname.data, hostname.length);
dst[hostname.length] = '\0';
if(hostname.length >= 6 &&
memcmp(hostname.data + hostname.length - 6, ".local", 6) == 0)
return UA_STATUSCODE_GOOD;
if(hostname.length >= 7 &&
memcmp(hostname.data + hostname.length - 7, ".local.", 7) == 0)
return UA_STATUSCODE_GOOD;
if(hostname.length + 6 >= dstSize)
return UA_STATUSCODE_BADOUTOFRANGE;
memcpy(dst + hostname.length, ".local", 7);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
addTxtPath(AvahiStringList **txt, UA_String path) {
char buf[256];
if(path.length == 0) {
*txt = avahi_string_list_add(*txt, "path=/");
return UA_STATUSCODE_GOOD;
}
size_t needed = path.length + 7;
if(path.data[0] != '/')
needed++;
if(needed > sizeof(buf))
return UA_STATUSCODE_BADOUTOFRANGE;
size_t pos = 0;
memcpy(buf + pos, "path=", 5);
pos += 5;
if(path.data[0] != '/')
buf[pos++] = '/';
memcpy(buf + pos, path.data, path.length);
pos += path.length;
buf[pos] = '\0';
*txt = avahi_string_list_add(*txt, buf);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
addTxtCaps(AvahiStringList **txt, const UA_String *caps, size_t capsSize) {
if(capsSize == 0) {
*txt = avahi_string_list_add(*txt, "caps=NA");
return UA_STATUSCODE_GOOD;
}
char buf[256];
size_t pos = 0;
memcpy(buf + pos, "caps=", 5);
pos += 5;
for(size_t i = 0; i < capsSize; i++) {
if(i > 0)
buf[pos++] = ',';
if(pos + caps[i].length >= sizeof(buf))
return UA_STATUSCODE_BADOUTOFRANGE;
memcpy(buf + pos, caps[i].data, caps[i].length);
pos += caps[i].length;
}
buf[pos] = '\0';
*txt = avahi_string_list_add(*txt, buf);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
copyCsvPartToString(const char *start, size_t len, UA_String *dst) {
dst->length = len;
dst->data = (UA_Byte*)UA_malloc(len + 1);
if(!dst->data)
return UA_STATUSCODE_BADOUTOFMEMORY;
memcpy(dst->data, start, len);
dst->data[len] = '\0';
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
setCapabilitiesFromCsv(ServerOnNetworkRecord *entry, const char *caps) {
if(!caps || caps[0] == '\0')
caps = "NA";
size_t count = 1;
for(const char *p = caps; *p; p++) {
if(*p == ',')
count++;
}
UA_Array_delete(entry->serverOnNetwork.serverCapabilities,
entry->serverOnNetwork.serverCapabilitiesSize,
&UA_TYPES[UA_TYPES_STRING]);
entry->serverOnNetwork.serverCapabilities = (UA_String*)
UA_Array_new(count, &UA_TYPES[UA_TYPES_STRING]);
if(!entry->serverOnNetwork.serverCapabilities) {
entry->serverOnNetwork.serverCapabilitiesSize = 0;
return UA_STATUSCODE_BADOUTOFMEMORY;
}
entry->serverOnNetwork.serverCapabilitiesSize = count;
const char *start = caps;
for(size_t i = 0; i < count; i++) {
const char *end = strchr(start, ',');
size_t len = end ? (size_t)(end - start) : strlen(start);
UA_StatusCode res =
copyCsvPartToString(start, len,
&entry->serverOnNetwork.serverCapabilities[i]);
if(res != UA_STATUSCODE_GOOD)
return res;
start = end ? end + 1 : start + len;
}
return UA_STATUSCODE_GOOD;
}
static const char *
getTxtValue(AvahiStringList *txt, const char *wantedKey) {
static char valueBuf[256];
for(AvahiStringList *cur = txt; cur; cur = avahi_string_list_get_next(cur)) {
char *key = NULL;
char *value = NULL;
if(avahi_string_list_get_pair(cur, &key, &value, NULL) < 0)
continue;
UA_Boolean match = (strcmp(key, wantedKey) == 0);
valueBuf[0] = '\0';
if(match && value)
snprintf(valueBuf, sizeof(valueBuf), "%s", value);
avahi_free(key);
avahi_free(value);
if(match)
return valueBuf;
}
return NULL;
}
static void
entryGroupCallback(AvahiEntryGroup *group, AvahiEntryGroupState state,
void *userdata) {
AvahiDriver *md = (AvahiDriver*)userdata;
if(!md)
return;
switch(state) {
case AVAHI_ENTRY_GROUP_ESTABLISHED:
UA_LOG_INFO(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: Entry group established");
break;
case AVAHI_ENTRY_GROUP_COLLISION:
UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: Entry group collision");
break;
case AVAHI_ENTRY_GROUP_FAILURE:
UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: Entry group failure: %s",
avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(group))));
break;
case AVAHI_ENTRY_GROUP_UNCOMMITED:
case AVAHI_ENTRY_GROUP_REGISTERING:
break;
}
}
static UA_StatusCode
publishLocalRecord(AvahiDriver *md, ServerOnNetworkRecord *entry) {
if(!md->announce)
return UA_STATUSCODE_GOOD;
if(!md->client)
return UA_STATUSCODE_BADINTERNALERROR;
UA_String hostname = UA_STRING_NULL;
UA_String path = UA_STRING_NULL;
UA_UInt16 port = 4840;
UA_StatusCode res =
UA_parseEndpointUrl(&entry->serverOnNetwork.discoveryUrl,
&hostname, &port, &path);
if(res != UA_STATUSCODE_GOOD)
return res;
char serviceName[UA_AVAHI_MAX_NAME_LENGTH];
res = stringToBuffer(entry->serverOnNetwork.serverName,
serviceName, sizeof(serviceName));
if(res != UA_STATUSCODE_GOOD)
return res;
char hostName[UA_AVAHI_MAX_NAME_LENGTH];
res = appendLocalSuffix(hostname, hostName, sizeof(hostName));
if(res != UA_STATUSCODE_GOOD)
return res;
AvahiStringList *txt = NULL;
res = addTxtPath(&txt, path);
if(res == UA_STATUSCODE_GOOD)
res = addTxtCaps(&txt, entry->serverOnNetwork.serverCapabilities,
entry->serverOnNetwork.serverCapabilitiesSize);
if(res != UA_STATUSCODE_GOOD) {
avahi_string_list_free(txt);
return res;
}
if(!entry->group) {
entry->group = avahi_entry_group_new(md->client, entryGroupCallback, md);
if(!entry->group) {
avahi_string_list_free(txt);
return UA_STATUSCODE_BADOUTOFMEMORY;
}
} else {
avahi_entry_group_reset(entry->group);
}
int rv = avahi_entry_group_add_service_strlst(entry->group, md->ifIndex,
AVAHI_PROTO_UNSPEC,
AVAHI_PUBLISH_USE_MULTICAST,
serviceName, "_opcua-tcp._tcp",
NULL, hostName, port, txt);
avahi_string_list_free(txt);
if(rv < 0) {
UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: Failed to add service %s: %s", serviceName,
avahi_strerror(rv));
return UA_STATUSCODE_BADINTERNALERROR;
}
rv = avahi_entry_group_commit(entry->group);
if(rv < 0) {
UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: Failed to commit service %s: %s", serviceName,
avahi_strerror(rv));
return UA_STATUSCODE_BADINTERNALERROR;
}
return UA_STATUSCODE_GOOD;
}
static void
removeLocalRecord(AvahiDriver *md, ServerOnNetworkRecord *entry) {
(void)md;
clearEntryGroup(&entry->group);
}
static void
registerRemoteIfComplete(AvahiDriver *md, ServerOnNetworkRecord *entry) {
if(!entry->srvSet || !entry->txtSet)
return;
UA_Server_registerServerOnNetwork(md->mdns.drv.server,
&entry->serverOnNetwork,
UA_KEYVALUEMAP_NULL);
}
static void
clientCallback(AvahiClient *client, AvahiClientState state, void *userdata) {
AvahiDriver *md = (AvahiDriver*)userdata;
if(!md)
return;
UA_LOG_INFO(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: Client state changed to %d", state);
if(state == AVAHI_CLIENT_FAILURE)
UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: Client failure: %s",
avahi_strerror(avahi_client_errno(client)));
}
static void
resolveCallback(AvahiServiceResolver *resolver, AvahiIfIndex interface,
AvahiProtocol protocol, AvahiResolverEvent event,
const char *name, const char *type, const char *domain,
const char *hostName, const AvahiAddress *address,
uint16_t port, AvahiStringList *txt,
AvahiLookupResultFlags flags, void *userdata) {
(void)interface;
(void)protocol;
(void)type;
(void)domain;
(void)address;
AvahiDriver *md = (AvahiDriver*)userdata;
if(!md)
goto cleanup;
if(event != AVAHI_RESOLVER_FOUND)
goto cleanup;
if(flags & AVAHI_LOOKUP_RESULT_LOCAL)
goto cleanup;
UA_String serverName = UA_STRING((char*)(uintptr_t)name);
ServerOnNetworkRecord *entry = findSON(md, serverName);
if(entry && isLocalOwnedRecord(entry))
goto cleanup;
if(!entry) {
UA_ServerOnNetwork son;
UA_ServerOnNetwork_init(&son);
son.serverName = serverName;
UA_StatusCode res = addSON(md, &son, AVAHI_RECORD_REMOTE_RECEIVED, &entry);
if(res != UA_STATUSCODE_GOOD)
goto cleanup;
}
UA_String_clear(&entry->serverOnNetwork.discoveryUrl);
UA_String_format(&entry->serverOnNetwork.discoveryUrl,
"opc.tcp://%s:%u", hostName, port);
const char *path = getTxtValue(txt, "path");
if(path && path[0] != '\0' && strcmp(path, "/") != 0)
UA_String_append(&entry->serverOnNetwork.discoveryUrl,
UA_STRING((char*)(uintptr_t)path));
const char *caps = getTxtValue(txt, "caps");
if(setCapabilitiesFromCsv(entry, caps) != UA_STATUSCODE_GOOD)
goto cleanup;
entry->srvSet = true;
entry->txtSet = true;
entry->origin = AVAHI_RECORD_REMOTE_RECEIVED;
registerRemoteIfComplete(md, entry);
cleanup:
if(resolver)
avahi_service_resolver_free(resolver);
}
static void
browseCallback(AvahiServiceBrowser *browser, AvahiIfIndex interface,
AvahiProtocol protocol, AvahiBrowserEvent event,
const char *name, const char *type, const char *domain,
AvahiLookupResultFlags flags, void *userdata) {
AvahiDriver *md = (AvahiDriver*)userdata;
if(!md)
return;
switch(event) {
case AVAHI_BROWSER_NEW: {
if(flags & AVAHI_LOOKUP_RESULT_LOCAL)
break;
UA_String serverName = UA_STRING((char*)(uintptr_t)name);
if(!findSON(md, serverName)) {
UA_ServerOnNetwork son;
UA_ServerOnNetwork_init(&son);
son.serverName = serverName;
ServerOnNetworkRecord *entry = NULL;
addSON(md, &son, AVAHI_RECORD_REMOTE_RECEIVED, &entry);
}
if(md->queryDetails) {
AvahiServiceResolver *resolver =
avahi_service_resolver_new(md->client, interface, protocol,
name, type, domain, AVAHI_PROTO_UNSPEC,
AVAHI_LOOKUP_USE_MULTICAST,
resolveCallback, md);
if(!resolver)
UA_LOG_WARNING(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: Failed to resolve service %s: %s", name,
avahi_strerror(avahi_client_errno(md->client)));
}
break;
}
case AVAHI_BROWSER_REMOVE: {
UA_String serverName = UA_STRING((char*)(uintptr_t)name);
ServerOnNetworkRecord *entry = findSON(md, serverName);
if(entry && isRemoteReceivedRecord(entry))
removeSONEntry(md, entry, true, false);
break;
}
case AVAHI_BROWSER_FAILURE:
UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: Browser failure: %s",
avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(browser))));
break;
case AVAHI_BROWSER_ALL_FOR_NOW:
case AVAHI_BROWSER_CACHE_EXHAUSTED:
break;
}
}
static void
pollAvahi(UA_Server *server, void *data) {
(void)server;
AvahiDriver *md = (AvahiDriver*)data;
if(!md->simplePoll)
return;
int rv = avahi_simple_poll_iterate(md->simplePoll, 0);
if(rv < 0)
UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: Poll failure: %s", avahi_strerror(rv));
}
static void
AvahiDriverNotificationCallback(UA_Driver *drv,
UA_ApplicationNotificationType type,
const UA_KeyValueMap payload) {
if(type != UA_APPLICATIONNOTIFICATIONTYPE_DISCOVERY_SERVERONNETWORK)
return;
if(drv->state != UA_LIFECYCLESTATE_STARTED)
return;
AvahiDriver *md = (AvahiDriver*)drv;
const UA_ServerOnNetwork *son = (const UA_ServerOnNetwork*)
UA_KeyValueMap_getScalar(&payload, UA_QUALIFIEDNAME(0, "server-on-network"),
&UA_TYPES[UA_TYPES_SERVERONNETWORK]);
if(!son)
return;
const UA_UInt32 *ttlParam = (const UA_UInt32*)
UA_KeyValueMap_getScalar(&payload, UA_QUALIFIEDNAME(0, "ttl"),
&UA_TYPES[UA_TYPES_UINT32]);
UA_UInt32 ttl = (ttlParam && *ttlParam > 0) ? *ttlParam : announceTTL(md);
const UA_Boolean *addedParam = (const UA_Boolean*)
UA_KeyValueMap_getScalar(&payload, UA_QUALIFIEDNAME(0, "server-added"),
&UA_TYPES[UA_TYPES_BOOLEAN]);
const UA_Boolean *updatedParam = (const UA_Boolean*)
UA_KeyValueMap_getScalar(&payload, UA_QUALIFIEDNAME(0, "server-updated"),
&UA_TYPES[UA_TYPES_BOOLEAN]);
const UA_Boolean *removedParam = (const UA_Boolean*)
UA_KeyValueMap_getScalar(&payload, UA_QUALIFIEDNAME(0, "server-removed"),
&UA_TYPES[UA_TYPES_BOOLEAN]);
UA_Boolean added = addedParam ? *addedParam : false;
UA_Boolean updated = updatedParam ? *updatedParam : false;
UA_Boolean removed = removedParam ? *removedParam : false;
ServerOnNetworkRecord *entry = findSON(md, son->serverName);
if(added || updated) {
if(entry) {
UA_ServerOnNetwork sonForCmp = *son;
sonForCmp.recordId = entry->serverOnNetwork.recordId;
UA_Order same = UA_order(&sonForCmp, &entry->serverOnNetwork,
&UA_TYPES[UA_TYPES_SERVERONNETWORK]);
if(same == UA_ORDER_EQ)
return;
UA_ServerOnNetwork tmp;
UA_StatusCode res = UA_ServerOnNetwork_copy(son, &tmp);
if(res != UA_STATUSCODE_GOOD)
return;
UA_ServerOnNetwork_clear(&entry->serverOnNetwork);
entry->serverOnNetwork = tmp;
} else {
UA_StatusCode res = addSON(md, son, AVAHI_RECORD_LOCAL_OWNED, &entry);
if(res != UA_STATUSCODE_GOOD)
return;
}
entry->origin = AVAHI_RECORD_LOCAL_OWNED;
entry->ttl = ttl;
publishLocalRecord(md, entry);
}
if(removed) {
if(!entry)
return;
if(isLocalOwnedRecord(entry))
removeLocalRecord(md, entry);
removeSONEntry(md, entry, false, false);
}
}
static UA_StatusCode
AvahiDriver_start(UA_Driver *drv) {
AvahiDriver *md = (AvahiDriver*)drv;
if(!drv->server)
return UA_STATUSCODE_BADINTERNALERROR;
if(drv->state != UA_LIFECYCLESTATE_STOPPED)
return UA_STATUSCODE_BADINTERNALERROR;
UA_ServerConfig *config = UA_Server_getConfig(drv->server);
md->logging = config->logging;
const UA_Boolean *listen = (const UA_Boolean*)
UA_KeyValueMap_getScalar(&drv->params, UA_QUALIFIEDNAME(0, "listen"),
&UA_TYPES[UA_TYPES_BOOLEAN]);
md->listen = listen ? *listen : false;
const UA_Boolean *announce = (const UA_Boolean*)
UA_KeyValueMap_getScalar(&drv->params, UA_QUALIFIEDNAME(0, "announce"),
&UA_TYPES[UA_TYPES_BOOLEAN]);
md->announce = announce ? *announce : false;
const UA_Boolean *queryPresence = (const UA_Boolean*)
UA_KeyValueMap_getScalar(&drv->params, UA_QUALIFIEDNAME(0, "query-presence"),
&UA_TYPES[UA_TYPES_BOOLEAN]);
md->queryPresence = queryPresence ? *queryPresence : false;
const UA_Boolean *queryDetails = (const UA_Boolean*)
UA_KeyValueMap_getScalar(&drv->params, UA_QUALIFIEDNAME(0, "query-details"),
&UA_TYPES[UA_TYPES_BOOLEAN]);
md->queryDetails = queryDetails ? *queryDetails : false;
const UA_UInt32 *queryInterval = (const UA_UInt32*)
UA_KeyValueMap_getScalar(&drv->params, UA_QUALIFIEDNAME(0, "query-interval"),
&UA_TYPES[UA_TYPES_UINT32]);
md->queryInterval = queryInterval ? *queryInterval : 0;
const UA_UInt32 *announceTTLParam = (const UA_UInt32*)
UA_KeyValueMap_getScalar(&drv->params, UA_QUALIFIEDNAME(0, "announce-ttl"),
&UA_TYPES[UA_TYPES_UINT32]);
md->announceTTL = announceTTLParam ? *announceTTLParam : 0;
UA_String_clear(&md->interface);
const UA_String *interface = (const UA_String*)
UA_KeyValueMap_getScalar(&drv->params, UA_QUALIFIEDNAME(0, "interface"),
&UA_TYPES[UA_TYPES_STRING]);
if(interface)
UA_String_copy(interface, &md->interface);
md->ifIndex = AVAHI_IF_UNSPEC;
if(md->interface.length > 0) {
char ifName[UA_AVAHI_MAX_NAME_LENGTH];
if(stringToBuffer(md->interface, ifName, sizeof(ifName)) == UA_STATUSCODE_GOOD) {
unsigned int idx = if_nametoindex(ifName);
if(idx > 0) {
md->ifIndex = (AvahiIfIndex)idx;
} else {
UA_LOG_WARNING(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: Interface '%s' is not a local interface "
"name; using all interfaces", ifName);
}
} else {
UA_LOG_WARNING(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: Interface parameter too long; using all interfaces");
}
}
if(!md->listen && (md->queryPresence || md->queryDetails)) {
UA_LOG_WARNING(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: Querying requires listen=true; disabling queries");
md->queryPresence = false;
md->queryDetails = false;
md->queryInterval = 0;
}
if(md->queryInterval > 0 && md->queryPresence)
UA_LOG_INFO(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: query-interval is accepted for configuration parity; "
"service discovery remains event-driven");
md->simplePoll = avahi_simple_poll_new();
if(!md->simplePoll)
return UA_STATUSCODE_BADINTERNALERROR;
int error = 0;
md->client = avahi_client_new(avahi_simple_poll_get(md->simplePoll),
AVAHI_CLIENT_NO_FAIL, clientCallback,
md, &error);
if(!md->client) {
UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: Failed to create client: %s", avahi_strerror(error));
return UA_STATUSCODE_BADINTERNALERROR;
}
if(md->listen || md->queryPresence || md->queryDetails) {
md->browser = avahi_service_browser_new(md->client, md->ifIndex,
AVAHI_PROTO_UNSPEC,
"_opcua-tcp._tcp", NULL,
AVAHI_LOOKUP_USE_MULTICAST,
browseCallback, md);
if(!md->browser) {
UA_LOG_ERROR(md->logging, UA_LOGCATEGORY_DISCOVERY,
"Avahi: Failed to create browser: %s",
avahi_strerror(avahi_client_errno(md->client)));
return UA_STATUSCODE_BADINTERNALERROR;
}
}
UA_Server_addRepeatedCallback(drv->server, pollAvahi, md,
UA_AVAHI_POLL_INTERVAL_MS,
&md->pollCallbackId);
drv->state = UA_LIFECYCLESTATE_STARTED;
return UA_STATUSCODE_GOOD;
}
static void
AvahiDriver_stop(UA_Driver *drv) {
AvahiDriver *md = (AvahiDriver*)drv;
if(drv->state == UA_LIFECYCLESTATE_STOPPED)
return;
drv->state = UA_LIFECYCLESTATE_STOPPING;
if(md->pollCallbackId) {
UA_Server_removeRepeatedCallback(drv->server, md->pollCallbackId);
md->pollCallbackId = 0;
}
if(md->browser) {
avahi_service_browser_free(md->browser);
md->browser = NULL;
}
for(ServerOnNetworkRecord *entry = md->serverList; entry; entry = entry->next) {
if(isLocalOwnedRecord(entry))
removeLocalRecord(md, entry);
}
for(AddressRecord *ar = md->addressList; ar; ar = ar->next)
clearEntryGroup(&ar->group);
if(md->client) {
avahi_client_free(md->client);
md->client = NULL;
}
if(md->simplePoll) {
avahi_simple_poll_free(md->simplePoll);
md->simplePoll = NULL;
}
drv->state = UA_LIFECYCLESTATE_STOPPED;
}
static UA_StatusCode
AvahiDriver_free(UA_Driver *drv) {
AvahiDriver *md = (AvahiDriver*)drv;
if(drv->state != UA_LIFECYCLESTATE_STOPPED)
return UA_STATUSCODE_BADINTERNALERROR;
ServerOnNetworkRecord *entry = md->serverList;
while(entry) {
ServerOnNetworkRecord *next = entry->next;
clearEntryGroup(&entry->group);
UA_ServerOnNetwork_clear(&entry->serverOnNetwork);
UA_free(entry);
entry = next;
}
AddressRecord *ar = md->addressList;
while(ar) {
AddressRecord *next = ar->next;
clearEntryGroup(&ar->group);
UA_String_clear(&ar->hostname);
UA_String_clear(&ar->address);
UA_free(ar);
ar = next;
}
UA_String_clear(&md->interface);
UA_KeyValueMap_clear(&md->mdns.drv.params);
UA_free(md);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
publishAddressRecord(UA_MdnsDriver *drv, UA_String hostname, UA_String address,
UA_UInt32 ttl, unsigned short type) {
(void)ttl;
AvahiDriver *md = (AvahiDriver*)drv;
if(!md->client)
return UA_STATUSCODE_BADINTERNALERROR;
char host[UA_AVAHI_MAX_NAME_LENGTH];
UA_StatusCode res = stringToBuffer(hostname, host, sizeof(host));
if(res != UA_STATUSCODE_GOOD)
return res;
char addr[AVAHI_ADDRESS_STR_MAX];
res = stringToBuffer(address, addr, sizeof(addr));
if(res != UA_STATUSCODE_GOOD)
return res;
AvahiProtocol proto = (type == 1) ? AVAHI_PROTO_INET : AVAHI_PROTO_INET6;
AvahiAddress avahiAddress;
if(!avahi_address_parse(addr, proto, &avahiAddress))
return UA_STATUSCODE_BADINVALIDARGUMENT;
AddressRecord *ar = (AddressRecord*)UA_calloc(1, sizeof(AddressRecord));
if(!ar)
return UA_STATUSCODE_BADOUTOFMEMORY;
UA_String_copy(&hostname, &ar->hostname);
UA_String_copy(&address, &ar->address);
ar->type = type;
ar->group = avahi_entry_group_new(md->client, entryGroupCallback, md);
if(!ar->group) {
UA_free(ar);
return UA_STATUSCODE_BADOUTOFMEMORY;
}
int rv = avahi_entry_group_add_address(ar->group, md->ifIndex,
AVAHI_PROTO_UNSPEC,
AVAHI_PUBLISH_USE_MULTICAST,
host, &avahiAddress);
if(rv < 0) {
clearEntryGroup(&ar->group);
UA_String_clear(&ar->hostname);
UA_String_clear(&ar->address);
UA_free(ar);
return UA_STATUSCODE_BADINTERNALERROR;
}
rv = avahi_entry_group_commit(ar->group);
if(rv < 0) {
clearEntryGroup(&ar->group);
UA_String_clear(&ar->hostname);
UA_String_clear(&ar->address);
UA_free(ar);
return UA_STATUSCODE_BADINTERNALERROR;
}
ar->next = md->addressList;
md->addressList = ar;
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
removeAddressRecord(UA_MdnsDriver *drv, UA_String hostname, UA_String address,
unsigned short type) {
AvahiDriver *md = (AvahiDriver*)drv;
AddressRecord **prev = &md->addressList;
while(*prev) {
AddressRecord *ar = *prev;
if(ar->type == type && UA_String_equal(&hostname, &ar->hostname) &&
UA_String_equal(&address, &ar->address)) {
*prev = ar->next;
clearEntryGroup(&ar->group);
UA_String_clear(&ar->hostname);
UA_String_clear(&ar->address);
UA_free(ar);
return UA_STATUSCODE_GOOD;
}
prev = &(*prev)->next;
}
return UA_STATUSCODE_BADNOTFOUND;
}
static UA_StatusCode
AvahiDriver_addARecord(UA_MdnsDriver *drv, UA_String hostname,
UA_String address, UA_UInt32 ttl) {
return publishAddressRecord(drv, hostname, address, ttl, 1);
}
static UA_StatusCode
AvahiDriver_removeARecord(UA_MdnsDriver *drv, UA_String hostname,
UA_String address) {
return removeAddressRecord(drv, hostname, address, 1);
}
static UA_StatusCode
AvahiDriver_addAAAARecord(UA_MdnsDriver *drv, UA_String hostname,
UA_String address, UA_UInt32 ttl) {
return publishAddressRecord(drv, hostname, address, ttl, 28);
}
static UA_StatusCode
AvahiDriver_removeAAAARecord(UA_MdnsDriver *drv, UA_String hostname,
UA_String address) {
return removeAddressRecord(drv, hostname, address, 28);
}
UA_MdnsDriver *
UA_MdnsDriver_Avahi(const UA_KeyValueMap params) {
AvahiDriver *md = (AvahiDriver*)UA_calloc(1, sizeof(AvahiDriver));
if(!md)
return NULL;
UA_StatusCode res = UA_KeyValueMap_copy(&params, &md->mdns.drv.params);
if(res != UA_STATUSCODE_GOOD) {
UA_free(md);
return NULL;
}
md->mdns.drv.name = UA_STRING("discovery-mdns-avahi");
md->mdns.drv.start = AvahiDriver_start;
md->mdns.drv.stop = AvahiDriver_stop;
md->mdns.drv.free = AvahiDriver_free;
md->mdns.drv.notificationCallback = AvahiDriverNotificationCallback;
md->mdns.drv.notificationFilter = UA_APPLICATIONNOTIFICATIONTYPE_DISCOVERY;
md->mdns.addARecord = AvahiDriver_addARecord;
md->mdns.removeARecord = AvahiDriver_removeARecord;
md->mdns.addAAAARecord = AvahiDriver_addAAAARecord;
md->mdns.removeAAAARecord = AvahiDriver_removeAAAARecord;
return &md->mdns;
}
#endif /* UA_ENABLE_DISCOVERY_MULTICAST_AVAHI */
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -14,8 +14,12 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <unistd.h>
#include <string.h> #include <string.h>
#ifdef UA_ARCHITECTURE_WIN32
# include <winsock2.h>
#else
# include <unistd.h>
#endif
int main(void) { int main(void) {
+1 -23
View File
@@ -48,29 +48,7 @@ int main(void) {
config->applicationDescription.applicationName = config->applicationDescription.applicationName =
UA_LOCALIZEDTEXT_ALLOC("en-US", "Open62541 Local Discovery Server"); UA_LOCALIZEDTEXT_ALLOC("en-US", "Open62541 Local Discovery Server");
// Enable the mDNS announce and response functionality config->serversOnNetworkEnabled = true;
config->mdnsEnabled = true;
config->mdnsConfig.mdnsServerName = UA_String_fromChars("LDS");
// See http://www.opcfoundation.org/UA/schemas/1.03/ServerCapabilities.csv
// For a LDS server, you should only indicate the LDS capability.
// If this instance is an LDS and at the same time a normal OPC UA server, you also have to indicate
// the additional capabilities.
// NOTE: UaExpert does not show LDS-only servers in the list.
// See also: https://forum.unified-automation.com/topic1987.html
// E.g. here we only set LDS, and you will not see it in UaExpert
config->mdnsConfig.serverCapabilitiesSize = 1;
UA_String *caps = (UA_String *) UA_Array_new(1, &UA_TYPES[UA_TYPES_STRING]);
if(!caps) {
/* Allocation failed for server capabilities array.
* Clean up and exit to avoid running with invalid config. */
UA_Server_delete(server);
return EXIT_FAILURE;
}
caps[0] = UA_String_fromChars("LDS");
config->mdnsConfig.serverCapabilities = caps;
/* timeout in seconds when to automatically remove a registered server from /* timeout in seconds when to automatically remove a registered server from
* the list, if it doesn't re-register within the given time frame. A value * the list, if it doesn't re-register within the given time frame. A value
+46 -24
View File
@@ -14,6 +14,7 @@
#include <open62541/client.h> #include <open62541/client.h>
#include <open62541/client_config_default.h> #include <open62541/client_config_default.h>
#include <open62541/driver/mdns.h>
#include <open62541/plugin/log_stdout.h> #include <open62541/plugin/log_stdout.h>
#include <open62541/server.h> #include <open62541/server.h>
#include <open62541/server_config_default.h> #include <open62541/server_config_default.h>
@@ -33,6 +34,38 @@ static void stopHandler(int sign) {
running = false; running = false;
} }
static UA_StatusCode
addMdnsDiscoveryDriver(UA_Server *server) {
UA_Boolean enabled = true;
UA_UInt32 queryInterval = 30;
UA_KeyValuePair params[5];
params[0].key = UA_QUALIFIEDNAME(0, "listen");
UA_Variant_setScalar(&params[0].value, &enabled, &UA_TYPES[UA_TYPES_BOOLEAN]);
params[1].key = UA_QUALIFIEDNAME(0, "announce");
UA_Variant_setScalar(&params[1].value, &enabled, &UA_TYPES[UA_TYPES_BOOLEAN]);
params[2].key = UA_QUALIFIEDNAME(0, "query-presence");
UA_Variant_setScalar(&params[2].value, &enabled, &UA_TYPES[UA_TYPES_BOOLEAN]);
params[3].key = UA_QUALIFIEDNAME(0, "query-details");
UA_Variant_setScalar(&params[3].value, &enabled, &UA_TYPES[UA_TYPES_BOOLEAN]);
params[4].key = UA_QUALIFIEDNAME(0, "query-interval");
UA_Variant_setScalar(&params[4].value, &queryInterval,
&UA_TYPES[UA_TYPES_UINT32]);
UA_KeyValueMap paramsMap = {5, params};
#ifdef UA_ENABLE_DISCOVERY_MULTICAST_AVAHI
UA_MdnsDriver *mdns = UA_MdnsDriver_Avahi(paramsMap);
#else
UA_MdnsDriver *mdns = UA_MdnsDriver_Mdnsd(paramsMap);
#endif
if(!mdns)
return UA_STATUSCODE_BADOUTOFMEMORY;
UA_StatusCode res = UA_Server_addDriver(server, &mdns->drv);
if(res != UA_STATUSCODE_GOOD)
mdns->drv.free(&mdns->drv);
return res;
}
static char *discovery_url = NULL; static char *discovery_url = NULL;
static void static void
@@ -236,33 +269,22 @@ int main(int argc, char **argv) {
config->applicationDescription.applicationUri = config->applicationDescription.applicationUri =
UA_String_fromChars("urn:open62541.example.server_multicast"); UA_String_fromChars("urn:open62541.example.server_multicast");
// Enable the mDNS announce and response functionality /* Enable the local ServerOnNetwork table used by FindServersOnNetwork.
config->mdnsEnabled = true; * The mDNS discovery driver fills this table with announced servers. */
config->serversOnNetworkEnabled = true;
config->mdnsConfig.mdnsServerName = UA_String_fromChars("Sample-Multicast-Server"); UA_StatusCode retval = addMdnsDiscoveryDriver(server);
if(retval != UA_STATUSCODE_GOOD) {
#ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
// Use loopback interface for mDNS announcements by default. "Could not add the mDNS discovery driver. StatusCode %s",
// This only works when the LDS and this server run on the same device. UA_StatusCode_name(retval));
// For deployment in LAN or across multiple devices, replace "127.0.0.1" with the IP of your network interface, UA_Server_delete(server);
// such as "192.168.1.100" or the IP of wlan0/eth0. UA_free(discovery_url);
config->mdnsInterfaceIP = UA_String_fromChars("127.0.0.1"); return EXIT_FAILURE;
#endif }
// See http://www.opcfoundation.org/UA/schemas/1.03/ServerCapabilities.csv
// For a LDS server, you should only indicate the LDS capability.
// If this instance is an LDS and at the same time a normal OPC UA server, you also have to indicate
// the additional capabilities.
// NOTE: UaExpert does not show LDS-only servers in the list.
// See also: https://forum.unified-automation.com/topic1987.html
config->mdnsConfig.serverCapabilitiesSize = 1;
UA_String *caps = (UA_String *) UA_Array_new(1, &UA_TYPES[UA_TYPES_STRING]);
caps[0] = UA_String_fromChars("LDS");
config->mdnsConfig.serverCapabilities = caps;
// Start the server and call iterate to wait for the multicast discovery of the LDS // Start the server and call iterate to wait for the multicast discovery of the LDS
UA_StatusCode retval = UA_Server_run_startup(server); retval = UA_Server_run_startup(server);
// callback which is called when a new server is detected through mDNS // callback which is called when a new server is detected through mDNS
// needs to be set after UA_Server_run_startup or UA_Server_run // needs to be set after UA_Server_run_startup or UA_Server_run
-1
View File
@@ -74,7 +74,6 @@ int main(int argc, char **argv) {
UA_String_clear(&config->applicationDescription.applicationUri); UA_String_clear(&config->applicationDescription.applicationUri);
config->applicationDescription.applicationUri = config->applicationDescription.applicationUri =
UA_String_fromChars("urn:open62541.example.server_register"); UA_String_fromChars("urn:open62541.example.server_register");
config->mdnsConfig.mdnsServerName = UA_String_fromChars("Sample-Server");
// See http://www.opcfoundation.org/UA/schemas/1.04/ServerCapabilities.csv // See http://www.opcfoundation.org/UA/schemas/1.04/ServerCapabilities.csv
//config.serverCapabilitiesSize = 1; //config.serverCapabilitiesSize = 1;
//UA_String caps = UA_String_fromChars("LDS"); //UA_String caps = UA_String_fromChars("LDS");
+86
View File
@@ -0,0 +1,86 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2026 (c) o6 Automation GmbH (Author: Julius Pfrommer)
*/
#ifndef UA_DRIVER_MDNS_H_
#define UA_DRIVER_MDNS_H_
#include <open62541/server.h>
/**
* MDNS Driver
* -----------
* An OPC UA application can announce / retract a server via multicast DNS. Also
* it receives multicast-based information about other servers. This
* functionality is implemented as a driver that integrates with open62541
* servers.
* Internally, the drivers use the EventLoop of the server to create and manage
* UDP sockets. They further use the UA_APPLICATIONNOTIFICATIONTYPE_DISCOVERY
* category for server notifications to receive relevant updates from the server
* at runtime.
*
* The following configuration parameters are forwarded in a key-value map
* during driver instantiation and/or set in the key-value map inside the
* generic driver structure before starting the driver.
*
* 0:listen [bool]
* Receive information about OPC UA servers on the network via mDNS. Calls
* UA_Server_registerServerOnNetwork and UA_Server_deregisterServerOnNetwork
* to store the server information locally.
*
* 0:announce [bool]
* Announce registered servers over mDNS. This receives register/deregister
* notifications from UA_Server_registerServerOnNetwork and forwards the
* information.
*
* 0:announce-ttl [uint32]
* Default TTL in seconds for announced mDNS records.
*
* 0:query-presence [bool]
* Send an mDNS PTR query for _opcua-tcp._tcp.local. on startup. Defaults to
* false.
*
* 0:query-details [bool]
* Send targeted SRV/TXT follow-up queries for partially received OPC UA
* service instances. Defaults to false.
*
* 0:query-interval [uint32]
* Interval in seconds for repeated presence queries. A value of zero
* disables repeated queries. Defaults to zero.
*
* 0:interface [string]
* Network interface for listening or sending. Can be either the IP address
* of the network interface or the interface name (e.g. 'eth0'). Cf. the UDP
* ConnectionManager parameters. */
typedef struct UA_MdnsDriver UA_MdnsDriver;
struct UA_MdnsDriver {
UA_Driver drv; /* Must be the first member */
/* Manually publish/remove address records. This is separate from the
* ServerOnNetwork announcement path as PTR/SRV/TXT records are always
* derived from the ServerOnNetwork data. The address strings are numeric
* IPv4/IPv6 literals. */
UA_StatusCode (*addARecord)(UA_MdnsDriver *mdns, UA_String hostname,
UA_String address, UA_UInt32 ttl);
UA_StatusCode (*removeARecord)(UA_MdnsDriver *mdns, UA_String hostname,
UA_String address);
UA_StatusCode (*addAAAARecord)(UA_MdnsDriver *mdns, UA_String hostname,
UA_String address, UA_UInt32 ttl);
UA_StatusCode (*removeAAAARecord)(UA_MdnsDriver *mdns, UA_String hostname,
UA_String address);
};
/* Based on the libmdnsd dependency */
UA_EXPORT UA_MdnsDriver *
UA_MdnsDriver_Mdnsd(const UA_KeyValueMap params);
/* Based on the libavahi dependency */
UA_EXPORT UA_MdnsDriver *
UA_MdnsDriver_Avahi(const UA_KeyValueMap params);
#endif /* UA_DRIVER_MDNS_H_ */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-6
View File
@@ -209,12 +209,6 @@ discoveryClientStateCallback(UA_Client *client,
respType = &UA_TYPES[UA_TYPES_REGISTERSERVER2RESPONSE]; respType = &UA_TYPES[UA_TYPES_REGISTERSERVER2RESPONSE];
request = &reg2; request = &reg2;
/* /\* Set the configuration that is only available for */
/* * UA_RegisterServer2Request *\/ */
/* UA_ExtensionObject_setValueNoDelete(&mdnsConfig, &sc->mdnsSelfConfig, */
/* &UA_TYPES[UA_TYPES_MDNSDISCOVERYCONFIGURATION]); */
/* reg2.discoveryConfigurationSize = 1; */
/* reg2.discoveryConfiguration = &mdnsConfig; */
} else { } else {
UA_RegisterServerRequest_init(&reg1); UA_RegisterServerRequest_init(&reg1);
setupRegisterRequest(ar, &reg1.requestHeader, &reg1.server); setupRegisterRequest(ar, &reg1.requestHeader, &reg1.server);
+10
View File
@@ -283,6 +283,16 @@ notifyServerOnNetwork(UA_Server *server,
notifyApplication(server, notifyApplication(server,
UA_APPLICATIONNOTIFICATIONTYPE_DISCOVERY_SERVERONNETWORK, UA_APPLICATIONNOTIFICATIONTYPE_DISCOVERY_SERVERONNETWORK,
kvm); kvm);
/* Specialized application callback for discovery notifications. Called
* directly (not via notifyApplication) to avoid recursion: the driver's
* own notification callback may itself trigger a register/deregister
* that flows back into notifyServerOnetwork. */
UA_ServerConfig *config = &server->config;
if(config->discoveryNotificationCallback)
config->discoveryNotificationCallback(server,
UA_APPLICATIONNOTIFICATIONTYPE_DISCOVERY_SERVERONNETWORK,
kvm);
} }
static void static void
+2 -1
View File
@@ -493,7 +493,8 @@ if(UA_ENABLE_ENCRYPTION)
endif() endif()
endif() endif()
if(UA_ENABLE_DISCOVERY AND UA_ENABLE_DISCOVERY_MULTICAST_MDNSD) if(UA_ENABLE_DISCOVERY AND (UA_ENABLE_DISCOVERY_MULTICAST_MDNSD OR
UA_ENABLE_DISCOVERY_MULTICAST_AVAHI))
ua_add_test(server/check_discovery_mdnsd.c) ua_add_test(server/check_discovery_mdnsd.c)
endif() endif()
-11
View File
@@ -55,8 +55,6 @@ static void start_server(void) {
UA_ServerConfig *config = UA_Server_getConfig(server); UA_ServerConfig *config = UA_Server_getConfig(server);
config->applicationDescription.applicationType = UA_APPLICATIONTYPE_SERVER; config->applicationDescription.applicationType = UA_APPLICATIONTYPE_SERVER;
config->mdnsEnabled = true;
config->mdnsConfig.mdnsServerName = UA_String_fromChars("Sample-Multicast-Server");
UA_Server_run_startup(server); UA_Server_run_startup(server);
pthread_create(&server_thread, NULL, serverloop, NULL); pthread_create(&server_thread, NULL, serverloop, NULL);
@@ -197,14 +195,6 @@ registerServer2Request(UA_Client *client) {
initUaRegisterServer(&request.server); initUaRegisterServer(&request.server);
request.discoveryConfigurationSize = 1;
request.discoveryConfiguration = UA_ExtensionObject_new();
UA_ExtensionObject_init(&request.discoveryConfiguration[0]);
// Set to NODELETE so that we can just use a pointer to the mdns config
request.discoveryConfiguration[0].encoding = UA_EXTENSIONOBJECT_DECODED_NODELETE;
request.discoveryConfiguration[0].content.decoded.type = &UA_TYPES[UA_TYPES_MDNSDISCOVERYCONFIGURATION];
request.discoveryConfiguration[0].content.decoded.data = &server->config.mdnsConfig;
// First try with RegisterServer2, if that isn't implemented, use RegisterServer // First try with RegisterServer2, if that isn't implemented, use RegisterServer
UA_RegisterServer2Response response; UA_RegisterServer2Response response;
UA_RegisterServer2Response_init(&response); UA_RegisterServer2Response_init(&response);
@@ -213,7 +203,6 @@ registerServer2Request(UA_Client *client) {
ASSERT_GOOD(response.responseHeader.serviceResult); ASSERT_GOOD(response.responseHeader.serviceResult);
UA_free(request.server.discoveryUrls); UA_free(request.server.discoveryUrls);
UA_ExtensionObject_delete(request.discoveryConfiguration);
UA_RegisterServer2Response_clear(&response); UA_RegisterServer2Response_clear(&response);
-6
View File
@@ -66,12 +66,6 @@ LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
UA_ServerConfig *config = UA_Server_getConfig(server);
// Enable the mDNS announce and response functionality
config->mdnsEnabled = true;
config->mdnsConfig.mdnsServerName = UA_String_fromChars("Sample-Multicast-Server");
retval = UA_Server_run_startup(server); retval = UA_Server_run_startup(server);
if(retval != UA_STATUSCODE_GOOD) { if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
File diff suppressed because it is too large Load Diff
-19
View File
@@ -82,25 +82,6 @@
asyncOperationTimeout: 120000, asyncOperationTimeout: 120000,
maxAsyncOperationQueueSize: 1000000, maxAsyncOperationQueueSize: 1000000,
// Discovery Multicast
mdnsEnabled: false,
mdns: {
mdnsServerName: "mdnsServerName",
// See https://reference.opcfoundation.org/GDS/v105/docs/D
serverCapabilities: [
// "NA" should be first and only server capability:
//"NA"
"LDS",
"GDS"
],
mdnsInterfaceIP: "127.0.0.1",
mdnsIpAddressList: [
1,
2,
3
]
},
subscriptionsEnabled: true, subscriptionsEnabled: true,
subscriptions: { subscriptions: {
// Limits for Subscriptions // Limits for Subscriptions
-36
View File
@@ -137,15 +137,6 @@
"description": "Discovery cleanup timeout in milliseconds (only if discovery enabled)", "description": "Discovery cleanup timeout in milliseconds (only if discovery enabled)",
"minimum": 0 "minimum": 0
}, },
"mdnsEnabled": {
"type": "boolean",
"description": "Enable mDNS discovery (only if multicast discovery enabled)",
"default": false
},
"mdns": {
"$ref": "#/definitions/MdnsConfiguration",
"description": "mDNS configuration (only if multicast discovery enabled)"
},
"subscriptionsEnabled": { "subscriptionsEnabled": {
"type": "boolean", "type": "boolean",
"description": "Enable subscriptions (only if subscriptions enabled)", "description": "Enable subscriptions (only if subscriptions enabled)",
@@ -313,33 +304,6 @@
} }
} }
}, },
"MdnsConfiguration": {
"type": "object",
"properties": {
"mdnsServerName": {
"type": "string",
"description": "mDNS server name"
},
"serverCapabilities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Server capabilities"
},
"mdnsInterfaceIP": {
"type": "string",
"description": "mDNS interface IP address"
},
"mdnsIpAddressList": {
"type": "array",
"items": {
"type": "integer"
},
"description": "mDNS IP address list (only if UA_HAS_GETIFADDR not defined)"
}
}
},
"SubscriptionConfiguration": { "SubscriptionConfiguration": {
"type": "object", "type": "object",
"properties": { "properties": {