/** * @file server_register.c * @brief OPC UA Server that registers with a Local Discovery Server. * * This program runs an OPC UA server that periodically registers itself with * a remote LDS using the RegisterServer2 service. Encryption is optional for * the server; the client connection to the LDS uses a separate certificate * pair. On shutdown, it deregisters from the LDS. */ #include "common.h" #include "config.h" #include #include #include #include #include #include #include #include volatile UA_Boolean g_running = true; static void _s_stop_handler (int sign) { g_running = false; } /* ======================================================================== * LDS Client Config Helper * ======================================================================== */ /** * Parameters for building a client config that connects to the LDS. * Gathered once in main, then reused for every register/deregister call. */ typedef struct { const char *app_uri; security_config sec; auth_config auth; int log_level; } lds_client_params; /** * Builds a fresh UA_ClientConfig from lds_client_params. * * UA_Server_registerDiscovery / deregisterDiscovery consume (clear) the * client config, so a new one must be created before every call. */ static UA_StatusCode _s_make_lds_client_config (UA_ClientConfig *cc, const lds_client_params *p) { memset (cc, 0, sizeof (UA_ClientConfig)); UA_StatusCode rv; if (p->sec.cert_path) rv = create_secure_client_config (cc, p->app_uri, &p->sec, &p->auth); else rv = create_unsecure_client_config (cc, p->app_uri, &p->auth); if (rv != UA_STATUSCODE_GOOD) return rv; cc->logging->context = (void *)(uintptr_t)p->log_level; return UA_STATUSCODE_GOOD; } /* ======================================================================== * Main * ======================================================================== */ int main (int argc, char **argv) { signal (SIGINT, _s_stop_handler); signal (SIGTERM, _s_stop_handler); if (argc < 4 || argc > 5) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Usage: %s " " [log-level]", argv[0]); return EXIT_FAILURE; } const char *discovery_endpoint = argv[3]; const char *log_level_str = (argc == 5) ? argv[4] : "info"; int log_level = parse_log_level (log_level_str); if (log_level < 0) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Unknown log level: %s " "(expected trace, debug, info, warning, error, fatal)", log_level_str); return EXIT_FAILURE; } /* ── Load server config ─────────────────────────────────────── */ int rc = EXIT_FAILURE; config server_cfg = { 0 }; config client_cfg = { 0 }; security_config server_sec = { 0 }; security_config client_sec = { 0 }; UA_Server *server = NULL; if (config_load (argv[1], &server_cfg) != 0) goto cleanup; int port = config_require_int (&server_cfg, "port", "ServerRegister"); const char *application_uri = config_require (&server_cfg, "applicationUri", "ServerRegister"); int register_interval = config_require_int (&server_cfg, "registerInterval", "ServerRegister"); if (!application_uri || port < 0 || register_interval < 0) goto cleanup; if (parse_security_config (&server_cfg, "ServerRegister", false, &server_sec) != 0) goto cleanup; auth_config server_auth; if (parse_auth_config (&server_cfg, "ServerRegister", &server_auth) != 0) goto cleanup; /* ── Load client config ─────────────────────────────────────── */ if (config_load (argv[2], &client_cfg) != 0) goto cleanup; const char *client_app_uri = config_require (&client_cfg, "applicationUri", "ServerRegister"); if (!client_app_uri) goto cleanup; if (parse_security_config (&client_cfg, "ServerRegister", true, &client_sec) != 0) goto cleanup; auth_config client_auth; if (parse_auth_config (&client_cfg, "ServerRegister", &client_auth) != 0) goto cleanup; /* ── Create and configure server ────────────────────────────── */ UA_StatusCode retval; server = create_server ((UA_UInt16)port, application_uri, server_sec.cert_path ? &server_sec : NULL, true, &retval); if (!server) goto cleanup; UA_ServerConfig *server_config = UA_Server_getConfig (server); server_config->logging->context = (void *)(uintptr_t)log_level; retval = configure_access_control (server_config, &server_auth); if (retval != UA_STATUSCODE_GOOD) goto cleanup; server_config->applicationDescription.applicationType = UA_APPLICATIONTYPE_SERVER; lds_client_params lds_params = { .app_uri = client_app_uri, .sec = client_sec, .auth = client_auth, .log_level = log_level, }; /* Use run_startup + manual event loop (instead of UA_Server_run) so we can periodically re-register with the LDS between iterations. */ UA_Server_run_startup (server); /* UA_Server_registerDiscovery consumes (clears) the client config, so _s_make_lds_client_config builds a fresh one for every call. */ UA_ClientConfig client_config; retval = _s_make_lds_client_config (&client_config, &lds_params); if (retval != UA_STATUSCODE_GOOD) { UA_Server_run_shutdown (server); goto cleanup; } UA_String discovery_url = UA_STRING_ALLOC (discovery_endpoint); retval = UA_Server_registerDiscovery (server, &client_config, discovery_url, UA_STRING_NULL); UA_String_clear (&discovery_url); if (retval != UA_STATUSCODE_GOOD) UA_LOG_WARNING (UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Initial register failed: %s", UA_StatusCode_name (retval)); /* Periodic re-registration loop. Re-register with the LDS every register_interval seconds so the LDS does not purge us. */ time_t last_register = time (NULL); while (g_running) { UA_Server_run_iterate (server, true); time_t now = time (NULL); if (now - last_register >= register_interval) { retval = _s_make_lds_client_config (&client_config, &lds_params); if (retval == UA_STATUSCODE_GOOD) { UA_String rereg_url = UA_STRING_ALLOC (discovery_endpoint); retval = UA_Server_registerDiscovery (server, &client_config, rereg_url, UA_STRING_NULL); UA_String_clear (&rereg_url); if (retval != UA_STATUSCODE_GOOD) UA_LOG_WARNING (UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Re-register failed: %s", UA_StatusCode_name (retval)); } last_register = now; } } /* Deregister from the LDS before shutting down so the LDS removes our entry immediately rather than waiting for the cleanup timeout. */ retval = _s_make_lds_client_config (&client_config, &lds_params); if (retval == UA_STATUSCODE_GOOD) { UA_String dereg_url = UA_STRING_ALLOC (discovery_endpoint); retval = UA_Server_deregisterDiscovery (server, &client_config, dereg_url); UA_String_clear (&dereg_url); if (retval != UA_STATUSCODE_GOOD) UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not unregister from discovery server: %s", UA_StatusCode_name (retval)); } UA_Server_run_shutdown (server); rc = EXIT_SUCCESS; cleanup: if (server) UA_Server_delete (server); free_trust_store (client_sec.trust_paths, client_sec.trust_size); free_trust_store (server_sec.trust_paths, server_sec.trust_size); config_free (&client_cfg); config_free (&server_cfg); return rc; }