ua_mdns.c 19 KB

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