| /* 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 <winsock2.h> |
| # include <iphlpapi.h> |
| # include <ws2tcpip.h> |
| #else |
| # include <sys/time.h> // for struct timeval |
| # include <netinet/in.h> // for struct ip_mreq |
| # include <ifaddrs.h> |
| # include <net/if.h> /* for IFF_RUNNING */ |
| # include <netdb.h> // 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; i<server->config.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; i<server->config.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); |
| |
| #if UA_MULTITHREADING >= 200 |
| UA_atomic_subSize(&dm->serverOnNetworkSize, 1); |
| entry->delayedCleanup.callback = NULL; /* Only free the structure */ |
| UA_WorkQueue_enqueueDelayed(&server->workQueue, &entry->delayedCleanup); |
| #else |
| dm->serverOnNetworkSize--; |
| UA_free(entry); |
| #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 */ |