ua_mdns.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. #include "ua_server_internal.h"
  5. #include "ua_mdns_internal.h"
  6. #include "ua_util.h"
  7. #ifdef UA_ENABLE_DISCOVERY_MULTICAST
  8. # ifdef UA_NO_AMALGAMATION
  9. # include "mdnsd/libmdnsd/xht.h"
  10. # include "mdnsd/libmdnsd/sdtxt.h"
  11. # endif
  12. # ifdef _WIN32
  13. # define _WINSOCK_DEPRECATED_NO_WARNINGS /* inet_ntoa is deprecated on MSVC but used for compatibility */
  14. # include <winsock2.h>
  15. # include <iphlpapi.h>
  16. # include <ws2tcpip.h>
  17. # else
  18. # include <sys/time.h> // for struct timeval
  19. # include <netinet/in.h> // for struct ip_mreq
  20. # include <ifaddrs.h>
  21. # include <net/if.h> /* for IFF_RUNNING */
  22. # include <netdb.h> // for recvfrom in cygwin
  23. # endif
  24. #ifndef STRDUP
  25. # if defined(__MINGW32__)
  26. static char *ua_strdup(const char *s) {
  27. char *p = UA_malloc(strlen(s) + 1);
  28. if(p) { strcpy(p, s); }
  29. return p;
  30. }
  31. # define STRDUP ua_strdup
  32. # elif defined(_WIN32)
  33. # define STRDUP _strdup
  34. # else
  35. # define STRDUP strdup
  36. # endif
  37. #endif
  38. // FIXME: Is this a required algorithm? Otherwise, reuse hashing for nodeids
  39. /* Generates a hash code for a string.
  40. * This function uses the ELF hashing algorithm as reprinted in
  41. * Andrew Binstock, "Hashing Rehashed," Dr. Dobb's Journal, April 1996.
  42. */
  43. static int mdns_hash_record(const char *s) {
  44. /* ELF hash uses unsigned chars and unsigned arithmetic for portability */
  45. const unsigned char *name = (const unsigned char *) s;
  46. unsigned long h = 0;
  47. while(*name) {
  48. h = (h << 4) + (unsigned long) (*name++);
  49. unsigned long g;
  50. if((g = (h & 0xF0000000UL)) != 0)
  51. h ^= (g >> 24);
  52. h &= ~g;
  53. }
  54. return (int) h;
  55. }
  56. static struct serverOnNetwork_list_entry *
  57. mdns_record_add_or_get(UA_Server *server, const char *record, const char *serverName,
  58. size_t serverNameLen, UA_Boolean createNew) {
  59. int hashIdx = mdns_hash_record(record) % SERVER_ON_NETWORK_HASH_PRIME;
  60. struct serverOnNetwork_hash_entry *hash_entry = server->serverOnNetworkHash[hashIdx];
  61. while (hash_entry) {
  62. size_t maxLen;
  63. if (serverNameLen > hash_entry->entry->serverOnNetwork.serverName.length)
  64. maxLen = hash_entry->entry->serverOnNetwork.serverName.length;
  65. else
  66. maxLen = serverNameLen;
  67. if (strncmp((char *) hash_entry->entry->serverOnNetwork.serverName.data, serverName, maxLen) == 0)
  68. return hash_entry->entry;
  69. hash_entry = hash_entry->next;
  70. }
  71. if(!createNew)
  72. return NULL;
  73. // not yet in list, create new one
  74. // todo: malloc may fail: return a statuscode
  75. struct serverOnNetwork_list_entry *listEntry =
  76. (serverOnNetwork_list_entry*)UA_malloc(sizeof(struct serverOnNetwork_list_entry));
  77. listEntry->created = UA_DateTime_now();
  78. listEntry->pathTmp = NULL;
  79. listEntry->txtSet = UA_FALSE;
  80. listEntry->srvSet = UA_FALSE;
  81. UA_ServerOnNetwork_init(&listEntry->serverOnNetwork);
  82. listEntry->serverOnNetwork.recordId = server->serverOnNetworkRecordIdCounter;
  83. listEntry->serverOnNetwork.serverName.length = serverNameLen;
  84. // todo: malloc may fail: return a statuscode
  85. listEntry->serverOnNetwork.serverName.data = (UA_Byte*)UA_malloc(serverNameLen);
  86. memcpy(listEntry->serverOnNetwork.serverName.data, serverName, serverNameLen);
  87. server->serverOnNetworkRecordIdCounter = UA_atomic_add(&server->serverOnNetworkRecordIdCounter, 1);
  88. if (server->serverOnNetworkRecordIdCounter == 0)
  89. server->serverOnNetworkRecordIdLastReset = UA_DateTime_now();
  90. // add to hash
  91. // todo: malloc may fail: return a statuscode
  92. struct serverOnNetwork_hash_entry *newHashEntry =
  93. (struct serverOnNetwork_hash_entry*)UA_malloc(sizeof(struct serverOnNetwork_hash_entry));
  94. newHashEntry->next = server->serverOnNetworkHash[hashIdx];
  95. server->serverOnNetworkHash[hashIdx] = newHashEntry;
  96. newHashEntry->entry = listEntry;
  97. LIST_INSERT_HEAD(&server->serverOnNetwork, listEntry, pointers);
  98. return listEntry;
  99. }
  100. static void
  101. mdns_record_remove(UA_Server *server, const char *record,
  102. struct serverOnNetwork_list_entry *entry) {
  103. // remove from hash
  104. int hashIdx = mdns_hash_record(record) % SERVER_ON_NETWORK_HASH_PRIME;
  105. struct serverOnNetwork_hash_entry *hash_entry = server->serverOnNetworkHash[hashIdx];
  106. struct serverOnNetwork_hash_entry *prevEntry = hash_entry;
  107. while(hash_entry) {
  108. if(hash_entry->entry == entry) {
  109. if(server->serverOnNetworkHash[hashIdx] == hash_entry)
  110. server->serverOnNetworkHash[hashIdx] = hash_entry->next;
  111. else if(prevEntry)
  112. prevEntry->next = hash_entry->next;
  113. break;
  114. }
  115. prevEntry = hash_entry;
  116. hash_entry = hash_entry->next;
  117. }
  118. UA_free(hash_entry);
  119. if(server->serverOnNetworkCallback)
  120. server->serverOnNetworkCallback(&entry->serverOnNetwork, UA_FALSE,
  121. entry->txtSet, server->serverOnNetworkCallbackData);
  122. // remove from list
  123. LIST_REMOVE(entry, pointers);
  124. UA_ServerOnNetwork_deleteMembers(&entry->serverOnNetwork);
  125. if(entry->pathTmp)
  126. UA_free(entry->pathTmp);
  127. #ifndef UA_ENABLE_MULTITHREADING
  128. server->serverOnNetworkSize--;
  129. UA_free(entry);
  130. #else
  131. server->serverOnNetworkSize = uatomic_add_return(&server->serverOnNetworkSize, -1);
  132. UA_Server_delayedFree(server, entry);
  133. #endif
  134. }
  135. static void
  136. mdns_append_path_to_url(UA_String *url, const char *path) {
  137. size_t pathLen = strlen(path);
  138. // todo: malloc may fail: return a statuscode
  139. char *newUrl = (char *)UA_malloc(url->length + pathLen);
  140. memcpy(newUrl, url->data, url->length);
  141. memcpy(newUrl + url->length, path, pathLen);
  142. url->length = url->length + pathLen;
  143. url->data = (UA_Byte *) newUrl;
  144. }
  145. static void
  146. setTxt(const struct resource *r,
  147. struct serverOnNetwork_list_entry *entry) {
  148. entry->txtSet = UA_TRUE;
  149. xht_t *x = txt2sd(r->rdata, r->rdlength);
  150. char *path = (char *) xht_get(x, "path");
  151. char *caps = (char *) xht_get(x, "caps");
  152. if(path && strlen(path) > 1) {
  153. if (!entry->srvSet) {
  154. /* txt arrived before SRV, thus cache path entry */
  155. // todo: malloc in strdup may fail: return a statuscode
  156. entry->pathTmp = STRDUP(path);
  157. } else {
  158. /* SRV already there and discovery URL set. Add path to discovery URL */
  159. mdns_append_path_to_url(&entry->serverOnNetwork.discoveryUrl, path);
  160. }
  161. }
  162. if(caps && strlen(caps) > 0) {
  163. /* count comma in caps */
  164. size_t capsCount = 1;
  165. for(size_t i = 0; caps[i]; i++) {
  166. if(caps[i] == ',')
  167. capsCount++;
  168. }
  169. /* set capabilities */
  170. entry->serverOnNetwork.serverCapabilitiesSize = capsCount;
  171. entry->serverOnNetwork.serverCapabilities =
  172. (UA_String *) UA_Array_new(capsCount, &UA_TYPES[UA_TYPES_STRING]);
  173. for(size_t i = 0; i < capsCount; i++) {
  174. char *nextStr = strchr(caps, ',');
  175. size_t len = nextStr ? (size_t) (nextStr - caps) : strlen(caps);
  176. entry->serverOnNetwork.serverCapabilities[i].length = len;
  177. // todo: malloc may fail: return a statuscode
  178. entry->serverOnNetwork.serverCapabilities[i].data = (UA_Byte*)UA_malloc(len);
  179. memcpy(entry->serverOnNetwork.serverCapabilities[i].data, caps, len);
  180. if (nextStr)
  181. caps = nextStr + 1;
  182. else
  183. break;
  184. }
  185. }
  186. xht_free(x);
  187. }
  188. // [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname].
  189. static void
  190. setSrv(UA_Server *server, const struct resource *r,
  191. struct serverOnNetwork_list_entry *entry) {
  192. entry->srvSet = UA_TRUE;
  193. // opc.tcp://[servername]:[port][path]
  194. size_t srvNameLen = strlen(r->known.srv.name);
  195. if(srvNameLen > 0 && r->known.srv.name[srvNameLen - 1] == '.')
  196. srvNameLen--;
  197. // todo: malloc may fail: return a statuscode
  198. char *newUrl = (char*)UA_malloc(10 + srvNameLen + 8);
  199. sprintf(newUrl, "opc.tcp://%.*s:%d", (int) srvNameLen,
  200. r->known.srv.name, r->known.srv.port);
  201. UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_SERVER,
  202. "Multicast DNS: found server: %s", newUrl);
  203. entry->serverOnNetwork.discoveryUrl = UA_String_fromChars(newUrl);
  204. UA_free(newUrl);
  205. if(entry->pathTmp) {
  206. mdns_append_path_to_url(&entry->serverOnNetwork.discoveryUrl, entry->pathTmp);
  207. UA_free(entry->pathTmp);
  208. }
  209. }
  210. /* This will be called by the mDNS library on every record which is received */
  211. void mdns_record_received(const struct resource *r, void *data) {
  212. UA_Server *server = (UA_Server *) data;
  213. /* we only need SRV and TXT records */
  214. // TODO: remove magic number
  215. if((r->clazz != QCLASS_IN && r->clazz != QCLASS_IN + 32768) ||
  216. (r->type != QTYPE_SRV && r->type != QTYPE_TXT))
  217. return;
  218. /* we only handle '_opcua-tcp._tcp.' records */
  219. char *opcStr = strstr(r->name, "_opcua-tcp._tcp.");
  220. if(!opcStr)
  221. return;
  222. /* Compute the length of the servername */
  223. size_t servernameLen = (size_t) (opcStr - r->name);
  224. if(servernameLen == 0)
  225. return;
  226. servernameLen--; // remove point
  227. /* Get entry */
  228. struct serverOnNetwork_list_entry *entry =
  229. mdns_record_add_or_get(server, r->name, r->name, servernameLen, r->ttl > 0);
  230. if(!entry)
  231. return;
  232. /* Check that the ttl is positive */
  233. if(r->ttl == 0) {
  234. UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_SERVER,
  235. "Multicast DNS: remove server (TTL=0): %.*s",
  236. entry->serverOnNetwork.discoveryUrl.length,
  237. entry->serverOnNetwork.discoveryUrl.data);
  238. mdns_record_remove(server, r->name, entry);
  239. return;
  240. }
  241. /* Update lastSeen */
  242. entry->lastSeen = UA_DateTime_nowMonotonic();
  243. /* TXT and SRV are already set */
  244. if(entry->txtSet && entry->srvSet)
  245. return;
  246. /* Add the resources */
  247. if(r->type == QTYPE_TXT && !entry->txtSet)
  248. setTxt(r, entry);
  249. else if (r->type == QTYPE_SRV && !entry->srvSet)
  250. setSrv(server, r, entry);
  251. /* Call callback to announce a new server */
  252. if(entry->srvSet && server->serverOnNetworkCallback)
  253. server->serverOnNetworkCallback(&entry->serverOnNetwork, UA_TRUE,
  254. entry->txtSet, server->serverOnNetworkCallbackData);
  255. }
  256. void mdns_create_txt(UA_Server *server, const char *fullServiceDomain, const char *path,
  257. const UA_String *capabilites, const size_t *capabilitiesSize,
  258. void (*conflict)(char *host, int type, void *arg)) {
  259. mdns_record_t *r = mdnsd_unique(server->mdnsDaemon, fullServiceDomain, QTYPE_TXT,
  260. 600, conflict, server);
  261. xht_t *h = xht_new(11);
  262. char *allocPath = NULL;
  263. if (!path || strlen(path) == 0) {
  264. xht_set(h, "path", "/");
  265. } else {
  266. // path does not contain slash, so add it here
  267. if (path[0] == '/')
  268. // todo: malloc in strdup may fail: return a statuscode
  269. allocPath = STRDUP(path);
  270. else {
  271. // todo: malloc may fail: return a statuscode
  272. allocPath = (char*)UA_malloc(strlen(path) + 2);
  273. allocPath[0] = '/';
  274. memcpy(allocPath + 1, path, strlen(path));
  275. allocPath[strlen(path) + 1] = '\0';
  276. }
  277. xht_set(h, "path", allocPath);
  278. }
  279. // calculate max string length:
  280. size_t capsLen = 0;
  281. for (size_t i = 0; i < *capabilitiesSize; i++) {
  282. // add comma or last \0
  283. capsLen += capabilites[i].length + 1;
  284. }
  285. char *caps = NULL;
  286. if(capsLen) {
  287. // freed when xht_free is called
  288. // todo: malloc may fail: return a statuscode
  289. caps = (char*)UA_malloc(sizeof(char) * capsLen);
  290. size_t idx = 0;
  291. for (size_t i = 0; i < *capabilitiesSize; i++) {
  292. strncpy(caps + idx, (const char *) capabilites[i].data, capabilites[i].length);
  293. idx += capabilites[i].length + 1;
  294. caps[idx - 1] = ',';
  295. }
  296. caps[idx - 1] = '\0';
  297. xht_set(h, "caps", caps);
  298. } else {
  299. xht_set(h, "caps", "NA");
  300. }
  301. int txtRecordLength;
  302. unsigned char *packet = sd2txt(h, &txtRecordLength);
  303. if(allocPath)
  304. UA_free(allocPath);
  305. if(caps)
  306. UA_free(caps);
  307. xht_free(h);
  308. mdnsd_set_raw(server->mdnsDaemon, r, (char *) packet, (unsigned short) txtRecordLength);
  309. UA_free(packet);
  310. }
  311. mdns_record_t *
  312. mdns_find_record(mdns_daemon_t *mdnsDaemon, unsigned short type,
  313. const char *host, const char *rdname) {
  314. mdns_record_t *r = mdnsd_get_published(mdnsDaemon, host);
  315. if(!r)
  316. return NULL;
  317. // search for the record with the correct ptr hostname
  318. while(r) {
  319. const mdns_answer_t *data = mdnsd_record_data(r);
  320. if(data->type == type && strcmp(data->rdname, rdname) == 0)
  321. return r;
  322. r = mdnsd_record_next(r);
  323. }
  324. return NULL;
  325. }
  326. /* set record in the given interface */
  327. static void
  328. mdns_set_address_record_if(UA_Server *server, const char *fullServiceDomain,
  329. const char *localDomain, char *addr, UA_UInt16 addr_len) {
  330. // [servername]-[hostname]._opcua-tcp._tcp.local. A [ip].
  331. mdns_record_t *r = mdnsd_shared(server->mdnsDaemon, fullServiceDomain, QTYPE_A, 600);
  332. mdnsd_set_raw(server->mdnsDaemon, r, addr, addr_len);
  333. // [hostname]. A [ip].
  334. r = mdnsd_shared(server->mdnsDaemon, localDomain, QTYPE_A, 600);
  335. mdnsd_set_raw(server->mdnsDaemon, r, addr, addr_len);
  336. }
  337. /* Loop over network interfaces and run set_address_record on each */
  338. #ifdef _WIN32
  339. // see http://stackoverflow.com/a/10838854/869402
  340. static IP_ADAPTER_ADDRESSES *
  341. getInterfaces(UA_Server *server) {
  342. IP_ADAPTER_ADDRESSES* adapter_addresses = NULL;
  343. // Start with a 16 KB buffer and resize if needed - multiple attempts in
  344. // case interfaces change while we are in the middle of querying them.
  345. DWORD adapter_addresses_buffer_size = 16 * 1024;
  346. for(size_t attempts = 0; attempts != 3; ++attempts) {
  347. // todo: malloc may fail: return a statuscode
  348. adapter_addresses = (IP_ADAPTER_ADDRESSES*)UA_malloc(adapter_addresses_buffer_size);
  349. DWORD error = GetAdaptersAddresses(AF_UNSPEC,
  350. GAA_FLAG_SKIP_ANYCAST |
  351. GAA_FLAG_SKIP_DNS_SERVER |
  352. GAA_FLAG_SKIP_FRIENDLY_NAME,
  353. NULL, adapter_addresses,
  354. &adapter_addresses_buffer_size);
  355. if(ERROR_SUCCESS == error) {
  356. UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
  357. "GetAdaptersAddresses returned an error. "
  358. "Not setting mDNS A records.");
  359. adapter_addresses = NULL;
  360. break;
  361. } else if (ERROR_BUFFER_OVERFLOW == error) {
  362. // Try again with the new size
  363. UA_free(adapter_addresses);
  364. adapter_addresses = NULL;
  365. continue;
  366. }
  367. /* Unexpected error */
  368. UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
  369. "GetAdaptersAddresses returned an unexpected error. "
  370. "Not setting mDNS A records.");
  371. UA_free(adapter_addresses);
  372. adapter_addresses = NULL;
  373. break;
  374. }
  375. return adapter_addresses;
  376. }
  377. void mdns_set_address_record(UA_Server *server, const char *fullServiceDomain,
  378. const char *localDomain) {
  379. IP_ADAPTER_ADDRESSES* adapter_addresses = getInterfaces(server);
  380. /* Iterate through all of the adapters */
  381. IP_ADAPTER_ADDRESSES* adapter = NULL;
  382. for(; adapter != NULL; adapter = adapter->Next) {
  383. /* Skip loopback adapters */
  384. if(IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType)
  385. continue;
  386. // Parse all IPv4 and IPv6 addresses
  387. IP_ADAPTER_UNICAST_ADDRESS* address = adapter->FirstUnicastAddress;
  388. for(; NULL != address; address = address->Next) {
  389. int family = address->Address.lpSockaddr->sa_family;
  390. if(AF_INET == family) {
  391. SOCKADDR_IN* ipv4 = (SOCKADDR_IN*)(address->Address.lpSockaddr); // IPv4
  392. mdns_set_address_record_if(server, fullServiceDomain, localDomain,
  393. (char *)&ipv4->sin_addr, 4);
  394. }
  395. /*else if (AF_INET6 == family) {
  396. // IPv6
  397. SOCKADDR_IN6* ipv6 = (SOCKADDR_IN6*)(address->Address.lpSockaddr);
  398. char str_buffer[INET6_ADDRSTRLEN] = {0};
  399. inet_ntop(AF_INET6, &(ipv6->sin6_addr), str_buffer, INET6_ADDRSTRLEN);
  400. std::string ipv6_str(str_buffer);
  401. // Detect and skip non-external addresses
  402. bool is_link_local(false);
  403. bool is_special_use(false);
  404. if(0 == ipv6_str.find("fe")) {
  405. char c = ipv6_str[2];
  406. if (c == '8' || c == '9' || c == 'a' || c == 'b')
  407. is_link_local = true;
  408. } else if (0 == ipv6_str.find("2001:0:")) {
  409. is_special_use = true;
  410. }
  411. if(!(is_link_local || is_special_use))
  412. ipAddrs.mIpv6.push_back(ipv6_str);
  413. }*/
  414. }
  415. }
  416. /* Cleanup */
  417. UA_free(adapter_addresses);
  418. adapter_addresses = NULL;
  419. }
  420. #else //_WIN32
  421. void mdns_set_address_record(UA_Server *server, const char *fullServiceDomain,
  422. const char *localDomain) {
  423. struct ifaddrs *ifaddr, *ifa;
  424. if(getifaddrs(&ifaddr) == -1) {
  425. UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
  426. "getifaddrs returned an unexpected error. Not setting mDNS A records.");
  427. return;
  428. }
  429. /* Walk through linked list, maintaining head pointer so we can free list later */
  430. int n;
  431. for(ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) {
  432. if(!ifa->ifa_addr)
  433. continue;
  434. if((strcmp("lo", ifa->ifa_name) == 0) ||
  435. !(ifa->ifa_flags & (IFF_RUNNING))||
  436. !(ifa->ifa_flags & (IFF_MULTICAST)))
  437. continue;
  438. /* IPv4 */
  439. if(ifa->ifa_addr->sa_family == AF_INET) {
  440. struct sockaddr_in* sa = (struct sockaddr_in*) ifa->ifa_addr;
  441. mdns_set_address_record_if(server, fullServiceDomain, localDomain,
  442. (char*)&sa->sin_addr.s_addr, 4);
  443. }
  444. /* IPv6 not implemented yet */
  445. }
  446. /* Clean up */
  447. freeifaddrs(ifaddr);
  448. }
  449. #endif //_WIN32
  450. #endif // UA_ENABLE_DISCOVERY_MULTICAST