/** * @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 #include volatile UA_Boolean running = true; static void stopHandler (int sign) { 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 *appUri; SecurityConfig sec; AuthConfig auth; int logLevel; } LdsClientParams; /** * Builds a fresh UA_ClientConfig from LdsClientParams. * * UA_Server_registerDiscovery / deregisterDiscovery consume (clear) the * client config, so a new one must be created before every call. */ static UA_StatusCode makeLdsClientConfig (UA_ClientConfig *cc, const LdsClientParams *p) { memset (cc, 0, sizeof (UA_ClientConfig)); UA_StatusCode rv; if (p->sec.certPath) rv = createSecureClientConfig (cc, p->appUri, &p->sec, &p->auth); else rv = createUnsecureClientConfig (cc, p->appUri, &p->auth); if (rv != UA_STATUSCODE_GOOD) return rv; cc->logging->context = (void *)(uintptr_t)p->logLevel; return UA_STATUSCODE_GOOD; } /* ======================================================================== * Main * ======================================================================== */ int main (int argc, char **argv) { signal (SIGINT, stopHandler); signal (SIGTERM, stopHandler); 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 *discoveryEndpoint = argv[3]; const char *logLevelStr = (argc == 5) ? argv[4] : "info"; int logLevel = parseLogLevel (logLevelStr); if (logLevel < 0) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Unknown log level: %s " "(expected trace, debug, info, warning, error, fatal)", logLevelStr); return EXIT_FAILURE; } /* ── Load server config ─────────────────────────────────────── */ int rc = EXIT_FAILURE; Config serverCfg = { 0 }; Config clientCfg = { 0 }; SecurityConfig serverSec = { 0 }; SecurityConfig clientSec = { 0 }; UA_Server *server = NULL; if (configLoad (argv[1], &serverCfg) != 0) goto cleanup; int port = configRequireInt (&serverCfg, "port", "ServerRegister"); const char *applicationUri = configRequire (&serverCfg, "applicationUri", "ServerRegister"); int registerInterval = configRequireInt (&serverCfg, "registerInterval", "ServerRegister"); if (!applicationUri || port < 0 || registerInterval < 0) goto cleanup; if (parseSecurityConfig (&serverCfg, "ServerRegister", false, &serverSec) != 0) goto cleanup; AuthConfig serverAuth; if (parseAuthConfig (&serverCfg, "ServerRegister", &serverAuth) != 0) goto cleanup; /* ── Load client config ─────────────────────────────────────── */ if (configLoad (argv[2], &clientCfg) != 0) goto cleanup; const char *clientAppUri = configRequire (&clientCfg, "applicationUri", "ServerRegister"); if (!clientAppUri) goto cleanup; if (parseSecurityConfig (&clientCfg, "ServerRegister", true, &clientSec) != 0) goto cleanup; AuthConfig clientAuth; if (parseAuthConfig (&clientCfg, "ServerRegister", &clientAuth) != 0) goto cleanup; /* ── Create and configure server ────────────────────────────── */ UA_StatusCode retval; server = createServer ((UA_UInt16)port, applicationUri, serverSec.certPath ? &serverSec : NULL, true, &retval); if (!server) goto cleanup; UA_ServerConfig *serverConfig = UA_Server_getConfig (server); serverConfig->logging->context = (void *)(uintptr_t)logLevel; /* Configure access control. UA_ServerConfig_setDefaultWithSecure- SecurityPolicies sets certificate-only auth by default, so we must always call UA_AccessControl_default to get the desired policy. */ switch (serverAuth.mode) { case AUTH_ANONYMOUS: retval = UA_AccessControl_default (serverConfig, true, NULL, 0, NULL); break; case AUTH_USER: { UA_UsernamePasswordLogin logins[1]; logins[0].username = UA_STRING ((char *)serverAuth.user.username); logins[0].password = UA_STRING ((char *)serverAuth.user.password); retval = UA_AccessControl_default (serverConfig, false, NULL, 1, logins); break; } case AUTH_CERT: /* cert auth — sessionPKI.verifyCertificate is set by createServer via setDefaultWithSecureSecurityPolicies, so UA_AccessControl_default will automatically advertise the X509 certificate token policy. */ retval = UA_AccessControl_default (serverConfig, false, NULL, 0, NULL); break; } if (retval != UA_STATUSCODE_GOOD) goto cleanup; serverConfig->applicationDescription.applicationType = UA_APPLICATIONTYPE_SERVER; LdsClientParams ldsParams = { .appUri = clientAppUri, .sec = clientSec, .auth = clientAuth, .logLevel = logLevel, }; /* 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 makeLdsClientConfig builds a fresh one for every call. */ UA_ClientConfig clientConfig; retval = makeLdsClientConfig (&clientConfig, &ldsParams); if (retval != UA_STATUSCODE_GOOD) { UA_Server_run_shutdown (server); goto cleanup; } UA_String discoveryUrl = UA_STRING_ALLOC (discoveryEndpoint); retval = UA_Server_registerDiscovery (server, &clientConfig, discoveryUrl, UA_STRING_NULL); UA_String_clear (&discoveryUrl); 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 registerInterval seconds so the LDS does not purge us. */ time_t lastRegister = time (NULL); while (running) { UA_Server_run_iterate (server, true); time_t now = time (NULL); if (now - lastRegister >= registerInterval) { retval = makeLdsClientConfig (&clientConfig, &ldsParams); if (retval == UA_STATUSCODE_GOOD) { UA_String reregUrl = UA_STRING_ALLOC (discoveryEndpoint); retval = UA_Server_registerDiscovery (server, &clientConfig, reregUrl, UA_STRING_NULL); UA_String_clear (&reregUrl); if (retval != UA_STATUSCODE_GOOD) UA_LOG_WARNING (UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Re-register failed: %s", UA_StatusCode_name (retval)); } lastRegister = now; } } /* Deregister from the LDS before shutting down so the LDS removes our entry immediately rather than waiting for the cleanup timeout. */ retval = makeLdsClientConfig (&clientConfig, &ldsParams); if (retval == UA_STATUSCODE_GOOD) { UA_String deregUrl = UA_STRING_ALLOC (discoveryEndpoint); retval = UA_Server_deregisterDiscovery (server, &clientConfig, deregUrl); UA_String_clear (&deregUrl); 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); freeTrustStore (clientSec.trustPaths, clientSec.trustSize); freeTrustStore (serverSec.trustPaths, serverSec.trustSize); configFree (&clientCfg); configFree (&serverCfg); return rc; }