server_multicast.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  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. return returnEndpoint;
  96. }
  97. #ifdef UA_ENABLE_ENCRYPTION
  98. /* loadFile parses the certificate file.
  99. *
  100. * @param path specifies the file name given in argv[]
  101. * @return Returns the file content after parsing */
  102. static UA_ByteString loadFile(const char *const path) {
  103. UA_ByteString fileContents = UA_BYTESTRING_NULL;
  104. if(path == NULL)
  105. return fileContents;
  106. /* Open the file */
  107. FILE *fp = fopen(path, "rb");
  108. if(!fp) {
  109. errno = 0; /* We read errno also from the tcp layer */
  110. return fileContents;
  111. }
  112. /* Get the file length, allocate the data and read */
  113. fseek(fp, 0, SEEK_END);
  114. fileContents.length = (size_t) ftell(fp);
  115. fileContents.data = (UA_Byte *) UA_malloc(fileContents.length * sizeof(UA_Byte));
  116. if(fileContents.data) {
  117. fseek(fp, 0, SEEK_SET);
  118. size_t read = fread(fileContents.data, sizeof(UA_Byte), fileContents.length, fp);
  119. if(read != fileContents.length)
  120. UA_ByteString_clear(&fileContents);
  121. } else {
  122. fileContents.length = 0;
  123. }
  124. fclose(fp);
  125. return fileContents;
  126. }
  127. #endif
  128. /**
  129. * Initialize a client instance which is used for calling the registerServer service.
  130. * If the given endpoint has securityMode NONE, a client with default configuration
  131. * is returned.
  132. * If it is using SignAndEncrypt, the client certificates must be provided as a
  133. * command line argument and then the client is initialized using these certificates.
  134. * @param endpointRegister The remote endpoint where this server should register
  135. * @param argc from the main method
  136. * @param argv from the main method
  137. * @return NULL or the initialized non-connected client
  138. */
  139. static
  140. UA_Client *getRegisterClient(UA_EndpointDescription *endpointRegister, int argc, char **argv) {
  141. if(endpointRegister->securityMode == UA_MESSAGESECURITYMODE_NONE) {
  142. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Using LDS endpoint with security None");
  143. UA_Client *client = UA_Client_new();
  144. UA_ClientConfig_setDefault(UA_Client_getConfig(client));
  145. return client;
  146. }
  147. #ifdef UA_ENABLE_ENCRYPTION
  148. if(endpointRegister->securityMode == UA_MESSAGESECURITYMODE_SIGN) {
  149. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  150. "LDS endpoint which only supports Sign is currently not supported");
  151. return NULL;
  152. }
  153. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  154. "Using LDS endpoint with security SignAndEncrypt");
  155. UA_ByteString certificate = UA_BYTESTRING_NULL;
  156. UA_ByteString privateKey = UA_BYTESTRING_NULL;
  157. UA_ByteString *trustList = NULL;
  158. size_t trustListSize = 0;
  159. UA_ByteString *revocationList = NULL;
  160. size_t revocationListSize = 0;
  161. if(argc < 3) {
  162. UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
  163. "The Certificate and key is missing."
  164. "The required arguments are "
  165. "<client-certificate.der> <client-private-key.der> "
  166. "[<trustlist1.crl>, ...]");
  167. return NULL;
  168. }
  169. certificate = loadFile(argv[1]);
  170. privateKey = loadFile(argv[2]);
  171. /* Load the trustList. Load revocationList is not supported now */
  172. if(argc > 3) {
  173. trustListSize = (size_t) argc - 3;
  174. UA_StatusCode retval = UA_ByteString_allocBuffer(trustList, trustListSize);
  175. if(retval != UA_STATUSCODE_GOOD) {
  176. UA_ByteString_clear(&certificate);
  177. UA_ByteString_clear(&privateKey);
  178. return NULL;
  179. }
  180. for(size_t trustListCount = 0; trustListCount < trustListSize; trustListCount++) {
  181. trustList[trustListCount] = loadFile(argv[trustListCount + 3]);
  182. }
  183. }
  184. /* Secure client initialization */
  185. UA_Client *clientRegister = UA_Client_new();
  186. UA_ClientConfig *cc = UA_Client_getConfig(clientRegister);
  187. UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey,
  188. trustList, trustListSize,
  189. revocationList, revocationListSize);
  190. cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
  191. UA_ByteString_clear(&certificate);
  192. UA_ByteString_clear(&privateKey);
  193. for(size_t deleteCount = 0; deleteCount < trustListSize; deleteCount++)
  194. UA_ByteString_clear(&trustList[deleteCount]);
  195. return clientRegister;
  196. #else
  197. return NULL;
  198. #endif
  199. }
  200. int main(int argc, char **argv) {
  201. signal(SIGINT, stopHandler); /* catches ctrl-c */
  202. signal(SIGTERM, stopHandler);
  203. UA_Server *server = UA_Server_new();
  204. UA_ServerConfig *config = UA_Server_getConfig(server);
  205. // use port 0 to dynamically assign port
  206. UA_ServerConfig_setMinimal(config, 0, NULL);
  207. // To enable mDNS discovery, set application type to discovery server.
  208. config->applicationDescription.applicationType = UA_APPLICATIONTYPE_DISCOVERYSERVER;
  209. UA_String_clear(&config->applicationDescription.applicationUri);
  210. config->applicationDescription.applicationUri =
  211. UA_String_fromChars("urn:open62541.example.server_multicast");
  212. config->discovery.mdns.mdnsServerName = UA_String_fromChars("Sample Multicast Server");
  213. // See http://www.opcfoundation.org/UA/schemas/1.03/ServerCapabilities.csv
  214. //config.serverCapabilitiesSize = 1;
  215. //UA_String caps = UA_String_fromChars("LDS");
  216. //config.serverCapabilities = &caps;
  217. // Start the server and call iterate to wait for the multicast discovery of the LDS
  218. UA_StatusCode retval = UA_Server_run_startup(server);
  219. // callback which is called when a new server is detected through mDNS
  220. // needs to be set after UA_Server_run_startup or UA_Server_run
  221. UA_Server_setServerOnNetworkCallback(server, serverOnNetworkCallback, NULL);
  222. if(retval != UA_STATUSCODE_GOOD) {
  223. UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  224. "Could not start the server. StatusCode %s",
  225. UA_StatusCode_name(retval));
  226. UA_Server_delete(server);
  227. UA_free(discovery_url);
  228. return EXIT_FAILURE;
  229. }
  230. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  231. "Server started. Waiting for announce of LDS Server.");
  232. while (running && discovery_url == NULL)
  233. UA_Server_run_iterate(server, true);
  234. if(!running) {
  235. UA_Server_delete(server);
  236. UA_free(discovery_url);
  237. return EXIT_FAILURE;
  238. }
  239. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "LDS-ME server found on %s", discovery_url);
  240. /* Check if the server supports sign and encrypt. OPC Foundation LDS
  241. * requires an encrypted session for RegisterServer call, our server
  242. * currently uses encrpytion optionally */
  243. UA_EndpointDescription *endpointRegister = getRegisterEndpointFromServer(discovery_url);
  244. UA_free(discovery_url);
  245. if(endpointRegister == NULL || endpointRegister->securityMode == UA_MESSAGESECURITYMODE_INVALID) {
  246. UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  247. "Could not find any suitable endpoints on discovery server");
  248. UA_Server_delete(server);
  249. return EXIT_FAILURE;
  250. }
  251. UA_Client *clientRegister = getRegisterClient(endpointRegister, argc, argv);
  252. if(!clientRegister) {
  253. UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
  254. "Could not create the client for remote registering");
  255. UA_Server_delete(server);
  256. return EXIT_FAILURE;
  257. }
  258. /* Connect the client */
  259. char *endpointUrl = (char*)UA_malloc(endpointRegister->endpointUrl.length + 1);
  260. memcpy(endpointUrl, endpointRegister->endpointUrl.data, endpointRegister->endpointUrl.length);
  261. endpointUrl[endpointRegister->endpointUrl.length] = 0;
  262. retval = UA_Server_addPeriodicServerRegisterCallback(server, clientRegister, endpointUrl,
  263. 10 * 60 * 1000, 500, NULL);
  264. if(retval != UA_STATUSCODE_GOOD) {
  265. UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  266. "Could not create periodic job for server register. StatusCode %s",
  267. UA_StatusCode_name(retval));
  268. UA_free(endpointUrl);
  269. UA_Client_disconnect(clientRegister);
  270. UA_Client_delete(clientRegister);
  271. UA_Server_delete(server);
  272. return EXIT_FAILURE;
  273. }
  274. while (running)
  275. UA_Server_run_iterate(server, true);
  276. UA_Server_run_shutdown(server);
  277. // UNregister the server from the discovery server.
  278. retval = UA_Server_unregister_discovery(server, clientRegister);
  279. if(retval != UA_STATUSCODE_GOOD)
  280. UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
  281. "Could not unregister server from discovery server. "
  282. "StatusCode %s", UA_StatusCode_name(retval));
  283. UA_free(endpointUrl);
  284. UA_Client_disconnect(clientRegister);
  285. UA_Client_delete(clientRegister);
  286. UA_Server_delete(server);
  287. return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
  288. }