/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2017 (c) Stefan Profanter, fortiss GmbH * Copyright 2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer) */ #include "ua_server_internal.h" #ifdef UA_ENABLE_DISCOVERY_MULTICAST #ifndef UA_ENABLE_AMALGAMATION #include "mdnsd/libmdnsd/xht.h" #include "mdnsd/libmdnsd/sdtxt.h" #endif #ifdef _WIN32 /* inet_ntoa is deprecated on MSVC but used for compatibility */ # define _WINSOCK_DEPRECATED_NO_WARNINGS # include # include # include #else # include // for struct timeval # include // for struct ip_mreq # include # include /* for IFF_RUNNING */ # include // for recvfrom in cygwin #endif /* FIXME: Is this a required algorithm? Otherwise, reuse hashing for nodeids */ /* Generates a hash code for a string. * This function uses the ELF hashing algorithm as reprinted in * Andrew Binstock, "Hashing Rehashed," Dr. Dobb's Journal, April 1996. */ static int mdns_hash_record(const char *s) { /* ELF hash uses unsigned chars and unsigned arithmetic for portability */ const unsigned char *name = (const unsigned char *) s; unsigned long h = 0; while(*name) { h = (h << 4) + (unsigned long) (*name++); unsigned long g; if((g = (h & 0xF0000000UL)) != 0) h ^= (g >> 24); h &= ~g; } return (int) h; } static struct serverOnNetwork_list_entry * mdns_record_add_or_get(UA_DiscoveryManager *dm, const char *record, const char *serverName, size_t serverNameLen, UA_Boolean createNew) { int hashIdx = mdns_hash_record(record) % SERVER_ON_NETWORK_HASH_PRIME; struct serverOnNetwork_hash_entry *hash_entry = dm->serverOnNetworkHash[hashIdx]; while(hash_entry) { size_t maxLen; if(serverNameLen > hash_entry->entry->serverOnNetwork.serverName.length) maxLen = hash_entry->entry->serverOnNetwork.serverName.length; else maxLen = serverNameLen; if(strncmp((char *) hash_entry->entry->serverOnNetwork.serverName.data, serverName, maxLen) == 0) return hash_entry->entry; hash_entry = hash_entry->next; } if(!createNew) return NULL; /* not yet in list, create new one */ /* todo: malloc may fail: return a statuscode */ struct serverOnNetwork_list_entry *listEntry = (serverOnNetwork_list_entry*) UA_malloc(sizeof(struct serverOnNetwork_list_entry)); listEntry->created = UA_DateTime_now(); listEntry->pathTmp = NULL; listEntry->txtSet = false; listEntry->srvSet = false; UA_ServerOnNetwork_init(&listEntry->serverOnNetwork); listEntry->serverOnNetwork.recordId = dm->serverOnNetworkRecordIdCounter; listEntry->serverOnNetwork.serverName.length = serverNameLen; /* todo: malloc may fail: return a statuscode */ listEntry->serverOnNetwork.serverName.data = (UA_Byte*)UA_malloc(serverNameLen); memcpy(listEntry->serverOnNetwork.serverName.data, serverName, serverNameLen); UA_atomic_addUInt32(&dm->serverOnNetworkRecordIdCounter, 1); if(dm->serverOnNetworkRecordIdCounter == 0) dm->serverOnNetworkRecordIdLastReset = UA_DateTime_now(); /* add to hash */ /* todo: malloc may fail: return a statuscode */ struct serverOnNetwork_hash_entry *newHashEntry = (struct serverOnNetwork_hash_entry*) UA_malloc(sizeof(struct serverOnNetwork_hash_entry)); newHashEntry->next = dm->serverOnNetworkHash[hashIdx]; dm->serverOnNetworkHash[hashIdx] = newHashEntry; newHashEntry->entry = listEntry; LIST_INSERT_HEAD(&dm->serverOnNetwork, listEntry, pointers); return listEntry; } #ifdef _WIN32 /* see http://stackoverflow.com/a/10838854/869402 */ static IP_ADAPTER_ADDRESSES * getInterfaces(const UA_Server *server) { IP_ADAPTER_ADDRESSES* adapter_addresses = NULL; /* Start with a 16 KB buffer and resize if needed - multiple attempts in * case interfaces change while we are in the middle of querying them. */ DWORD adapter_addresses_buffer_size = 16 * 1024; for(size_t attempts = 0; attempts != 3; ++attempts) { /* todo: malloc may fail: return a statuscode */ adapter_addresses = (IP_ADAPTER_ADDRESSES*)UA_malloc(adapter_addresses_buffer_size); if(!adapter_addresses) { UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, "GetAdaptersAddresses out of memory"); adapter_addresses = NULL; break; } DWORD error = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME, NULL, adapter_addresses, &adapter_addresses_buffer_size); if(ERROR_SUCCESS == error) { break; } else if (ERROR_BUFFER_OVERFLOW == error) { /* Try again with the new size */ UA_free(adapter_addresses); adapter_addresses = NULL; continue; } /* Unexpected error */ UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, "GetAdaptersAddresses returned an unexpected error. " "Not setting mDNS A records."); UA_free(adapter_addresses); adapter_addresses = NULL; break; } return adapter_addresses; } #endif /* _WIN32 */ static UA_Boolean mdns_is_self_announce(const UA_Server *server, const struct serverOnNetwork_list_entry *entry) { for (size_t i=0; iconfig.networkLayersSize; i++) { UA_ServerNetworkLayer *nl = &server->config.networkLayers[i]; if(UA_String_equal(&entry->serverOnNetwork.discoveryUrl, &nl->discoveryUrl)) return true; // check discoveryUrl ignoring tailing slash if ((( nl->discoveryUrl.length == entry->serverOnNetwork.discoveryUrl.length +1 && nl->discoveryUrl.data[nl->discoveryUrl.length-1] == '/' ) || ( entry->serverOnNetwork.discoveryUrl.length == nl->discoveryUrl.length +1 && entry->serverOnNetwork.discoveryUrl.data[entry->serverOnNetwork.discoveryUrl.length-1] == '/' ) ) && memcmp(nl->discoveryUrl.data, entry->serverOnNetwork.discoveryUrl.data, UA_MIN(nl->discoveryUrl.length, entry->serverOnNetwork.discoveryUrl.length)) == 0 ) { return true; } if (nl->discoveryUrl.length == entry->serverOnNetwork.discoveryUrl.length +1 && nl->discoveryUrl.data[nl->discoveryUrl.length-1] == '/' && memcmp(nl->discoveryUrl.data, entry->serverOnNetwork.discoveryUrl.data, nl->discoveryUrl.length-1) == 0 ) { return true; } } /* The discovery URL may also just contain the IP address, but in our * discovery URL we are using hostname thus previous check may not detect * the same URL. Therefore we also check if the name matches: */ UA_String hostnameRemote = UA_STRING_NULL; UA_UInt16 portRemote = 4840; UA_String pathRemote = UA_STRING_NULL; UA_StatusCode retval = UA_parseEndpointUrl(&entry->serverOnNetwork.discoveryUrl, &hostnameRemote, &portRemote, &pathRemote); if(retval != UA_STATUSCODE_GOOD) { /* skip invalid url */ return false; } #ifdef _WIN32 IP_ADAPTER_ADDRESSES* adapter_addresses = getInterfaces(server); if(!adapter_addresses) { UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, "getifaddrs returned an unexpected error. Not setting mDNS A records."); return false; } #else struct ifaddrs *ifaddr, *ifa; if(getifaddrs(&ifaddr) == -1) { UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, "getifaddrs returned an unexpected error. Not setting mDNS A records."); return false; } #endif UA_Boolean isSelf = false; for (size_t i=0; iconfig.networkLayersSize; i++) { UA_ServerNetworkLayer *nl = &server->config.networkLayers[i]; UA_String hostnameSelf = UA_STRING_NULL; UA_UInt16 portSelf = 4840; UA_String pathSelf = UA_STRING_NULL; retval = UA_parseEndpointUrl(&nl->discoveryUrl, &hostnameSelf, &portSelf, &pathSelf); if(retval != UA_STATUSCODE_GOOD) { /* skip invalid url */ continue; } if (portRemote != portSelf) continue; #ifdef _WIN32 /* Iterate through all of the adapters */ IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; for(; adapter != NULL; adapter = adapter->Next) { /* Skip loopback adapters */ if(IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType) continue; /* Parse all IPv4 and IPv6 addresses */ IP_ADAPTER_UNICAST_ADDRESS* address = adapter->FirstUnicastAddress; for(; NULL != address; address = address->Next) { int family = address->Address.lpSockaddr->sa_family; if(AF_INET == family) { SOCKADDR_IN* ipv4 = (SOCKADDR_IN*)(address->Address.lpSockaddr); /* IPv4 */ char *ipStr = inet_ntoa(ipv4->sin_addr); if(strncmp((const char*)hostnameRemote.data, ipStr, hostnameRemote.length) == 0) { isSelf = true; break; } } else if(AF_INET6 == family) { /* IPv6 not implemented yet */ } } if (isSelf) break; } #else /* Walk through linked list, maintaining head pointer so we can free * list later */ int n; for(ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) { if(!ifa->ifa_addr) continue; if((strcmp("lo", ifa->ifa_name) == 0) || !(ifa->ifa_flags & (IFF_RUNNING))|| !(ifa->ifa_flags & (IFF_MULTICAST))) continue; /* IPv4 */ if(ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in* sa = (struct sockaddr_in*) ifa->ifa_addr; char *ipStr = inet_ntoa(sa->sin_addr); if(strncmp((const char*)hostnameRemote.data, ipStr, hostnameRemote.length) == 0) { isSelf = true; break; } } /* IPv6 not implemented yet */ } #endif if (isSelf) break; } #ifdef _WIN32 /* Cleanup */ UA_free(adapter_addresses); adapter_addresses = NULL; #else /* Clean up */ freeifaddrs(ifaddr); #endif return isSelf; } static void mdns_record_remove(UA_Server *server, const char *record, struct serverOnNetwork_list_entry *entry) { UA_DiscoveryManager *dm = &server->discoveryManager; /* remove from hash */ int hashIdx = mdns_hash_record(record) % SERVER_ON_NETWORK_HASH_PRIME; struct serverOnNetwork_hash_entry *hash_entry = dm->serverOnNetworkHash[hashIdx]; struct serverOnNetwork_hash_entry *prevEntry = hash_entry; while(hash_entry) { if(hash_entry->entry == entry) { if(dm->serverOnNetworkHash[hashIdx] == hash_entry) dm->serverOnNetworkHash[hashIdx] = hash_entry->next; else if(prevEntry) prevEntry->next = hash_entry->next; break; } prevEntry = hash_entry; hash_entry = hash_entry->next; } UA_free(hash_entry); if(dm->serverOnNetworkCallback && !mdns_is_self_announce(server, entry)) dm->serverOnNetworkCallback(&entry->serverOnNetwork, false, entry->txtSet, dm->serverOnNetworkCallbackData); /* remove from list */ LIST_REMOVE(entry, pointers); UA_ServerOnNetwork_deleteMembers(&entry->serverOnNetwork); if(entry->pathTmp) UA_free(entry->pathTmp); #ifndef UA_ENABLE_MULTITHREADING dm->serverOnNetworkSize--; UA_free(entry); #else UA_atomic_subSize(&dm->serverOnNetworkSize, 1); entry->delayedCleanup.callback = NULL; /* Only free the structure */ UA_WorkQueue_enqueueDelayed(&server->workQueue, &entry->delayedCleanup); #endif } static void mdns_append_path_to_url(UA_String *url, const char *path) { size_t pathLen = strlen(path); /* todo: malloc may fail: return a statuscode */ char *newUrl = (char *)UA_malloc(url->length + pathLen); memcpy(newUrl, url->data, url->length); memcpy(newUrl + url->length, path, pathLen); url->length = url->length + pathLen; url->data = (UA_Byte *) newUrl; } static void setTxt(UA_Server *server, const struct resource *r, struct serverOnNetwork_list_entry *entry) { entry->txtSet = true; xht_t *x = txt2sd(r->rdata, r->rdlength); char *path = (char *) xht_get(x, "path"); char *caps = (char *) xht_get(x, "caps"); size_t pathLen = path ? strlen(path) : 0; if(path && pathLen > 1) { if(!entry->srvSet) { /* txt arrived before SRV, thus cache path entry */ if (!entry->pathTmp) { entry->pathTmp = (char*)UA_malloc(pathLen+1); if (!entry->pathTmp) { UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, "Cannot alloc memory for mDNS srv path"); return; } memcpy(&(entry->pathTmp), &path, pathLen); entry->pathTmp[pathLen] = '\0'; } } else { /* SRV already there and discovery URL set. Add path to discovery URL */ mdns_append_path_to_url(&entry->serverOnNetwork.discoveryUrl, path); } } if(caps && strlen(caps) > 0) { /* count comma in caps */ size_t capsCount = 1; for(size_t i = 0; caps[i]; i++) { if(caps[i] == ',') capsCount++; } /* set capabilities */ entry->serverOnNetwork.serverCapabilitiesSize = capsCount; entry->serverOnNetwork.serverCapabilities = (UA_String *) UA_Array_new(capsCount, &UA_TYPES[UA_TYPES_STRING]); for(size_t i = 0; i < capsCount; i++) { char *nextStr = strchr(caps, ','); size_t len = nextStr ? (size_t) (nextStr - caps) : strlen(caps); entry->serverOnNetwork.serverCapabilities[i].length = len; /* todo: malloc may fail: return a statuscode */ entry->serverOnNetwork.serverCapabilities[i].data = (UA_Byte*)UA_malloc(len); memcpy(entry->serverOnNetwork.serverCapabilities[i].data, caps, len); if(nextStr) caps = nextStr + 1; else break; } } xht_free(x); } /* [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname]. */ static void setSrv(UA_Server *server, const struct resource *r, struct serverOnNetwork_list_entry *entry) { entry->srvSet = true; /* The specification Part 12 says: The hostname maps onto the SRV record * target field. If the hostname is an IPAddress then it must be converted * to a domain name. If this cannot be done then LDS shall report an * error. */ size_t srvNameLen = strlen(r->known.srv.name); if(srvNameLen > 0 && r->known.srv.name[srvNameLen - 1] == '.') /* cut off last dot */ srvNameLen--; /* opc.tcp://[servername]:[port][path] */ char *newUrl = (char*)UA_malloc(10 + srvNameLen + 8); if (!newUrl) { UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, "Cannot allocate char for discovery url. Out of memory."); return; } UA_snprintf(newUrl, 10 + srvNameLen + 8, "opc.tcp://%.*s:%d", (int) srvNameLen, r->known.srv.name, r->known.srv.port); UA_LOG_INFO(&server->config.logger, UA_LOGCATEGORY_SERVER, "Multicast DNS: found server: %s", newUrl); entry->serverOnNetwork.discoveryUrl = UA_String_fromChars(newUrl); UA_free(newUrl); if(entry->pathTmp) { mdns_append_path_to_url(&entry->serverOnNetwork.discoveryUrl, entry->pathTmp); UA_free(entry->pathTmp); } } /* This will be called by the mDNS library on every record which is received */ void mdns_record_received(const struct resource *r, void *data) { UA_Server *server = (UA_Server *) data; /* we only need SRV and TXT records */ /* TODO: remove magic number */ if((r->clazz != QCLASS_IN && r->clazz != QCLASS_IN + 32768) || (r->type != QTYPE_SRV && r->type != QTYPE_TXT)) return; /* we only handle '_opcua-tcp._tcp.' records */ char *opcStr = strstr(r->name, "_opcua-tcp._tcp."); if(!opcStr) return; /* Compute the length of the servername */ size_t servernameLen = (size_t) (opcStr - r->name); if(servernameLen == 0) return; servernameLen--; /* remove point */ /* Get entry */ struct serverOnNetwork_list_entry *entry = mdns_record_add_or_get(&server->discoveryManager, r->name, r->name, servernameLen, r->ttl > 0); if(!entry) return; /* Check that the ttl is positive */ if(r->ttl == 0) { UA_LOG_INFO(&server->config.logger, UA_LOGCATEGORY_SERVER, "Multicast DNS: remove server (TTL=0): %.*s", (int)entry->serverOnNetwork.discoveryUrl.length, entry->serverOnNetwork.discoveryUrl.data); mdns_record_remove(server, r->name, entry); return; } /* Update lastSeen */ entry->lastSeen = UA_DateTime_nowMonotonic(); /* TXT and SRV are already set */ if(entry->txtSet && entry->srvSet) { // call callback for every mdns package we received. // This will also call the callback multiple times if (server->discoveryManager.serverOnNetworkCallback && !mdns_is_self_announce(server, entry)) server->discoveryManager. serverOnNetworkCallback(&entry->serverOnNetwork, true, entry->txtSet, server->discoveryManager.serverOnNetworkCallbackData); return; } /* Add the resources */ if(r->type == QTYPE_TXT && !entry->txtSet) setTxt(server, r, entry); else if (r->type == QTYPE_SRV && !entry->srvSet) setSrv(server, r, entry); /* Call callback to announce a new server */ if(entry->srvSet && server->discoveryManager.serverOnNetworkCallback && !mdns_is_self_announce(server, entry)) server->discoveryManager. serverOnNetworkCallback(&entry->serverOnNetwork, true, entry->txtSet, server->discoveryManager.serverOnNetworkCallbackData); } void mdns_create_txt(UA_Server *server, const char *fullServiceDomain, const char *path, const UA_String *capabilites, const size_t capabilitiesSize, void (*conflict)(char *host, int type, void *arg)) { mdns_record_t *r = mdnsd_unique(server->discoveryManager.mdnsDaemon, fullServiceDomain, QTYPE_TXT, 600, conflict, server); xht_t *h = xht_new(11); char *allocPath = NULL; if(!path || strlen(path) == 0) { xht_set(h, "path", "/"); } else { /* path does not contain slash, so add it here */ size_t pathLen = strlen(path); if(path[0] == '/') { allocPath = (char*)UA_malloc(pathLen+1); if (!allocPath) { UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, "Cannot alloc memory for txt path"); return; } memcpy(&allocPath, &path, pathLen); allocPath[pathLen] = '\0'; } else { /* todo: malloc may fail: return a statuscode */ allocPath = (char*)UA_malloc(pathLen + 2); allocPath[0] = '/'; memcpy(allocPath + 1, path, pathLen); allocPath[pathLen + 1] = '\0'; } xht_set(h, "path", allocPath); } /* calculate max string length: */ size_t capsLen = 0; for(size_t i = 0; i < capabilitiesSize; i++) { /* add comma or last \0 */ capsLen += capabilites[i].length + 1; } char *caps = NULL; if(capsLen) { /* freed when xht_free is called */ /* todo: malloc may fail: return a statuscode */ caps = (char*)UA_malloc(sizeof(char) * capsLen); size_t idx = 0; for(size_t i = 0; i < capabilitiesSize; i++) { memcpy(caps + idx, (const char *) capabilites[i].data, capabilites[i].length); idx += capabilites[i].length + 1; caps[idx - 1] = ','; } caps[idx - 1] = '\0'; xht_set(h, "caps", caps); } else { xht_set(h, "caps", "NA"); } int txtRecordLength; unsigned char *packet = sd2txt(h, &txtRecordLength); if(allocPath) UA_free(allocPath); if(caps) UA_free(caps); xht_free(h); mdnsd_set_raw(server->discoveryManager.mdnsDaemon, r, (char *) packet, (unsigned short) txtRecordLength); UA_free(packet); } mdns_record_t * mdns_find_record(mdns_daemon_t *mdnsDaemon, unsigned short type, const char *host, const char *rdname) { mdns_record_t *r = mdnsd_get_published(mdnsDaemon, host); if(!r) return NULL; /* search for the record with the correct ptr hostname */ while(r) { const mdns_answer_t *data = mdnsd_record_data(r); if(data->type == type && strcmp(data->rdname, rdname) == 0) return r; r = mdnsd_record_next(r); } return NULL; } /* set record in the given interface */ static void mdns_set_address_record_if(UA_DiscoveryManager *dm, const char *fullServiceDomain, const char *localDomain, char *addr, UA_UInt16 addr_len) { /* [servername]-[hostname]._opcua-tcp._tcp.local. A [ip]. */ mdns_record_t *r = mdnsd_shared(dm->mdnsDaemon, fullServiceDomain, QTYPE_A, 600); mdnsd_set_raw(dm->mdnsDaemon, r, addr, addr_len); /* [hostname]. A [ip]. */ r = mdnsd_shared(dm->mdnsDaemon, localDomain, QTYPE_A, 600); mdnsd_set_raw(dm->mdnsDaemon, r, addr, addr_len); } /* Loop over network interfaces and run set_address_record on each */ #ifdef _WIN32 void mdns_set_address_record(UA_Server *server, const char *fullServiceDomain, const char *localDomain) { IP_ADAPTER_ADDRESSES* adapter_addresses = getInterfaces(server); if(!adapter_addresses) return; /* Iterate through all of the adapters */ IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; for(; adapter != NULL; adapter = adapter->Next) { /* Skip loopback adapters */ if(IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType) continue; /* Parse all IPv4 and IPv6 addresses */ IP_ADAPTER_UNICAST_ADDRESS* address = adapter->FirstUnicastAddress; for(; NULL != address; address = address->Next) { int family = address->Address.lpSockaddr->sa_family; if(AF_INET == family) { SOCKADDR_IN* ipv4 = (SOCKADDR_IN*)(address->Address.lpSockaddr); /* IPv4 */ mdns_set_address_record_if(&server->discoveryManager, fullServiceDomain, localDomain, (char *)&ipv4->sin_addr, 4); } else if(AF_INET6 == family) { /* IPv6 */ #if 0 SOCKADDR_IN6* ipv6 = (SOCKADDR_IN6*)(address->Address.lpSockaddr); char str_buffer[INET6_ADDRSTRLEN] = {0}; inet_ntop(AF_INET6, &(ipv6->sin6_addr), str_buffer, INET6_ADDRSTRLEN); std::string ipv6_str(str_buffer); /* Detect and skip non-external addresses */ UA_Boolean is_link_local(false); UA_Boolean is_special_use(false); if(0 == ipv6_str.find("fe")) { char c = ipv6_str[2]; if(c == '8' || c == '9' || c == 'a' || c == 'b') is_link_local = true; } else if (0 == ipv6_str.find("2001:0:")) { is_special_use = true; } if(!(is_link_local || is_special_use)) ipAddrs.mIpv6.push_back(ipv6_str); #endif } } } /* Cleanup */ UA_free(adapter_addresses); adapter_addresses = NULL; } #else /* _WIN32 */ void mdns_set_address_record(UA_Server *server, const char *fullServiceDomain, const char *localDomain) { struct ifaddrs *ifaddr, *ifa; if(getifaddrs(&ifaddr) == -1) { UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, "getifaddrs returned an unexpected error. Not setting mDNS A records."); return; } /* Walk through linked list, maintaining head pointer so we can free list later */ int n; for(ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) { if(!ifa->ifa_addr) continue; if((strcmp("lo", ifa->ifa_name) == 0) || !(ifa->ifa_flags & (IFF_RUNNING))|| !(ifa->ifa_flags & (IFF_MULTICAST))) continue; /* IPv4 */ if(ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in* sa = (struct sockaddr_in*) ifa->ifa_addr; mdns_set_address_record_if(&server->discoveryManager, fullServiceDomain, localDomain, (char*)&sa->sin_addr.s_addr, 4); } /* IPv6 not implemented yet */ } /* Clean up */ freeifaddrs(ifaddr); } #endif /* _WIN32 */ #endif /* UA_ENABLE_DISCOVERY_MULTICAST */