server_multicast.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. /* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
  2. * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
  3. /*
  4. * A simple server instance which registers with the discovery server.
  5. * Compared to server_register.c this example waits until the LDS server announces
  6. * itself through mDNS. Therefore the LDS server needs to support multicast extension
  7. * (i.e., LDS-ME).
  8. */
  9. #include <open62541/client.h>
  10. #include <open62541/client_config_default.h>
  11. #include <open62541/plugin/log_stdout.h>
  12. #include <open62541/server.h>
  13. #include <open62541/server_config_default.h>
  14. #include <signal.h>
  15. #include <stdlib.h>
  16. const UA_ByteString UA_SECURITY_POLICY_BASIC128_URI =
  17. {56, (UA_Byte *)"http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15"};
  18. UA_Boolean running = true;
  19. static void stopHandler(int sign) {
  20. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
  21. running = false;
  22. }
  23. char *discovery_url = NULL;
  24. static void
  25. serverOnNetworkCallback(const UA_ServerOnNetwork *serverOnNetwork, UA_Boolean isServerAnnounce,
  26. UA_Boolean isTxtReceived, void *data) {
  27. if(discovery_url != NULL || !isServerAnnounce) {
  28. UA_LOG_DEBUG(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  29. "serverOnNetworkCallback called, but discovery URL "
  30. "already initialized or is not announcing. Ignoring.");
  31. return; // we already have everything we need or we only want server announces
  32. }
  33. if(!isTxtReceived)
  34. return; // we wait until the corresponding TXT record is announced.
  35. // Problem: how to handle if a Server does not announce the
  36. // optional TXT?
  37. // here you can filter for a specific LDS server, e.g. call FindServers on
  38. // the serverOnNetwork to make sure you are registering with the correct
  39. // LDS. We will ignore this for now
  40. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Another server announced itself on %.*s",
  41. (int)serverOnNetwork->discoveryUrl.length, serverOnNetwork->discoveryUrl.data);
  42. if(discovery_url != NULL)
  43. UA_free(discovery_url);
  44. discovery_url = (char*)UA_malloc(serverOnNetwork->discoveryUrl.length + 1);
  45. memcpy(discovery_url, serverOnNetwork->discoveryUrl.data, serverOnNetwork->discoveryUrl.length);
  46. discovery_url[serverOnNetwork->discoveryUrl.length] = 0;
  47. }
  48. /*
  49. * Get the endpoint from the server, where we can call RegisterServer2 (or RegisterServer).
  50. * This is normally the endpoint with highest supported encryption mode.
  51. *
  52. * @param discoveryServerUrl The discovery url from the remote server
  53. * @return The endpoint description (which needs to be freed) or NULL
  54. */
  55. static
  56. UA_EndpointDescription *getRegisterEndpointFromServer(const char *discoveryServerUrl) {
  57. UA_Client *client = UA_Client_new();
  58. UA_ClientConfig_setDefault(UA_Client_getConfig(client));
  59. UA_EndpointDescription *endpointArray = NULL;
  60. size_t endpointArraySize = 0;
  61. UA_StatusCode retval = UA_Client_getEndpoints(client, discoveryServerUrl,
  62. &endpointArraySize, &endpointArray);
  63. if(retval != UA_STATUSCODE_GOOD) {
  64. UA_Array_delete(endpointArray, endpointArraySize,
  65. &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
  66. UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  67. "GetEndpoints failed with %s", UA_StatusCode_name(retval));
  68. UA_Client_delete(client);
  69. return NULL;
  70. }
  71. UA_LOG_DEBUG(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Server has %lu endpoints", (unsigned long)endpointArraySize);
  72. UA_EndpointDescription *foundEndpoint = NULL;
  73. for(size_t i = 0; i < endpointArraySize; i++) {
  74. UA_LOG_DEBUG(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "\tURL = %.*s, SecurityMode = %s",
  75. (int) endpointArray[i].endpointUrl.length,
  76. endpointArray[i].endpointUrl.data,
  77. endpointArray[i].securityMode == UA_MESSAGESECURITYMODE_NONE ? "None" :
  78. endpointArray[i].securityMode == UA_MESSAGESECURITYMODE_SIGN ? "Sign" :
  79. endpointArray[i].securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT ? "SignAndEncrypt" :
  80. "Invalid"
  81. );
  82. // find the endpoint with highest supported security mode
  83. if((UA_String_equal(&endpointArray[i].securityPolicyUri, &UA_SECURITY_POLICY_NONE_URI) ||
  84. UA_String_equal(&endpointArray[i].securityPolicyUri, &UA_SECURITY_POLICY_BASIC128_URI)) && (
  85. foundEndpoint == NULL || foundEndpoint->securityMode < endpointArray[i].securityMode))
  86. foundEndpoint = &endpointArray[i];
  87. }
  88. UA_EndpointDescription *returnEndpoint = NULL;
  89. if(foundEndpoint != NULL) {
  90. returnEndpoint = UA_EndpointDescription_new();
  91. UA_EndpointDescription_copy(foundEndpoint, returnEndpoint);
  92. }
  93. UA_Array_delete(endpointArray, endpointArraySize,
  94. &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
  95. UA_Client_delete(client);
  96. return returnEndpoint;
  97. }
  98. #ifdef UA_ENABLE_ENCRYPTION
  99. /* loadFile parses the certificate file.
  100. *
  101. * @param path specifies the file name given in argv[]
  102. * @return Returns the file content after parsing */
  103. static UA_ByteString loadFile(const char *const path) {
  104. UA_ByteString fileContents = UA_BYTESTRING_NULL;
  105. if(path == NULL)
  106. return fileContents;
  107. /* Open the file */
  108. FILE *fp = fopen(path, "rb");
  109. if(!fp) {
  110. errno = 0; /* We read errno also from the tcp layer */
  111. return fileContents;
  112. }
  113. /* Get the file length, allocate the data and read */
  114. fseek(fp, 0, SEEK_END);
  115. fileContents.length = (size_t) ftell(fp);
  116. fileContents.data = (UA_Byte *) UA_malloc(fileContents.length * sizeof(UA_Byte));
  117. if(fileContents.data) {
  118. fseek(fp, 0, SEEK_SET);
  119. size_t read = fread(fileContents.data, sizeof(UA_Byte), fileContents.length, fp);
  120. if(read != fileContents.length)
  121. UA_ByteString_clear(&fileContents);
  122. } else {
  123. fileContents.length = 0;
  124. }
  125. fclose(fp);
  126. return fileContents;
  127. }
  128. #endif
  129. /**
  130. * Initialize a client instance which is used for calling the registerServer service.
  131. * If the given endpoint has securityMode NONE, a client with default configuration
  132. * is returned.
  133. * If it is using SignAndEncrypt, the client certificates must be provided as a
  134. * command line argument and then the client is initialized using these certificates.
  135. * @param endpointRegister The remote endpoint where this server should register
  136. * @param argc from the main method
  137. * @param argv from the main method
  138. * @return NULL or the initialized non-connected client
  139. */
  140. static
  141. UA_Client *getRegisterClient(UA_EndpointDescription *endpointRegister, int argc, char **argv) {
  142. if(endpointRegister->securityMode == UA_MESSAGESECURITYMODE_NONE) {
  143. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Using LDS endpoint with security None");
  144. UA_Client *client = UA_Client_new();
  145. UA_ClientConfig_setDefault(UA_Client_getConfig(client));
  146. return client;
  147. }
  148. #ifdef UA_ENABLE_ENCRYPTION
  149. if(endpointRegister->securityMode == UA_MESSAGESECURITYMODE_SIGN) {
  150. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  151. "LDS endpoint which only supports Sign is currently not supported");
  152. return NULL;
  153. }
  154. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  155. "Using LDS endpoint with security SignAndEncrypt");
  156. UA_ByteString certificate = UA_BYTESTRING_NULL;
  157. UA_ByteString privateKey = UA_BYTESTRING_NULL;
  158. UA_ByteString *trustList = NULL;
  159. size_t trustListSize = 0;
  160. UA_ByteString *revocationList = NULL;
  161. size_t revocationListSize = 0;
  162. if(argc < 3) {
  163. UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
  164. "The Certificate and key is missing."
  165. "The required arguments are "
  166. "<client-certificate.der> <client-private-key.der> "
  167. "[<trustlist1.crl>, ...]");
  168. return NULL;
  169. }
  170. certificate = loadFile(argv[1]);
  171. privateKey = loadFile(argv[2]);
  172. /* Load the trustList. Load revocationList is not supported now */
  173. if(argc > 3) {
  174. trustListSize = (size_t) argc - 3;
  175. UA_StatusCode retval = UA_ByteString_allocBuffer(trustList, trustListSize);
  176. if(retval != UA_STATUSCODE_GOOD) {
  177. UA_ByteString_clear(&certificate);
  178. UA_ByteString_clear(&privateKey);
  179. return NULL;
  180. }
  181. for(size_t trustListCount = 0; trustListCount < trustListSize; trustListCount++) {
  182. trustList[trustListCount] = loadFile(argv[trustListCount + 3]);
  183. }
  184. }
  185. /* Secure client initialization */
  186. UA_Client *clientRegister = UA_Client_new();
  187. UA_ClientConfig *cc = UA_Client_getConfig(clientRegister);
  188. UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey,
  189. trustList, trustListSize,
  190. revocationList, revocationListSize);
  191. cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
  192. UA_ByteString_clear(&certificate);
  193. UA_ByteString_clear(&privateKey);
  194. for(size_t deleteCount = 0; deleteCount < trustListSize; deleteCount++)
  195. UA_ByteString_clear(&trustList[deleteCount]);
  196. return clientRegister;
  197. #else
  198. return NULL;
  199. #endif
  200. }
  201. int main(int argc, char **argv) {
  202. signal(SIGINT, stopHandler); /* catches ctrl-c */
  203. signal(SIGTERM, stopHandler);
  204. UA_Server *server = UA_Server_new();
  205. UA_ServerConfig *config = UA_Server_getConfig(server);
  206. // use port 0 to dynamically assign port
  207. UA_ServerConfig_setMinimal(config, 0, NULL);
  208. // An LDS server normally has the application type to DISCOVERYSERVER.
  209. // Since this instance implements LDS and other OPC UA services, we set the type to SERVER.
  210. // NOTE: Using DISCOVERYSERVER will cause UaExpert to not show this instance in the server list.
  211. // See also: https://forum.unified-automation.com/topic1987.html
  212. config->applicationDescription.applicationType = UA_APPLICATIONTYPE_SERVER;
  213. UA_String_clear(&config->applicationDescription.applicationUri);
  214. config->applicationDescription.applicationUri =
  215. UA_String_fromChars("urn:open62541.example.server_multicast");
  216. // Enable the mDNS announce and response functionality
  217. config->discovery.mdnsEnable = true;
  218. config->discovery.mdns.mdnsServerName = UA_String_fromChars("Sample Multicast Server");
  219. // See http://www.opcfoundation.org/UA/schemas/1.03/ServerCapabilities.csv
  220. // For a LDS server, you should only indicate the LDS capability.
  221. // If this instance is an LDS and at the same time a normal OPC UA server, you also have to indicate
  222. // the additional capabilities.
  223. // NOTE: UaExpert does not show LDS-only servers in the list.
  224. // See also: https://forum.unified-automation.com/topic1987.html
  225. config->discovery.mdns.serverCapabilitiesSize = 2;
  226. UA_String *caps = (UA_String *) UA_Array_new(2, &UA_TYPES[UA_TYPES_STRING]);
  227. caps[0] = UA_String_fromChars("LDS");
  228. caps[1] = UA_String_fromChars("NA");
  229. config->discovery.mdns.serverCapabilities = caps;
  230. // Start the server and call iterate to wait for the multicast discovery of the LDS
  231. UA_StatusCode retval = UA_Server_run_startup(server);
  232. // callback which is called when a new server is detected through mDNS
  233. // needs to be set after UA_Server_run_startup or UA_Server_run
  234. UA_Server_setServerOnNetworkCallback(server, serverOnNetworkCallback, NULL);
  235. if(retval != UA_STATUSCODE_GOOD) {
  236. UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  237. "Could not start the server. StatusCode %s",
  238. UA_StatusCode_name(retval));
  239. UA_Server_delete(server);
  240. UA_free(discovery_url);
  241. return EXIT_FAILURE;
  242. }
  243. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  244. "Server started. Waiting for announce of LDS Server.");
  245. while (running && discovery_url == NULL)
  246. UA_Server_run_iterate(server, true);
  247. if(!running) {
  248. UA_Server_delete(server);
  249. UA_free(discovery_url);
  250. return EXIT_FAILURE;
  251. }
  252. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "LDS-ME server found on %s", discovery_url);
  253. /* Check if the server supports sign and encrypt. OPC Foundation LDS
  254. * requires an encrypted session for RegisterServer call, our server
  255. * currently uses encrpytion optionally */
  256. UA_EndpointDescription *endpointRegister = getRegisterEndpointFromServer(discovery_url);
  257. UA_free(discovery_url);
  258. if(endpointRegister == NULL || endpointRegister->securityMode == UA_MESSAGESECURITYMODE_INVALID) {
  259. UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  260. "Could not find any suitable endpoints on discovery server");
  261. UA_Server_delete(server);
  262. return EXIT_FAILURE;
  263. }
  264. UA_Client *clientRegister = getRegisterClient(endpointRegister, argc, argv);
  265. if(!clientRegister) {
  266. UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
  267. "Could not create the client for remote registering");
  268. UA_Server_delete(server);
  269. return EXIT_FAILURE;
  270. }
  271. /* Connect the client */
  272. char *endpointUrl = (char*)UA_malloc(endpointRegister->endpointUrl.length + 1);
  273. memcpy(endpointUrl, endpointRegister->endpointUrl.data, endpointRegister->endpointUrl.length);
  274. endpointUrl[endpointRegister->endpointUrl.length] = 0;
  275. UA_EndpointDescription_delete(endpointRegister);
  276. retval = UA_Server_addPeriodicServerRegisterCallback(server, clientRegister, endpointUrl,
  277. 10 * 60 * 1000, 500, NULL);
  278. if(retval != UA_STATUSCODE_GOOD) {
  279. UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  280. "Could not create periodic job for server register. StatusCode %s",
  281. UA_StatusCode_name(retval));
  282. UA_free(endpointUrl);
  283. UA_Client_disconnect(clientRegister);
  284. UA_Client_delete(clientRegister);
  285. UA_Server_delete(server);
  286. return EXIT_FAILURE;
  287. }
  288. while (running)
  289. UA_Server_run_iterate(server, true);
  290. UA_Server_run_shutdown(server);
  291. // UNregister the server from the discovery server.
  292. retval = UA_Server_unregister_discovery(server, clientRegister);
  293. if(retval != UA_STATUSCODE_GOOD)
  294. UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  295. "Could not unregister server from discovery server. "
  296. "StatusCode %s", UA_StatusCode_name(retval));
  297. UA_free(endpointUrl);
  298. UA_Client_disconnect(clientRegister);
  299. UA_Client_delete(clientRegister);
  300. UA_Server_delete(server);
  301. return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
  302. }