Parcourir la source

Use hostname resolving to correctly handle mDNS SRV target announces

Stefan Profanter il y a 6 ans
Parent
commit
473c0e960b

+ 0 - 7
examples/discovery/server_multicast.c

@@ -63,7 +63,6 @@ writeInteger(UA_Server *server, const UA_NodeId *sessionId,
 }
 
 char *discovery_url = NULL;
-UA_String *self_discovery_url = NULL;
 
 static void
 serverOnNetworkCallback(const UA_ServerOnNetwork *serverOnNetwork, UA_Boolean isServerAnnounce,
@@ -76,11 +75,6 @@ serverOnNetworkCallback(const UA_ServerOnNetwork *serverOnNetwork, UA_Boolean is
         return; // we already have everything we need or we only want server announces
     }
 
-    if(self_discovery_url != NULL && UA_String_equal(&serverOnNetwork->discoveryUrl, self_discovery_url)) {
-        // skip self
-        return;
-    }
-
     if(!isTxtReceived)
         return; // we wait until the corresponding TXT record is announced.
                 // Problem: how to handle if a Server does not announce the
@@ -280,7 +274,6 @@ int main(int argc, char **argv) {
     //UA_String caps = UA_String_fromChars("LDS");
     //config.serverCapabilities = ∩︀
     UA_Server *server = UA_Server_new(config);
-    self_discovery_url = &config->networkLayers[0].discoveryUrl;
 
     /* add a variable node to the address space */
     UA_Int32 myInteger = 42;

+ 335 - 55
src/server/ua_mdns.c

@@ -122,6 +122,264 @@ delayedFree(UA_Server *server, void *data) {
 }
 #endif
 
+
+
+static struct mdnsHostnameToIp_list_entry *
+mdns_hostname_add_or_get(UA_Server *server, const char *hostname, struct in_addr addr, UA_Boolean createNew) {
+    int hashIdx = mdns_hash_record(hostname) % MDNS_HOSTNAME_TO_IP_HASH_PRIME;
+    struct mdnsHostnameToIp_hash_entry *hash_entry = server->mdnsHostnameToIpHash[hashIdx];
+
+    size_t hostnameLen = strlen(hostname);
+    if (hostnameLen == 0)
+        return NULL;
+
+    if(hostname[hostnameLen - 1] == '.')
+        // cut off last dot
+        hostnameLen--;
+
+    while (hash_entry) {
+        if (hash_entry->entry->mdnsHostname.length == hostnameLen &&
+            strncmp((char *) hash_entry->entry->mdnsHostname.data, hostname, hostnameLen) == 0)
+            return hash_entry->entry;
+        hash_entry = hash_entry->next;
+    }
+
+    if(!createNew)
+        return NULL;
+
+    // not yet in list, create new one
+    struct mdnsHostnameToIp_list_entry *listEntry =
+        (mdnsHostnameToIp_list_entry*)UA_malloc(sizeof(struct mdnsHostnameToIp_list_entry));
+    if (!listEntry)
+        return NULL;
+    listEntry->mdnsHostname.data = (UA_Byte*)UA_malloc(hostnameLen);
+    if (!listEntry->mdnsHostname.data) {
+        UA_free(listEntry);
+        return NULL;
+    }
+    memcpy(listEntry->mdnsHostname.data, hostname, hostnameLen);
+    listEntry->mdnsHostname.length = hostnameLen;
+    listEntry->addr = addr;
+
+    // add to hash
+    struct mdnsHostnameToIp_hash_entry *newHashEntry =
+        (struct mdnsHostnameToIp_hash_entry*)UA_malloc(sizeof(struct mdnsHostnameToIp_hash_entry));
+    if (!newHashEntry) {
+        UA_String_deleteMembers(&listEntry->mdnsHostname);
+        UA_free(listEntry);
+        return NULL;
+    }
+    newHashEntry->next = server->mdnsHostnameToIpHash[hashIdx];
+    server->mdnsHostnameToIpHash[hashIdx] = newHashEntry;
+    newHashEntry->entry = listEntry;
+    LIST_INSERT_HEAD(&server->mdnsHostnameToIp, listEntry, pointers);
+    return listEntry;
+}
+
+/*static void
+mdns_hostname_remove(UA_Server *server, const char *record,
+                   struct mdnsHostnameToIp_list_entry *entry) {
+    // remove from hash
+    int hashIdx = mdns_hash_record(record) % MDNS_HOSTNAME_TO_IP_HASH_PRIME;
+    struct mdnsHostnameToIp_hash_entry *hash_entry = server->mdnsHostnameToIpHash[hashIdx];
+    struct mdnsHostnameToIp_hash_entry *prevEntry = hash_entry;
+    while(hash_entry) {
+        if(hash_entry->entry == entry) {
+            if(server->mdnsHostnameToIpHash[hashIdx] == hash_entry)
+                server->mdnsHostnameToIpHash[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);
+
+    // remove from list
+    LIST_REMOVE(entry, pointers);
+    UA_String_deleteMembers(&entry->mdnsHostname);
+    UA_free(entry);
+}*/
+
+#ifdef _WIN32
+
+// see http://stackoverflow.com/a/10838854/869402
+static IP_ADAPTER_ADDRESSES *
+getInterfaces(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(UA_Server *server, 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 UA_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 UA_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 UA_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 UA_FALSE;
+    }
+#endif
+
+
+    UA_Boolean isSelf = UA_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->Next;
+        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 = UA_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 = UA_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);
+    return isSelf;
+#endif
+
+}
+
 static void
 mdns_record_remove(UA_Server *server, const char *record,
                    struct serverOnNetwork_list_entry *entry) {
@@ -142,7 +400,8 @@ mdns_record_remove(UA_Server *server, const char *record,
     }
     UA_free(hash_entry);
 
-    if(server->serverOnNetworkCallback)
+    if(server->serverOnNetworkCallback &&
+       !mdns_is_self_announce(server, entry))
         server->serverOnNetworkCallback(&entry->serverOnNetwork, UA_FALSE,
                                         entry->txtSet, server->serverOnNetworkCallbackData);
 
@@ -226,15 +485,62 @@ setSrv(UA_Server *server, const struct resource *r,
        struct serverOnNetwork_list_entry *entry) {
     entry->srvSet = UA_TRUE;
 
-    // opc.tcp://[servername]:[port][path]
-    size_t srvNameLen = strlen(r->known.srv.name);
-    if(srvNameLen > 0 && r->known.srv.name[srvNameLen - 1] == '.')
-        srvNameLen--;
 
-    // todo: malloc may fail: return a statuscode
-    char *newUrl = (char*)UA_malloc(10 + srvNameLen + 8);
-    UA_snprintf(newUrl, 10 + srvNameLen + 8, "opc.tcp://%.*s:%d/", (int) srvNameLen,
-             r->known.srv.name, r->known.srv.port);
+    // 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.
+
+
+    // The correct way would be:
+    // 1. Take the target field (known.srv.name), which is something like my-host.local.
+    // 2. Check additional mdns Answers, which resolve the target to an IP address
+    // 3. Use that IP address to get a hostname (as the spec says)
+
+    // just a dummy, not used
+    struct in_addr tmp = {0};
+
+    mdnsHostnameToIp_list_entry *hostnameEntry = mdns_hostname_add_or_get(server, r->known.srv.name, tmp, UA_FALSE);
+
+    char *newUrl;
+    if (hostnameEntry) {
+        char remote_name[NI_MAXHOST];
+        struct sockaddr_in sockaddr;
+        sockaddr.sin_family = AF_INET;
+        sockaddr.sin_addr = hostnameEntry->addr;
+        int res = getnameinfo((struct sockaddr*)&sockaddr,
+                              sizeof(struct sockaddr_storage),
+                              remote_name, sizeof(remote_name),
+                              NULL, 0, NI_NAMEREQD);
+        newUrl = (char*)UA_malloc(10 + NI_MAXHOST + 8);
+        if (!newUrl)
+            return; //TODO show error message
+        if(res == 0) {
+            UA_snprintf(newUrl, 10 + NI_MAXHOST + 8, "opc.tcp://%s:%d", remote_name, r->known.srv.port);
+        } else {
+            char ipinput[INET_ADDRSTRLEN];
+            inet_ntop(AF_INET, &(hostnameEntry->addr), ipinput, INET_ADDRSTRLEN);
+            UA_LOG_SOCKET_ERRNO_WRAP(
+                UA_LOG_DEBUG(server->config.logger, UA_LOGCATEGORY_SERVER,
+                             "Multicast: Can not resolve IP address to hostname: %s - %s. Using IP instead",ipinput, errno_str));
+
+            UA_snprintf(newUrl, 10 + INET_ADDRSTRLEN + 8, "opc.tcp://%s:%d", ipinput, r->known.srv.port);
+        }
+    } else {
+        // fallback to just using the given target name
+        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]
+        newUrl = (char*)UA_malloc(10 + srvNameLen + 8);
+        if (!newUrl)
+            return; //TODO show error message
+        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);
@@ -247,15 +553,32 @@ setSrv(UA_Server *server, const struct resource *r,
 }
 
 
+// [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname].
+static void
+setAddress(UA_Server *server, const struct resource *r) {
+    if (r->type != QTYPE_A)
+        return;
+
+    if (!mdns_hostname_add_or_get(server, r->name, r->known.a.ip, UA_TRUE)) {
+        // should we log an error?
+    }
+}
+
+
 /* 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))
+       (r->type != QTYPE_SRV && r->type != QTYPE_TXT && r->type != QTYPE_A))
         return;
 
+    if (r->type == QTYPE_A) {
+        setAddress(server, r);
+        return;
+    }
+
     /* we only handle '_opcua-tcp._tcp.' records */
     char *opcStr = strstr(r->name, "_opcua-tcp._tcp.");
     if(!opcStr)
@@ -297,7 +620,8 @@ void mdns_record_received(const struct resource *r, void *data) {
         setSrv(server, r, entry);
 
     /* Call callback to announce a new server */
-    if(entry->srvSet && server->serverOnNetworkCallback)
+    if(entry->srvSet && server->serverOnNetworkCallback &&
+       !mdns_is_self_announce(server, entry))
         server->serverOnNetworkCallback(&entry->serverOnNetwork, UA_TRUE,
                                         entry->txtSet, server->serverOnNetworkCallbackData);
 }
@@ -395,50 +719,6 @@ mdns_set_address_record_if(UA_Server *server, const char *fullServiceDomain,
 /* Loop over network interfaces and run set_address_record on each */
 #ifdef _WIN32
 
-// see http://stackoverflow.com/a/10838854/869402
-static IP_ADAPTER_ADDRESSES *
-getInterfaces(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;
-}
-
 void mdns_set_address_record(UA_Server *server, const char *fullServiceDomain,
                              const char *localDomain) {
     IP_ADAPTER_ADDRESSES* adapter_addresses = getInterfaces(server);

+ 21 - 0
src/server/ua_server.c

@@ -182,6 +182,22 @@ void UA_Server_delete(UA_Server *server) {
             currHash = nextHash;
         }
     }
+
+    mdnsHostnameToIp_list_entry *mhi, *mhi_tmp;
+    LIST_FOREACH_SAFE(mhi, &server->mdnsHostnameToIp, pointers, mhi_tmp) {
+        LIST_REMOVE(mhi, pointers);
+        UA_String_deleteMembers(&mhi->mdnsHostname);
+        UA_free(mhi);
+    }
+
+    for(size_t i = 0; i < MDNS_HOSTNAME_TO_IP_HASH_PRIME; i++) {
+        mdnsHostnameToIp_hash_entry* currHash = server->mdnsHostnameToIpHash[i];
+        while(currHash) {
+            mdnsHostnameToIp_hash_entry* nextHash = currHash->next;
+            UA_free(currHash);
+            currHash = nextHash;
+        }
+    }
 # endif
 
 #endif
@@ -300,6 +316,11 @@ UA_Server_new(const UA_ServerConfig *config) {
     memset(server->serverOnNetworkHash, 0,
            sizeof(struct serverOnNetwork_hash_entry*) * SERVER_ON_NETWORK_HASH_PRIME);
 
+
+    LIST_INIT(&server->mdnsHostnameToIp);
+    memset(server->mdnsHostnameToIpHash, 0,
+           sizeof(struct mdnsHostnameToIp_hash_entry*) * MDNS_HOSTNAME_TO_IP_HASH_PRIME);
+
     server->serverOnNetworkCallback = NULL;
     server->serverOnNetworkCallbackData = NULL;
 #endif

+ 17 - 0
src/server/ua_server_internal.h

@@ -92,6 +92,18 @@ typedef struct serverOnNetwork_hash_entry {
     struct serverOnNetwork_hash_entry* next;
 } serverOnNetwork_hash_entry;
 
+typedef struct mdnsHostnameToIp_list_entry {
+    LIST_ENTRY(mdnsHostnameToIp_list_entry) pointers;
+    UA_String mdnsHostname;
+    struct in_addr addr;
+} mdnsHostnameToIp_list_entry;
+
+#define MDNS_HOSTNAME_TO_IP_HASH_PRIME 1009
+typedef struct mdnsHostnameToIp_hash_entry {
+    mdnsHostnameToIp_list_entry* entry;
+    struct mdnsHostnameToIp_hash_entry* next;
+} mdnsHostnameToIp_hash_entry;
+
 #endif /* UA_ENABLE_DISCOVERY_MULTICAST */
 #endif /* UA_ENABLE_DISCOVERY */
 
@@ -129,6 +141,11 @@ struct UA_Server {
     UA_Server_serverOnNetworkCallback serverOnNetworkCallback;
     void* serverOnNetworkCallbackData;
 
+
+    LIST_HEAD(mdnsHostnameToIp_list, mdnsHostnameToIp_list_entry) mdnsHostnameToIp; // doubly-linked list of hostname to IP mapping (from mDNS)
+    // hash mapping hostname to ip
+    struct mdnsHostnameToIp_hash_entry* mdnsHostnameToIpHash[MDNS_HOSTNAME_TO_IP_HASH_PRIME];
+
 # endif
 #endif
 

+ 3 - 3
src/server/ua_services_discovery_multicast.c

@@ -39,7 +39,7 @@ multicastWorkerLoop(UA_Server *server) {
             break;
         } else if (retVal == 2) {
             UA_LOG_SOCKET_ERRNO_WRAP(
-                UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                UA_LOG_DEBUG(server->config.logger, UA_LOGCATEGORY_SERVER,
                          "Multicast error: Can not write to socket. %s", errno_str));
             break;
         }
@@ -552,12 +552,12 @@ iterateMulticastDiscoveryServer(UA_Server* server, UA_DateTime *nextRepeat,
                                        processIn, true, &next_sleep);
     if(retval == 1) {
         UA_LOG_SOCKET_ERRNO_WRAP(
-                UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+               UA_LOG_DEBUG(server->config.logger, UA_LOGCATEGORY_SERVER,
                      "Multicast error: Can not read from socket. %s", errno_str));
         return UA_STATUSCODE_BADNOCOMMUNICATION;
     } else if(retval == 2) {
         UA_LOG_SOCKET_ERRNO_WRAP(
-                UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                UA_LOG_DEBUG(server->config.logger, UA_LOGCATEGORY_SERVER,
                      "Multicast error: Can not write to socket. %s", errno_str));
         return UA_STATUSCODE_BADNOCOMMUNICATION;
     }