diff --git a/CMakeLists.txt b/CMakeLists.txt index ef3bd5b6d18..e3e29c5f579 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) @@ -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 @@ -833,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 @@ -936,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 @@ -1073,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/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 97670f3ea9c..e5c7f2439ca 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 @@ -1356,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; config->maxSessionTimeout = 10 * 60 * 10000; /* 10 minutes */ /* Revolve the SecureChannel token every 300 seconds */ @@ -1398,24 +1402,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/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 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 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) diff --git a/src/server/ua_discovery.c b/src/server/ua_discovery.c new file mode 100644 index 00000000000..71e2577a750 --- /dev/null +++ b/src/server/ua_discovery.c @@ -0,0 +1,517 @@ +/* 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 2014-2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer) + * Copyright 2014, 2017 (c) Florian Palm + * Copyright 2015-2016, 2019 (c) Sten Grüner + * Copyright 2015 (c) Chris Iatrou + * 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 +#include "ua_discovery.h" +#include "ua_server_internal.h" + +#ifdef UA_ENABLE_DISCOVERY + +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); +} + +static UA_StatusCode +UA_DiscoveryManager_free(UA_Server *server, + struct UA_ServerComponent *sc) { + UA_DiscoveryManager *dm = (UA_DiscoveryManager*)sc; + + if(sc->state != UA_LIFECYCLESTATE_STOPPED) { + UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, + "Cannot delete the DiscoveryManager because " + "it is not stopped"); + return UA_STATUSCODE_BADINTERNALERROR; + } + + registeredServer_list_entry *rs, *rs_tmp; + LIST_FOREACH_SAFE(rs, &dm->registeredServers, pointers, rs_tmp) { + LIST_REMOVE(rs, pointers); + UA_RegisteredServer_clear(&rs->registeredServer); + UA_free(rs); + } + +# ifdef UA_ENABLE_DISCOVERY_MULTICAST + serverOnNetwork_list_entry *son, *son_tmp; + LIST_FOREACH_SAFE(son, &dm->serverOnNetwork, pointers, son_tmp) { + LIST_REMOVE(son, pointers); + UA_ServerOnNetwork_clear(&son->serverOnNetwork); + if(son->pathTmp) + UA_free(son->pathTmp); + UA_free(son); + } + + UA_String_clear(&dm->selfFqdnMdnsRecord); + + for(size_t i = 0; i < SERVER_ON_NETWORK_HASH_SIZE; i++) { + serverOnNetwork_hash_entry* currHash = dm->serverOnNetworkHash[i]; + while(currHash) { + serverOnNetwork_hash_entry* nextHash = currHash->next; + UA_free(currHash); + currHash = nextHash; + } + } +# endif /* UA_ENABLE_DISCOVERY_MULTICAST */ + + UA_free(dm); + return UA_STATUSCODE_GOOD; +} + +/* Cleanup server registration: If the semaphore file path is set, then it just + * checks the existence of the file. When it is deleted, the registration is + * removed. If there is no semaphore file, then the registration will be removed + * if it is older than 60 minutes. */ +static void +UA_DiscoveryManager_cleanupTimedOut(UA_Server *server, + void *data) { + UA_DiscoveryManager *dm = (UA_DiscoveryManager*)data; + + /* TimedOut gives the last DateTime at which we must have seen the + * registered server. Otherwise it is timed out. */ + UA_DateTime timedOut = UA_DateTime_nowMonotonic(); + if(server->config.discoveryCleanupTimeout) + timedOut -= server->config.discoveryCleanupTimeout * UA_DATETIME_SEC; + + registeredServer_list_entry *current, *temp; + LIST_FOREACH_SAFE(current, &dm->registeredServers, pointers, temp) { + UA_Boolean semaphoreDeleted = false; + +#ifdef UA_ENABLE_DISCOVERY_SEMAPHORE + if(current->registeredServer.semaphoreFilePath.length) { + size_t fpSize = current->registeredServer.semaphoreFilePath.length+1; + char* filePath = (char *)UA_malloc(fpSize); + if(filePath) { + memcpy(filePath, current->registeredServer.semaphoreFilePath.data, + current->registeredServer.semaphoreFilePath.length ); + filePath[current->registeredServer.semaphoreFilePath.length] = '\0'; + semaphoreDeleted = UA_fileExists(filePath) == false; + UA_free(filePath); + } else { + UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, + "Cannot check registration semaphore. Out of memory"); + } + } +#endif + + if(semaphoreDeleted || + (server->config.discoveryCleanupTimeout && + current->lastSeen < timedOut)) { + if(semaphoreDeleted) { + UA_LOG_INFO(&server->config.logger, UA_LOGCATEGORY_SERVER, + "Registration of server with URI %.*s is removed because " + "the semaphore file '%.*s' was deleted", + (int)current->registeredServer.serverUri.length, + current->registeredServer.serverUri.data, + (int)current->registeredServer.semaphoreFilePath.length, + current->registeredServer.semaphoreFilePath.data); + } else { + // cppcheck-suppress unreadVariable + UA_LOG_INFO(&server->config.logger, UA_LOGCATEGORY_SERVER, + "Registration of server with URI %.*s has timed out " + "and is removed", + (int)current->registeredServer.serverUri.length, + current->registeredServer.serverUri.data); + } + LIST_REMOVE(current, pointers); + UA_RegisteredServer_clear(¤t->registeredServer); + UA_free(current); + dm->registeredServersSize--; + } + } + +#ifdef UA_ENABLE_DISCOVERY_MULTICAST + /* Send out multicast */ + sendMulticastMessages(dm); +#endif +} + +static UA_StatusCode +UA_DiscoveryManager_start(UA_Server *server, + struct UA_ServerComponent *sc) { + if(sc->state != UA_LIFECYCLESTATE_STOPPED) + return UA_STATUSCODE_BADINTERNALERROR; + + UA_DiscoveryManager *dm = (UA_DiscoveryManager*)sc; + UA_StatusCode res = addRepeatedCallback(server, UA_DiscoveryManager_cleanupTimedOut, + dm, 1000.0, &dm->discoveryCallbackId); + if(res != UA_STATUSCODE_GOOD) + return res; + + dm->logging = &server->config.logger; + dm->serverConfig = &server->config; + +#ifdef UA_ENABLE_DISCOVERY_MULTICAST + if(server->config.mdnsEnabled) + startMulticastDiscoveryServer(server); +#endif + + UA_DiscoveryManager_setState(server, dm, UA_LIFECYCLESTATE_STARTED); + return UA_STATUSCODE_GOOD; +} + +static void +UA_DiscoveryManager_stop(UA_Server *server, + struct UA_ServerComponent *sc) { + if(sc->state != UA_LIFECYCLESTATE_STARTED) + return; + + 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 + + UA_DiscoveryManager_setState(server, dm, UA_LIFECYCLESTATE_STOPPED); +} + +UA_ServerComponent * +UA_DiscoveryManager_new(UA_Server *server) { + UA_DiscoveryManager *dm = (UA_DiscoveryManager*) + UA_calloc(1, sizeof(UA_DiscoveryManager)); + if(!dm) + return NULL; + +#ifdef UA_ENABLE_DISCOVERY_MULTICAST + dm->serverOnNetworkRecordIdLastReset = UA_DateTime_now(); +#endif /* UA_ENABLE_DISCOVERY_MULTICAST */ + + dm->sc.name = UA_STRING("discovery"); + dm->sc.start = UA_DiscoveryManager_start; + dm->sc.stop = UA_DiscoveryManager_stop; + dm->sc.free = UA_DiscoveryManager_free; + 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_discovery_manager.h b/src/server/ua_discovery.h similarity index 91% rename from src/server/ua_discovery_manager.h rename to src/server/ua_discovery.h index e4bc0c35174..0a6214a3f8f 100644 --- a/src/server/ua_discovery_manager.h +++ b/src/server/ua_discovery.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_discovery_manager.c b/src/server/ua_discovery_manager.c deleted file mode 100644 index e9c7e9bfff3..00000000000 --- a/src/server/ua_discovery_manager.c +++ /dev/null @@ -1,217 +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 2014-2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer) - * Copyright 2014, 2017 (c) Florian Palm - * Copyright 2015-2016, 2019 (c) Sten Grüner - * Copyright 2015 (c) Chris Iatrou - * Copyright 2015-2016 (c) Oleksiy Vasylyev - * Copyright 2016-2017 (c) Stefan Profanter, fortiss GmbH - * Copyright 2017 (c) Julian Grothoff - */ - -#include "ua_discovery_manager.h" -#include "ua_server_internal.h" - -#ifdef UA_ENABLE_DISCOVERY - -void -UA_DiscoveryManager_setState(UA_Server *server, - UA_DiscoveryManager *dm, - UA_LifecycleState state) { - if(state == dm->sc.state) - return; - dm->sc.state = state; - if(dm->sc.notifyState) - dm->sc.notifyState(server, &dm->sc, state); -} - -static UA_StatusCode -UA_DiscoveryManager_free(UA_Server *server, - struct UA_ServerComponent *sc) { - UA_DiscoveryManager *dm = (UA_DiscoveryManager*)sc; - - if(sc->state != UA_LIFECYCLESTATE_STOPPED) { - UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, - "Cannot delete the DiscoveryManager because " - "it is not stopped"); - return UA_STATUSCODE_BADINTERNALERROR; - } - - registeredServer_list_entry *rs, *rs_tmp; - LIST_FOREACH_SAFE(rs, &dm->registeredServers, pointers, rs_tmp) { - LIST_REMOVE(rs, pointers); - 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; - LIST_FOREACH_SAFE(son, &dm->serverOnNetwork, pointers, son_tmp) { - LIST_REMOVE(son, pointers); - UA_ServerOnNetwork_clear(&son->serverOnNetwork); - if(son->pathTmp) - UA_free(son->pathTmp); - UA_free(son); - } - - UA_String_clear(&dm->selfFqdnMdnsRecord); - - for(size_t i = 0; i < SERVER_ON_NETWORK_HASH_SIZE; i++) { - serverOnNetwork_hash_entry* currHash = dm->serverOnNetworkHash[i]; - while(currHash) { - serverOnNetwork_hash_entry* nextHash = currHash->next; - UA_free(currHash); - currHash = nextHash; - } - } -# endif /* UA_ENABLE_DISCOVERY_MULTICAST */ - - UA_free(dm); - return UA_STATUSCODE_GOOD; -} - -/* Cleanup server registration: If the semaphore file path is set, then it just - * checks the existence of the file. When it is deleted, the registration is - * removed. If there is no semaphore file, then the registration will be removed - * if it is older than 60 minutes. */ -static void -UA_DiscoveryManager_cleanupTimedOut(UA_Server *server, - void *data) { - UA_DiscoveryManager *dm = (UA_DiscoveryManager*)data; - - /* TimedOut gives the last DateTime at which we must have seen the - * registered server. Otherwise it is timed out. */ - UA_DateTime timedOut = UA_DateTime_nowMonotonic(); - if(server->config.discoveryCleanupTimeout) - timedOut -= server->config.discoveryCleanupTimeout * UA_DATETIME_SEC; - - registeredServer_list_entry *current, *temp; - LIST_FOREACH_SAFE(current, &dm->registeredServers, pointers, temp) { - UA_Boolean semaphoreDeleted = false; - -#ifdef UA_ENABLE_DISCOVERY_SEMAPHORE - if(current->registeredServer.semaphoreFilePath.length) { - size_t fpSize = current->registeredServer.semaphoreFilePath.length+1; - char* filePath = (char *)UA_malloc(fpSize); - if(filePath) { - memcpy(filePath, current->registeredServer.semaphoreFilePath.data, - current->registeredServer.semaphoreFilePath.length ); - filePath[current->registeredServer.semaphoreFilePath.length] = '\0'; - semaphoreDeleted = UA_fileExists(filePath) == false; - UA_free(filePath); - } else { - UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, - "Cannot check registration semaphore. Out of memory"); - } - } -#endif - - if(semaphoreDeleted || - (server->config.discoveryCleanupTimeout && - current->lastSeen < timedOut)) { - if(semaphoreDeleted) { - UA_LOG_INFO(&server->config.logger, UA_LOGCATEGORY_SERVER, - "Registration of server with URI %.*s is removed because " - "the semaphore file '%.*s' was deleted", - (int)current->registeredServer.serverUri.length, - current->registeredServer.serverUri.data, - (int)current->registeredServer.semaphoreFilePath.length, - current->registeredServer.semaphoreFilePath.data); - } else { - // cppcheck-suppress unreadVariable - UA_LOG_INFO(&server->config.logger, UA_LOGCATEGORY_SERVER, - "Registration of server with URI %.*s has timed out " - "and is removed", - (int)current->registeredServer.serverUri.length, - current->registeredServer.serverUri.data); - } - LIST_REMOVE(current, pointers); - UA_RegisteredServer_clear(¤t->registeredServer); - UA_free(current); - dm->registeredServersSize--; - } - } - -#ifdef UA_ENABLE_DISCOVERY_MULTICAST - /* Send out multicast */ - sendMulticastMessages(dm); -#endif -} - -static UA_StatusCode -UA_DiscoveryManager_start(UA_Server *server, - struct UA_ServerComponent *sc) { - if(sc->state != UA_LIFECYCLESTATE_STOPPED) - return UA_STATUSCODE_BADINTERNALERROR; - - UA_DiscoveryManager *dm = (UA_DiscoveryManager*)sc; - UA_StatusCode res = addRepeatedCallback(server, UA_DiscoveryManager_cleanupTimedOut, - dm, 1000.0, &dm->discoveryCallbackId); - if(res != UA_STATUSCODE_GOOD) - return res; - - dm->logging = &server->config.logger; - dm->serverConfig = &server->config; - -#ifdef UA_ENABLE_DISCOVERY_MULTICAST - if(server->config.mdnsEnabled) - startMulticastDiscoveryServer(server); -#endif - - UA_DiscoveryManager_setState(server, dm, UA_LIFECYCLESTATE_STARTED); - return UA_STATUSCODE_GOOD; -} - -static void -UA_DiscoveryManager_stop(UA_Server *server, - struct UA_ServerComponent *sc) { - if(sc->state != UA_LIFECYCLESTATE_STARTED) - return; - - UA_DiscoveryManager *dm = (UA_DiscoveryManager*)sc; - removeCallback(server, dm->discoveryCallbackId); - -#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 * -UA_DiscoveryManager_new(UA_Server *server) { - UA_DiscoveryManager *dm = (UA_DiscoveryManager*) - UA_calloc(1, sizeof(UA_DiscoveryManager)); - if(!dm) - return NULL; - -#ifdef UA_ENABLE_DISCOVERY_MULTICAST - dm->serverOnNetworkRecordIdLastReset = UA_DateTime_now(); -#endif /* UA_ENABLE_DISCOVERY_MULTICAST */ - - dm->sc.name = UA_STRING("discovery"); - dm->sc.start = UA_DiscoveryManager_start; - dm->sc.stop = UA_DiscoveryManager_stop; - dm->sc.free = UA_DiscoveryManager_free; - return &dm->sc; -} - -#endif /* UA_ENABLE_DISCOVERY */ diff --git a/src/server/ua_services_discovery_multicast.c b/src/server/ua_discovery_mdns.c similarity index 56% rename from src/server/ua_services_discovery_multicast.c rename to src/server/ua_discovery_mdns.c index 7716e80262f..4cde3c05d14 100644 --- a/src/server/ua_services_discovery_multicast.c +++ b/src/server/ua_discovery_mdns.c @@ -7,20 +7,630 @@ * Copyright 2017 (c) Thomas Stalder, Blue Time Concept SA */ -#include "ua_discovery_manager.h" -#include "ua_services.h" +#include "ua_discovery.h" +#include "ua_server_internal.h" -#include "../deps/mp_printf.h" +#ifdef UA_ENABLE_DISCOVERY_MULTICAST + +#ifndef UA_ENABLE_AMALGAMATION +#include "mdnsd/libmdnsd/xht.h" +#include "mdnsd/libmdnsd/sdtxt.h" +#endif -#if defined(UA_ENABLE_DISCOVERY) && defined(UA_ENABLE_DISCOVERY_MULTICAST) +#include "../deps/mp_printf.h" #ifdef _WIN32 +/* inet_ntoa is deprecated on MSVC but used for compatibility */ +# define _WINSOCK_DEPRECATED_NO_WARNINGS # include +# include +# include #else # include # include -# include +# include // for struct timeval +# include // for struct ip_mreq +# if defined(UA_HAS_GETIFADDR) +# include +# endif /* UA_HAS_GETIFADDR */ +# include /* for IFF_RUNNING */ +# include // for recvfrom in cygwin +#endif + +static struct serverOnNetwork_list_entry * +mdns_record_add_or_get(UA_DiscoveryManager *dm, const char *record, const char *serverName, + size_t serverNameLen, UA_Boolean createNew) { + UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)record, + strlen(record)) % SERVER_ON_NETWORK_HASH_SIZE; + struct serverOnNetwork_hash_entry *hash_entry = dm->serverOnNetworkHash[hashIdx]; + + while(hash_entry) { + size_t maxLen; + if(serverNameLen > hash_entry->entry->serverOnNetwork.serverName.length) + maxLen = hash_entry->entry->serverOnNetwork.serverName.length; + else + maxLen = serverNameLen; + + if(strncmp((char *) hash_entry->entry->serverOnNetwork.serverName.data, + serverName, maxLen) == 0) + return hash_entry->entry; + hash_entry = hash_entry->next; + } + + if(!createNew) + return NULL; + + struct serverOnNetwork_list_entry *listEntry; + UA_StatusCode retval = UA_DiscoveryManager_addEntryToServersOnNetwork(dm, record, serverName, + serverNameLen, &listEntry); + if (retval != UA_STATUSCODE_GOOD) + return NULL; + + return listEntry; +} + + +UA_StatusCode +UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, + const char *fqdnMdnsRecord, + const char *serverName, + size_t serverNameLen, + struct serverOnNetwork_list_entry **addedEntry) { + struct serverOnNetwork_list_entry *entry = + mdns_record_add_or_get(dm, fqdnMdnsRecord, serverName, + serverNameLen, false); + if(entry) { + if(addedEntry != NULL) + *addedEntry = entry; + return UA_STATUSCODE_BADALREADYEXISTS; + } + + UA_LOG_DEBUG(dm->logging, UA_LOGCATEGORY_SERVER, + "Multicast DNS: Add entry to ServersOnNetwork: %s (%*.s)", + fqdnMdnsRecord, (int)serverNameLen, serverName); + + struct serverOnNetwork_list_entry *listEntry = (serverOnNetwork_list_entry*) + UA_malloc(sizeof(struct serverOnNetwork_list_entry)); + if(!listEntry) + return UA_STATUSCODE_BADOUTOFMEMORY; + listEntry->created = UA_DateTime_now(); + listEntry->pathTmp = NULL; + listEntry->txtSet = false; + listEntry->srvSet = false; + UA_ServerOnNetwork_init(&listEntry->serverOnNetwork); + listEntry->serverOnNetwork.recordId = dm->serverOnNetworkRecordIdCounter; + listEntry->serverOnNetwork.serverName.data = (UA_Byte*)UA_malloc(serverNameLen); + if (!listEntry->serverOnNetwork.serverName.data) { + UA_free(listEntry); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + listEntry->serverOnNetwork.serverName.length = serverNameLen; + memcpy(listEntry->serverOnNetwork.serverName.data, serverName, serverNameLen); + dm->serverOnNetworkRecordIdCounter++; + if(dm->serverOnNetworkRecordIdCounter == 0) + dm->serverOnNetworkRecordIdLastReset = UA_DateTime_now(); + listEntry->lastSeen = UA_DateTime_nowMonotonic(); + + /* add to hash */ + UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)fqdnMdnsRecord, + strlen(fqdnMdnsRecord)) % SERVER_ON_NETWORK_HASH_SIZE; + struct serverOnNetwork_hash_entry *newHashEntry = (struct serverOnNetwork_hash_entry*) + UA_malloc(sizeof(struct serverOnNetwork_hash_entry)); + if (!newHashEntry) { + UA_free(listEntry->serverOnNetwork.serverName.data); + UA_free(listEntry); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + newHashEntry->next = dm->serverOnNetworkHash[hashIdx]; + dm->serverOnNetworkHash[hashIdx] = newHashEntry; + newHashEntry->entry = listEntry; + + LIST_INSERT_HEAD(&dm->serverOnNetwork, listEntry, pointers); + if(addedEntry != NULL) + *addedEntry = listEntry; + + return UA_STATUSCODE_GOOD; +} + +#ifdef _WIN32 + +/* see http://stackoverflow.com/a/10838854/869402 */ +static IP_ADAPTER_ADDRESSES * +getInterfaces(UA_DiscoveryManager *dm) { + IP_ADAPTER_ADDRESSES* adapter_addresses = NULL; + + /* Start with a 16 KB buffer and resize if needed - multiple attempts in + * case interfaces change while we are in the middle of querying them. */ + DWORD adapter_addresses_buffer_size = 16 * 1024; + for(size_t attempts = 0; attempts != 3; ++attempts) { + /* todo: malloc may fail: return a statuscode */ + adapter_addresses = (IP_ADAPTER_ADDRESSES*)UA_malloc(adapter_addresses_buffer_size); + if(!adapter_addresses) { + UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_DISCOVERY, + "GetAdaptersAddresses out of memory"); + adapter_addresses = NULL; + break; + } + DWORD error = GetAdaptersAddresses(AF_UNSPEC, + GAA_FLAG_SKIP_ANYCAST | + GAA_FLAG_SKIP_DNS_SERVER | + GAA_FLAG_SKIP_FRIENDLY_NAME, + NULL, adapter_addresses, + &adapter_addresses_buffer_size); + + if(ERROR_SUCCESS == error) { + break; + } else if (ERROR_BUFFER_OVERFLOW == error) { + /* Try again with the new size */ + UA_free(adapter_addresses); + adapter_addresses = NULL; + continue; + } + + /* Unexpected error */ + UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_SERVER, + "GetAdaptersAddresses returned an unexpected error. " + "Not setting mDNS A records."); + UA_free(adapter_addresses); + adapter_addresses = NULL; + break; + } + return adapter_addresses; +} + +#endif /* _WIN32 */ + +UA_StatusCode +UA_DiscoveryManager_removeEntryFromServersOnNetwork(UA_DiscoveryManager *dm, + const char *fqdnMdnsRecord, + const char *serverName, + size_t serverNameLen) { + UA_LOG_DEBUG(dm->logging, UA_LOGCATEGORY_SERVER, + "Multicast DNS: Remove entry from ServersOnNetwork: %s (%*.s)", + fqdnMdnsRecord, (int)serverNameLen, serverName); + + struct serverOnNetwork_list_entry *entry = + mdns_record_add_or_get(dm, fqdnMdnsRecord, serverName, + serverNameLen, false); + if(!entry) + return UA_STATUSCODE_BADNOTFOUND; + + UA_String recordStr; + // Cast away const because otherwise the pointer cannot be assigned. + // Be careful what you do with recordStr! + recordStr.data = (UA_Byte*)(uintptr_t)fqdnMdnsRecord; + recordStr.length = strlen(fqdnMdnsRecord); + + /* remove from hash */ + UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)recordStr.data, + recordStr.length) % SERVER_ON_NETWORK_HASH_SIZE; + struct serverOnNetwork_hash_entry *hash_entry = dm->serverOnNetworkHash[hashIdx]; + struct serverOnNetwork_hash_entry *prevEntry = hash_entry; + while(hash_entry) { + if(hash_entry->entry == entry) { + if(dm->serverOnNetworkHash[hashIdx] == hash_entry) + dm->serverOnNetworkHash[hashIdx] = hash_entry->next; + else if(prevEntry) + prevEntry->next = hash_entry->next; + break; + } + prevEntry = hash_entry; + hash_entry = hash_entry->next; + } + UA_free(hash_entry); + + if(dm->serverOnNetworkCallback && + !UA_String_equal(&dm->selfFqdnMdnsRecord, &recordStr)) + dm->serverOnNetworkCallback(&entry->serverOnNetwork, false, + entry->txtSet, + dm->serverOnNetworkCallbackData); + + /* Remove from list */ + LIST_REMOVE(entry, pointers); + UA_ServerOnNetwork_clear(&entry->serverOnNetwork); + if(entry->pathTmp) + UA_free(entry->pathTmp); + UA_free(entry); + return UA_STATUSCODE_GOOD; +} + +static void +mdns_append_path_to_url(UA_String *url, const char *path) { + size_t pathLen = strlen(path); + /* todo: malloc may fail: return a statuscode */ + char *newUrl = (char *)UA_malloc(url->length + pathLen); + memcpy(newUrl, url->data, url->length); + memcpy(newUrl + url->length, path, pathLen); + UA_String_clear(url); + url->length = url->length + pathLen; + url->data = (UA_Byte *) newUrl; +} + +static void +setTxt(UA_DiscoveryManager *dm, const struct resource *r, + struct serverOnNetwork_list_entry *entry) { + entry->txtSet = true; + xht_t *x = txt2sd(r->rdata, r->rdlength); + char *path = (char *) xht_get(x, "path"); + char *caps = (char *) xht_get(x, "caps"); + + size_t pathLen = path ? strlen(path) : 0; + + if(path && pathLen > 1) { + if(!entry->srvSet) { + /* txt arrived before SRV, thus cache path entry */ + if (!entry->pathTmp) { + entry->pathTmp = (char*)UA_malloc(pathLen+1); + if (!entry->pathTmp) { + UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_SERVER, + "Cannot alloc memory for mDNS srv path"); + return; + } + memcpy(&(entry->pathTmp), &path, pathLen); + entry->pathTmp[pathLen] = '\0'; + } + } else { + /* SRV already there and discovery URL set. Add path to discovery URL */ + mdns_append_path_to_url(&entry->serverOnNetwork.discoveryUrl, path); + } + } + + if(caps && strlen(caps) > 0) { + /* count comma in caps */ + size_t capsCount = 1; + for(size_t i = 0; caps[i]; i++) { + if(caps[i] == ',') + capsCount++; + } + + /* set capabilities */ + entry->serverOnNetwork.serverCapabilitiesSize = capsCount; + entry->serverOnNetwork.serverCapabilities = + (UA_String *) UA_Array_new(capsCount, &UA_TYPES[UA_TYPES_STRING]); + + for(size_t i = 0; i < capsCount; i++) { + char *nextStr = strchr(caps, ','); + size_t len = nextStr ? (size_t) (nextStr - caps) : strlen(caps); + entry->serverOnNetwork.serverCapabilities[i].length = len; + /* todo: malloc may fail: return a statuscode */ + entry->serverOnNetwork.serverCapabilities[i].data = (UA_Byte*)UA_malloc(len); + memcpy(entry->serverOnNetwork.serverCapabilities[i].data, caps, len); + if(nextStr) + caps = nextStr + 1; + else + break; + } + } + xht_free(x); +} + +/* [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname]. */ +static void +setSrv(UA_DiscoveryManager *dm, const struct resource *r, + struct serverOnNetwork_list_entry *entry) { + entry->srvSet = true; + + /* The specification Part 12 says: The hostname maps onto the SRV record + * target field. If the hostname is an IPAddress then it must be converted + * to a domain name. If this cannot be done then LDS shall report an + * error. */ + + size_t srvNameLen = strlen(r->known.srv.name); + if(srvNameLen > 0 && r->known.srv.name[srvNameLen - 1] == '.') + /* cut off last dot */ + srvNameLen--; + /* opc.tcp://[servername]:[port][path] */ + char *newUrl = (char*)UA_malloc(10 + srvNameLen + 8 + 1); + if (!newUrl) { + UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_SERVER, + "Cannot allocate char for discovery url. Out of memory."); + return; + } + + mp_snprintf(newUrl, 10 + srvNameLen + 8, "opc.tcp://%.*s:%d", (int) srvNameLen, + r->known.srv.name, r->known.srv.port); + + entry->serverOnNetwork.discoveryUrl = UA_String_fromChars(newUrl); + UA_LOG_INFO(dm->logging, UA_LOGCATEGORY_SERVER, + "Multicast DNS: found server: %.*s", + (int)entry->serverOnNetwork.discoveryUrl.length, + (char*)entry->serverOnNetwork.discoveryUrl.data); + UA_free(newUrl); + + if(entry->pathTmp) { + mdns_append_path_to_url(&entry->serverOnNetwork.discoveryUrl, entry->pathTmp); + UA_free(entry->pathTmp); + } +} + +/* This will be called by the mDNS library on every record which is received */ +void +mdns_record_received(const struct resource *r, void *data) { + UA_DiscoveryManager *dm = (UA_DiscoveryManager*) data; + + /* we only need SRV and TXT records */ + /* TODO: remove magic number */ + if((r->clazz != QCLASS_IN && r->clazz != QCLASS_IN + 32768) || + (r->type != QTYPE_SRV && r->type != QTYPE_TXT)) + return; + + /* we only handle '_opcua-tcp._tcp.' records */ + char *opcStr = strstr(r->name, "_opcua-tcp._tcp."); + if(!opcStr) + return; + + UA_String recordStr; + recordStr.data = (UA_Byte*)r->name; + recordStr.length = strlen(r->name); + UA_Boolean isSelfAnnounce = UA_String_equal(&dm->selfFqdnMdnsRecord, &recordStr); + if(isSelfAnnounce) + return; // ignore itself + + /* Compute the length of the servername */ + size_t servernameLen = (size_t) (opcStr - r->name); + if(servernameLen == 0) + return; + servernameLen--; /* remove point */ + + /* Get entry */ + struct serverOnNetwork_list_entry *entry = + mdns_record_add_or_get(dm, r->name, r->name, + servernameLen, r->ttl > 0); + if(!entry) + return; + + /* Check that the ttl is positive */ + if(r->ttl == 0) { + UA_LOG_INFO(dm->logging, UA_LOGCATEGORY_SERVER, + "Multicast DNS: remove server (TTL=0): %.*s", + (int)entry->serverOnNetwork.discoveryUrl.length, + entry->serverOnNetwork.discoveryUrl.data); + UA_DiscoveryManager_removeEntryFromServersOnNetwork(dm, r->name, r->name, + servernameLen); + return; + } + + /* Update lastSeen */ + entry->lastSeen = UA_DateTime_nowMonotonic(); + + /* TXT and SRV are already set */ + if(entry->txtSet && entry->srvSet) { + // call callback for every mdns package we received. + // This will also call the callback multiple times + if(dm->serverOnNetworkCallback) + dm->serverOnNetworkCallback(&entry->serverOnNetwork, true, entry->txtSet, + dm->serverOnNetworkCallbackData); + return; + } + + /* Add the resources */ + if(r->type == QTYPE_TXT && !entry->txtSet) + setTxt(dm, r, entry); + else if (r->type == QTYPE_SRV && !entry->srvSet) + setSrv(dm, r, entry); + + /* Call callback to announce a new server */ + if(entry->srvSet && dm->serverOnNetworkCallback) + dm->serverOnNetworkCallback(&entry->serverOnNetwork, true, entry->txtSet, + dm->serverOnNetworkCallbackData); +} + +void +mdns_create_txt(UA_DiscoveryManager *dm, const char *fullServiceDomain, const char *path, + const UA_String *capabilites, const size_t capabilitiesSize, + void (*conflict)(char *host, int type, void *arg)) { + mdns_record_t *r = mdnsd_unique(dm->mdnsDaemon, fullServiceDomain, + QTYPE_TXT, 600, conflict, dm); + xht_t *h = xht_new(11); + char *allocPath = NULL; + if(!path || strlen(path) == 0) { + xht_set(h, "path", "/"); + } else { + /* path does not contain slash, so add it here */ + size_t pathLen = strlen(path); + if(path[0] == '/') { + allocPath = (char*)UA_malloc(pathLen+1); + if(!allocPath) { + UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_SERVER, + "Cannot alloc memory for txt path"); + return; + } + memcpy(allocPath, path, pathLen); + allocPath[pathLen] = '\0'; + } else { + allocPath = (char*)UA_malloc(pathLen + 2); + if(!allocPath) { + UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_SERVER, + "Cannot alloc memory for txt path"); + return; + } + allocPath[0] = '/'; + memcpy(allocPath + 1, path, pathLen); + allocPath[pathLen + 1] = '\0'; + } + xht_set(h, "path", allocPath); + } + + /* calculate max string length: */ + size_t capsLen = 0; + for(size_t i = 0; i < capabilitiesSize; i++) { + /* add comma or last \0 */ + capsLen += capabilites[i].length + 1; + } + + char *caps = NULL; + if(capsLen) { + /* freed when xht_free is called */ + /* todo: malloc may fail: return a statuscode */ + caps = (char*)UA_malloc(sizeof(char) * capsLen); + size_t idx = 0; + for(size_t i = 0; i < capabilitiesSize; i++) { + memcpy(caps + idx, (const char *) capabilites[i].data, capabilites[i].length); + idx += capabilites[i].length + 1; + caps[idx - 1] = ','; + } + caps[idx - 1] = '\0'; + + xht_set(h, "caps", caps); + } else { + xht_set(h, "caps", "NA"); + } + + int txtRecordLength; + unsigned char *packet = sd2txt(h, &txtRecordLength); + if(allocPath) + UA_free(allocPath); + if(caps) + UA_free(caps); + xht_free(h); + mdnsd_set_raw(dm->mdnsDaemon, r, (char *) packet, + (unsigned short) txtRecordLength); + UA_free(packet); +} + +mdns_record_t * +mdns_find_record(mdns_daemon_t *mdnsDaemon, unsigned short type, + const char *host, const char *rdname) { + mdns_record_t *r = mdnsd_get_published(mdnsDaemon, host); + if(!r) + return NULL; + + /* search for the record with the correct ptr hostname */ + while(r) { + const mdns_answer_t *data = mdnsd_record_data(r); + if(data->type == type && strcmp(data->rdname, rdname) == 0) + return r; + r = mdnsd_record_next(r); + } + return NULL; +} + +/* set record in the given interface */ +static void +mdns_set_address_record_if(UA_DiscoveryManager *dm, const char *fullServiceDomain, + const char *localDomain, char *addr, UA_UInt16 addr_len) { + /* [servername]-[hostname]._opcua-tcp._tcp.local. A [ip]. */ + mdns_record_t *r = mdnsd_shared(dm->mdnsDaemon, fullServiceDomain, QTYPE_A, 600); + mdnsd_set_raw(dm->mdnsDaemon, r, addr, addr_len); + + /* [hostname]. A [ip]. */ + r = mdnsd_shared(dm->mdnsDaemon, localDomain, QTYPE_A, 600); + mdnsd_set_raw(dm->mdnsDaemon, r, addr, addr_len); +} + +/* Loop over network interfaces and run set_address_record on each */ +#ifdef _WIN32 + +void mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, + const char *localDomain) { + IP_ADAPTER_ADDRESSES* adapter_addresses = getInterfaces(dm); + if(!adapter_addresses) + return; + + /* Iterate through all of the adapters */ + IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; + for(; adapter != NULL; adapter = adapter->Next) { + /* Skip loopback adapters */ + if(IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType) + continue; + + /* Parse all IPv4 and IPv6 addresses */ + IP_ADAPTER_UNICAST_ADDRESS* address = adapter->FirstUnicastAddress; + for(; NULL != address; address = address->Next) { + int family = address->Address.lpSockaddr->sa_family; + if(AF_INET == family) { + SOCKADDR_IN* ipv4 = (SOCKADDR_IN*)(address->Address.lpSockaddr); /* IPv4 */ + mdns_set_address_record_if(dm, fullServiceDomain, + localDomain, (char *)&ipv4->sin_addr, 4); + } else if(AF_INET6 == family) { + /* IPv6 */ +#if 0 + SOCKADDR_IN6* ipv6 = (SOCKADDR_IN6*)(address->Address.lpSockaddr); + + char str_buffer[INET6_ADDRSTRLEN] = {0}; + inet_ntop(AF_INET6, &(ipv6->sin6_addr), str_buffer, INET6_ADDRSTRLEN); + + std::string ipv6_str(str_buffer); + + /* Detect and skip non-external addresses */ + UA_Boolean is_link_local(false); + UA_Boolean is_special_use(false); + + if(0 == ipv6_str.find("fe")) { + char c = ipv6_str[2]; + if(c == '8' || c == '9' || c == 'a' || c == 'b') + is_link_local = true; + } else if (0 == ipv6_str.find("2001:0:")) { + is_special_use = true; + } + + if(!(is_link_local || is_special_use)) + ipAddrs.mIpv6.push_back(ipv6_str); #endif + } + } + } + + /* Cleanup */ + UA_free(adapter_addresses); + adapter_addresses = NULL; +} + +#elif defined(UA_HAS_GETIFADDR) + +void +mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, + const char *localDomain) { + struct ifaddrs *ifaddr; + struct ifaddrs *ifa; + if(getifaddrs(&ifaddr) == -1) { + UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_SERVER, + "getifaddrs returned an unexpected error. Not setting mDNS A records."); + return; + } + + /* Walk through linked list, maintaining head pointer so we can free list later */ + int n; + for(ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) { + if(!ifa->ifa_addr) + continue; + + if((strcmp("lo", ifa->ifa_name) == 0) || + !(ifa->ifa_flags & (IFF_RUNNING))|| + !(ifa->ifa_flags & (IFF_MULTICAST))) + continue; + + /* IPv4 */ + if(ifa->ifa_addr->sa_family == AF_INET) { + struct sockaddr_in* sa = (struct sockaddr_in*) ifa->ifa_addr; + mdns_set_address_record_if(dm, fullServiceDomain, + localDomain, (char*)&sa->sin_addr.s_addr, 4); + } + + /* IPv6 not implemented yet */ + } + + /* Clean up */ + freeifaddrs(ifaddr); +} +#else /* _WIN32 */ + +void +mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, + const char *localDomain) { + if(dm->serverConfig->mdnsIpAddressListSize == 0) { + UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_SERVER, + "If UA_HAS_GETIFADDR is false, config.mdnsIpAddressList must be set"); + return; + } + + for(size_t i=0; i< dm->serverConfig->mdnsIpAddressListSize; i++) { + mdns_set_address_record_if(dm, fullServiceDomain, localDomain, + (char*)&dm->serverConfig->mdnsIpAddressList[i], 4); + } +} + +#endif /* _WIN32 */ typedef enum { UA_DISCOVERY_TCP, /* OPC UA TCP mapping */ @@ -103,14 +713,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; } @@ -398,95 +1007,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, @@ -860,4 +1380,4 @@ UA_Discovery_removeRecord(UA_DiscoveryManager *dm, const UA_String *servername, return UA_STATUSCODE_GOOD; } -#endif /* defined(UA_ENABLE_DISCOVERY) && defined(UA_ENABLE_DISCOVERY_MULTICAST) */ +#endif /* UA_ENABLE_DISCOVERY_MULTICAST */ diff --git a/src/server/ua_server_discovery.c b/src/server/ua_server_discovery.c deleted file mode 100644 index 4db20985d76..00000000000 --- a/src/server/ua_server_discovery.c +++ /dev/null @@ -1,124 +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_server_internal.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 - } - - request.server.serverNames = &server->config.applicationDescription.applicationName; - request.server.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; - -#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_TYPES[UA_TYPES_MDNSDISCOVERYCONFIGURATION]); -#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); - } - - if(serviceResult != UA_STATUSCODE_GOOD) { - UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_CLIENT, - "RegisterServer/RegisterServer2 failed with statuscode %s", - UA_StatusCode_name(serviceResult)); - } - - return serviceResult; -} - -UA_StatusCode -UA_Server_register_discovery(UA_Server *server, UA_Client *client, - const char* semaphoreFilePath) { - UA_LOCK(&server->serviceMutex); - UA_StatusCode retval = register_server_with_discovery_server(server, client, - false, semaphoreFilePath); - UA_UNLOCK(&server->serviceMutex); - return retval; -} - -UA_StatusCode -UA_Server_unregister_discovery(UA_Server *server, UA_Client *client) { - UA_LOCK(&server->serviceMutex); - UA_StatusCode retval = register_server_with_discovery_server(server, client, - true, NULL); - UA_UNLOCK(&server->serviceMutex); - return retval; -} - -#endif /* UA_ENABLE_DISCOVERY */ diff --git a/src/server/ua_server_discovery_mdns.c b/src/server/ua_server_discovery_mdns.c deleted file mode 100644 index 075b0225a5f..00000000000 --- a/src/server/ua_server_discovery_mdns.c +++ /dev/null @@ -1,632 +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) - */ - -#include "ua_discovery_manager.h" -#include "ua_server_internal.h" - -#ifdef UA_ENABLE_DISCOVERY_MULTICAST - -#ifndef UA_ENABLE_AMALGAMATION -#include "mdnsd/libmdnsd/xht.h" -#include "mdnsd/libmdnsd/sdtxt.h" -#endif - -#include "../deps/mp_printf.h" - -#ifdef _WIN32 -/* inet_ntoa is deprecated on MSVC but used for compatibility */ -# define _WINSOCK_DEPRECATED_NO_WARNINGS -# include -# include -# include -#else -# include // for struct timeval -# include // for struct ip_mreq -# if defined(UA_HAS_GETIFADDR) -# include -# endif /* UA_HAS_GETIFADDR */ -# include /* for IFF_RUNNING */ -# include // for recvfrom in cygwin -#endif - -static struct serverOnNetwork_list_entry * -mdns_record_add_or_get(UA_DiscoveryManager *dm, const char *record, const char *serverName, - size_t serverNameLen, UA_Boolean createNew) { - UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)record, - strlen(record)) % SERVER_ON_NETWORK_HASH_SIZE; - struct serverOnNetwork_hash_entry *hash_entry = dm->serverOnNetworkHash[hashIdx]; - - while(hash_entry) { - size_t maxLen; - if(serverNameLen > hash_entry->entry->serverOnNetwork.serverName.length) - maxLen = hash_entry->entry->serverOnNetwork.serverName.length; - else - maxLen = serverNameLen; - - if(strncmp((char *) hash_entry->entry->serverOnNetwork.serverName.data, - serverName, maxLen) == 0) - return hash_entry->entry; - hash_entry = hash_entry->next; - } - - if(!createNew) - return NULL; - - struct serverOnNetwork_list_entry *listEntry; - UA_StatusCode retval = UA_DiscoveryManager_addEntryToServersOnNetwork(dm, record, serverName, - serverNameLen, &listEntry); - if (retval != UA_STATUSCODE_GOOD) - return NULL; - - return listEntry; -} - - -UA_StatusCode -UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, - const char *fqdnMdnsRecord, - const char *serverName, - size_t serverNameLen, - struct serverOnNetwork_list_entry **addedEntry) { - struct serverOnNetwork_list_entry *entry = - mdns_record_add_or_get(dm, fqdnMdnsRecord, serverName, - serverNameLen, false); - if(entry) { - if(addedEntry != NULL) - *addedEntry = entry; - return UA_STATUSCODE_BADALREADYEXISTS; - } - - UA_LOG_DEBUG(dm->logging, UA_LOGCATEGORY_SERVER, - "Multicast DNS: Add entry to ServersOnNetwork: %s (%*.s)", - fqdnMdnsRecord, (int)serverNameLen, serverName); - - struct serverOnNetwork_list_entry *listEntry = (serverOnNetwork_list_entry*) - UA_malloc(sizeof(struct serverOnNetwork_list_entry)); - if(!listEntry) - return UA_STATUSCODE_BADOUTOFMEMORY; - listEntry->created = UA_DateTime_now(); - listEntry->pathTmp = NULL; - listEntry->txtSet = false; - listEntry->srvSet = false; - UA_ServerOnNetwork_init(&listEntry->serverOnNetwork); - listEntry->serverOnNetwork.recordId = dm->serverOnNetworkRecordIdCounter; - listEntry->serverOnNetwork.serverName.data = (UA_Byte*)UA_malloc(serverNameLen); - if (!listEntry->serverOnNetwork.serverName.data) { - UA_free(listEntry); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - listEntry->serverOnNetwork.serverName.length = serverNameLen; - memcpy(listEntry->serverOnNetwork.serverName.data, serverName, serverNameLen); - dm->serverOnNetworkRecordIdCounter++; - if(dm->serverOnNetworkRecordIdCounter == 0) - dm->serverOnNetworkRecordIdLastReset = UA_DateTime_now(); - listEntry->lastSeen = UA_DateTime_nowMonotonic(); - - /* add to hash */ - UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)fqdnMdnsRecord, - strlen(fqdnMdnsRecord)) % SERVER_ON_NETWORK_HASH_SIZE; - struct serverOnNetwork_hash_entry *newHashEntry = (struct serverOnNetwork_hash_entry*) - UA_malloc(sizeof(struct serverOnNetwork_hash_entry)); - if (!newHashEntry) { - UA_free(listEntry->serverOnNetwork.serverName.data); - UA_free(listEntry); - return UA_STATUSCODE_BADOUTOFMEMORY; - } - newHashEntry->next = dm->serverOnNetworkHash[hashIdx]; - dm->serverOnNetworkHash[hashIdx] = newHashEntry; - newHashEntry->entry = listEntry; - - LIST_INSERT_HEAD(&dm->serverOnNetwork, listEntry, pointers); - if(addedEntry != NULL) - *addedEntry = listEntry; - - return UA_STATUSCODE_GOOD; -} - -#ifdef _WIN32 - -/* see http://stackoverflow.com/a/10838854/869402 */ -static IP_ADAPTER_ADDRESSES * -getInterfaces(UA_DiscoveryManager *dm) { - IP_ADAPTER_ADDRESSES* adapter_addresses = NULL; - - /* Start with a 16 KB buffer and resize if needed - multiple attempts in - * case interfaces change while we are in the middle of querying them. */ - DWORD adapter_addresses_buffer_size = 16 * 1024; - for(size_t attempts = 0; attempts != 3; ++attempts) { - /* todo: malloc may fail: return a statuscode */ - adapter_addresses = (IP_ADAPTER_ADDRESSES*)UA_malloc(adapter_addresses_buffer_size); - if(!adapter_addresses) { - UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_DISCOVERY, - "GetAdaptersAddresses out of memory"); - adapter_addresses = NULL; - break; - } - DWORD error = GetAdaptersAddresses(AF_UNSPEC, - GAA_FLAG_SKIP_ANYCAST | - GAA_FLAG_SKIP_DNS_SERVER | - GAA_FLAG_SKIP_FRIENDLY_NAME, - NULL, adapter_addresses, - &adapter_addresses_buffer_size); - - if(ERROR_SUCCESS == error) { - break; - } else if (ERROR_BUFFER_OVERFLOW == error) { - /* Try again with the new size */ - UA_free(adapter_addresses); - adapter_addresses = NULL; - continue; - } - - /* Unexpected error */ - UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_SERVER, - "GetAdaptersAddresses returned an unexpected error. " - "Not setting mDNS A records."); - UA_free(adapter_addresses); - adapter_addresses = NULL; - break; - } - return adapter_addresses; -} - -#endif /* _WIN32 */ - -UA_StatusCode -UA_DiscoveryManager_removeEntryFromServersOnNetwork(UA_DiscoveryManager *dm, - const char *fqdnMdnsRecord, - const char *serverName, - size_t serverNameLen) { - UA_LOG_DEBUG(dm->logging, UA_LOGCATEGORY_SERVER, - "Multicast DNS: Remove entry from ServersOnNetwork: %s (%*.s)", - fqdnMdnsRecord, (int)serverNameLen, serverName); - - struct serverOnNetwork_list_entry *entry = - mdns_record_add_or_get(dm, fqdnMdnsRecord, serverName, - serverNameLen, false); - if(!entry) - return UA_STATUSCODE_BADNOTFOUND; - - UA_String recordStr; - // Cast away const because otherwise the pointer cannot be assigned. - // Be careful what you do with recordStr! - recordStr.data = (UA_Byte*)(uintptr_t)fqdnMdnsRecord; - recordStr.length = strlen(fqdnMdnsRecord); - - /* remove from hash */ - UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)recordStr.data, - recordStr.length) % SERVER_ON_NETWORK_HASH_SIZE; - struct serverOnNetwork_hash_entry *hash_entry = dm->serverOnNetworkHash[hashIdx]; - struct serverOnNetwork_hash_entry *prevEntry = hash_entry; - while(hash_entry) { - if(hash_entry->entry == entry) { - if(dm->serverOnNetworkHash[hashIdx] == hash_entry) - dm->serverOnNetworkHash[hashIdx] = hash_entry->next; - else if(prevEntry) - prevEntry->next = hash_entry->next; - break; - } - prevEntry = hash_entry; - hash_entry = hash_entry->next; - } - UA_free(hash_entry); - - if(dm->serverOnNetworkCallback && - !UA_String_equal(&dm->selfFqdnMdnsRecord, &recordStr)) - dm->serverOnNetworkCallback(&entry->serverOnNetwork, false, - entry->txtSet, - dm->serverOnNetworkCallbackData); - - /* Remove from list */ - LIST_REMOVE(entry, pointers); - UA_ServerOnNetwork_clear(&entry->serverOnNetwork); - if(entry->pathTmp) - UA_free(entry->pathTmp); - UA_free(entry); - return UA_STATUSCODE_GOOD; -} - -static void -mdns_append_path_to_url(UA_String *url, const char *path) { - size_t pathLen = strlen(path); - /* todo: malloc may fail: return a statuscode */ - char *newUrl = (char *)UA_malloc(url->length + pathLen); - memcpy(newUrl, url->data, url->length); - memcpy(newUrl + url->length, path, pathLen); - UA_String_clear(url); - url->length = url->length + pathLen; - url->data = (UA_Byte *) newUrl; -} - -static void -setTxt(UA_DiscoveryManager *dm, const struct resource *r, - struct serverOnNetwork_list_entry *entry) { - entry->txtSet = true; - xht_t *x = txt2sd(r->rdata, r->rdlength); - char *path = (char *) xht_get(x, "path"); - char *caps = (char *) xht_get(x, "caps"); - - size_t pathLen = path ? strlen(path) : 0; - - if(path && pathLen > 1) { - if(!entry->srvSet) { - /* txt arrived before SRV, thus cache path entry */ - if (!entry->pathTmp) { - entry->pathTmp = (char*)UA_malloc(pathLen+1); - if (!entry->pathTmp) { - UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_SERVER, - "Cannot alloc memory for mDNS srv path"); - return; - } - memcpy(&(entry->pathTmp), &path, pathLen); - entry->pathTmp[pathLen] = '\0'; - } - } else { - /* SRV already there and discovery URL set. Add path to discovery URL */ - mdns_append_path_to_url(&entry->serverOnNetwork.discoveryUrl, path); - } - } - - if(caps && strlen(caps) > 0) { - /* count comma in caps */ - size_t capsCount = 1; - for(size_t i = 0; caps[i]; i++) { - if(caps[i] == ',') - capsCount++; - } - - /* set capabilities */ - entry->serverOnNetwork.serverCapabilitiesSize = capsCount; - entry->serverOnNetwork.serverCapabilities = - (UA_String *) UA_Array_new(capsCount, &UA_TYPES[UA_TYPES_STRING]); - - for(size_t i = 0; i < capsCount; i++) { - char *nextStr = strchr(caps, ','); - size_t len = nextStr ? (size_t) (nextStr - caps) : strlen(caps); - entry->serverOnNetwork.serverCapabilities[i].length = len; - /* todo: malloc may fail: return a statuscode */ - entry->serverOnNetwork.serverCapabilities[i].data = (UA_Byte*)UA_malloc(len); - memcpy(entry->serverOnNetwork.serverCapabilities[i].data, caps, len); - if(nextStr) - caps = nextStr + 1; - else - break; - } - } - xht_free(x); -} - -/* [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname]. */ -static void -setSrv(UA_DiscoveryManager *dm, const struct resource *r, - struct serverOnNetwork_list_entry *entry) { - entry->srvSet = true; - - /* The specification Part 12 says: The hostname maps onto the SRV record - * target field. If the hostname is an IPAddress then it must be converted - * to a domain name. If this cannot be done then LDS shall report an - * error. */ - - size_t srvNameLen = strlen(r->known.srv.name); - if(srvNameLen > 0 && r->known.srv.name[srvNameLen - 1] == '.') - /* cut off last dot */ - srvNameLen--; - /* opc.tcp://[servername]:[port][path] */ - char *newUrl = (char*)UA_malloc(10 + srvNameLen + 8 + 1); - if (!newUrl) { - UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_SERVER, - "Cannot allocate char for discovery url. Out of memory."); - return; - } - - mp_snprintf(newUrl, 10 + srvNameLen + 8, "opc.tcp://%.*s:%d", (int) srvNameLen, - r->known.srv.name, r->known.srv.port); - - entry->serverOnNetwork.discoveryUrl = UA_String_fromChars(newUrl); - UA_LOG_INFO(dm->logging, UA_LOGCATEGORY_SERVER, - "Multicast DNS: found server: %.*s", - (int)entry->serverOnNetwork.discoveryUrl.length, - (char*)entry->serverOnNetwork.discoveryUrl.data); - UA_free(newUrl); - - if(entry->pathTmp) { - mdns_append_path_to_url(&entry->serverOnNetwork.discoveryUrl, entry->pathTmp); - UA_free(entry->pathTmp); - } -} - -/* This will be called by the mDNS library on every record which is received */ -void -mdns_record_received(const struct resource *r, void *data) { - UA_DiscoveryManager *dm = (UA_DiscoveryManager*) data; - - /* we only need SRV and TXT records */ - /* TODO: remove magic number */ - if((r->clazz != QCLASS_IN && r->clazz != QCLASS_IN + 32768) || - (r->type != QTYPE_SRV && r->type != QTYPE_TXT)) - return; - - /* we only handle '_opcua-tcp._tcp.' records */ - char *opcStr = strstr(r->name, "_opcua-tcp._tcp."); - if(!opcStr) - return; - - UA_String recordStr; - recordStr.data = (UA_Byte*)r->name; - recordStr.length = strlen(r->name); - UA_Boolean isSelfAnnounce = UA_String_equal(&dm->selfFqdnMdnsRecord, &recordStr); - if(isSelfAnnounce) - return; // ignore itself - - /* Compute the length of the servername */ - size_t servernameLen = (size_t) (opcStr - r->name); - if(servernameLen == 0) - return; - servernameLen--; /* remove point */ - - /* Get entry */ - struct serverOnNetwork_list_entry *entry = - mdns_record_add_or_get(dm, r->name, r->name, - servernameLen, r->ttl > 0); - if(!entry) - return; - - /* Check that the ttl is positive */ - if(r->ttl == 0) { - UA_LOG_INFO(dm->logging, UA_LOGCATEGORY_SERVER, - "Multicast DNS: remove server (TTL=0): %.*s", - (int)entry->serverOnNetwork.discoveryUrl.length, - entry->serverOnNetwork.discoveryUrl.data); - UA_DiscoveryManager_removeEntryFromServersOnNetwork(dm, r->name, r->name, - servernameLen); - return; - } - - /* Update lastSeen */ - entry->lastSeen = UA_DateTime_nowMonotonic(); - - /* TXT and SRV are already set */ - if(entry->txtSet && entry->srvSet) { - // call callback for every mdns package we received. - // This will also call the callback multiple times - if(dm->serverOnNetworkCallback) - dm->serverOnNetworkCallback(&entry->serverOnNetwork, true, entry->txtSet, - dm->serverOnNetworkCallbackData); - return; - } - - /* Add the resources */ - if(r->type == QTYPE_TXT && !entry->txtSet) - setTxt(dm, r, entry); - else if (r->type == QTYPE_SRV && !entry->srvSet) - setSrv(dm, r, entry); - - /* Call callback to announce a new server */ - if(entry->srvSet && dm->serverOnNetworkCallback) - dm->serverOnNetworkCallback(&entry->serverOnNetwork, true, entry->txtSet, - dm->serverOnNetworkCallbackData); -} - -void -mdns_create_txt(UA_DiscoveryManager *dm, const char *fullServiceDomain, const char *path, - const UA_String *capabilites, const size_t capabilitiesSize, - void (*conflict)(char *host, int type, void *arg)) { - mdns_record_t *r = mdnsd_unique(dm->mdnsDaemon, fullServiceDomain, - QTYPE_TXT, 600, conflict, dm); - xht_t *h = xht_new(11); - char *allocPath = NULL; - if(!path || strlen(path) == 0) { - xht_set(h, "path", "/"); - } else { - /* path does not contain slash, so add it here */ - size_t pathLen = strlen(path); - if(path[0] == '/') { - allocPath = (char*)UA_malloc(pathLen+1); - if(!allocPath) { - UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_SERVER, - "Cannot alloc memory for txt path"); - return; - } - memcpy(allocPath, path, pathLen); - allocPath[pathLen] = '\0'; - } else { - allocPath = (char*)UA_malloc(pathLen + 2); - if(!allocPath) { - UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_SERVER, - "Cannot alloc memory for txt path"); - return; - } - allocPath[0] = '/'; - memcpy(allocPath + 1, path, pathLen); - allocPath[pathLen + 1] = '\0'; - } - xht_set(h, "path", allocPath); - } - - /* calculate max string length: */ - size_t capsLen = 0; - for(size_t i = 0; i < capabilitiesSize; i++) { - /* add comma or last \0 */ - capsLen += capabilites[i].length + 1; - } - - char *caps = NULL; - if(capsLen) { - /* freed when xht_free is called */ - /* todo: malloc may fail: return a statuscode */ - caps = (char*)UA_malloc(sizeof(char) * capsLen); - size_t idx = 0; - for(size_t i = 0; i < capabilitiesSize; i++) { - memcpy(caps + idx, (const char *) capabilites[i].data, capabilites[i].length); - idx += capabilites[i].length + 1; - caps[idx - 1] = ','; - } - caps[idx - 1] = '\0'; - - xht_set(h, "caps", caps); - } else { - xht_set(h, "caps", "NA"); - } - - int txtRecordLength; - unsigned char *packet = sd2txt(h, &txtRecordLength); - if(allocPath) - UA_free(allocPath); - if(caps) - UA_free(caps); - xht_free(h); - mdnsd_set_raw(dm->mdnsDaemon, r, (char *) packet, - (unsigned short) txtRecordLength); - UA_free(packet); -} - -mdns_record_t * -mdns_find_record(mdns_daemon_t *mdnsDaemon, unsigned short type, - const char *host, const char *rdname) { - mdns_record_t *r = mdnsd_get_published(mdnsDaemon, host); - if(!r) - return NULL; - - /* search for the record with the correct ptr hostname */ - while(r) { - const mdns_answer_t *data = mdnsd_record_data(r); - if(data->type == type && strcmp(data->rdname, rdname) == 0) - return r; - r = mdnsd_record_next(r); - } - return NULL; -} - -/* set record in the given interface */ -static void -mdns_set_address_record_if(UA_DiscoveryManager *dm, const char *fullServiceDomain, - const char *localDomain, char *addr, UA_UInt16 addr_len) { - /* [servername]-[hostname]._opcua-tcp._tcp.local. A [ip]. */ - mdns_record_t *r = mdnsd_shared(dm->mdnsDaemon, fullServiceDomain, QTYPE_A, 600); - mdnsd_set_raw(dm->mdnsDaemon, r, addr, addr_len); - - /* [hostname]. A [ip]. */ - r = mdnsd_shared(dm->mdnsDaemon, localDomain, QTYPE_A, 600); - mdnsd_set_raw(dm->mdnsDaemon, r, addr, addr_len); -} - -/* Loop over network interfaces and run set_address_record on each */ -#ifdef _WIN32 - -void mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, - const char *localDomain) { - IP_ADAPTER_ADDRESSES* adapter_addresses = getInterfaces(dm); - if(!adapter_addresses) - return; - - /* Iterate through all of the adapters */ - IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; - for(; adapter != NULL; adapter = adapter->Next) { - /* Skip loopback adapters */ - if(IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType) - continue; - - /* Parse all IPv4 and IPv6 addresses */ - IP_ADAPTER_UNICAST_ADDRESS* address = adapter->FirstUnicastAddress; - for(; NULL != address; address = address->Next) { - int family = address->Address.lpSockaddr->sa_family; - if(AF_INET == family) { - SOCKADDR_IN* ipv4 = (SOCKADDR_IN*)(address->Address.lpSockaddr); /* IPv4 */ - mdns_set_address_record_if(dm, fullServiceDomain, - localDomain, (char *)&ipv4->sin_addr, 4); - } else if(AF_INET6 == family) { - /* IPv6 */ -#if 0 - SOCKADDR_IN6* ipv6 = (SOCKADDR_IN6*)(address->Address.lpSockaddr); - - char str_buffer[INET6_ADDRSTRLEN] = {0}; - inet_ntop(AF_INET6, &(ipv6->sin6_addr), str_buffer, INET6_ADDRSTRLEN); - - std::string ipv6_str(str_buffer); - - /* Detect and skip non-external addresses */ - UA_Boolean is_link_local(false); - UA_Boolean is_special_use(false); - - if(0 == ipv6_str.find("fe")) { - char c = ipv6_str[2]; - if(c == '8' || c == '9' || c == 'a' || c == 'b') - is_link_local = true; - } else if (0 == ipv6_str.find("2001:0:")) { - is_special_use = true; - } - - if(!(is_link_local || is_special_use)) - ipAddrs.mIpv6.push_back(ipv6_str); -#endif - } - } - } - - /* Cleanup */ - UA_free(adapter_addresses); - adapter_addresses = NULL; -} - -#elif defined(UA_HAS_GETIFADDR) - -void -mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, - const char *localDomain) { - struct ifaddrs *ifaddr; - struct ifaddrs *ifa; - if(getifaddrs(&ifaddr) == -1) { - UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_SERVER, - "getifaddrs returned an unexpected error. Not setting mDNS A records."); - return; - } - - /* Walk through linked list, maintaining head pointer so we can free list later */ - int n; - for(ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) { - if(!ifa->ifa_addr) - continue; - - if((strcmp("lo", ifa->ifa_name) == 0) || - !(ifa->ifa_flags & (IFF_RUNNING))|| - !(ifa->ifa_flags & (IFF_MULTICAST))) - continue; - - /* IPv4 */ - if(ifa->ifa_addr->sa_family == AF_INET) { - struct sockaddr_in* sa = (struct sockaddr_in*) ifa->ifa_addr; - mdns_set_address_record_if(dm, fullServiceDomain, - localDomain, (char*)&sa->sin_addr.s_addr, 4); - } - - /* IPv6 not implemented yet */ - } - - /* Clean up */ - freeifaddrs(ifaddr); -} -#else /* _WIN32 */ - -void -mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, - const char *localDomain) { - if(dm->serverConfig->mdnsIpAddressListSize == 0) { - UA_LOG_ERROR(dm->logging, UA_LOGCATEGORY_SERVER, - "If UA_HAS_GETIFADDR is false, config.mdnsIpAddressList must be set"); - return; - } - - for(size_t i=0; i< dm->serverConfig->mdnsIpAddressListSize; i++) { - mdns_set_address_record_if(dm, fullServiceDomain, localDomain, - (char*)&dm->serverConfig->mdnsIpAddressList[i], 4); - } -} - -#endif /* _WIN32 */ - -#endif /* UA_ENABLE_DISCOVERY_MULTICAST */ 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_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"); diff --git a/src/server/ua_services_discovery.c b/src/server/ua_services_discovery.c index 0eaaa04d9e3..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 @@ -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"); @@ -581,185 +672,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/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);