From 0b33021dce94c0042edf6aed324a0fa2ab84132a Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 25 Oct 2023 11:40:46 +0200 Subject: [PATCH 01/10] feat(server): Only allow subscription transfer if the same user was used --- plugins/ua_accesscontrol_default.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/plugins/ua_accesscontrol_default.c b/plugins/ua_accesscontrol_default.c index 30b58bb03e6..c78f60c9a5a 100644 --- a/plugins/ua_accesscontrol_default.c +++ b/plugins/ua_accesscontrol_default.c @@ -224,7 +224,20 @@ static UA_Boolean allowTransferSubscription_default(UA_Server *server, UA_AccessControl *ac, const UA_NodeId *oldSessionId, void *oldSessionContext, const UA_NodeId *newSessionId, void *newSessionContext) { - return true; + /* Allow the transfer if the same user-id was used to activate both sessions */ + UA_Variant session1UserId; + UA_Variant_init(&session1UserId); + UA_Server_getSessionAttribute(server, oldSessionId, + UA_QUALIFIEDNAME(0, "clientUserId"), + &session1UserId); + UA_Variant session2UserId; + UA_Variant_init(&session2UserId); + UA_Server_getSessionAttribute(server, newSessionId, + UA_QUALIFIEDNAME(0, "clientUserId"), + &session2UserId); + + return (UA_order(&session1UserId, &session2UserId, + &UA_TYPES[UA_TYPES_VARIANT]) == UA_ORDER_EQ); } #endif From 0c13cc61c2ab95236169e83e28e2edddf06e7f60 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 23 Oct 2023 22:37:59 +0200 Subject: [PATCH 02/10] refactor(server): Instantiate an internal client to register at a DiscoveryServer --- CMakeLists.txt | 2 +- examples/discovery/server_multicast.c | 59 ++-- examples/discovery/server_register.c | 41 +-- examples/server_ctt.c | 50 ++- include/open62541/server.h | 80 ++--- src/server/ua_discovery_manager.c | 41 ++- src/server/ua_discovery_manager.h | 30 +- src/server/ua_server_discovery.c | 344 ++++++++++++++----- src/server/ua_server_internal.h | 5 - src/server/ua_services_discovery.c | 181 ---------- src/server/ua_services_discovery_multicast.c | 13 +- tests/server/check_discovery.c | 140 +++----- 12 files changed, 446 insertions(+), 540 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ef3bd5b6d18..e02594f7b8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -774,8 +774,8 @@ set(exported_headers ${PROJECT_BINARY_DIR}/src_generated/open62541/config.h ${PROJECT_SOURCE_DIR}/include/open62541/plugin/nodestore.h ${PROJECT_SOURCE_DIR}/include/open62541/plugin/historydatabase.h ${PROJECT_SOURCE_DIR}/include/open62541/server_pubsub.h - ${PROJECT_SOURCE_DIR}/include/open62541/server.h ${PROJECT_SOURCE_DIR}/include/open62541/client.h + ${PROJECT_SOURCE_DIR}/include/open62541/server.h ${PROJECT_SOURCE_DIR}/include/open62541/client_highlevel.h ${PROJECT_SOURCE_DIR}/include/open62541/client_subscriptions.h ${PROJECT_SOURCE_DIR}/include/open62541/client_highlevel_async.h) diff --git a/examples/discovery/server_multicast.c b/examples/discovery/server_multicast.c index 7a3ff907f24..1395fcfe582 100644 --- a/examples/discovery/server_multicast.c +++ b/examples/discovery/server_multicast.c @@ -70,8 +70,8 @@ serverOnNetworkCallback(const UA_ServerOnNetwork *serverOnNetwork, UA_Boolean is * @param discoveryServerUrl The discovery url from the remote server * @return The endpoint description (which needs to be freed) or NULL */ -static -UA_EndpointDescription *getRegisterEndpointFromServer(const char *discoveryServerUrl) { +static UA_EndpointDescription * +getRegisterEndpointFromServer(const char *discoveryServerUrl) { UA_Client *client = UA_Client_new(); UA_ClientConfig_setDefault(UA_Client_getConfig(client)); UA_EndpointDescription *endpointArray = NULL; @@ -161,19 +161,19 @@ static UA_ByteString loadFile(const char *const path) { * @param argv from the main method * @return NULL or the initialized non-connected client */ -static -UA_Client *getRegisterClient(UA_EndpointDescription *endpointRegister, int argc, char **argv) { +static UA_StatusCode +getRegisterClient(UA_ClientConfig *cc, UA_EndpointDescription *endpointRegister, int argc, char **argv) { + UA_ClientConfig_setDefault(cc); if(endpointRegister->securityMode == UA_MESSAGESECURITYMODE_NONE) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Using LDS endpoint with security None"); - UA_Client *client = UA_Client_new(); - UA_ClientConfig_setDefault(UA_Client_getConfig(client)); - return client; + return UA_STATUSCODE_GOOD; } + #ifdef UA_ENABLE_ENCRYPTION if(endpointRegister->securityMode == UA_MESSAGESECURITYMODE_SIGN) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "LDS endpoint which only supports Sign is currently not supported"); - return NULL; + return UA_STATUSCODE_BADINTERNALERROR; } UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, @@ -185,7 +185,7 @@ UA_Client *getRegisterClient(UA_EndpointDescription *endpointRegister, int argc, "The required arguments are " " " "[, ...]"); - return NULL; + return UA_STATUSCODE_BADINTERNALERROR; } /* Load certificate and key */ @@ -203,8 +203,6 @@ UA_Client *getRegisterClient(UA_EndpointDescription *endpointRegister, int argc, trustList[trustListCount] = loadFile(argv[trustListCount + 3]); /* Secure client initialization */ - UA_Client *clientRegister = UA_Client_new(); - UA_ClientConfig *cc = UA_Client_getConfig(clientRegister); UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey, trustList, trustListSize, revocationList, revocationListSize); @@ -214,11 +212,9 @@ UA_Client *getRegisterClient(UA_EndpointDescription *endpointRegister, int argc, UA_ByteString_clear(&privateKey); for(size_t deleteCount = 0; deleteCount < trustListSize; deleteCount++) UA_ByteString_clear(&trustList[deleteCount]); - - return clientRegister; -#else - return NULL; #endif + + return UA_STATUSCODE_GOOD; } int main(int argc, char **argv) { @@ -300,49 +296,46 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } - UA_Client *clientRegister = getRegisterClient(endpointRegister, argc, argv); - if(!clientRegister) { + UA_ClientConfig cc; + memset(&cc, 0, sizeof(UA_ClientConfig)); + retval = getRegisterClient(&cc, endpointRegister, argc, argv); + if(retval != UA_STATUSCODE_GOOD) { UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Could not create the client for remote registering"); + UA_ClientConfig_clear(&cc); UA_Server_run_shutdown(server); UA_Server_delete(server); + UA_EndpointDescription_delete(endpointRegister); return EXIT_FAILURE; } - /* Connect the client */ - char *endpointUrl = (char*)UA_malloc(endpointRegister->endpointUrl.length + 1); - memcpy(endpointUrl, endpointRegister->endpointUrl.data, endpointRegister->endpointUrl.length); - endpointUrl[endpointRegister->endpointUrl.length] = 0; - UA_EndpointDescription_delete(endpointRegister); - retval = UA_Server_addPeriodicServerRegisterCallback(server, clientRegister, endpointUrl, - 10 * 60 * 1000, 500, NULL); + /* Register the server */ + retval = UA_Server_registerDiscovery(server, &cc, endpointRegister->endpointUrl, UA_STRING_NULL); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not create periodic job for server register. StatusCode %s", UA_StatusCode_name(retval)); - UA_free(endpointUrl); - UA_Client_disconnect(clientRegister); - UA_Client_delete(clientRegister); UA_Server_run_shutdown(server); UA_Server_delete(server); + UA_EndpointDescription_delete(endpointRegister); return EXIT_FAILURE; } - while (running) + while(running) UA_Server_run_iterate(server, true); UA_Server_run_shutdown(server); - // UNregister the server from the discovery server. - retval = UA_Server_unregister_discovery(server, clientRegister); + /* Deregister the server from the discovery server */ + memset(&cc, 0, sizeof(UA_ClientConfig)); + retval = getRegisterClient(&cc, endpointRegister, argc, argv); + retval |= UA_Server_deregisterDiscovery(server, &cc, endpointRegister->endpointUrl); if(retval != UA_STATUSCODE_GOOD) UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not unregister server from discovery server. " "StatusCode %s", UA_StatusCode_name(retval)); - UA_free(endpointUrl); - UA_Client_disconnect(clientRegister); - UA_Client_delete(clientRegister); UA_Server_delete(server); + UA_EndpointDescription_delete(endpointRegister); return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/examples/discovery/server_register.c b/examples/discovery/server_register.c index dca76f58cec..476f10a675d 100644 --- a/examples/discovery/server_register.c +++ b/examples/discovery/server_register.c @@ -97,50 +97,39 @@ int main(int argc, char **argv) { myIntegerName, UA_NODEID_NULL, attr, dateDataSource, &myInteger, NULL); - UA_Client *clientRegister = UA_Client_new(); - UA_ClientConfig_setDefault(UA_Client_getConfig(clientRegister)); + UA_Server_run_startup(server); + + // register server + UA_ClientConfig cc; + memset(&cc, 0, sizeof(UA_ClientConfig)); + UA_ClientConfig_setDefault(&cc); - // periodic server register after 10 Minutes, delay first register for 500ms - UA_UInt64 callbackId; UA_StatusCode retval = - UA_Server_addPeriodicServerRegisterCallback(server, clientRegister, DISCOVERY_SERVER_ENDPOINT, - 10 * 60 * 1000, 500, &callbackId); - // UA_StatusCode retval = UA_Server_addPeriodicServerRegisterJob(server, - // "opc.tcp://localhost:4840", 10*60*1000, 500, NULL); + UA_Server_registerDiscovery(server, &cc, + UA_STRING(DISCOVERY_SERVER_ENDPOINT), UA_STRING_NULL); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not create periodic job for server register. StatusCode %s", UA_StatusCode_name(retval)); - UA_Client_disconnect(clientRegister); - UA_Client_delete(clientRegister); UA_Server_delete(server); return EXIT_FAILURE; } - retval = UA_Server_run(server, &running); - - if(retval != UA_STATUSCODE_GOOD) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "Could not start the server. StatusCode %s", - UA_StatusCode_name(retval)); - UA_Client_disconnect(clientRegister); - UA_Client_delete(clientRegister); - UA_Server_delete(server); - return EXIT_FAILURE; - } + while(running) + UA_Server_run_iterate(server, true); // Unregister the server from the discovery server. - retval = UA_Server_unregister_discovery(server, clientRegister); + memset(&cc, 0, sizeof(UA_ClientConfig)); + UA_ClientConfig_setDefault(&cc); + retval = UA_Server_deregisterDiscovery(server, &cc, + UA_STRING(DISCOVERY_SERVER_ENDPOINT)); //retval = UA_Server_unregister_discovery(server, "opc.tcp://localhost:4840" ); if(retval != UA_STATUSCODE_GOOD) UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not unregister server from discovery server. StatusCode %s", UA_StatusCode_name(retval)); - UA_Server_removeCallback(server, callbackId); - - UA_Client_disconnect(clientRegister); - UA_Client_delete(clientRegister); + UA_Server_run_shutdown(server); UA_Server_delete(server); return EXIT_SUCCESS; } diff --git a/examples/server_ctt.c b/examples/server_ctt.c index 075e2dee2fc..f2b5e3a5c45 100644 --- a/examples/server_ctt.c +++ b/examples/server_ctt.c @@ -35,8 +35,6 @@ static UA_UsernamePasswordLogin usernamePasswords[2] = { static const UA_NodeId baseDataVariableType = {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_BASEDATAVARIABLETYPE}}; static const UA_NodeId accessDenied = {1, UA_NODEIDTYPE_NUMERIC, {1337}}; -static UA_Client *ldsClientRegister; -static UA_UInt64 ldsCallbackId; /* Custom AccessControl policy that disallows access to one specific node */ static UA_Byte @@ -853,24 +851,30 @@ setInformationModel(UA_Server *server) { #endif } +static UA_Boolean hasRegistered = false; + +static void +notifyState(UA_Server *server, UA_LifecycleState state) { #ifdef UA_ENABLE_DISCOVERY -static void configureLdsRegistration(UA_Server *server){ - ldsClientRegister = UA_Client_new(); - UA_ClientConfig_setDefault(UA_Client_getConfig(ldsClientRegister)); - - // periodic server register after 1 Minutes, delay first register for 500ms - UA_StatusCode retval = - UA_Server_addPeriodicServerRegisterCallback(server, ldsClientRegister, "opc.tcp://localhost:4840", - 60 * 1000, 500, &ldsCallbackId); - if(retval != UA_STATUSCODE_GOOD) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "Could not create periodic job for server register. StatusCode %s", - UA_StatusCode_name(retval)); - UA_Client_disconnect(ldsClientRegister); - UA_Client_delete(ldsClientRegister); + if(state == UA_LIFECYCLESTATE_STARTED && !hasRegistered) { + UA_ClientConfig cc; + memset(&cc, 0, sizeof(UA_ClientConfig)); + UA_ClientConfig_setDefault(&cc); + UA_Server_registerDiscovery(server, &cc, + UA_STRING("opc.tcp://localhost:4840"), + UA_STRING_NULL); + hasRegistered = true; + } + if(state != UA_LIFECYCLESTATE_STARTED && hasRegistered) { + UA_ClientConfig cc; + memset(&cc, 0, sizeof(UA_ClientConfig)); + UA_ClientConfig_setDefault(&cc); + UA_Server_deregisterDiscovery(server, &cc, + UA_STRING("opc.tcp://localhost:4840")); + hasRegistered = false; } -} #endif +} #ifdef UA_ENABLE_ENCRYPTION static void @@ -1397,24 +1401,16 @@ int main(int argc, char **argv) { config->applicationDescription.applicationUri = UA_String_fromChars("urn:open62541.server.application"); + /* Lifecycle config */ config->shutdownDelay = 5000.0; /* 5s */ + config->notifyLifecycleState = notifyState; setInformationModel(server); -#ifdef UA_ENABLE_DISCOVERY - - configureLdsRegistration(server); - -#endif - /* run server */ res = UA_Server_runUntilInterrupt(server); cleanup: - UA_Server_removeCallback(server, ldsCallbackId); - UA_Server_unregister_discovery(server, ldsClientRegister); - UA_Client_disconnect(ldsClientRegister); - UA_Client_delete(ldsClientRegister); UA_Server_delete(server); UA_ByteString_clear(&certificate); diff --git a/include/open62541/server.h b/include/open62541/server.h index 741981f04f8..6eac5064561 100644 --- a/include/open62541/server.h +++ b/include/open62541/server.h @@ -27,6 +27,8 @@ #include #include +#include + #ifdef UA_ENABLE_PUBSUB #include #endif @@ -991,64 +993,38 @@ UA_Server_forEachChildNodeCall(UA_Server *server, UA_NodeId parentNodeId, /** * Discovery - * --------- */ -/* Register the given server instance at the discovery server. - * This should be called periodically. - * The semaphoreFilePath is optional. If the given file is deleted, - * the server will automatically be unregistered. This could be - * for example a pid file which is deleted if the server crashes. + * --------- * - * When the server shuts down you need to call unregister. + * Registering at a Discovery Server + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ + +/* Register the given server instance at the discovery server. This should be + * called periodically, for example every 10 minutes, depending on the + * configuration of the discovery server. You should also call + * _unregisterDiscovery when the server shuts down. * - * @param server - * @param client the client which is used to call the RegisterServer. It must - * already be connected to the correct endpoint - * @param semaphoreFilePath optional parameter pointing to semaphore file. */ + * The supplied client configuration is used to create a new client to connect + * to the discovery server. The client configuration is moved over to the server + * and eventually cleaned up internally. The structure pointed at by `cc` is + * zeroed to avoid accessing outdated information. + * + * The eventloop and logging plugins in the client configuration are replaced by + * those configured in the server. */ UA_StatusCode UA_EXPORT UA_THREADSAFE -UA_Server_register_discovery(UA_Server *server, struct UA_Client *client, - const char* semaphoreFilePath); +UA_Server_registerDiscovery(UA_Server *server, UA_ClientConfig *cc, + const UA_String discoveryServerUrl, + const UA_String semaphoreFilePath); -/* Unregister the given server instance from the discovery server. - * This should only be called when the server is shutting down. - * @param server - * @param client the client which is used to call the RegisterServer. It must - * already be connected to the correct endpoint */ +/* Deregister the given server instance from the discovery server. + * This should be called when the server is shutting down. */ UA_StatusCode UA_EXPORT UA_THREADSAFE -UA_Server_unregister_discovery(UA_Server *server, struct UA_Client *client); +UA_Server_deregisterDiscovery(UA_Server *server, UA_ClientConfig *cc, + const UA_String discoveryServerUrl); - /* Adds a periodic callback to register the server with the LDS (local - * discovery server) periodically. The interval between each register call is - * given as second parameter. It should be 10 minutes by default (= - * 10*60*1000). - * - * The delayFirstRegisterMs parameter indicates the delay for the first - * register call. If it is 0, the first register call will be after intervalMs - * milliseconds, otherwise the server's first register will be after - * delayFirstRegisterMs. - * - * When you manually unregister the server, you also need to cancel the - * periodic callback, otherwise it will be automatically be registered again. - * - * If you call this method multiple times for the same discoveryServerUrl, the - * older periodic callback will be removed. - * - * @param server - * @param client the client which is used to call the RegisterServer. It must - * not yet be connected and will be connected for every register call - * to the given discoveryServerUrl. - * @param discoveryServerUrl where this server should register itself. The - * string will be copied internally. Therefore you can free it after - * calling this method. - * @param intervalMs - * @param delayFirstRegisterMs - * @param periodicCallbackId */ -UA_StatusCode UA_EXPORT UA_THREADSAFE -UA_Server_addPeriodicServerRegisterCallback(UA_Server *server, - struct UA_Client *client, - const char* discoveryServerUrl, - UA_Double intervalMs, - UA_Double delayFirstRegisterMs, - UA_UInt64 *periodicCallbackId); +/** + * Operating a Discovery Server + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + */ /* Callback for RegisterServer. Data is passed from the register call */ typedef void diff --git a/src/server/ua_discovery_manager.c b/src/server/ua_discovery_manager.c index e9c7e9bfff3..e58f772f579 100644 --- a/src/server/ua_discovery_manager.c +++ b/src/server/ua_discovery_manager.c @@ -11,6 +11,7 @@ * Copyright 2017 (c) Julian Grothoff */ +#include #include "ua_discovery_manager.h" #include "ua_server_internal.h" @@ -20,8 +21,26 @@ void UA_DiscoveryManager_setState(UA_Server *server, UA_DiscoveryManager *dm, UA_LifecycleState state) { + /* Check if open connections remain */ + if(state == UA_LIFECYCLESTATE_STOPPING || + state == UA_LIFECYCLESTATE_STOPPED) { + state = UA_LIFECYCLESTATE_STOPPED; +#ifdef UA_ENABLE_DISCOVERY_MULTICAST + if(dm->mdnsRecvConnectionsSize != 0 || dm->mdnsSendConnection != 0) + state = UA_LIFECYCLESTATE_STOPPING; +#endif + + for(size_t i = 0; i < UA_MAXREGISTERREQUESTS; i++) { + if(dm->registerRequests[i].client != NULL) + state = UA_LIFECYCLESTATE_STOPPING; + } + } + + /* No change */ if(state == dm->sc.state) return; + + /* Set the new state and notify */ dm->sc.state = state; if(dm->sc.notifyState) dm->sc.notifyState(server, &dm->sc, state); @@ -45,14 +64,6 @@ UA_DiscoveryManager_free(UA_Server *server, UA_RegisteredServer_clear(&rs->registeredServer); UA_free(rs); } - periodicServerRegisterCallback_entry *ps, *ps_tmp; - LIST_FOREACH_SAFE(ps, &dm->periodicServerRegisterCallbacks, pointers, ps_tmp) { - LIST_REMOVE(ps, pointers); - if(ps->callback->discovery_server_url) - UA_free(ps->callback->discovery_server_url); - UA_free(ps->callback); - UA_free(ps); - } # ifdef UA_ENABLE_DISCOVERY_MULTICAST serverOnNetwork_list_entry *son, *son_tmp; @@ -181,19 +192,19 @@ UA_DiscoveryManager_stop(UA_Server *server, UA_DiscoveryManager *dm = (UA_DiscoveryManager*)sc; removeCallback(server, dm->discoveryCallbackId); + /* Cancel all outstanding register requests */ + for(size_t i = 0; i < UA_MAXREGISTERREQUESTS; i++) { + if(dm->registerRequests[i].client == NULL) + continue; + UA_Client_disconnectSecureChannelAsync(dm->registerRequests[i].client); + } + #ifdef UA_ENABLE_DISCOVERY_MULTICAST if(server->config.mdnsEnabled) stopMulticastDiscoveryServer(server); #endif -#ifdef UA_ENABLE_DISCOVERY_MULTICAST - if(dm->mdnsRecvConnectionsSize == 0 && dm->mdnsSendConnection == 0) - UA_DiscoveryManager_setState(server, dm, UA_LIFECYCLESTATE_STOPPED); - else - UA_DiscoveryManager_setState(server, dm, UA_LIFECYCLESTATE_STOPPING); -#else UA_DiscoveryManager_setState(server, dm, UA_LIFECYCLESTATE_STOPPED); -#endif } UA_ServerComponent * diff --git a/src/server/ua_discovery_manager.h b/src/server/ua_discovery_manager.h index e4bc0c35174..0a6214a3f8f 100644 --- a/src/server/ua_discovery_manager.h +++ b/src/server/ua_discovery_manager.h @@ -26,19 +26,17 @@ typedef struct registeredServer_list_entry { UA_DateTime lastSeen; } registeredServer_list_entry; -struct PeriodicServerRegisterCallback { - UA_UInt64 id; - UA_Double this_interval; - UA_Double default_interval; - UA_Boolean registered; - struct UA_Client* client; - char* discovery_server_url; -}; - -typedef struct periodicServerRegisterCallback_entry { - LIST_ENTRY(periodicServerRegisterCallback_entry) pointers; - struct PeriodicServerRegisterCallback *callback; -} periodicServerRegisterCallback_entry; +/* Store async register service calls. So we can cancel outstanding requests + * during shutdown. */ +typedef struct { + UA_DelayedCallback cleanupCallback; /* delayed cleanup */ + UA_Server *server; + UA_DiscoveryManager *dm; + UA_Client *client; + UA_String semaphoreFilePath; + UA_Boolean unregister; +} asyncRegisterRequest; +#define UA_MAXREGISTERREQUESTS 4 #ifdef UA_ENABLE_DISCOVERY_MULTICAST @@ -79,9 +77,11 @@ struct UA_DiscoveryManager { /* Taken from the server config during startup */ UA_Logger *logging; - const UA_ServerConfig *serverConfig; + UA_ServerConfig *serverConfig; + + /* Outstanding requests. So they can be cancelled during shutdown. */ + asyncRegisterRequest registerRequests[UA_MAXREGISTERREQUESTS]; - LIST_HEAD(, periodicServerRegisterCallback_entry) periodicServerRegisterCallbacks; LIST_HEAD(, registeredServer_list_entry) registeredServers; size_t registeredServersSize; UA_Server_registerServerCallback registerServerCallback; diff --git a/src/server/ua_server_discovery.c b/src/server/ua_server_discovery.c index 4db20985d76..50fd68ed727 100644 --- a/src/server/ua_server_discovery.c +++ b/src/server/ua_server_discovery.c @@ -9,116 +9,292 @@ #include -#include "ua_server_internal.h" +#include "ua_discovery_manager.h" #ifdef UA_ENABLE_DISCOVERY -UA_StatusCode -register_server_with_discovery_server(UA_Server *server, - void *pClient, - const UA_Boolean isUnregister, - const char* semaphoreFilePath) { - UA_Client *client = (UA_Client *) pClient; - - /* Prepare the request. Do not cleanup the request after the service call, - * as the members are stack-allocated or point into the server config. */ - UA_RegisterServer2Request request; - UA_RegisterServer2Request_init(&request); - request.requestHeader.timeoutHint = 10000; - - request.server.isOnline = !isUnregister; - request.server.serverUri = server->config.applicationDescription.applicationUri; - request.server.productUri = server->config.applicationDescription.productUri; - request.server.serverType = server->config.applicationDescription.applicationType; - request.server.gatewayServerUri = server->config.applicationDescription.gatewayServerUri; - - if(semaphoreFilePath) { -#ifdef UA_ENABLE_DISCOVERY_SEMAPHORE - request.server.semaphoreFilePath = - UA_STRING((char*)(uintptr_t)semaphoreFilePath); /* dirty cast */ -#else - UA_LOG_WARNING(&server->config.logger, UA_LOGCATEGORY_CLIENT, - "Ignoring semaphore file path. open62541 not compiled " - "with UA_ENABLE_DISCOVERY_SEMAPHORE=ON"); -#endif +static void +asyncRegisterRequest_clear(void *app, void *context) { + UA_Server *server = (UA_Server*)app; + asyncRegisterRequest *ar = (asyncRegisterRequest*)context; + UA_DiscoveryManager *dm = ar->dm; + + UA_String_clear(&ar->semaphoreFilePath); + if(ar->client) + UA_Client_delete(ar->client); + memset(ar, 0, sizeof(asyncRegisterRequest)); + + /* The Discovery manager is fully stopped? */ + UA_DiscoveryManager_setState(server, dm, dm->sc.state); +} + +static void +asyncRegisterRequest_clearAsync(asyncRegisterRequest *ar) { + UA_Server *server = ar->server; + UA_ServerConfig *sc = &server->config; + UA_EventLoop *el = sc->eventLoop; + + ar->cleanupCallback.callback = asyncRegisterRequest_clear; + ar->cleanupCallback.application = server; + ar->cleanupCallback.context = ar; + el->addDelayedCallback(el, &ar->cleanupCallback); +} + +static void +registerAsyncResponse(UA_Client *client, void *userdata, + UA_UInt32 requestId, void *resp) { + asyncRegisterRequest *ar = (asyncRegisterRequest*)userdata; + const UA_ServerConfig *sc = ar->dm->serverConfig; + UA_RegisterServerResponse *response = (UA_RegisterServerResponse*)resp; + if(response->responseHeader.serviceResult == UA_STATUSCODE_GOOD) { + UA_LOG_INFO(&sc->logger, UA_LOGCATEGORY_SERVER, + "RegisterServer succeeded"); + } else { + UA_LOG_WARNING(&sc->logger, UA_LOGCATEGORY_SERVER, + "RegisterServer failed with statuscode %s", + UA_StatusCode_name(response->responseHeader.serviceResult)); } - request.server.serverNames = &server->config.applicationDescription.applicationName; - request.server.serverNamesSize = 1; + /* Close the client connection, will be cleaned up in the client state + * callback when closing is complete */ + UA_Client_disconnectSecureChannelAsync(ar->client); +} + +static void +setupRegisterRequest(asyncRegisterRequest *ar, UA_RequestHeader *rh, + UA_RegisteredServer *rs) { + UA_ServerConfig *sc = ar->dm->serverConfig; - /* Mirror the discovery urls from the server config (includes hostnames from + rh->timeoutHint = 10000; + + rs->isOnline = !ar->unregister; + rs->serverUri = sc->applicationDescription.applicationUri; + rs->productUri = sc->applicationDescription.productUri; + rs->serverType = sc->applicationDescription.applicationType; + rs->gatewayServerUri = sc->applicationDescription.gatewayServerUri; + rs->semaphoreFilePath = ar->semaphoreFilePath; + + rs->serverNames = &sc->applicationDescription.applicationName; + rs->serverNamesSize = 1; + + /* Mirror the discovery URLs from the server config (includes hostnames from * the network layers) */ - request.server.discoveryUrls = - server->config.applicationDescription.discoveryUrls; - request.server.discoveryUrlsSize = - server->config.applicationDescription.discoveryUrlsSize; + rs->discoveryUrls = sc->applicationDescription.discoveryUrls; + rs->discoveryUrlsSize = sc->applicationDescription.discoveryUrlsSize; +} + +static void +register2AsyncResponse(UA_Client *client, void *userdata, + UA_UInt32 requestId, void *resp) { + asyncRegisterRequest *ar = (asyncRegisterRequest*)userdata; + const UA_ServerConfig *sc = ar->dm->serverConfig; + UA_RegisterServer2Response *response = (UA_RegisterServer2Response*)resp; + + /* Success */ + UA_StatusCode serviceResult = response->responseHeader.serviceResult; + if(serviceResult == UA_STATUSCODE_GOOD) { + /* Close the client connection, will be cleaned up in the client state + * callback when closing is complete */ + UA_Client_disconnectSecureChannelAsync(ar->client); + UA_LOG_INFO(&sc->logger, UA_LOGCATEGORY_SERVER, + "RegisterServer succeeded"); + return; + } + + /* Unrecoverable error */ + if(serviceResult != UA_STATUSCODE_BADNOTIMPLEMENTED && + serviceResult != UA_STATUSCODE_BADSERVICEUNSUPPORTED) { + /* Close the client connection, will be cleaned up in the client state + * callback when closing is complete */ + UA_Client_disconnectSecureChannelAsync(ar->client); + UA_LOG_WARNING(&sc->logger, UA_LOGCATEGORY_SERVER, + "RegisterServer failed with error %s", + UA_StatusCode_name(serviceResult)); + return; + } + + /* Try RegisterServer */ + UA_RegisterServerRequest request; + UA_RegisterServerRequest_init(&request); + setupRegisterRequest(ar, &request.requestHeader, &request.server); + UA_StatusCode res = + __UA_Client_AsyncService(client, &request, + &UA_TYPES[UA_TYPES_REGISTERSERVERREQUEST], + registerAsyncResponse, + &UA_TYPES[UA_TYPES_REGISTERSERVERRESPONSE], + ar, NULL); + if(res != UA_STATUSCODE_GOOD) { + /* Close the client connection, will be cleaned up in the client state + * callback when closing is complete */ + UA_Client_disconnectSecureChannelAsync(ar->client); + UA_LOG_ERROR(&sc->logger, UA_LOGCATEGORY_CLIENT, + "RegisterServer2 failed with statuscode %s", + UA_StatusCode_name(res)); + } +} + +static void +discoveryClientStateCallback(UA_Client *client, + UA_SecureChannelState channelState, + UA_SessionState sessionState, + UA_StatusCode connectStatus) { + asyncRegisterRequest *ar = (asyncRegisterRequest*) + UA_Client_getContext(client); + UA_ServerConfig *sc = ar->dm->serverConfig; + + /* Connection failed */ + if(connectStatus != UA_STATUSCODE_GOOD) { + if(connectStatus != UA_STATUSCODE_BADCONNECTIONCLOSED) { + UA_LOG_ERROR(&sc->logger, UA_LOGCATEGORY_SERVER, + "Could not connect to the Discovery server with error %s", + UA_StatusCode_name(connectStatus)); + } + /* If fully closed, delete the client and clean up */ + if(channelState == UA_SECURECHANNELSTATE_CLOSED) + asyncRegisterRequest_clearAsync(ar); + return; + } + + /* Wait until the SecureChannel is open */ + if(channelState != UA_SECURECHANNELSTATE_OPEN) + return; + + /* Prepare the request. This does not allocate memory */ + UA_RegisterServer2Request request; + UA_RegisterServer2Request_init(&request); + setupRegisterRequest(ar, &request.requestHeader, &request.server); + /* Set the configuration that is only available for UA_RegisterServer2Request */ #ifdef UA_ENABLE_DISCOVERY_MULTICAST - request.discoveryConfigurationSize = 1; - request.discoveryConfiguration = UA_ExtensionObject_new(); - // Set to NODELETE so that we can just use a pointer to the mdns config - UA_ExtensionObject_setValueNoDelete(request.discoveryConfiguration, - &server->config.mdnsConfig, + UA_ExtensionObject mdnsConfig; + UA_ExtensionObject_setValueNoDelete(ar->request.discoveryConfiguration, + &sc->mdnsConfig, &UA_TYPES[UA_TYPES_MDNSDISCOVERYCONFIGURATION]); + ar->request.discoveryConfigurationSize = 1; + ar->request.discoveryConfiguration = &mdnsConfig; #endif - // First try with RegisterServer2, if that isn't implemented, use RegisterServer - UA_RegisterServer2Response response; - __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_REGISTERSERVER2REQUEST], - &response, &UA_TYPES[UA_TYPES_REGISTERSERVER2RESPONSE]); - - UA_StatusCode serviceResult = response.responseHeader.serviceResult; - UA_RegisterServer2Response_clear(&response); - UA_Array_delete(request.discoveryConfiguration, - request.discoveryConfigurationSize, - &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]); - - if(serviceResult == UA_STATUSCODE_BADNOTIMPLEMENTED || - serviceResult == UA_STATUSCODE_BADSERVICEUNSUPPORTED) { - /* Try RegisterServer */ - UA_RegisterServerRequest request_fallback; - UA_RegisterServerRequest_init(&request_fallback); - /* Copy from RegisterServer2 request */ - request_fallback.requestHeader = request.requestHeader; - request_fallback.server = request.server; - - UA_RegisterServerResponse response_fallback; - - __UA_Client_Service(client, &request_fallback, - &UA_TYPES[UA_TYPES_REGISTERSERVERREQUEST], - &response_fallback, - &UA_TYPES[UA_TYPES_REGISTERSERVERRESPONSE]); - - serviceResult = response_fallback.responseHeader.serviceResult; - UA_RegisterServerResponse_clear(&response_fallback); + /* Try to call RegisterServer2 */ + UA_StatusCode res = + __UA_Client_AsyncService(client, &request, + &UA_TYPES[UA_TYPES_REGISTERSERVER2REQUEST], + register2AsyncResponse, + &UA_TYPES[UA_TYPES_REGISTERSERVER2RESPONSE], + ar, NULL); + if(res != UA_STATUSCODE_GOOD) { + /* Close the client connection, will be cleaned up in the client state + * callback when closing is complete */ + UA_Client_disconnectSecureChannelAsync(ar->client); + UA_LOG_ERROR(&sc->logger, UA_LOGCATEGORY_CLIENT, + "RegisterServer2 failed with statuscode %s", + UA_StatusCode_name(res)); + } +} + +static UA_StatusCode +UA_Server_register(UA_Server *server, UA_ClientConfig *cc, UA_Boolean unregister, + const UA_String discoveryServerUrl, + const UA_String semaphoreFilePath) { + /* Get the discovery manager */ + UA_DiscoveryManager *dm = (UA_DiscoveryManager*) + getServerComponentByName(server, UA_STRING("discovery")); + if(!dm) { + UA_ClientConfig_clear(cc); + return UA_STATUSCODE_BADINTERNALERROR; } - if(serviceResult != UA_STATUSCODE_GOOD) { - UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_CLIENT, - "RegisterServer/RegisterServer2 failed with statuscode %s", - UA_StatusCode_name(serviceResult)); + /* Check that the discovery manager is running */ + UA_ServerConfig *sc = &server->config; + if(dm->sc.state != UA_LIFECYCLESTATE_STARTED) { + UA_LOG_ERROR(&sc->logger, UA_LOGCATEGORY_SERVER, + "The server must be started for registering"); + UA_ClientConfig_clear(cc); + return UA_STATUSCODE_BADINTERNALERROR; } - return serviceResult; + /* Find a free slot for storing the async request information */ + asyncRegisterRequest *ar = NULL; + for(size_t i = 0; i < UA_MAXREGISTERREQUESTS; i++) { + if(dm->registerRequests[i].client == NULL) { + ar = &dm->registerRequests[i]; + break; + } + } + if(!ar) { + UA_LOG_ERROR(&sc->logger, UA_LOGCATEGORY_SERVER, + "Too many outstanding register requests. Cannot proceed."); + UA_ClientConfig_clear(cc); + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* Use the EventLoop from the server for the client */ + if(cc->eventLoop && !cc->externalEventLoop) + cc->eventLoop->free(cc->eventLoop); + cc->eventLoop = sc->eventLoop; + cc->externalEventLoop = true; + + /* Use the logging from the server */ + if(cc->logging->clear) + cc->logging->clear(&cc->logging); + cc->logging = sc->logging; + + /* Set the state callback method and context */ + cc->stateCallback = discoveryClientStateCallback; + cc->clientContext = ar; + + /* Open only a SecureChannel */ + cc->noSession = true; + + /* Move the endpoint url */ + UA_String_clear(&cc->endpointUrl); + UA_String_copy(&discoveryServerUrl, &cc->endpointUrl); + + /* Instantiate the client */ + ar->client = UA_Client_newWithConfig(cc); + if(!ar->client) { + UA_ClientConfig_clear(cc); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + + /* Zero out the supplied config */ + memset(cc, 0, sizeof(UA_ClientConfig)); + + /* Finish setting up the context */ + ar->server = server; + ar->dm = dm; + ar->unregister = unregister; + UA_String_copy(&semaphoreFilePath, &ar->semaphoreFilePath); + + /* Connect asynchronously. The register service is called once the + * connection is open. */ + return __UA_Client_connect(ar->client, true); } UA_StatusCode -UA_Server_register_discovery(UA_Server *server, UA_Client *client, - const char* semaphoreFilePath) { +UA_Server_registerDiscovery(UA_Server *server, UA_ClientConfig *cc, + const UA_String discoveryServerUrl, + const UA_String semaphoreFilePath) { + UA_LOG_INFO(&server->config.logger, UA_LOGCATEGORY_SERVER, + "Registering at the DiscoveryServer: %.*s", + (int)discoveryServerUrl.length, discoveryServerUrl.data); UA_LOCK(&server->serviceMutex); - UA_StatusCode retval = register_server_with_discovery_server(server, client, - false, semaphoreFilePath); + UA_StatusCode res = + UA_Server_register(server, cc, false, discoveryServerUrl, semaphoreFilePath); UA_UNLOCK(&server->serviceMutex); - return retval; + return res; } UA_StatusCode -UA_Server_unregister_discovery(UA_Server *server, UA_Client *client) { +UA_Server_deregisterDiscovery(UA_Server *server, UA_ClientConfig *cc, + const UA_String discoveryServerUrl) { + UA_LOG_INFO(&server->config.logger, UA_LOGCATEGORY_SERVER, + "Deregistering at the DiscoveryServer: %.*s", + (int)discoveryServerUrl.length, discoveryServerUrl.data); UA_LOCK(&server->serviceMutex); - UA_StatusCode retval = register_server_with_discovery_server(server, client, - true, NULL); + UA_StatusCode res = + UA_Server_register(server, cc, true, discoveryServerUrl, UA_STRING_NULL); UA_UNLOCK(&server->serviceMutex); - return retval; + return res; } #endif /* UA_ENABLE_DISCOVERY */ diff --git a/src/server/ua_server_internal.h b/src/server/ua_server_internal.h index 349afb45308..667e9bb6489 100644 --- a/src/server/ua_server_internal.h +++ b/src/server/ua_server_internal.h @@ -532,11 +532,6 @@ addRepeatedCallback(UA_Server *server, UA_ServerCallback callback, #ifdef UA_ENABLE_DISCOVERY UA_ServerComponent * UA_DiscoveryManager_new(UA_Server *server); - -UA_StatusCode -register_server_with_discovery_server(UA_Server *server, void *client, - const UA_Boolean isUnregister, - const char* semaphoreFilePath); #endif UA_ServerComponent * diff --git a/src/server/ua_services_discovery.c b/src/server/ua_services_discovery.c index 0eaaa04d9e3..d149951256d 100644 --- a/src/server/ua_services_discovery.c +++ b/src/server/ua_services_discovery.c @@ -581,185 +581,4 @@ void Service_RegisterServer2(UA_Server *server, UA_Session *session, response->diagnosticInfos); } -/* Called by the UA_Server callback. The OPC UA specification says: - * - * > If an error occurs during registration (e.g. the Discovery Server is not running) then the Server - * > must periodically re-attempt registration. The frequency of these attempts should start at 1 second - * > but gradually increase until the registration frequency is the same as what it would be if not - * > errors occurred. The recommended approach would double the period each attempt until reaching the maximum. - * - * We will do so by using the additional data parameter which holds information - * if the next interval is default or if it is a repeated call. */ -static void -periodicServerRegister(UA_Server *server, void *data) { - UA_assert(data != NULL); - UA_LOCK(&server->serviceMutex); - - struct PeriodicServerRegisterCallback *cb = (struct PeriodicServerRegisterCallback *)data; - - UA_StatusCode retval = UA_Client_connectSecureChannel(cb->client, cb->discovery_server_url); - if (retval == UA_STATUSCODE_GOOD) { - /* Register - You can also use a semaphore file. That file must exist. When the file is - deleted, the server is automatically unregistered. The semaphore file has - to be accessible by the discovery server - - UA_StatusCode retval = UA_Server_register_discovery(server, - "opc.tcp://localhost:4840", "/path/to/some/file"); - */ - retval = register_server_with_discovery_server(server, cb->client, false, NULL); - if (retval == UA_STATUSCODE_BADCONNECTIONCLOSED) { - /* If the periodic interval is higher than the maximum lifetime of - * the session, the server will close the connection. In this case - * we should try to reconnect */ - UA_Client_disconnect(cb->client); - retval = UA_Client_connectSecureChannel(cb->client, cb->discovery_server_url); - if (retval == UA_STATUSCODE_GOOD) { - retval = register_server_with_discovery_server(server, cb->client, false, NULL); - } - } - } - /* Registering failed */ - if(retval != UA_STATUSCODE_GOOD) { - UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, - "Could not register server with discovery server. " - "Is the discovery server started? StatusCode %s", - UA_StatusCode_name(retval)); - - /* If the server was previously registered, retry in one second, - * else, double the previous interval */ - UA_Double nextInterval = 1000.0; - if(!cb->registered) - nextInterval = cb->this_interval * 2; - - /* The interval should be smaller than the default interval */ - if(nextInterval > cb->default_interval) - nextInterval = cb->default_interval; - - cb->this_interval = nextInterval; - changeRepeatedCallbackInterval(server, cb->id, nextInterval); - UA_UNLOCK(&server->serviceMutex); - return; - } - - /* Registering succeeded */ - UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_SERVER, - "Server successfully registered. Next periodical register will be in %d seconds", - (int)(cb->default_interval/1000)); - - if(!cb->registered) { - retval = changeRepeatedCallbackInterval(server, cb->id, cb->default_interval); - /* If changing the interval fails, try again after the next registering */ - if(retval == UA_STATUSCODE_GOOD) - cb->registered = true; - } - UA_UNLOCK(&server->serviceMutex); -} - -UA_StatusCode -UA_Server_addPeriodicServerRegisterCallback(UA_Server *server, - struct UA_Client *client, - const char* discoveryServerUrl, - UA_Double intervalMs, - UA_Double delayFirstRegisterMs, - UA_UInt64 *periodicCallbackId) { - UA_LOCK(&server->serviceMutex); - - UA_DiscoveryManager *dm = (UA_DiscoveryManager*) - getServerComponentByName(server, UA_STRING("discovery")); - if(!dm) { - UA_UNLOCK(&server->serviceMutex); - return UA_STATUSCODE_BADINTERNALERROR; - } - - /* No valid server URL */ - if(!discoveryServerUrl) { - UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, - "No discovery server URL provided"); - UA_UNLOCK(&server->serviceMutex); - return UA_STATUSCODE_BADINTERNALERROR; - } - - /* The client is already connected */ - UA_SecureChannelState scState = UA_SECURECHANNELSTATE_CLOSED; - UA_Client_getState(client, &scState, NULL, NULL); - if(scState != UA_SECURECHANNELSTATE_CLOSED) { - UA_UNLOCK(&server->serviceMutex); - return UA_STATUSCODE_BADINVALIDSTATE; - } - - /* Check if we are already registering with the given discovery url and - * remove the old periodic call */ - periodicServerRegisterCallback_entry *rs, *rs_tmp; - LIST_FOREACH_SAFE(rs, &dm->periodicServerRegisterCallbacks, pointers, rs_tmp) { - if(strcmp(rs->callback->discovery_server_url, discoveryServerUrl) == 0) { - UA_LOG_INFO(&server->config.logger, UA_LOGCATEGORY_SERVER, - "There is already a register callback for '%s' in place. " - "Removing the older one.", discoveryServerUrl); - removeCallback(server, rs->callback->id); - LIST_REMOVE(rs, pointers); - UA_free(rs->callback->discovery_server_url); - UA_free(rs->callback); - UA_free(rs); - break; - } - } - - /* Allocate and initialize */ - struct PeriodicServerRegisterCallback* cb = (struct PeriodicServerRegisterCallback*) - UA_malloc(sizeof(struct PeriodicServerRegisterCallback)); - if(!cb) { - UA_UNLOCK(&server->serviceMutex); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - - /* Start repeating a failed register after 1s, then increase the delay. Set - * to 500ms, as the delay is doubled before changing the callback - * interval.*/ - cb->this_interval = 500.0; - cb->default_interval = intervalMs; - cb->registered = false; - cb->client = client; - size_t len = strlen(discoveryServerUrl); - cb->discovery_server_url = (char*)UA_malloc(len+1); - if (!cb->discovery_server_url) { - UA_free(cb); - UA_UNLOCK(&server->serviceMutex); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - memcpy(cb->discovery_server_url, discoveryServerUrl, len+1); - - /* Add the callback */ - UA_StatusCode retval = - addRepeatedCallback(server, periodicServerRegister, - cb, delayFirstRegisterMs, &cb->id); - if(retval != UA_STATUSCODE_GOOD) { - UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, - "Could not create periodic job for server register. " - "StatusCode %s", UA_StatusCode_name(retval)); - UA_free(cb); - UA_UNLOCK(&server->serviceMutex); - return retval; - } - -#ifndef __clang_analyzer__ - // the analyzer reports on LIST_INSERT_HEAD a use after free false positive - periodicServerRegisterCallback_entry *newEntry = (periodicServerRegisterCallback_entry*) - UA_malloc(sizeof(periodicServerRegisterCallback_entry)); - if(!newEntry) { - removeCallback(server, cb->id); - UA_free(cb); - UA_UNLOCK(&server->serviceMutex); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - newEntry->callback = cb; - LIST_INSERT_HEAD(&dm->periodicServerRegisterCallbacks, newEntry, pointers); -#endif - - if(periodicCallbackId) - *periodicCallbackId = cb->id; - UA_UNLOCK(&server->serviceMutex); - return UA_STATUSCODE_GOOD; -} - #endif /* UA_ENABLE_DISCOVERY */ diff --git a/src/server/ua_services_discovery_multicast.c b/src/server/ua_services_discovery_multicast.c index 7716e80262f..4978995dfe2 100644 --- a/src/server/ua_services_discovery_multicast.c +++ b/src/server/ua_services_discovery_multicast.c @@ -103,14 +103,13 @@ MulticastDiscoveryCallback(UA_ConnectionManager *cm, uintptr_t connectionId, if(state == UA_CONNECTIONSTATE_CLOSING) { mdnsRemoveConnection(dm, connectionId, recv); - if(dm->sc.state == UA_LIFECYCLESTATE_STOPPING) { - /* If we are stopping, was the last open socket closed? */ - if(dm->mdnsSendConnection == 0 && dm->mdnsRecvConnectionsSize == 0) - UA_DiscoveryManager_setState(server, dm, UA_LIFECYCLESTATE_STOPPED); - } else { - /* Restart mdns sockets */ + /* Fully stopped? Internally checks if all sockets are closed. */ + UA_DiscoveryManager_setState(server, dm, dm->sc.state); + + /* Restart mdns sockets if not shutting down */ + if(dm->sc.state == UA_LIFECYCLESTATE_STARTED) startMulticastDiscoveryServer(server); - } + return; } diff --git a/tests/server/check_discovery.c b/tests/server/check_discovery.c index 5b949c91f56..9a18ea6f77e 100644 --- a/tests/server/check_discovery.c +++ b/tests/server/check_discovery.c @@ -146,30 +146,42 @@ teardown_register(void) { static void registerServer(void) { - UA_Client *clientRegister = UA_Client_new(); - UA_ClientConfig_setDefault(UA_Client_getConfig(clientRegister)); + UA_ClientConfig cc; + memset(&cc, 0, sizeof(UA_ClientConfig)); + UA_ClientConfig_setDefault(&cc); - UA_StatusCode retval = - UA_Client_connectSecureChannel(clientRegister, "opc.tcp://localhost:4840"); - ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); - retval = UA_Server_register_discovery(server_register, clientRegister, NULL); - ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); - UA_Client_disconnect(clientRegister); - UA_Client_delete(clientRegister); + *running_register = false; + THREAD_JOIN(server_thread_register); + + UA_StatusCode res = + UA_Server_registerDiscovery(server_register, &cc, + UA_STRING("opc.tcp://localhost:4840"), + UA_STRING_NULL); + *running_register = true; + THREAD_CREATE(server_thread_register, serverloop_register); + + ck_assert_uint_eq(res, UA_STATUSCODE_GOOD); + UA_realSleep(1000); } static void unregisterServer(void) { - UA_Client *clientRegister = UA_Client_new(); - UA_ClientConfig_setDefault(UA_Client_getConfig(clientRegister)); + UA_ClientConfig cc; + memset(&cc, 0, sizeof(UA_ClientConfig)); + UA_ClientConfig_setDefault(&cc); - UA_StatusCode retval = - UA_Client_connectSecureChannel(clientRegister, "opc.tcp://localhost:4840"); - ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); - retval = UA_Server_unregister_discovery(server_register, clientRegister); - ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); - UA_Client_disconnect(clientRegister); - UA_Client_delete(clientRegister); + *running_register = false; + THREAD_JOIN(server_thread_register); + + UA_StatusCode res = + UA_Server_deregisterDiscovery(server_register, &cc, + UA_STRING("opc.tcp://localhost:4840")); + + *running_register = true; + THREAD_CREATE(server_thread_register, serverloop_register); + + ck_assert_uint_eq(res, UA_STATUSCODE_GOOD); + UA_realSleep(1000); } #ifdef UA_ENABLE_DISCOVERY_SEMAPHORE @@ -188,16 +200,23 @@ Server_register_semaphore(void) { fclose(fp); #endif - UA_Client *clientRegister = UA_Client_new(); - UA_ClientConfig_setDefault(UA_Client_getConfig(clientRegister)); + UA_ClientConfig cc; + memset(&cc, 0, sizeof(UA_ClientConfig)); + UA_ClientConfig_setDefault(&cc); - UA_StatusCode retval = - UA_Client_connectSecureChannel(clientRegister, "opc.tcp://localhost:4840"); - ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); - retval = UA_Server_register_discovery(server_register, clientRegister, SEMAPHORE_PATH); - ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); - UA_Client_disconnect(clientRegister); - UA_Client_delete(clientRegister); + *running_register = false; + THREAD_JOIN(server_thread_register); + + UA_StatusCode res = + UA_Server_registerDiscovery(server_register, &cc, + UA_STRING("opc.tcp://localhost:4840"), + UA_STRING(SEMAPHORE_PATH)); + + *running_register = true; + THREAD_CREATE(server_thread_register, serverloop_register); + + ck_assert_uint_eq(res, UA_STATUSCODE_GOOD); + UA_realSleep(1000); } static void @@ -208,32 +227,6 @@ Server_unregister_semaphore(void) { #endif /* UA_ENABLE_DISCOVERY_SEMAPHORE */ -static void -Server_register_periodic(void) { - ck_assert(clientRegisterRepeated == NULL); - clientRegisterRepeated = UA_Client_new(); - UA_ClientConfig_setDefault(UA_Client_getConfig(clientRegisterRepeated)); - ck_assert(clientRegisterRepeated != NULL); - - // periodic register every minute, first register immediately - UA_StatusCode retval = - UA_Server_addPeriodicServerRegisterCallback(server_register, clientRegisterRepeated, - "opc.tcp://localhost:4840", 60*1000, 100, - &periodicRegisterCallbackId); - ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); -} - -static void -Server_unregister_periodic(void) { - UA_Server_removeRepeatedCallback(server_register, periodicRegisterCallbackId); - UA_StatusCode retval = - UA_Server_unregister_discovery(server_register, clientRegisterRepeated); - ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); - UA_Client_disconnect(clientRegisterRepeated); - UA_Client_delete(clientRegisterRepeated); - clientRegisterRepeated=NULL; -} - static void FindAndCheck(const UA_String expectedUris[], size_t expectedUrisSize, const UA_String expectedLocales[], @@ -241,7 +234,6 @@ FindAndCheck(const UA_String expectedUris[], size_t expectedUrisSize, const char *filterUri, const char *filterLocale) { UA_Client *client = UA_Client_new(); - UA_ClientConfig_setDefault(UA_Client_getConfig(client)); UA_ApplicationDescription* applicationDescriptionArray = NULL; size_t applicationDescriptionArraySize = 0; @@ -308,7 +300,6 @@ FindOnNetworkAndCheck(UA_String expectedServerNames[], size_t expectedServerName const char *filterUri, const char *filterLocale, const char** filterCapabilities, size_t filterCapabilitiesSize) { UA_Client *client = UA_Client_new(); - UA_ClientConfig_setDefault(UA_Client_getConfig(client)); UA_ServerOnNetwork* serverOnNetwork = NULL; size_t serverOnNetworkSize = 0; @@ -408,7 +399,6 @@ GetEndpointsAndCheck(const char* discoveryUrl, const char* filterTransportProfil const UA_String *expectedEndpointUrls, size_t expectedEndpointUrlsSize) { UA_Client *client = UA_Client_new(); - UA_ClientConfig_setDefault(UA_Client_getConfig(client)); ck_assert_uint_eq(UA_Client_connect(client, discoveryUrl), UA_STATUSCODE_GOOD); @@ -564,38 +554,6 @@ START_TEST(Server_registerUnregister) { } END_TEST -START_TEST(Server_registerRetry) { - Server_register_periodic(); - - // wait a bit to let first try run through - UA_fakeSleep(1000); - UA_realSleep(1000); - - // now start LDS - setup_lds(); - // first retry is after 2 seconds, then 4, so it should be enough to wait 3 seconds - UA_fakeSleep(3000); - UA_realSleep(3000); - - // check if there - Client_find_registered(); - Server_unregister_periodic(); - Client_find_discovery(); - teardown_lds(); -} -END_TEST - -START_TEST(Server_registerUnregister_periodic) { - Server_register_periodic(); - - // wait for first register delay - UA_fakeSleep(1000); - UA_realSleep(1000); - - Server_unregister_periodic(); -} -END_TEST - START_TEST(Server_registerTimeout) { registerServer(); Client_find_registered(); @@ -663,14 +621,8 @@ static Suite* testSuite_Client(void) { tcase_add_unchecked_fixture(tc_register, setup_lds, teardown_lds); tcase_add_unchecked_fixture(tc_register, setup_register, teardown_register); tcase_add_test(tc_register, Server_registerUnregister); - tcase_add_test(tc_register, Server_registerUnregister_periodic); suite_add_tcase(s,tc_register); - TCase *tc_register_retry = tcase_create("RegisterServer Retry"); - tcase_add_unchecked_fixture(tc_register_retry, setup_register, teardown_register); - tcase_add_test(tc_register_retry, Server_registerRetry); - suite_add_tcase(s,tc_register_retry); - #ifdef UA_ENABLE_DISCOVERY_MULTICAST TCase *tc_register_find = tcase_create("RegisterServer and FindServers"); tcase_add_unchecked_fixture(tc_register_find, setup_lds, teardown_lds); From 3d9561e9ff535c699eb2831585e5568a05a75a9a Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 24 Oct 2023 22:59:21 +0200 Subject: [PATCH 03/10] refactor(examples): Adjust SecureChannel limit for the CTT server --- examples/server_ctt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/server_ctt.c b/examples/server_ctt.c index f2b5e3a5c45..16beba661bf 100644 --- a/examples/server_ctt.c +++ b/examples/server_ctt.c @@ -1360,8 +1360,8 @@ int main(int argc, char **argv) { #endif /* UA_ENABLE_ENCRYPTION */ /* Limit the number of SecureChannels and Sessions */ - config->maxSecureChannels = 40; - config->maxSessions = 10; + config->maxSecureChannels = 60; + config->maxSessions = 50; /* Revolve the SecureChannel token every 300 seconds */ config->maxSecurityTokenLifetime = 300000; From e128299b759b9d7eaabe0e00dc3501d28441bc4c Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 24 Oct 2023 22:59:48 +0200 Subject: [PATCH 04/10] refactor(client): Remove local validation whether a session is required The previous check was faulty. For example, the RegisterServer Service does not require a Session also. Whether a session is required is up to the server. This also aligns with the async API. --- src/client/ua_client.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/client/ua_client.c b/src/client/ua_client.c index 73cf61d6c52..b5e0722770f 100644 --- a/src/client/ua_client.c +++ b/src/client/ua_client.c @@ -783,17 +783,6 @@ __Client_AsyncService(UA_Client *client, const void *request, return UA_STATUSCODE_BADSERVERNOTCONNECTED; } - /* Do we need a Session for this Service? Is the Session connected? */ - if(requestType != &UA_TYPES[UA_TYPES_CREATESESSIONREQUEST] && - requestType != &UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST] && - requestType != &UA_TYPES[UA_TYPES_GETENDPOINTSREQUEST] && - requestType != &UA_TYPES[UA_TYPES_FINDSERVERSREQUEST] && - client->sessionState < UA_SESSIONSTATE_ACTIVATED) { - UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, - "Session must be connected to send a request of this type"); - return UA_STATUSCODE_BADSERVERNOTCONNECTED; - } - /* Prepare the entry for the linked list */ AsyncServiceCall *ac = (AsyncServiceCall*)UA_malloc(sizeof(AsyncServiceCall)); if(!ac) From 097916244ead0d4830f1fd83157179a1e28954ef Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 24 Oct 2023 23:14:50 +0200 Subject: [PATCH 05/10] feat(plugin): Notify the application when the server is going to shutdown with a delay (UA_Server_runUntilInterrupt) --- plugins/ua_config_default.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/ua_config_default.c b/plugins/ua_config_default.c index 02350fb6fda..2f9ff0ce7b7 100644 --- a/plugins/ua_config_default.c +++ b/plugins/ua_config_default.c @@ -99,6 +99,10 @@ interruptServer(UA_InterruptManager *im, uintptr_t interruptHandle, UA_Server_addTimedCallback(ic->server, shutdownServer, ic, UA_DateTime_nowMonotonic() + (UA_DateTime)(config->shutdownDelay * UA_DATETIME_MSEC), NULL); + + /* Notify the application that the server is stopping */ + if(config->notifyLifecycleState) + config->notifyLifecycleState(ic->server, UA_LIFECYCLESTATE_STOPPING); } UA_StatusCode From 949ff61a8315779a7fc8f11bd121568bed963cb3 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 24 Oct 2023 23:19:50 +0200 Subject: [PATCH 06/10] refactor(server): Move registering functionality to ua_discovery_manager.c --- CMakeLists.txt | 1 - src/server/ua_discovery_manager.c | 289 ++++++++++++++++++++++++++++ src/server/ua_server_discovery.c | 300 ------------------------------ 3 files changed, 289 insertions(+), 301 deletions(-) delete mode 100644 src/server/ua_server_discovery.c diff --git a/CMakeLists.txt b/CMakeLists.txt index e02594f7b8f..81e0c4554f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -822,7 +822,6 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c ${PROJECT_SOURCE_DIR}/src/server/ua_server_config.c ${PROJECT_SOURCE_DIR}/src/server/ua_server_binary.c ${PROJECT_SOURCE_DIR}/src/server/ua_server_utils.c - ${PROJECT_SOURCE_DIR}/src/server/ua_server_discovery.c ${PROJECT_SOURCE_DIR}/src/server/ua_server_async.c ${PROJECT_SOURCE_DIR}/src/server/ua_services_view.c ${PROJECT_SOURCE_DIR}/src/server/ua_services_method.c diff --git a/src/server/ua_discovery_manager.c b/src/server/ua_discovery_manager.c index e58f772f579..7a3c3d71329 100644 --- a/src/server/ua_discovery_manager.c +++ b/src/server/ua_discovery_manager.c @@ -9,6 +9,8 @@ * Copyright 2015-2016 (c) Oleksiy Vasylyev * Copyright 2016-2017 (c) Stefan Profanter, fortiss GmbH * Copyright 2017 (c) Julian Grothoff + * Copyright 2017 (c) Stefan Profanter, fortiss GmbH + * Copyright 2017 (c) HMS Industrial Networks AB (Author: Jonas Green) */ #include @@ -225,4 +227,291 @@ UA_DiscoveryManager_new(UA_Server *server) { return &dm->sc; } +/********************************/ +/* Register at Discovery Server */ +/********************************/ + +static void +asyncRegisterRequest_clear(void *app, void *context) { + UA_Server *server = (UA_Server*)app; + asyncRegisterRequest *ar = (asyncRegisterRequest*)context; + UA_DiscoveryManager *dm = ar->dm; + + UA_String_clear(&ar->semaphoreFilePath); + if(ar->client) + UA_Client_delete(ar->client); + memset(ar, 0, sizeof(asyncRegisterRequest)); + + /* The Discovery manager is fully stopped? */ + UA_DiscoveryManager_setState(server, dm, dm->sc.state); +} + +static void +asyncRegisterRequest_clearAsync(asyncRegisterRequest *ar) { + UA_Server *server = ar->server; + UA_ServerConfig *sc = &server->config; + UA_EventLoop *el = sc->eventLoop; + + ar->cleanupCallback.callback = asyncRegisterRequest_clear; + ar->cleanupCallback.application = server; + ar->cleanupCallback.context = ar; + el->addDelayedCallback(el, &ar->cleanupCallback); +} + +static void +registerAsyncResponse(UA_Client *client, void *userdata, + UA_UInt32 requestId, void *resp) { + asyncRegisterRequest *ar = (asyncRegisterRequest*)userdata; + const UA_ServerConfig *sc = ar->dm->serverConfig; + UA_RegisterServerResponse *response = (UA_RegisterServerResponse*)resp; + if(response->responseHeader.serviceResult == UA_STATUSCODE_GOOD) { + UA_LOG_INFO(&sc->logger, UA_LOGCATEGORY_SERVER, + "RegisterServer succeeded"); + } else { + UA_LOG_WARNING(&sc->logger, UA_LOGCATEGORY_SERVER, + "RegisterServer failed with statuscode %s", + UA_StatusCode_name(response->responseHeader.serviceResult)); + } + + /* Close the client connection, will be cleaned up in the client state + * callback when closing is complete */ + UA_Client_disconnectSecureChannelAsync(ar->client); +} + +static void +setupRegisterRequest(asyncRegisterRequest *ar, UA_RequestHeader *rh, + UA_RegisteredServer *rs) { + UA_ServerConfig *sc = ar->dm->serverConfig; + + rh->timeoutHint = 10000; + + rs->isOnline = !ar->unregister; + rs->serverUri = sc->applicationDescription.applicationUri; + rs->productUri = sc->applicationDescription.productUri; + rs->serverType = sc->applicationDescription.applicationType; + rs->gatewayServerUri = sc->applicationDescription.gatewayServerUri; + rs->semaphoreFilePath = ar->semaphoreFilePath; + + rs->serverNames = &sc->applicationDescription.applicationName; + rs->serverNamesSize = 1; + + /* Mirror the discovery URLs from the server config (includes hostnames from + * the network layers) */ + rs->discoveryUrls = sc->applicationDescription.discoveryUrls; + rs->discoveryUrlsSize = sc->applicationDescription.discoveryUrlsSize; +} + +static void +register2AsyncResponse(UA_Client *client, void *userdata, + UA_UInt32 requestId, void *resp) { + asyncRegisterRequest *ar = (asyncRegisterRequest*)userdata; + const UA_ServerConfig *sc = ar->dm->serverConfig; + UA_RegisterServer2Response *response = (UA_RegisterServer2Response*)resp; + + /* Success */ + UA_StatusCode serviceResult = response->responseHeader.serviceResult; + if(serviceResult == UA_STATUSCODE_GOOD) { + /* Close the client connection, will be cleaned up in the client state + * callback when closing is complete */ + UA_Client_disconnectSecureChannelAsync(ar->client); + UA_LOG_INFO(&sc->logger, UA_LOGCATEGORY_SERVER, + "RegisterServer succeeded"); + return; + } + + /* Unrecoverable error */ + if(serviceResult != UA_STATUSCODE_BADNOTIMPLEMENTED && + serviceResult != UA_STATUSCODE_BADSERVICEUNSUPPORTED) { + /* Close the client connection, will be cleaned up in the client state + * callback when closing is complete */ + UA_Client_disconnectSecureChannelAsync(ar->client); + UA_LOG_WARNING(&sc->logger, UA_LOGCATEGORY_SERVER, + "RegisterServer failed with error %s", + UA_StatusCode_name(serviceResult)); + return; + } + + /* Try RegisterServer */ + UA_RegisterServerRequest request; + UA_RegisterServerRequest_init(&request); + setupRegisterRequest(ar, &request.requestHeader, &request.server); + UA_StatusCode res = + __UA_Client_AsyncService(client, &request, + &UA_TYPES[UA_TYPES_REGISTERSERVERREQUEST], + registerAsyncResponse, + &UA_TYPES[UA_TYPES_REGISTERSERVERRESPONSE], + ar, NULL); + if(res != UA_STATUSCODE_GOOD) { + /* Close the client connection, will be cleaned up in the client state + * callback when closing is complete */ + UA_Client_disconnectSecureChannelAsync(ar->client); + UA_LOG_ERROR(&sc->logger, UA_LOGCATEGORY_CLIENT, + "RegisterServer2 failed with statuscode %s", + UA_StatusCode_name(res)); + } +} + +static void +discoveryClientStateCallback(UA_Client *client, + UA_SecureChannelState channelState, + UA_SessionState sessionState, + UA_StatusCode connectStatus) { + asyncRegisterRequest *ar = (asyncRegisterRequest*) + UA_Client_getContext(client); + UA_ServerConfig *sc = ar->dm->serverConfig; + + /* Connection failed */ + if(connectStatus != UA_STATUSCODE_GOOD) { + if(connectStatus != UA_STATUSCODE_BADCONNECTIONCLOSED) { + UA_LOG_ERROR(&sc->logger, UA_LOGCATEGORY_SERVER, + "Could not connect to the Discovery server with error %s", + UA_StatusCode_name(connectStatus)); + } + /* If fully closed, delete the client and clean up */ + if(channelState == UA_SECURECHANNELSTATE_CLOSED) + asyncRegisterRequest_clearAsync(ar); + return; + } + + /* Wait until the SecureChannel is open */ + if(channelState != UA_SECURECHANNELSTATE_OPEN) + return; + + /* Prepare the request. This does not allocate memory */ + UA_RegisterServer2Request request; + UA_RegisterServer2Request_init(&request); + setupRegisterRequest(ar, &request.requestHeader, &request.server); + + /* Set the configuration that is only available for UA_RegisterServer2Request */ +#ifdef UA_ENABLE_DISCOVERY_MULTICAST + UA_ExtensionObject mdnsConfig; + UA_ExtensionObject_setValueNoDelete(&mdnsConfig, &sc->mdnsConfig, + &UA_TYPES[UA_TYPES_MDNSDISCOVERYCONFIGURATION]); + request.discoveryConfigurationSize = 1; + request.discoveryConfiguration = &mdnsConfig; +#endif + + /* Try to call RegisterServer2 */ + UA_StatusCode res = + __UA_Client_AsyncService(client, &request, + &UA_TYPES[UA_TYPES_REGISTERSERVER2REQUEST], + register2AsyncResponse, + &UA_TYPES[UA_TYPES_REGISTERSERVER2RESPONSE], + ar, NULL); + if(res != UA_STATUSCODE_GOOD) { + /* Close the client connection, will be cleaned up in the client state + * callback when closing is complete */ + UA_Client_disconnectSecureChannelAsync(ar->client); + UA_LOG_ERROR(&sc->logger, UA_LOGCATEGORY_CLIENT, + "RegisterServer2 failed with statuscode %s", + UA_StatusCode_name(res)); + } +} + +static UA_StatusCode +UA_Server_register(UA_Server *server, UA_ClientConfig *cc, UA_Boolean unregister, + const UA_String discoveryServerUrl, + const UA_String semaphoreFilePath) { + /* Get the discovery manager */ + UA_DiscoveryManager *dm = (UA_DiscoveryManager*) + getServerComponentByName(server, UA_STRING("discovery")); + if(!dm) { + UA_ClientConfig_clear(cc); + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* Check that the discovery manager is running */ + UA_ServerConfig *sc = &server->config; + if(dm->sc.state != UA_LIFECYCLESTATE_STARTED) { + UA_LOG_ERROR(&sc->logger, UA_LOGCATEGORY_SERVER, + "The server must be started for registering"); + UA_ClientConfig_clear(cc); + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* Find a free slot for storing the async request information */ + asyncRegisterRequest *ar = NULL; + for(size_t i = 0; i < UA_MAXREGISTERREQUESTS; i++) { + if(dm->registerRequests[i].client == NULL) { + ar = &dm->registerRequests[i]; + break; + } + } + if(!ar) { + UA_LOG_ERROR(&sc->logger, UA_LOGCATEGORY_SERVER, + "Too many outstanding register requests. Cannot proceed."); + UA_ClientConfig_clear(cc); + return UA_STATUSCODE_BADINTERNALERROR; + } + + /* Use the EventLoop from the server for the client */ + if(cc->eventLoop && !cc->externalEventLoop) + cc->eventLoop->free(cc->eventLoop); + cc->eventLoop = sc->eventLoop; + cc->externalEventLoop = true; + + /* Use the logging from the server */ + if(cc->logging->clear) + cc->logging->clear(&cc->logging); + cc->logging = sc->logging; + + /* Set the state callback method and context */ + cc->stateCallback = discoveryClientStateCallback; + cc->clientContext = ar; + + /* Open only a SecureChannel */ + cc->noSession = true; + + /* Move the endpoint url */ + UA_String_clear(&cc->endpointUrl); + UA_String_copy(&discoveryServerUrl, &cc->endpointUrl); + + /* Instantiate the client */ + ar->client = UA_Client_newWithConfig(cc); + if(!ar->client) { + UA_ClientConfig_clear(cc); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + + /* Zero out the supplied config */ + memset(cc, 0, sizeof(UA_ClientConfig)); + + /* Finish setting up the context */ + ar->server = server; + ar->dm = dm; + ar->unregister = unregister; + UA_String_copy(&semaphoreFilePath, &ar->semaphoreFilePath); + + /* Connect asynchronously. The register service is called once the + * connection is open. */ + return __UA_Client_connect(ar->client, true); +} + +UA_StatusCode +UA_Server_registerDiscovery(UA_Server *server, UA_ClientConfig *cc, + const UA_String discoveryServerUrl, + const UA_String semaphoreFilePath) { + UA_LOG_INFO(&server->config.logger, UA_LOGCATEGORY_SERVER, + "Registering at the DiscoveryServer: %.*s", + (int)discoveryServerUrl.length, discoveryServerUrl.data); + UA_LOCK(&server->serviceMutex); + UA_StatusCode res = + UA_Server_register(server, cc, false, discoveryServerUrl, semaphoreFilePath); + UA_UNLOCK(&server->serviceMutex); + return res; +} + +UA_StatusCode +UA_Server_deregisterDiscovery(UA_Server *server, UA_ClientConfig *cc, + const UA_String discoveryServerUrl) { + UA_LOG_INFO(&server->config.logger, UA_LOGCATEGORY_SERVER, + "Deregistering at the DiscoveryServer: %.*s", + (int)discoveryServerUrl.length, discoveryServerUrl.data); + UA_LOCK(&server->serviceMutex); + UA_StatusCode res = + UA_Server_register(server, cc, true, discoveryServerUrl, UA_STRING_NULL); + UA_UNLOCK(&server->serviceMutex); + return res; +} + #endif /* UA_ENABLE_DISCOVERY */ diff --git a/src/server/ua_server_discovery.c b/src/server/ua_server_discovery.c deleted file mode 100644 index 50fd68ed727..00000000000 --- a/src/server/ua_server_discovery.c +++ /dev/null @@ -1,300 +0,0 @@ -/* 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) Fraunhofer IOSB (Author: Julius Pfrommer) - * Copyright 2017 (c) Stefan Profanter, fortiss GmbH - * Copyright 2017 (c) HMS Industrial Networks AB (Author: Jonas Green) - */ - -#include - -#include "ua_discovery_manager.h" - -#ifdef UA_ENABLE_DISCOVERY - -static void -asyncRegisterRequest_clear(void *app, void *context) { - UA_Server *server = (UA_Server*)app; - asyncRegisterRequest *ar = (asyncRegisterRequest*)context; - UA_DiscoveryManager *dm = ar->dm; - - UA_String_clear(&ar->semaphoreFilePath); - if(ar->client) - UA_Client_delete(ar->client); - memset(ar, 0, sizeof(asyncRegisterRequest)); - - /* The Discovery manager is fully stopped? */ - UA_DiscoveryManager_setState(server, dm, dm->sc.state); -} - -static void -asyncRegisterRequest_clearAsync(asyncRegisterRequest *ar) { - UA_Server *server = ar->server; - UA_ServerConfig *sc = &server->config; - UA_EventLoop *el = sc->eventLoop; - - ar->cleanupCallback.callback = asyncRegisterRequest_clear; - ar->cleanupCallback.application = server; - ar->cleanupCallback.context = ar; - el->addDelayedCallback(el, &ar->cleanupCallback); -} - -static void -registerAsyncResponse(UA_Client *client, void *userdata, - UA_UInt32 requestId, void *resp) { - asyncRegisterRequest *ar = (asyncRegisterRequest*)userdata; - const UA_ServerConfig *sc = ar->dm->serverConfig; - UA_RegisterServerResponse *response = (UA_RegisterServerResponse*)resp; - if(response->responseHeader.serviceResult == UA_STATUSCODE_GOOD) { - UA_LOG_INFO(&sc->logger, UA_LOGCATEGORY_SERVER, - "RegisterServer succeeded"); - } else { - UA_LOG_WARNING(&sc->logger, UA_LOGCATEGORY_SERVER, - "RegisterServer failed with statuscode %s", - UA_StatusCode_name(response->responseHeader.serviceResult)); - } - - /* Close the client connection, will be cleaned up in the client state - * callback when closing is complete */ - UA_Client_disconnectSecureChannelAsync(ar->client); -} - -static void -setupRegisterRequest(asyncRegisterRequest *ar, UA_RequestHeader *rh, - UA_RegisteredServer *rs) { - UA_ServerConfig *sc = ar->dm->serverConfig; - - rh->timeoutHint = 10000; - - rs->isOnline = !ar->unregister; - rs->serverUri = sc->applicationDescription.applicationUri; - rs->productUri = sc->applicationDescription.productUri; - rs->serverType = sc->applicationDescription.applicationType; - rs->gatewayServerUri = sc->applicationDescription.gatewayServerUri; - rs->semaphoreFilePath = ar->semaphoreFilePath; - - rs->serverNames = &sc->applicationDescription.applicationName; - rs->serverNamesSize = 1; - - /* Mirror the discovery URLs from the server config (includes hostnames from - * the network layers) */ - rs->discoveryUrls = sc->applicationDescription.discoveryUrls; - rs->discoveryUrlsSize = sc->applicationDescription.discoveryUrlsSize; -} - -static void -register2AsyncResponse(UA_Client *client, void *userdata, - UA_UInt32 requestId, void *resp) { - asyncRegisterRequest *ar = (asyncRegisterRequest*)userdata; - const UA_ServerConfig *sc = ar->dm->serverConfig; - UA_RegisterServer2Response *response = (UA_RegisterServer2Response*)resp; - - /* Success */ - UA_StatusCode serviceResult = response->responseHeader.serviceResult; - if(serviceResult == UA_STATUSCODE_GOOD) { - /* Close the client connection, will be cleaned up in the client state - * callback when closing is complete */ - UA_Client_disconnectSecureChannelAsync(ar->client); - UA_LOG_INFO(&sc->logger, UA_LOGCATEGORY_SERVER, - "RegisterServer succeeded"); - return; - } - - /* Unrecoverable error */ - if(serviceResult != UA_STATUSCODE_BADNOTIMPLEMENTED && - serviceResult != UA_STATUSCODE_BADSERVICEUNSUPPORTED) { - /* Close the client connection, will be cleaned up in the client state - * callback when closing is complete */ - UA_Client_disconnectSecureChannelAsync(ar->client); - UA_LOG_WARNING(&sc->logger, UA_LOGCATEGORY_SERVER, - "RegisterServer failed with error %s", - UA_StatusCode_name(serviceResult)); - return; - } - - /* Try RegisterServer */ - UA_RegisterServerRequest request; - UA_RegisterServerRequest_init(&request); - setupRegisterRequest(ar, &request.requestHeader, &request.server); - UA_StatusCode res = - __UA_Client_AsyncService(client, &request, - &UA_TYPES[UA_TYPES_REGISTERSERVERREQUEST], - registerAsyncResponse, - &UA_TYPES[UA_TYPES_REGISTERSERVERRESPONSE], - ar, NULL); - if(res != UA_STATUSCODE_GOOD) { - /* Close the client connection, will be cleaned up in the client state - * callback when closing is complete */ - UA_Client_disconnectSecureChannelAsync(ar->client); - UA_LOG_ERROR(&sc->logger, UA_LOGCATEGORY_CLIENT, - "RegisterServer2 failed with statuscode %s", - UA_StatusCode_name(res)); - } -} - -static void -discoveryClientStateCallback(UA_Client *client, - UA_SecureChannelState channelState, - UA_SessionState sessionState, - UA_StatusCode connectStatus) { - asyncRegisterRequest *ar = (asyncRegisterRequest*) - UA_Client_getContext(client); - UA_ServerConfig *sc = ar->dm->serverConfig; - - /* Connection failed */ - if(connectStatus != UA_STATUSCODE_GOOD) { - if(connectStatus != UA_STATUSCODE_BADCONNECTIONCLOSED) { - UA_LOG_ERROR(&sc->logger, UA_LOGCATEGORY_SERVER, - "Could not connect to the Discovery server with error %s", - UA_StatusCode_name(connectStatus)); - } - /* If fully closed, delete the client and clean up */ - if(channelState == UA_SECURECHANNELSTATE_CLOSED) - asyncRegisterRequest_clearAsync(ar); - return; - } - - /* Wait until the SecureChannel is open */ - if(channelState != UA_SECURECHANNELSTATE_OPEN) - return; - - /* Prepare the request. This does not allocate memory */ - UA_RegisterServer2Request request; - UA_RegisterServer2Request_init(&request); - setupRegisterRequest(ar, &request.requestHeader, &request.server); - - /* Set the configuration that is only available for UA_RegisterServer2Request */ -#ifdef UA_ENABLE_DISCOVERY_MULTICAST - UA_ExtensionObject mdnsConfig; - UA_ExtensionObject_setValueNoDelete(ar->request.discoveryConfiguration, - &sc->mdnsConfig, - &UA_TYPES[UA_TYPES_MDNSDISCOVERYCONFIGURATION]); - ar->request.discoveryConfigurationSize = 1; - ar->request.discoveryConfiguration = &mdnsConfig; -#endif - - /* Try to call RegisterServer2 */ - UA_StatusCode res = - __UA_Client_AsyncService(client, &request, - &UA_TYPES[UA_TYPES_REGISTERSERVER2REQUEST], - register2AsyncResponse, - &UA_TYPES[UA_TYPES_REGISTERSERVER2RESPONSE], - ar, NULL); - if(res != UA_STATUSCODE_GOOD) { - /* Close the client connection, will be cleaned up in the client state - * callback when closing is complete */ - UA_Client_disconnectSecureChannelAsync(ar->client); - UA_LOG_ERROR(&sc->logger, UA_LOGCATEGORY_CLIENT, - "RegisterServer2 failed with statuscode %s", - UA_StatusCode_name(res)); - } -} - -static UA_StatusCode -UA_Server_register(UA_Server *server, UA_ClientConfig *cc, UA_Boolean unregister, - const UA_String discoveryServerUrl, - const UA_String semaphoreFilePath) { - /* Get the discovery manager */ - UA_DiscoveryManager *dm = (UA_DiscoveryManager*) - getServerComponentByName(server, UA_STRING("discovery")); - if(!dm) { - UA_ClientConfig_clear(cc); - return UA_STATUSCODE_BADINTERNALERROR; - } - - /* Check that the discovery manager is running */ - UA_ServerConfig *sc = &server->config; - if(dm->sc.state != UA_LIFECYCLESTATE_STARTED) { - UA_LOG_ERROR(&sc->logger, UA_LOGCATEGORY_SERVER, - "The server must be started for registering"); - UA_ClientConfig_clear(cc); - return UA_STATUSCODE_BADINTERNALERROR; - } - - /* Find a free slot for storing the async request information */ - asyncRegisterRequest *ar = NULL; - for(size_t i = 0; i < UA_MAXREGISTERREQUESTS; i++) { - if(dm->registerRequests[i].client == NULL) { - ar = &dm->registerRequests[i]; - break; - } - } - if(!ar) { - UA_LOG_ERROR(&sc->logger, UA_LOGCATEGORY_SERVER, - "Too many outstanding register requests. Cannot proceed."); - UA_ClientConfig_clear(cc); - return UA_STATUSCODE_BADINTERNALERROR; - } - - /* Use the EventLoop from the server for the client */ - if(cc->eventLoop && !cc->externalEventLoop) - cc->eventLoop->free(cc->eventLoop); - cc->eventLoop = sc->eventLoop; - cc->externalEventLoop = true; - - /* Use the logging from the server */ - if(cc->logging->clear) - cc->logging->clear(&cc->logging); - cc->logging = sc->logging; - - /* Set the state callback method and context */ - cc->stateCallback = discoveryClientStateCallback; - cc->clientContext = ar; - - /* Open only a SecureChannel */ - cc->noSession = true; - - /* Move the endpoint url */ - UA_String_clear(&cc->endpointUrl); - UA_String_copy(&discoveryServerUrl, &cc->endpointUrl); - - /* Instantiate the client */ - ar->client = UA_Client_newWithConfig(cc); - if(!ar->client) { - UA_ClientConfig_clear(cc); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - - /* Zero out the supplied config */ - memset(cc, 0, sizeof(UA_ClientConfig)); - - /* Finish setting up the context */ - ar->server = server; - ar->dm = dm; - ar->unregister = unregister; - UA_String_copy(&semaphoreFilePath, &ar->semaphoreFilePath); - - /* Connect asynchronously. The register service is called once the - * connection is open. */ - return __UA_Client_connect(ar->client, true); -} - -UA_StatusCode -UA_Server_registerDiscovery(UA_Server *server, UA_ClientConfig *cc, - const UA_String discoveryServerUrl, - const UA_String semaphoreFilePath) { - UA_LOG_INFO(&server->config.logger, UA_LOGCATEGORY_SERVER, - "Registering at the DiscoveryServer: %.*s", - (int)discoveryServerUrl.length, discoveryServerUrl.data); - UA_LOCK(&server->serviceMutex); - UA_StatusCode res = - UA_Server_register(server, cc, false, discoveryServerUrl, semaphoreFilePath); - UA_UNLOCK(&server->serviceMutex); - return res; -} - -UA_StatusCode -UA_Server_deregisterDiscovery(UA_Server *server, UA_ClientConfig *cc, - const UA_String discoveryServerUrl) { - UA_LOG_INFO(&server->config.logger, UA_LOGCATEGORY_SERVER, - "Deregistering at the DiscoveryServer: %.*s", - (int)discoveryServerUrl.length, discoveryServerUrl.data); - UA_LOCK(&server->serviceMutex); - UA_StatusCode res = - UA_Server_register(server, cc, true, discoveryServerUrl, UA_STRING_NULL); - UA_UNLOCK(&server->serviceMutex); - return res; -} - -#endif /* UA_ENABLE_DISCOVERY */ From 72bcb7b5d2a8d681f775647dab6ea828ddbe8869 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 24 Oct 2023 23:32:42 +0200 Subject: [PATCH 07/10] refactor(server): Move FindServersOnNetwork to ua_services_discovery.c --- src/server/ua_services_discovery.c | 91 ++++++++++++++++++++ src/server/ua_services_discovery_multicast.c | 90 ------------------- 2 files changed, 91 insertions(+), 90 deletions(-) diff --git a/src/server/ua_services_discovery.c b/src/server/ua_services_discovery.c index d149951256d..2fc7a5a4958 100644 --- a/src/server/ua_services_discovery.c +++ b/src/server/ua_services_discovery.c @@ -188,6 +188,97 @@ void Service_FindServers(UA_Server *server, UA_Session *session, } } +#if defined(UA_ENABLE_DISCOVERY) && defined(UA_ENABLE_DISCOVERY_MULTICAST) +/* All filter criteria must be fulfilled in the list entry. The comparison is + * case insensitive. Returns true if the entry matches the filter. */ +static UA_Boolean +entryMatchesCapabilityFilter(size_t serverCapabilityFilterSize, + UA_String *serverCapabilityFilter, + serverOnNetwork_list_entry* current) { + /* If the entry has less capabilities defined than the filter, there's no match */ + if(serverCapabilityFilterSize > current->serverOnNetwork.serverCapabilitiesSize) + return false; + for(size_t i = 0; i < serverCapabilityFilterSize; i++) { + UA_Boolean capabilityFound = false; + for(size_t j = 0; j < current->serverOnNetwork.serverCapabilitiesSize; j++) { + if(UA_String_equal_ignorecase(&serverCapabilityFilter[i], + ¤t->serverOnNetwork.serverCapabilities[j])) { + capabilityFound = true; + break; + } + } + if(!capabilityFound) + return false; + } + return true; +} + +void +Service_FindServersOnNetwork(UA_Server *server, UA_Session *session, + const UA_FindServersOnNetworkRequest *request, + UA_FindServersOnNetworkResponse *response) { + UA_LOCK_ASSERT(&server->serviceMutex, 1); + + UA_DiscoveryManager *dm = (UA_DiscoveryManager*) + getServerComponentByName(server, UA_STRING("discovery")); + if(!dm) { + response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR; + return; + } + + if(!server->config.mdnsEnabled) { + response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTIMPLEMENTED; + return; + } + + /* Set LastCounterResetTime */ + response->lastCounterResetTime = + dm->serverOnNetworkRecordIdLastReset; + + /* Compute the max number of records to return */ + UA_UInt32 recordCount = 0; + if(request->startingRecordId < dm->serverOnNetworkRecordIdCounter) + recordCount = dm->serverOnNetworkRecordIdCounter - request->startingRecordId; + if(request->maxRecordsToReturn && recordCount > request->maxRecordsToReturn) + recordCount = UA_MIN(recordCount, request->maxRecordsToReturn); + if(recordCount == 0) { + response->serversSize = 0; + return; + } + + /* Iterate over all records and add to filtered list */ + UA_UInt32 filteredCount = 0; + UA_STACKARRAY(UA_ServerOnNetwork*, filtered, recordCount); + serverOnNetwork_list_entry* current; + LIST_FOREACH(current, &dm->serverOnNetwork, pointers) { + if(filteredCount >= recordCount) + break; + if(current->serverOnNetwork.recordId < request->startingRecordId) + continue; + if(!entryMatchesCapabilityFilter(request->serverCapabilityFilterSize, + request->serverCapabilityFilter, current)) + continue; + filtered[filteredCount++] = ¤t->serverOnNetwork; + } + + if(filteredCount == 0) + return; + + /* Allocate the array for the response */ + response->servers = (UA_ServerOnNetwork*) + UA_malloc(sizeof(UA_ServerOnNetwork)*filteredCount); + if(!response->servers) { + response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY; + return; + } + response->serversSize = filteredCount; + + /* Copy the server names */ + for(size_t i = 0; i < filteredCount; i++) + UA_ServerOnNetwork_copy(filtered[i], &response->servers[filteredCount-i-1]); +} +#endif + static const UA_String UA_SECURITY_POLICY_BASIC256SHA256_URI = UA_STRING_STATIC("http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"); diff --git a/src/server/ua_services_discovery_multicast.c b/src/server/ua_services_discovery_multicast.c index 4978995dfe2..0448e5af1c4 100644 --- a/src/server/ua_services_discovery_multicast.c +++ b/src/server/ua_services_discovery_multicast.c @@ -8,7 +8,6 @@ */ #include "ua_discovery_manager.h" -#include "ua_services.h" #include "../deps/mp_printf.h" @@ -397,95 +396,6 @@ stopMulticastDiscoveryServer(UA_Server *server) { } } -/* All filter criteria must be fulfilled in the list entry. The comparison is - * case insensitive. Returns true if the entry matches the filter. */ -static UA_Boolean -entryMatchesCapabilityFilter(size_t serverCapabilityFilterSize, - UA_String *serverCapabilityFilter, - serverOnNetwork_list_entry* current) { - /* If the entry has less capabilities defined than the filter, there's no match */ - if(serverCapabilityFilterSize > current->serverOnNetwork.serverCapabilitiesSize) - return false; - for(size_t i = 0; i < serverCapabilityFilterSize; i++) { - UA_Boolean capabilityFound = false; - for(size_t j = 0; j < current->serverOnNetwork.serverCapabilitiesSize; j++) { - if(UA_String_equal_ignorecase(&serverCapabilityFilter[i], - ¤t->serverOnNetwork.serverCapabilities[j])) { - capabilityFound = true; - break; - } - } - if(!capabilityFound) - return false; - } - return true; -} - -void -Service_FindServersOnNetwork(UA_Server *server, UA_Session *session, - const UA_FindServersOnNetworkRequest *request, - UA_FindServersOnNetworkResponse *response) { - UA_LOCK_ASSERT(&server->serviceMutex, 1); - - UA_DiscoveryManager *dm = (UA_DiscoveryManager*) - getServerComponentByName(server, UA_STRING("discovery")); - if(!dm) { - response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR; - return; - } - - if(!server->config.mdnsEnabled) { - response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTIMPLEMENTED; - return; - } - - /* Set LastCounterResetTime */ - response->lastCounterResetTime = - dm->serverOnNetworkRecordIdLastReset; - - /* Compute the max number of records to return */ - UA_UInt32 recordCount = 0; - if(request->startingRecordId < dm->serverOnNetworkRecordIdCounter) - recordCount = dm->serverOnNetworkRecordIdCounter - request->startingRecordId; - if(request->maxRecordsToReturn && recordCount > request->maxRecordsToReturn) - recordCount = UA_MIN(recordCount, request->maxRecordsToReturn); - if(recordCount == 0) { - response->serversSize = 0; - return; - } - - /* Iterate over all records and add to filtered list */ - UA_UInt32 filteredCount = 0; - UA_STACKARRAY(UA_ServerOnNetwork*, filtered, recordCount); - serverOnNetwork_list_entry* current; - LIST_FOREACH(current, &dm->serverOnNetwork, pointers) { - if(filteredCount >= recordCount) - break; - if(current->serverOnNetwork.recordId < request->startingRecordId) - continue; - if(!entryMatchesCapabilityFilter(request->serverCapabilityFilterSize, - request->serverCapabilityFilter, current)) - continue; - filtered[filteredCount++] = ¤t->serverOnNetwork; - } - - if(filteredCount == 0) - return; - - /* Allocate the array for the response */ - response->servers = (UA_ServerOnNetwork*) - UA_malloc(sizeof(UA_ServerOnNetwork)*filteredCount); - if(!response->servers) { - response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY; - return; - } - response->serversSize = filteredCount; - - /* Copy the server names */ - for(size_t i = 0; i < filteredCount; i++) - UA_ServerOnNetwork_copy(filtered[i], &response->servers[filteredCount-i-1]); -} - void UA_Discovery_updateMdnsForDiscoveryUrl(UA_DiscoveryManager *dm, const UA_String *serverName, const UA_MdnsDiscoveryConfiguration *mdnsConfig, From acb380a20a06ba9984c50523e48539266c3c9337 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 24 Oct 2023 23:39:10 +0200 Subject: [PATCH 08/10] refactor(server): Merge ua_services_discovery_multicast.c into ua_server_discovery_mdns.c --- CMakeLists.txt | 1 - src/server/ua_server_discovery_mdns.c | 751 ++++++++++++++++++ src/server/ua_services_discovery_multicast.c | 772 ------------------- 3 files changed, 751 insertions(+), 773 deletions(-) delete mode 100644 src/server/ua_services_discovery_multicast.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 81e0c4554f9..1846c93da0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -832,7 +832,6 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c ${PROJECT_SOURCE_DIR}/src/server/ua_services_monitoreditem.c ${PROJECT_SOURCE_DIR}/src/server/ua_services_securechannel.c ${PROJECT_SOURCE_DIR}/src/server/ua_services_nodemanagement.c - ${PROJECT_SOURCE_DIR}/src/server/ua_services_discovery_multicast.c # pubsub ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_networkmessage.c ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_eventloop.c diff --git a/src/server/ua_server_discovery_mdns.c b/src/server/ua_server_discovery_mdns.c index 075b0225a5f..4374f1a3f55 100644 --- a/src/server/ua_server_discovery_mdns.c +++ b/src/server/ua_server_discovery_mdns.c @@ -4,6 +4,7 @@ * * Copyright 2017 (c) Stefan Profanter, fortiss GmbH * Copyright 2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer) + * Copyright 2017 (c) Thomas Stalder, Blue Time Concept SA */ #include "ua_discovery_manager.h" @@ -25,6 +26,8 @@ # include # include #else +# include +# include # include // for struct timeval # include // for struct ip_mreq # if defined(UA_HAS_GETIFADDR) @@ -629,4 +632,752 @@ mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, #endif /* _WIN32 */ +typedef enum { + UA_DISCOVERY_TCP, /* OPC UA TCP mapping */ + UA_DISCOVERY_TLS /* OPC UA HTTPS mapping */ +} UA_DiscoveryProtocol; + +/* Create a mDNS Record for the given server info and adds it to the mDNS output + * queue. + * + * Additionally this method also adds the given server to the internal + * serversOnNetwork list so that a client finds it when calling + * FindServersOnNetwork. */ +static UA_StatusCode +UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String *servername, + const UA_String *hostname, UA_UInt16 port, + const UA_String *path, const UA_DiscoveryProtocol protocol, + UA_Boolean createTxt, const UA_String* capabilites, + const size_t capabilitiesSize, + UA_Boolean isSelf); + +/* Create a mDNS Record for the given server info with TTL=0 and adds it to the + * mDNS output queue. + * + * Additionally this method also removes the given server from the internal + * serversOnNetwork list so that a client gets the updated data when calling + * FindServersOnNetwork. */ +static UA_StatusCode +UA_Discovery_removeRecord(UA_DiscoveryManager *dm, const UA_String *servername, + const UA_String *hostname, UA_UInt16 port, + UA_Boolean removeTxt); + +static int +discovery_multicastQueryAnswer(mdns_answer_t *a, void *arg); + +static void +mdnsAddConnection(UA_DiscoveryManager *dm, uintptr_t connectionId, + UA_Boolean recv) { + if(!recv) { + dm->mdnsSendConnection = connectionId; + return; + } + for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { + if(dm->mdnsRecvConnections[i] == connectionId) + return; + } + + for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { + if(dm->mdnsRecvConnections[i] != 0) + continue; + dm->mdnsRecvConnections[i] = connectionId; + dm->mdnsRecvConnectionsSize++; + break; + } +} + +static void +mdnsRemoveConnection(UA_DiscoveryManager *dm, uintptr_t connectionId, + UA_Boolean recv) { + if(dm->mdnsSendConnection == connectionId) { + dm->mdnsSendConnection = 0; + return; + } + for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { + if(dm->mdnsRecvConnections[i] != connectionId) + continue; + dm->mdnsRecvConnections[i] = 0; + dm->mdnsRecvConnectionsSize--; + break; + } +} + +static void +MulticastDiscoveryCallback(UA_ConnectionManager *cm, uintptr_t connectionId, + void *application, void **connectionContext, + UA_ConnectionState state, const UA_KeyValueMap *params, + UA_ByteString msg, UA_Boolean recv) { + UA_Server *server = (UA_Server*)application; + UA_DiscoveryManager *dm = *(UA_DiscoveryManager**)connectionContext; + + if(state == UA_CONNECTIONSTATE_CLOSING) { + mdnsRemoveConnection(dm, connectionId, recv); + + /* Fully stopped? Internally checks if all sockets are closed. */ + UA_DiscoveryManager_setState(server, dm, dm->sc.state); + + /* Restart mdns sockets if not shutting down */ + if(dm->sc.state == UA_LIFECYCLESTATE_STARTED) + startMulticastDiscoveryServer(server); + + return; + } + + mdnsAddConnection(dm, connectionId, recv); + + if(msg.length == 0) + return; + + /* Prepare the sockaddrinfo */ + const UA_UInt16 *port = (const UA_UInt16*) + UA_KeyValueMap_getScalar(params, UA_QUALIFIEDNAME(0, "remote-port"), + &UA_TYPES[UA_TYPES_UINT16]); + const UA_String *address = (const UA_String*) + UA_KeyValueMap_getScalar(params, UA_QUALIFIEDNAME(0, "remote-address"), + &UA_TYPES[UA_TYPES_STRING]); + if(!port || !address) + return; + + char portStr[16]; + UA_UInt16 myPort = *port; + for(size_t i = 0; i < 16; i++) { + if(myPort == 0) { + portStr[i] = 0; + break; + } + unsigned char rem = (unsigned char)(myPort % 10); + portStr[i] = (char)(rem + 48); /* to ascii */ + myPort = myPort / 10; + } + + struct addrinfo *infoptr; + int res = getaddrinfo((const char*)address->data, portStr, NULL, &infoptr); + if(res != 0) + return; + + /* Parse and process the message */ + struct message mm; + memset(&mm, 0, sizeof(struct message)); + UA_Boolean rr = message_parse(&mm, (unsigned char*)msg.data, msg.length); + if(rr) + mdnsd_in(dm->mdnsDaemon, &mm, infoptr->ai_addr, + (unsigned short)infoptr->ai_addrlen); + freeaddrinfo(infoptr); +} + +void +sendMulticastMessages(UA_DiscoveryManager *dm) { + if(!dm->cm || dm->mdnsSendConnection == 0) + return; + UA_ConnectionManager *cm = dm->cm; + + struct sockaddr ip; + memset(&ip, 0, sizeof(struct sockaddr)); + ip.sa_family = AF_INET; /* Ipv4 */ + + struct message mm; + memset(&mm, 0, sizeof(struct message)); + + unsigned short sport = 0; + while(mdnsd_out(dm->mdnsDaemon, &mm, &ip, &sport) > 0) { + int len = message_packet_len(&mm); + char* buf = (char*)message_packet(&mm); + if(len > 0) { + UA_ByteString sendBuf = UA_BYTESTRING_NULL; + UA_StatusCode rv = cm->allocNetworkBuffer(cm, dm->mdnsSendConnection, + &sendBuf, (size_t)len); + if(rv == UA_STATUSCODE_GOOD) { + memcpy(sendBuf.data, buf, sendBuf.length); + cm->sendWithConnection(cm, dm->mdnsSendConnection, + &UA_KEYVALUEMAP_NULL, &sendBuf); + } + } + } +} + +static void +MulticastDiscoveryRecvCallback(UA_ConnectionManager *cm, uintptr_t connectionId, + void *application, void **connectionContext, + UA_ConnectionState state, const UA_KeyValueMap *params, + UA_ByteString msg) { + MulticastDiscoveryCallback(cm, connectionId, application, connectionContext, + state, params, msg, true); +} + +static void +MulticastDiscoverySendCallback(UA_ConnectionManager *cm, uintptr_t connectionId, + void *application, void **connectionContext, + UA_ConnectionState state, const UA_KeyValueMap *params, + UA_ByteString msg) { + MulticastDiscoveryCallback(cm, connectionId, application, connectionContext, + state, params, msg, false); +} + +static UA_StatusCode +addMdnsRecordForNetworkLayer(UA_DiscoveryManager *dm, const UA_String *appName, + const UA_String *discoveryUrl) { + UA_String hostname = UA_STRING_NULL; + UA_UInt16 port = 4840; + UA_String path = UA_STRING_NULL; + UA_StatusCode retval = + UA_parseEndpointUrl(discoveryUrl, &hostname, &port, &path); + if(retval != UA_STATUSCODE_GOOD) { + UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "Server url is invalid: %.*s", + (int)discoveryUrl->length, discoveryUrl->data); + return retval; + } + + retval = UA_Discovery_addRecord(dm, appName, &hostname, port, + &path, UA_DISCOVERY_TCP, true, + dm->serverConfig->mdnsConfig.serverCapabilities, + dm->serverConfig->mdnsConfig.serverCapabilitiesSize, + true); + if(retval != UA_STATUSCODE_GOOD) { + UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "Cannot add mDNS Record: %s", UA_StatusCode_name(retval)); + return retval; + } + return UA_STATUSCODE_GOOD; +} + +#ifndef IN_ZERONET +#define IN_ZERONET(addr) ((addr & IN_CLASSA_NET) == 0) +#endif + +/* Create multicast 224.0.0.251:5353 socket */ +static void +discovery_createMulticastSocket(UA_Server* server, UA_DiscoveryManager *dm) { + /* Find the connection manager */ + if(!dm->cm) { + UA_String udpString = UA_STRING("udp"); + for(UA_EventSource *es = server->config.eventLoop->eventSources; + es != NULL; es = es->next) { + /* Is this a usable connection manager? */ + if(es->eventSourceType != UA_EVENTSOURCETYPE_CONNECTIONMANAGER) + continue; + UA_ConnectionManager *cm = (UA_ConnectionManager*)es; + if(UA_String_equal(&udpString, &cm->protocol)) { + dm->cm = cm; + break; + } + } + } + + if(!dm->cm) { + UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "No UDP communication supported"); + return; + } + + /* Set up the parameters */ + UA_KeyValuePair params[6]; + size_t paramsSize = 5; + + UA_UInt16 port = 5353; + UA_String address = UA_STRING("224.0.0.251"); + UA_UInt32 ttl = 255; + UA_Boolean reuse = true; + UA_Boolean listen = true; + + params[0].key = UA_QUALIFIEDNAME(0, "port"); + UA_Variant_setScalar(¶ms[0].value, &port, &UA_TYPES[UA_TYPES_UINT16]); + params[1].key = UA_QUALIFIEDNAME(0, "address"); + UA_Variant_setScalar(¶ms[1].value, &address, &UA_TYPES[UA_TYPES_STRING]); + params[2].key = UA_QUALIFIEDNAME(0, "listen"); + UA_Variant_setScalar(¶ms[2].value, &listen, &UA_TYPES[UA_TYPES_BOOLEAN]); + params[3].key = UA_QUALIFIEDNAME(0, "reuse"); + UA_Variant_setScalar(¶ms[3].value, &reuse, &UA_TYPES[UA_TYPES_BOOLEAN]); + params[4].key = UA_QUALIFIEDNAME(0, "ttl"); + UA_Variant_setScalar(¶ms[4].value, &ttl, &UA_TYPES[UA_TYPES_UINT32]); + if(server->config.mdnsInterfaceIP.length > 0) { + params[5].key = UA_QUALIFIEDNAME(0, "interface"); + UA_Variant_setScalar(¶ms[5].value, &server->config.mdnsInterfaceIP, + &UA_TYPES[UA_TYPES_STRING]); + paramsSize++; + } + + /* Open the listen connection */ + UA_KeyValueMap kvm = {paramsSize, params}; + UA_StatusCode res = UA_STATUSCODE_GOOD; + + if(dm->mdnsRecvConnectionsSize == 0) { + res = dm->cm->openConnection(dm->cm, &kvm, server, dm, + MulticastDiscoveryRecvCallback); + if(res != UA_STATUSCODE_GOOD) + UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "Could not create the mdns UDP multicast listen connection"); + } + + /* Open the send connection */ + listen = false; + if(dm->mdnsSendConnection == 0) { + res = dm->cm->openConnection(dm->cm, &kvm, server, dm, + MulticastDiscoverySendCallback); + if(res != UA_STATUSCODE_GOOD) + UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "Could not create the mdns UDP multicast send connection"); + } +} + +void +startMulticastDiscoveryServer(UA_Server *server) { + /* Initialize the mdns daemon */ + UA_DiscoveryManager *dm = (UA_DiscoveryManager*) + getServerComponentByName(server, UA_STRING("discovery")); + if(!dm) + return; + + if(!dm->mdnsDaemon) { + dm->mdnsDaemon = mdnsd_new(QCLASS_IN, 1000); + mdnsd_register_receive_callback(dm->mdnsDaemon, mdns_record_received, dm); + } + +#if defined(UA_ARCHITECTURE_WIN32) || defined(UA_ARCHITECTURE_WEC7) + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); +#endif + + /* Open the mdns listen socket */ + if(dm->mdnsSendConnection == 0) + discovery_createMulticastSocket(server, dm); + if(dm->mdnsSendConnection == 0) { + UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_DISCOVERY, + "Could not create multicast socket"); + return; + } + + /* Add record for the server itself */ + UA_String *appName = &server->config.mdnsConfig.mdnsServerName; + for(size_t i = 0; i < server->config.serverUrlsSize; i++) + addMdnsRecordForNetworkLayer(dm, appName, &server->config.serverUrls[i]); + + /* Send a multicast probe to find any other OPC UA server on the network + * through mDNS */ + mdnsd_query(dm->mdnsDaemon, "_opcua-tcp._tcp.local.", + QTYPE_PTR,discovery_multicastQueryAnswer, server); +} + +void +stopMulticastDiscoveryServer(UA_Server *server) { + UA_DiscoveryManager *dm = (UA_DiscoveryManager*) + getServerComponentByName(server, UA_STRING("discovery")); + if(!dm) + return; + + for(size_t i = 0; i < server->config.serverUrlsSize; i++) { + UA_String hostname = UA_STRING_NULL; + UA_String path = UA_STRING_NULL; + UA_UInt16 port = 0; + + UA_StatusCode retval = + UA_parseEndpointUrl(&server->config.serverUrls[i], + &hostname, &port, &path); + + if(retval != UA_STATUSCODE_GOOD || hostname.length == 0) + continue; + + UA_Discovery_removeRecord(dm, &server->config.mdnsConfig.mdnsServerName, + &hostname, port, true); + } + + /* Stop the cyclic polling callback */ + if(dm->mdnsCallbackId != 0) { + UA_EventLoop *el = server->config.eventLoop; + if(el) { + el->removeCyclicCallback(el, dm->mdnsCallbackId); + dm->mdnsCallbackId = 0; + } + } + + /* Clean up mdns daemon */ + if(dm->mdnsDaemon) { + mdnsd_shutdown(dm->mdnsDaemon); + mdnsd_free(dm->mdnsDaemon); + dm->mdnsDaemon = NULL; + } + + /* Close the socket */ + if(dm->cm) { + if(dm->mdnsSendConnection) + dm->cm->closeConnection(dm->cm, dm->mdnsSendConnection); + for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) + if(dm->mdnsRecvConnections[i] != 0) + dm->cm->closeConnection(dm->cm, dm->mdnsRecvConnections[i]); + } +} + +void +UA_Discovery_updateMdnsForDiscoveryUrl(UA_DiscoveryManager *dm, const UA_String *serverName, + const UA_MdnsDiscoveryConfiguration *mdnsConfig, + const UA_String *discoveryUrl, + UA_Boolean isOnline, UA_Boolean updateTxt) { + UA_String hostname = UA_STRING_NULL; + UA_UInt16 port = 4840; + UA_String path = UA_STRING_NULL; + UA_StatusCode retval = + UA_parseEndpointUrl(discoveryUrl, &hostname, &port, &path); + if(retval != UA_STATUSCODE_GOOD) { + UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "Server url invalid: %.*s", + (int)discoveryUrl->length, discoveryUrl->data); + return; + } + + if(!isOnline) { + UA_StatusCode removeRetval = + UA_Discovery_removeRecord(dm, serverName, &hostname, + port, updateTxt); + if(removeRetval != UA_STATUSCODE_GOOD) + UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "Could not remove mDNS record for hostname %.*s.", + (int)serverName->length, serverName->data); + return; + } + + UA_String *capabilities = NULL; + size_t capabilitiesSize = 0; + if(mdnsConfig) { + capabilities = mdnsConfig->serverCapabilities; + capabilitiesSize = mdnsConfig->serverCapabilitiesSize; + } + + UA_StatusCode addRetval = + UA_Discovery_addRecord(dm, serverName, &hostname, + port, &path, UA_DISCOVERY_TCP, updateTxt, + capabilities, capabilitiesSize, + false); + if(addRetval != UA_STATUSCODE_GOOD) + UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "Could not add mDNS record for hostname %.*s.", + (int)serverName->length, serverName->data); +} + +void +UA_Server_setServerOnNetworkCallback(UA_Server *server, + UA_Server_serverOnNetworkCallback cb, + void* data) { + UA_LOCK(&server->serviceMutex); + UA_DiscoveryManager *dm = (UA_DiscoveryManager*) + getServerComponentByName(server, UA_STRING("discovery")); + if(dm) { + dm->serverOnNetworkCallback = cb; + dm->serverOnNetworkCallbackData = data; + } + UA_UNLOCK(&server->serviceMutex); +} + +static void +UA_Discovery_multicastConflict(char *name, int type, void *arg) { + /* In case logging is disabled */ + (void)name; + (void)type; + + UA_DiscoveryManager *dm = (UA_DiscoveryManager*) arg; + UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS name conflict detected: " + "'%s' for type %d", name, type); +} + +/* Create a service domain with the format [servername]-[hostname]._opcua-tcp._tcp.local. */ +static void +createFullServiceDomain(char *outServiceDomain, size_t maxLen, + const UA_String *servername, const UA_String *hostname) { + size_t hostnameLen = hostname->length; + size_t servernameLen = servername->length; + + maxLen -= 24; /* the length we have remaining before the opc ua postfix and + * the trailing zero */ + + /* Can we use hostname and servername with full length? */ + if(hostnameLen + servernameLen + 1 > maxLen) { + if(servernameLen + 2 > maxLen) { + servernameLen = maxLen; + hostnameLen = 0; + } else { + hostnameLen = maxLen - servernameLen - 1; + } + } + + size_t offset = 0; + if(hostnameLen > 0) { + mp_snprintf(outServiceDomain, maxLen + 1, "%.*s-%.*s", + (int) servernameLen, (char *) servername->data, + (int) hostnameLen, (char *) hostname->data); + offset = servernameLen + hostnameLen + 1; + //replace all dots with minus. Otherwise mDNS is not valid + for(size_t i=servernameLen+1; idata); + offset = servernameLen; + } + mp_snprintf(&outServiceDomain[offset], 24, "._opcua-tcp._tcp.local."); +} + +/* Check if mDNS already has an entry for given hostname and port combination */ +static UA_Boolean +UA_Discovery_recordExists(UA_DiscoveryManager *dm, const char* fullServiceDomain, + unsigned short port, const UA_DiscoveryProtocol protocol) { + // [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname]. + mdns_record_t *r = mdnsd_get_published(dm->mdnsDaemon, fullServiceDomain); + while(r) { + const mdns_answer_t *data = mdnsd_record_data(r); + if(data->type == QTYPE_SRV && (port == 0 || data->srv.port == port)) + return true; + r = mdnsd_record_next(r); + } + return false; +} + +static int +discovery_multicastQueryAnswer(mdns_answer_t *a, void *arg) { + UA_Server *server = (UA_Server*) arg; + UA_DiscoveryManager *dm = (UA_DiscoveryManager*) + getServerComponentByName(server, UA_STRING("discovery")); + if(!dm) + return 0; + + if(a->type != QTYPE_PTR) + return 0; + + if(a->rdname == NULL) + return 0; + + /* Skip, if we already know about this server */ + UA_Boolean exists = + UA_Discovery_recordExists(dm, a->rdname, 0, UA_DISCOVERY_TCP); + if(exists == true) + return 0; + + if(mdnsd_has_query(dm->mdnsDaemon, a->rdname)) + return 0; + + UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_DISCOVERY, + "mDNS send query for: %s SRV&TXT %s", a->name, a->rdname); + + mdnsd_query(dm->mdnsDaemon, a->rdname, QTYPE_SRV, + discovery_multicastQueryAnswer, server); + mdnsd_query(dm->mdnsDaemon, a->rdname, QTYPE_TXT, + discovery_multicastQueryAnswer, server); + return 0; +} + +static UA_StatusCode +UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String *servername, + const UA_String *hostname, UA_UInt16 port, + const UA_String *path, const UA_DiscoveryProtocol protocol, + UA_Boolean createTxt, const UA_String* capabilites, + const size_t capabilitiesSize, + UA_Boolean isSelf) { + /* We assume that the hostname is not an IP address, but a valid domain + * name. It is required by the OPC UA spec (see Part 12, DiscoveryURL to DNS + * SRV mapping) to always use the hostname instead of the IP address. */ + + if(capabilitiesSize > 0 && !capabilites) + return UA_STATUSCODE_BADINVALIDARGUMENT; + + size_t hostnameLen = hostname->length; + size_t servernameLen = servername->length; + if(hostnameLen == 0 || servernameLen == 0) + return UA_STATUSCODE_BADOUTOFRANGE; + + /* Use a limit for the hostname length to make sure full string fits into 63 + * chars (limited by DNS spec) */ + if(hostnameLen+servernameLen + 1 > 63) { /* include dash between servername-hostname */ + UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS: Combination of hostname+servername exceeds " + "maximum of 62 chars. It will be truncated."); + } else if(hostnameLen > 63) { + UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS: Hostname length exceeds maximum of 63 chars. " + "It will be truncated."); + } + + if(!dm->mdnsMainSrvAdded) { + mdns_record_t *r = + mdnsd_shared(dm->mdnsDaemon, "_services._dns-sd._udp.local.", + QTYPE_PTR, 600); + mdnsd_set_host(dm->mdnsDaemon, r, "_opcua-tcp._tcp.local."); + dm->mdnsMainSrvAdded = true; + } + + /* [servername]-[hostname]._opcua-tcp._tcp.local. */ + char fullServiceDomain[63+24]; + createFullServiceDomain(fullServiceDomain, 63+24, servername, hostname); + + UA_Boolean exists = UA_Discovery_recordExists(dm, fullServiceDomain, + port, protocol); + if(exists == true) + return UA_STATUSCODE_GOOD; + + UA_LOG_INFO(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS: add record for domain: %s", fullServiceDomain); + + if(isSelf && dm->selfFqdnMdnsRecord.length == 0) { + dm->selfFqdnMdnsRecord = UA_STRING_ALLOC(fullServiceDomain); + if(!dm->selfFqdnMdnsRecord.data) + return UA_STATUSCODE_BADOUTOFMEMORY; + } + + struct serverOnNetwork_list_entry *listEntry; + /* The servername is servername + hostname. It is the same which we get + * through mDNS and therefore we need to match servername */ + UA_StatusCode retval = + UA_DiscoveryManager_addEntryToServersOnNetwork(dm, fullServiceDomain, + fullServiceDomain, + UA_MIN(63, (servernameLen+hostnameLen)+1), + &listEntry); + if(retval != UA_STATUSCODE_GOOD && + retval != UA_STATUSCODE_BADALREADYEXISTS) + return retval; + + /* If entry is already in list, skip initialization of capabilities and txt+srv */ + if(retval != UA_STATUSCODE_BADALREADYEXISTS) { + /* if capabilitiesSize is 0, then add default cap 'NA' */ + listEntry->serverOnNetwork.serverCapabilitiesSize = UA_MAX(1, capabilitiesSize); + listEntry->serverOnNetwork.serverCapabilities = (UA_String *) + UA_Array_new(listEntry->serverOnNetwork.serverCapabilitiesSize, + &UA_TYPES[UA_TYPES_STRING]); + if(!listEntry->serverOnNetwork.serverCapabilities) + return UA_STATUSCODE_BADOUTOFMEMORY; + if(capabilitiesSize == 0) { + UA_String na; + na.length = 2; + na.data = (UA_Byte *) (uintptr_t) "NA"; + UA_String_copy(&na, &listEntry->serverOnNetwork.serverCapabilities[0]); + } else { + for(size_t i = 0; i < capabilitiesSize; i++) + UA_String_copy(&capabilites[i], + &listEntry->serverOnNetwork.serverCapabilities[i]); + } + + listEntry->txtSet = true; + + UA_STACKARRAY(char, newUrl, 10 + hostnameLen + 8 + path->length + 1); + mp_snprintf(newUrl, 10 + hostnameLen + 8 + path->length + 1, + "opc.tcp://%.*s:%d%s%.*s", (int) hostnameLen, + hostname->data, port, path->length > 0 ? "/" : "", + (int) path->length, path->data); + listEntry->serverOnNetwork.discoveryUrl = UA_String_fromChars(newUrl); + listEntry->srvSet = true; + } + + /* _services._dns-sd._udp.local. PTR _opcua-tcp._tcp.local */ + + /* check if there is already a PTR entry for the given service. */ + + /* _opcua-tcp._tcp.local. PTR [servername]-[hostname]._opcua-tcp._tcp.local. */ + mdns_record_t *r = + mdns_find_record(dm->mdnsDaemon, QTYPE_PTR, + "_opcua-tcp._tcp.local.", fullServiceDomain); + if(!r) { + r = mdnsd_shared(dm->mdnsDaemon, "_opcua-tcp._tcp.local.", + QTYPE_PTR, 600); + mdnsd_set_host(dm->mdnsDaemon, r, fullServiceDomain); + } + + /* The first 63 characters of the hostname (or less) */ + size_t maxHostnameLen = UA_MIN(hostnameLen, 63); + char localDomain[65]; + memcpy(localDomain, hostname->data, maxHostnameLen); + localDomain[maxHostnameLen] = '.'; + localDomain[maxHostnameLen+1] = '\0'; + + /* [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname]. */ + r = mdnsd_unique(dm->mdnsDaemon, fullServiceDomain, + QTYPE_SRV, 600, UA_Discovery_multicastConflict, dm); + mdnsd_set_srv(dm->mdnsDaemon, r, 0, 0, port, localDomain); + + /* A/AAAA record for all ip addresses. + * [servername]-[hostname]._opcua-tcp._tcp.local. A [ip]. + * [hostname]. A [ip]. */ + mdns_set_address_record(dm, fullServiceDomain, localDomain); + + /* TXT record: [servername]-[hostname]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,... */ + UA_STACKARRAY(char, pathChars, path->length + 1); + if(createTxt) { + if(path->length > 0) + memcpy(pathChars, path->data, path->length); + pathChars[path->length] = 0; + mdns_create_txt(dm, fullServiceDomain, pathChars, capabilites, + capabilitiesSize, UA_Discovery_multicastConflict); + } + + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +UA_Discovery_removeRecord(UA_DiscoveryManager *dm, const UA_String *servername, + const UA_String *hostname, UA_UInt16 port, + UA_Boolean removeTxt) { + /* use a limit for the hostname length to make sure full string fits into 63 + * chars (limited by DNS spec) */ + size_t hostnameLen = hostname->length; + size_t servernameLen = servername->length; + if(hostnameLen == 0 || servernameLen == 0) + return UA_STATUSCODE_BADOUTOFRANGE; + + if(hostnameLen+servernameLen+1 > 63) { /* include dash between servername-hostname */ + UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS: Combination of hostname+servername exceeds " + "maximum of 62 chars. It will be truncated."); + } + + /* [servername]-[hostname]._opcua-tcp._tcp.local. */ + char fullServiceDomain[63 + 24]; + createFullServiceDomain(fullServiceDomain, 63+24, servername, hostname); + + UA_LOG_INFO(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS: remove record for domain: %s", + fullServiceDomain); + + UA_StatusCode retval = + UA_DiscoveryManager_removeEntryFromServersOnNetwork(dm, fullServiceDomain, + fullServiceDomain, UA_MIN(63, (servernameLen+hostnameLen)+1)); + if(retval != UA_STATUSCODE_GOOD) + return retval; + + /* _opcua-tcp._tcp.local. PTR [servername]-[hostname]._opcua-tcp._tcp.local. */ + mdns_record_t *r = + mdns_find_record(dm->mdnsDaemon, QTYPE_PTR, + "_opcua-tcp._tcp.local.", fullServiceDomain); + if(!r) { + UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS: could not remove record. " + "PTR Record not found for domain: %s", fullServiceDomain); + return UA_STATUSCODE_BADNOTHINGTODO; + } + mdnsd_done(dm->mdnsDaemon, r); + + /* looks for [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 + * port hostname.local. and TXT record: + * [servername]-[hostname]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,... + * and A record: [servername]-[hostname]._opcua-tcp._tcp.local. A [ip] */ + mdns_record_t *r2 = + mdnsd_get_published(dm->mdnsDaemon, fullServiceDomain); + if(!r2) { + UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS: could not remove record. Record not " + "found for domain: %s", fullServiceDomain); + return UA_STATUSCODE_BADNOTHINGTODO; + } + + while(r2) { + const mdns_answer_t *data = mdnsd_record_data(r2); + mdns_record_t *next = mdnsd_record_next(r2); + if((removeTxt && data->type == QTYPE_TXT) || + (removeTxt && data->type == QTYPE_A) || + data->srv.port == port) { + mdnsd_done(dm->mdnsDaemon, r2); + } + r2 = next; + } + + return UA_STATUSCODE_GOOD; +} + #endif /* UA_ENABLE_DISCOVERY_MULTICAST */ diff --git a/src/server/ua_services_discovery_multicast.c b/src/server/ua_services_discovery_multicast.c deleted file mode 100644 index 0448e5af1c4..00000000000 --- a/src/server/ua_services_discovery_multicast.c +++ /dev/null @@ -1,772 +0,0 @@ -/* 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 - */ - -#include "ua_discovery_manager.h" - -#include "../deps/mp_printf.h" - -#if defined(UA_ENABLE_DISCOVERY) && defined(UA_ENABLE_DISCOVERY_MULTICAST) - -#ifdef _WIN32 -# include -#else -# include -# include -# include -#endif - -typedef enum { - UA_DISCOVERY_TCP, /* OPC UA TCP mapping */ - UA_DISCOVERY_TLS /* OPC UA HTTPS mapping */ -} UA_DiscoveryProtocol; - -/* Create a mDNS Record for the given server info and adds it to the mDNS output - * queue. - * - * Additionally this method also adds the given server to the internal - * serversOnNetwork list so that a client finds it when calling - * FindServersOnNetwork. */ -static UA_StatusCode -UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String *servername, - const UA_String *hostname, UA_UInt16 port, - const UA_String *path, const UA_DiscoveryProtocol protocol, - UA_Boolean createTxt, const UA_String* capabilites, - const size_t capabilitiesSize, - UA_Boolean isSelf); - -/* Create a mDNS Record for the given server info with TTL=0 and adds it to the - * mDNS output queue. - * - * Additionally this method also removes the given server from the internal - * serversOnNetwork list so that a client gets the updated data when calling - * FindServersOnNetwork. */ -static UA_StatusCode -UA_Discovery_removeRecord(UA_DiscoveryManager *dm, const UA_String *servername, - const UA_String *hostname, UA_UInt16 port, - UA_Boolean removeTxt); - -static int -discovery_multicastQueryAnswer(mdns_answer_t *a, void *arg); - -static void -mdnsAddConnection(UA_DiscoveryManager *dm, uintptr_t connectionId, - UA_Boolean recv) { - if(!recv) { - dm->mdnsSendConnection = connectionId; - return; - } - for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { - if(dm->mdnsRecvConnections[i] == connectionId) - return; - } - - for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { - if(dm->mdnsRecvConnections[i] != 0) - continue; - dm->mdnsRecvConnections[i] = connectionId; - dm->mdnsRecvConnectionsSize++; - break; - } -} - -static void -mdnsRemoveConnection(UA_DiscoveryManager *dm, uintptr_t connectionId, - UA_Boolean recv) { - if(dm->mdnsSendConnection == connectionId) { - dm->mdnsSendConnection = 0; - return; - } - for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { - if(dm->mdnsRecvConnections[i] != connectionId) - continue; - dm->mdnsRecvConnections[i] = 0; - dm->mdnsRecvConnectionsSize--; - break; - } -} - -static void -MulticastDiscoveryCallback(UA_ConnectionManager *cm, uintptr_t connectionId, - void *application, void **connectionContext, - UA_ConnectionState state, const UA_KeyValueMap *params, - UA_ByteString msg, UA_Boolean recv) { - UA_Server *server = (UA_Server*)application; - UA_DiscoveryManager *dm = *(UA_DiscoveryManager**)connectionContext; - - if(state == UA_CONNECTIONSTATE_CLOSING) { - mdnsRemoveConnection(dm, connectionId, recv); - - /* Fully stopped? Internally checks if all sockets are closed. */ - UA_DiscoveryManager_setState(server, dm, dm->sc.state); - - /* Restart mdns sockets if not shutting down */ - if(dm->sc.state == UA_LIFECYCLESTATE_STARTED) - startMulticastDiscoveryServer(server); - - return; - } - - mdnsAddConnection(dm, connectionId, recv); - - if(msg.length == 0) - return; - - /* Prepare the sockaddrinfo */ - const UA_UInt16 *port = (const UA_UInt16*) - UA_KeyValueMap_getScalar(params, UA_QUALIFIEDNAME(0, "remote-port"), - &UA_TYPES[UA_TYPES_UINT16]); - const UA_String *address = (const UA_String*) - UA_KeyValueMap_getScalar(params, UA_QUALIFIEDNAME(0, "remote-address"), - &UA_TYPES[UA_TYPES_STRING]); - if(!port || !address) - return; - - char portStr[16]; - UA_UInt16 myPort = *port; - for(size_t i = 0; i < 16; i++) { - if(myPort == 0) { - portStr[i] = 0; - break; - } - unsigned char rem = (unsigned char)(myPort % 10); - portStr[i] = (char)(rem + 48); /* to ascii */ - myPort = myPort / 10; - } - - struct addrinfo *infoptr; - int res = getaddrinfo((const char*)address->data, portStr, NULL, &infoptr); - if(res != 0) - return; - - /* Parse and process the message */ - struct message mm; - memset(&mm, 0, sizeof(struct message)); - UA_Boolean rr = message_parse(&mm, (unsigned char*)msg.data, msg.length); - if(rr) - mdnsd_in(dm->mdnsDaemon, &mm, infoptr->ai_addr, - (unsigned short)infoptr->ai_addrlen); - freeaddrinfo(infoptr); -} - -void -sendMulticastMessages(UA_DiscoveryManager *dm) { - if(!dm->cm || dm->mdnsSendConnection == 0) - return; - UA_ConnectionManager *cm = dm->cm; - - struct sockaddr ip; - memset(&ip, 0, sizeof(struct sockaddr)); - ip.sa_family = AF_INET; /* Ipv4 */ - - struct message mm; - memset(&mm, 0, sizeof(struct message)); - - unsigned short sport = 0; - while(mdnsd_out(dm->mdnsDaemon, &mm, &ip, &sport) > 0) { - int len = message_packet_len(&mm); - char* buf = (char*)message_packet(&mm); - if(len > 0) { - UA_ByteString sendBuf = UA_BYTESTRING_NULL; - UA_StatusCode rv = cm->allocNetworkBuffer(cm, dm->mdnsSendConnection, - &sendBuf, (size_t)len); - if(rv == UA_STATUSCODE_GOOD) { - memcpy(sendBuf.data, buf, sendBuf.length); - cm->sendWithConnection(cm, dm->mdnsSendConnection, - &UA_KEYVALUEMAP_NULL, &sendBuf); - } - } - } -} - -static void -MulticastDiscoveryRecvCallback(UA_ConnectionManager *cm, uintptr_t connectionId, - void *application, void **connectionContext, - UA_ConnectionState state, const UA_KeyValueMap *params, - UA_ByteString msg) { - MulticastDiscoveryCallback(cm, connectionId, application, connectionContext, - state, params, msg, true); -} - -static void -MulticastDiscoverySendCallback(UA_ConnectionManager *cm, uintptr_t connectionId, - void *application, void **connectionContext, - UA_ConnectionState state, const UA_KeyValueMap *params, - UA_ByteString msg) { - MulticastDiscoveryCallback(cm, connectionId, application, connectionContext, - state, params, msg, false); -} - -static UA_StatusCode -addMdnsRecordForNetworkLayer(UA_DiscoveryManager *dm, const UA_String *appName, - const UA_String *discoveryUrl) { - UA_String hostname = UA_STRING_NULL; - UA_UInt16 port = 4840; - UA_String path = UA_STRING_NULL; - UA_StatusCode retval = - UA_parseEndpointUrl(discoveryUrl, &hostname, &port, &path); - if(retval != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "Server url is invalid: %.*s", - (int)discoveryUrl->length, discoveryUrl->data); - return retval; - } - - retval = UA_Discovery_addRecord(dm, appName, &hostname, port, - &path, UA_DISCOVERY_TCP, true, - dm->serverConfig->mdnsConfig.serverCapabilities, - dm->serverConfig->mdnsConfig.serverCapabilitiesSize, - true); - if(retval != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "Cannot add mDNS Record: %s", UA_StatusCode_name(retval)); - return retval; - } - return UA_STATUSCODE_GOOD; -} - -#ifndef IN_ZERONET -#define IN_ZERONET(addr) ((addr & IN_CLASSA_NET) == 0) -#endif - -/* Create multicast 224.0.0.251:5353 socket */ -static void -discovery_createMulticastSocket(UA_Server* server, UA_DiscoveryManager *dm) { - /* Find the connection manager */ - if(!dm->cm) { - UA_String udpString = UA_STRING("udp"); - for(UA_EventSource *es = server->config.eventLoop->eventSources; - es != NULL; es = es->next) { - /* Is this a usable connection manager? */ - if(es->eventSourceType != UA_EVENTSOURCETYPE_CONNECTIONMANAGER) - continue; - UA_ConnectionManager *cm = (UA_ConnectionManager*)es; - if(UA_String_equal(&udpString, &cm->protocol)) { - dm->cm = cm; - break; - } - } - } - - if(!dm->cm) { - UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "No UDP communication supported"); - return; - } - - /* Set up the parameters */ - UA_KeyValuePair params[6]; - size_t paramsSize = 5; - - UA_UInt16 port = 5353; - UA_String address = UA_STRING("224.0.0.251"); - UA_UInt32 ttl = 255; - UA_Boolean reuse = true; - UA_Boolean listen = true; - - params[0].key = UA_QUALIFIEDNAME(0, "port"); - UA_Variant_setScalar(¶ms[0].value, &port, &UA_TYPES[UA_TYPES_UINT16]); - params[1].key = UA_QUALIFIEDNAME(0, "address"); - UA_Variant_setScalar(¶ms[1].value, &address, &UA_TYPES[UA_TYPES_STRING]); - params[2].key = UA_QUALIFIEDNAME(0, "listen"); - UA_Variant_setScalar(¶ms[2].value, &listen, &UA_TYPES[UA_TYPES_BOOLEAN]); - params[3].key = UA_QUALIFIEDNAME(0, "reuse"); - UA_Variant_setScalar(¶ms[3].value, &reuse, &UA_TYPES[UA_TYPES_BOOLEAN]); - params[4].key = UA_QUALIFIEDNAME(0, "ttl"); - UA_Variant_setScalar(¶ms[4].value, &ttl, &UA_TYPES[UA_TYPES_UINT32]); - if(server->config.mdnsInterfaceIP.length > 0) { - params[5].key = UA_QUALIFIEDNAME(0, "interface"); - UA_Variant_setScalar(¶ms[5].value, &server->config.mdnsInterfaceIP, - &UA_TYPES[UA_TYPES_STRING]); - paramsSize++; - } - - /* Open the listen connection */ - UA_KeyValueMap kvm = {paramsSize, params}; - UA_StatusCode res = UA_STATUSCODE_GOOD; - - if(dm->mdnsRecvConnectionsSize == 0) { - res = dm->cm->openConnection(dm->cm, &kvm, server, dm, - MulticastDiscoveryRecvCallback); - if(res != UA_STATUSCODE_GOOD) - UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "Could not create the mdns UDP multicast listen connection"); - } - - /* Open the send connection */ - listen = false; - if(dm->mdnsSendConnection == 0) { - res = dm->cm->openConnection(dm->cm, &kvm, server, dm, - MulticastDiscoverySendCallback); - if(res != UA_STATUSCODE_GOOD) - UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "Could not create the mdns UDP multicast send connection"); - } -} - -void -startMulticastDiscoveryServer(UA_Server *server) { - /* Initialize the mdns daemon */ - UA_DiscoveryManager *dm = (UA_DiscoveryManager*) - getServerComponentByName(server, UA_STRING("discovery")); - if(!dm) - return; - - if(!dm->mdnsDaemon) { - dm->mdnsDaemon = mdnsd_new(QCLASS_IN, 1000); - mdnsd_register_receive_callback(dm->mdnsDaemon, mdns_record_received, dm); - } - -#if defined(UA_ARCHITECTURE_WIN32) || defined(UA_ARCHITECTURE_WEC7) - WSADATA wsaData; - WSAStartup(MAKEWORD(2, 2), &wsaData); -#endif - - /* Open the mdns listen socket */ - if(dm->mdnsSendConnection == 0) - discovery_createMulticastSocket(server, dm); - if(dm->mdnsSendConnection == 0) { - UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_DISCOVERY, - "Could not create multicast socket"); - return; - } - - /* Add record for the server itself */ - UA_String *appName = &server->config.mdnsConfig.mdnsServerName; - for(size_t i = 0; i < server->config.serverUrlsSize; i++) - addMdnsRecordForNetworkLayer(dm, appName, &server->config.serverUrls[i]); - - /* Send a multicast probe to find any other OPC UA server on the network - * through mDNS */ - mdnsd_query(dm->mdnsDaemon, "_opcua-tcp._tcp.local.", - QTYPE_PTR,discovery_multicastQueryAnswer, server); -} - -void -stopMulticastDiscoveryServer(UA_Server *server) { - UA_DiscoveryManager *dm = (UA_DiscoveryManager*) - getServerComponentByName(server, UA_STRING("discovery")); - if(!dm) - return; - - for(size_t i = 0; i < server->config.serverUrlsSize; i++) { - UA_String hostname = UA_STRING_NULL; - UA_String path = UA_STRING_NULL; - UA_UInt16 port = 0; - - UA_StatusCode retval = - UA_parseEndpointUrl(&server->config.serverUrls[i], - &hostname, &port, &path); - - if(retval != UA_STATUSCODE_GOOD || hostname.length == 0) - continue; - - UA_Discovery_removeRecord(dm, &server->config.mdnsConfig.mdnsServerName, - &hostname, port, true); - } - - /* Stop the cyclic polling callback */ - if(dm->mdnsCallbackId != 0) { - UA_EventLoop *el = server->config.eventLoop; - if(el) { - el->removeCyclicCallback(el, dm->mdnsCallbackId); - dm->mdnsCallbackId = 0; - } - } - - /* Clean up mdns daemon */ - if(dm->mdnsDaemon) { - mdnsd_shutdown(dm->mdnsDaemon); - mdnsd_free(dm->mdnsDaemon); - dm->mdnsDaemon = NULL; - } - - /* Close the socket */ - if(dm->cm) { - if(dm->mdnsSendConnection) - dm->cm->closeConnection(dm->cm, dm->mdnsSendConnection); - for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) - if(dm->mdnsRecvConnections[i] != 0) - dm->cm->closeConnection(dm->cm, dm->mdnsRecvConnections[i]); - } -} - -void -UA_Discovery_updateMdnsForDiscoveryUrl(UA_DiscoveryManager *dm, const UA_String *serverName, - const UA_MdnsDiscoveryConfiguration *mdnsConfig, - const UA_String *discoveryUrl, - UA_Boolean isOnline, UA_Boolean updateTxt) { - UA_String hostname = UA_STRING_NULL; - UA_UInt16 port = 4840; - UA_String path = UA_STRING_NULL; - UA_StatusCode retval = - UA_parseEndpointUrl(discoveryUrl, &hostname, &port, &path); - if(retval != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "Server url invalid: %.*s", - (int)discoveryUrl->length, discoveryUrl->data); - return; - } - - if(!isOnline) { - UA_StatusCode removeRetval = - UA_Discovery_removeRecord(dm, serverName, &hostname, - port, updateTxt); - if(removeRetval != UA_STATUSCODE_GOOD) - UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "Could not remove mDNS record for hostname %.*s.", - (int)serverName->length, serverName->data); - return; - } - - UA_String *capabilities = NULL; - size_t capabilitiesSize = 0; - if(mdnsConfig) { - capabilities = mdnsConfig->serverCapabilities; - capabilitiesSize = mdnsConfig->serverCapabilitiesSize; - } - - UA_StatusCode addRetval = - UA_Discovery_addRecord(dm, serverName, &hostname, - port, &path, UA_DISCOVERY_TCP, updateTxt, - capabilities, capabilitiesSize, - false); - if(addRetval != UA_STATUSCODE_GOOD) - UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "Could not add mDNS record for hostname %.*s.", - (int)serverName->length, serverName->data); -} - -void -UA_Server_setServerOnNetworkCallback(UA_Server *server, - UA_Server_serverOnNetworkCallback cb, - void* data) { - UA_LOCK(&server->serviceMutex); - UA_DiscoveryManager *dm = (UA_DiscoveryManager*) - getServerComponentByName(server, UA_STRING("discovery")); - if(dm) { - dm->serverOnNetworkCallback = cb; - dm->serverOnNetworkCallbackData = data; - } - UA_UNLOCK(&server->serviceMutex); -} - -static void -UA_Discovery_multicastConflict(char *name, int type, void *arg) { - /* In case logging is disabled */ - (void)name; - (void)type; - - UA_DiscoveryManager *dm = (UA_DiscoveryManager*) arg; - UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "Multicast DNS name conflict detected: " - "'%s' for type %d", name, type); -} - -/* Create a service domain with the format [servername]-[hostname]._opcua-tcp._tcp.local. */ -static void -createFullServiceDomain(char *outServiceDomain, size_t maxLen, - const UA_String *servername, const UA_String *hostname) { - size_t hostnameLen = hostname->length; - size_t servernameLen = servername->length; - - maxLen -= 24; /* the length we have remaining before the opc ua postfix and - * the trailing zero */ - - /* Can we use hostname and servername with full length? */ - if(hostnameLen + servernameLen + 1 > maxLen) { - if(servernameLen + 2 > maxLen) { - servernameLen = maxLen; - hostnameLen = 0; - } else { - hostnameLen = maxLen - servernameLen - 1; - } - } - - size_t offset = 0; - if(hostnameLen > 0) { - mp_snprintf(outServiceDomain, maxLen + 1, "%.*s-%.*s", - (int) servernameLen, (char *) servername->data, - (int) hostnameLen, (char *) hostname->data); - offset = servernameLen + hostnameLen + 1; - //replace all dots with minus. Otherwise mDNS is not valid - for(size_t i=servernameLen+1; idata); - offset = servernameLen; - } - mp_snprintf(&outServiceDomain[offset], 24, "._opcua-tcp._tcp.local."); -} - -/* Check if mDNS already has an entry for given hostname and port combination */ -static UA_Boolean -UA_Discovery_recordExists(UA_DiscoveryManager *dm, const char* fullServiceDomain, - unsigned short port, const UA_DiscoveryProtocol protocol) { - // [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname]. - mdns_record_t *r = mdnsd_get_published(dm->mdnsDaemon, fullServiceDomain); - while(r) { - const mdns_answer_t *data = mdnsd_record_data(r); - if(data->type == QTYPE_SRV && (port == 0 || data->srv.port == port)) - return true; - r = mdnsd_record_next(r); - } - return false; -} - -static int -discovery_multicastQueryAnswer(mdns_answer_t *a, void *arg) { - UA_Server *server = (UA_Server*) arg; - UA_DiscoveryManager *dm = (UA_DiscoveryManager*) - getServerComponentByName(server, UA_STRING("discovery")); - if(!dm) - return 0; - - if(a->type != QTYPE_PTR) - return 0; - - if(a->rdname == NULL) - return 0; - - /* Skip, if we already know about this server */ - UA_Boolean exists = - UA_Discovery_recordExists(dm, a->rdname, 0, UA_DISCOVERY_TCP); - if(exists == true) - return 0; - - if(mdnsd_has_query(dm->mdnsDaemon, a->rdname)) - return 0; - - UA_LOG_DEBUG(&server->config.logger, UA_LOGCATEGORY_DISCOVERY, - "mDNS send query for: %s SRV&TXT %s", a->name, a->rdname); - - mdnsd_query(dm->mdnsDaemon, a->rdname, QTYPE_SRV, - discovery_multicastQueryAnswer, server); - mdnsd_query(dm->mdnsDaemon, a->rdname, QTYPE_TXT, - discovery_multicastQueryAnswer, server); - return 0; -} - -static UA_StatusCode -UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String *servername, - const UA_String *hostname, UA_UInt16 port, - const UA_String *path, const UA_DiscoveryProtocol protocol, - UA_Boolean createTxt, const UA_String* capabilites, - const size_t capabilitiesSize, - UA_Boolean isSelf) { - /* We assume that the hostname is not an IP address, but a valid domain - * name. It is required by the OPC UA spec (see Part 12, DiscoveryURL to DNS - * SRV mapping) to always use the hostname instead of the IP address. */ - - if(capabilitiesSize > 0 && !capabilites) - return UA_STATUSCODE_BADINVALIDARGUMENT; - - size_t hostnameLen = hostname->length; - size_t servernameLen = servername->length; - if(hostnameLen == 0 || servernameLen == 0) - return UA_STATUSCODE_BADOUTOFRANGE; - - /* Use a limit for the hostname length to make sure full string fits into 63 - * chars (limited by DNS spec) */ - if(hostnameLen+servernameLen + 1 > 63) { /* include dash between servername-hostname */ - UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "Multicast DNS: Combination of hostname+servername exceeds " - "maximum of 62 chars. It will be truncated."); - } else if(hostnameLen > 63) { - UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "Multicast DNS: Hostname length exceeds maximum of 63 chars. " - "It will be truncated."); - } - - if(!dm->mdnsMainSrvAdded) { - mdns_record_t *r = - mdnsd_shared(dm->mdnsDaemon, "_services._dns-sd._udp.local.", - QTYPE_PTR, 600); - mdnsd_set_host(dm->mdnsDaemon, r, "_opcua-tcp._tcp.local."); - dm->mdnsMainSrvAdded = true; - } - - /* [servername]-[hostname]._opcua-tcp._tcp.local. */ - char fullServiceDomain[63+24]; - createFullServiceDomain(fullServiceDomain, 63+24, servername, hostname); - - UA_Boolean exists = UA_Discovery_recordExists(dm, fullServiceDomain, - port, protocol); - if(exists == true) - return UA_STATUSCODE_GOOD; - - UA_LOG_INFO(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "Multicast DNS: add record for domain: %s", fullServiceDomain); - - if(isSelf && dm->selfFqdnMdnsRecord.length == 0) { - dm->selfFqdnMdnsRecord = UA_STRING_ALLOC(fullServiceDomain); - if(!dm->selfFqdnMdnsRecord.data) - return UA_STATUSCODE_BADOUTOFMEMORY; - } - - struct serverOnNetwork_list_entry *listEntry; - /* The servername is servername + hostname. It is the same which we get - * through mDNS and therefore we need to match servername */ - UA_StatusCode retval = - UA_DiscoveryManager_addEntryToServersOnNetwork(dm, fullServiceDomain, - fullServiceDomain, - UA_MIN(63, (servernameLen+hostnameLen)+1), - &listEntry); - if(retval != UA_STATUSCODE_GOOD && - retval != UA_STATUSCODE_BADALREADYEXISTS) - return retval; - - /* If entry is already in list, skip initialization of capabilities and txt+srv */ - if(retval != UA_STATUSCODE_BADALREADYEXISTS) { - /* if capabilitiesSize is 0, then add default cap 'NA' */ - listEntry->serverOnNetwork.serverCapabilitiesSize = UA_MAX(1, capabilitiesSize); - listEntry->serverOnNetwork.serverCapabilities = (UA_String *) - UA_Array_new(listEntry->serverOnNetwork.serverCapabilitiesSize, - &UA_TYPES[UA_TYPES_STRING]); - if(!listEntry->serverOnNetwork.serverCapabilities) - return UA_STATUSCODE_BADOUTOFMEMORY; - if(capabilitiesSize == 0) { - UA_String na; - na.length = 2; - na.data = (UA_Byte *) (uintptr_t) "NA"; - UA_String_copy(&na, &listEntry->serverOnNetwork.serverCapabilities[0]); - } else { - for(size_t i = 0; i < capabilitiesSize; i++) - UA_String_copy(&capabilites[i], - &listEntry->serverOnNetwork.serverCapabilities[i]); - } - - listEntry->txtSet = true; - - UA_STACKARRAY(char, newUrl, 10 + hostnameLen + 8 + path->length + 1); - mp_snprintf(newUrl, 10 + hostnameLen + 8 + path->length + 1, - "opc.tcp://%.*s:%d%s%.*s", (int) hostnameLen, - hostname->data, port, path->length > 0 ? "/" : "", - (int) path->length, path->data); - listEntry->serverOnNetwork.discoveryUrl = UA_String_fromChars(newUrl); - listEntry->srvSet = true; - } - - /* _services._dns-sd._udp.local. PTR _opcua-tcp._tcp.local */ - - /* check if there is already a PTR entry for the given service. */ - - /* _opcua-tcp._tcp.local. PTR [servername]-[hostname]._opcua-tcp._tcp.local. */ - mdns_record_t *r = - mdns_find_record(dm->mdnsDaemon, QTYPE_PTR, - "_opcua-tcp._tcp.local.", fullServiceDomain); - if(!r) { - r = mdnsd_shared(dm->mdnsDaemon, "_opcua-tcp._tcp.local.", - QTYPE_PTR, 600); - mdnsd_set_host(dm->mdnsDaemon, r, fullServiceDomain); - } - - /* The first 63 characters of the hostname (or less) */ - size_t maxHostnameLen = UA_MIN(hostnameLen, 63); - char localDomain[65]; - memcpy(localDomain, hostname->data, maxHostnameLen); - localDomain[maxHostnameLen] = '.'; - localDomain[maxHostnameLen+1] = '\0'; - - /* [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname]. */ - r = mdnsd_unique(dm->mdnsDaemon, fullServiceDomain, - QTYPE_SRV, 600, UA_Discovery_multicastConflict, dm); - mdnsd_set_srv(dm->mdnsDaemon, r, 0, 0, port, localDomain); - - /* A/AAAA record for all ip addresses. - * [servername]-[hostname]._opcua-tcp._tcp.local. A [ip]. - * [hostname]. A [ip]. */ - mdns_set_address_record(dm, fullServiceDomain, localDomain); - - /* TXT record: [servername]-[hostname]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,... */ - UA_STACKARRAY(char, pathChars, path->length + 1); - if(createTxt) { - if(path->length > 0) - memcpy(pathChars, path->data, path->length); - pathChars[path->length] = 0; - mdns_create_txt(dm, fullServiceDomain, pathChars, capabilites, - capabilitiesSize, UA_Discovery_multicastConflict); - } - - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -UA_Discovery_removeRecord(UA_DiscoveryManager *dm, const UA_String *servername, - const UA_String *hostname, UA_UInt16 port, - UA_Boolean removeTxt) { - /* use a limit for the hostname length to make sure full string fits into 63 - * chars (limited by DNS spec) */ - size_t hostnameLen = hostname->length; - size_t servernameLen = servername->length; - if(hostnameLen == 0 || servernameLen == 0) - return UA_STATUSCODE_BADOUTOFRANGE; - - if(hostnameLen+servernameLen+1 > 63) { /* include dash between servername-hostname */ - UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "Multicast DNS: Combination of hostname+servername exceeds " - "maximum of 62 chars. It will be truncated."); - } - - /* [servername]-[hostname]._opcua-tcp._tcp.local. */ - char fullServiceDomain[63 + 24]; - createFullServiceDomain(fullServiceDomain, 63+24, servername, hostname); - - UA_LOG_INFO(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "Multicast DNS: remove record for domain: %s", - fullServiceDomain); - - UA_StatusCode retval = - UA_DiscoveryManager_removeEntryFromServersOnNetwork(dm, fullServiceDomain, - fullServiceDomain, UA_MIN(63, (servernameLen+hostnameLen)+1)); - if(retval != UA_STATUSCODE_GOOD) - return retval; - - /* _opcua-tcp._tcp.local. PTR [servername]-[hostname]._opcua-tcp._tcp.local. */ - mdns_record_t *r = - mdns_find_record(dm->mdnsDaemon, QTYPE_PTR, - "_opcua-tcp._tcp.local.", fullServiceDomain); - if(!r) { - UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "Multicast DNS: could not remove record. " - "PTR Record not found for domain: %s", fullServiceDomain); - return UA_STATUSCODE_BADNOTHINGTODO; - } - mdnsd_done(dm->mdnsDaemon, r); - - /* looks for [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 - * port hostname.local. and TXT record: - * [servername]-[hostname]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,... - * and A record: [servername]-[hostname]._opcua-tcp._tcp.local. A [ip] */ - mdns_record_t *r2 = - mdnsd_get_published(dm->mdnsDaemon, fullServiceDomain); - if(!r2) { - UA_LOG_WARNING(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "Multicast DNS: could not remove record. Record not " - "found for domain: %s", fullServiceDomain); - return UA_STATUSCODE_BADNOTHINGTODO; - } - - while(r2) { - const mdns_answer_t *data = mdnsd_record_data(r2); - mdns_record_t *next = mdnsd_record_next(r2); - if((removeTxt && data->type == QTYPE_TXT) || - (removeTxt && data->type == QTYPE_A) || - data->srv.port == port) { - mdnsd_done(dm->mdnsDaemon, r2); - } - r2 = next; - } - - return UA_STATUSCODE_GOOD; -} - -#endif /* defined(UA_ENABLE_DISCOVERY) && defined(UA_ENABLE_DISCOVERY_MULTICAST) */ From b6eacc607858f8f9d9b443a85ddf751618f78281 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 24 Oct 2023 23:45:34 +0200 Subject: [PATCH 09/10] refactor(server): Unified naming scheme for ua_discovery* files --- CMakeLists.txt | 6 +++--- src/server/{ua_discovery_manager.c => ua_discovery.c} | 2 +- src/server/{ua_discovery_manager.h => ua_discovery.h} | 0 .../{ua_server_discovery_mdns.c => ua_discovery_mdns.c} | 2 +- src/server/ua_services_discovery.c | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename src/server/{ua_discovery_manager.c => ua_discovery.c} (99%) rename src/server/{ua_discovery_manager.h => ua_discovery.h} (100%) rename src/server/{ua_server_discovery_mdns.c => ua_discovery_mdns.c} (99%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1846c93da0e..e3e29c5f579 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -934,7 +934,7 @@ if(UA_ENABLE_DISCOVERY_MULTICAST) ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.h ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/mdnsd.h) list(APPEND lib_sources - ${PROJECT_SOURCE_DIR}/src/server/ua_server_discovery_mdns.c + ${PROJECT_SOURCE_DIR}/src/server/ua_discovery_mdns.c ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/1035.c ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/xht.c ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.c @@ -1071,8 +1071,8 @@ list(INSERT plugin_sources 0 endif() if(UA_ENABLE_DISCOVERY) - list(APPEND lib_headers ${PROJECT_SOURCE_DIR}/src/server/ua_discovery_manager.h) - list(APPEND lib_sources ${PROJECT_SOURCE_DIR}/src/server/ua_discovery_manager.c) + list(APPEND lib_headers ${PROJECT_SOURCE_DIR}/src/server/ua_discovery.h) + list(APPEND lib_sources ${PROJECT_SOURCE_DIR}/src/server/ua_discovery.c) endif() if(UA_ENABLE_NODESETLOADER) diff --git a/src/server/ua_discovery_manager.c b/src/server/ua_discovery.c similarity index 99% rename from src/server/ua_discovery_manager.c rename to src/server/ua_discovery.c index 7a3c3d71329..71e2577a750 100644 --- a/src/server/ua_discovery_manager.c +++ b/src/server/ua_discovery.c @@ -14,7 +14,7 @@ */ #include -#include "ua_discovery_manager.h" +#include "ua_discovery.h" #include "ua_server_internal.h" #ifdef UA_ENABLE_DISCOVERY diff --git a/src/server/ua_discovery_manager.h b/src/server/ua_discovery.h similarity index 100% rename from src/server/ua_discovery_manager.h rename to src/server/ua_discovery.h diff --git a/src/server/ua_server_discovery_mdns.c b/src/server/ua_discovery_mdns.c similarity index 99% rename from src/server/ua_server_discovery_mdns.c rename to src/server/ua_discovery_mdns.c index 4374f1a3f55..4cde3c05d14 100644 --- a/src/server/ua_server_discovery_mdns.c +++ b/src/server/ua_discovery_mdns.c @@ -7,7 +7,7 @@ * Copyright 2017 (c) Thomas Stalder, Blue Time Concept SA */ -#include "ua_discovery_manager.h" +#include "ua_discovery.h" #include "ua_server_internal.h" #ifdef UA_ENABLE_DISCOVERY_MULTICAST diff --git a/src/server/ua_services_discovery.c b/src/server/ua_services_discovery.c index 2fc7a5a4958..166d5e7ba38 100644 --- a/src/server/ua_services_discovery.c +++ b/src/server/ua_services_discovery.c @@ -12,7 +12,7 @@ */ #include "ua_server_internal.h" -#include "ua_discovery_manager.h" +#include "ua_discovery.h" #include "ua_services.h" #ifdef UA_ENABLE_DISCOVERY From 461c45f855b93a687a51b427e0efe17ee05ad929 Mon Sep 17 00:00:00 2001 From: Andreas Ebner Date: Wed, 25 Oct 2023 09:50:23 +0200 Subject: [PATCH 10/10] fix(server) remove unused capabilities from 1.05 nodeset --- src/server/ua_server_ns0.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/server/ua_server_ns0.c b/src/server/ua_server_ns0.c index 5388cc06c58..fd7878842cb 100644 --- a/src/server/ua_server_ns0.c +++ b/src/server/ua_server_ns0.c @@ -1047,6 +1047,17 @@ initNS0(UA_Server *server) { deleteNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERREDUNDANCY_REDUNDANTSERVERARRAY), true); deleteNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERREDUNDANCY_SERVERURIARRAY), true); deleteNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERREDUNDANCY_SERVERNETWORKGROUPS), true); + deleteNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERCAPABILITIES_CONFORMANCEUNITS), true); + deleteNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_URISVERSION), true); + deleteNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERCAPABILITIES_CONFORMANCEUNITS), true); + deleteNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERCAPABILITIES_MAXMONITOREDITEMS), true); + deleteNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERCAPABILITIES_MAXMONITOREDITEMSPERSUBSCRIPTION), true); + deleteNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERCAPABILITIES_MAXMONITOREDITEMSQUEUESIZE), true); + deleteNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERCAPABILITIES_MAXSELECTCLAUSEPARAMETERS), true); + deleteNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERCAPABILITIES_MAXSESSIONS), true); + deleteNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERCAPABILITIES_MAXSUBSCRIPTIONS), true); + deleteNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERCAPABILITIES_MAXSUBSCRIPTIONSPERSESSION), true); + deleteNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERCAPABILITIES_MAXWHERECLAUSEPARAMETERS), true); /* ServerCapabilities - LocaleIdArray */ UA_LocaleId locale_en = UA_STRING("en");