From 88b86d6a7a0c5a2a6ae5ef228b0a774487b8af94 Mon Sep 17 00:00:00 2001
From: Jon Shallow <supjps-libcoap@jpshallow.com>
Date: Fri, 10 Jan 2025 20:13:17 +0000
Subject: [PATCH] mcast: Support specifying output interface for IPv6 multicast
 packets

Specify the interface or scope_id in the IP address as a %suffix.

For example:-

examples/coap-client -N coap://[ff02::fd%interface]
---
 examples/coap-client.c |  1 +
 man/coap-client.txt.in |  7 +++++++
 src/coap_address.c     |  2 +-
 src/coap_io.c          | 28 ++++++++++++++++++++++++++++
 4 files changed, 37 insertions(+), 1 deletion(-)

diff --git a/examples/coap-client.c b/examples/coap-client.c
index 46cdd2b711..eb4fd8fe31 100644
--- a/examples/coap-client.c
+++ b/examples/coap-client.c
@@ -651,6 +651,7 @@ usage(const char *program, const char *version) {
           "\tcoap-client -m get coap+tcp://%%2Funix%%2Fdomain%%2Fpath%%2Fstream/.well-known/core\n"
           "\tcoap-client -m get coaps://[::1]/.well-known/core\n"
           "\tcoap-client -m get coaps+tcp://[::1]/.well-known/core\n"
+          "\tcoap-client -m get -N coap://[ff02::fd%%ens32]/.well-known/core\n"
           "\tcoap-client -m get coaps://%%2Funix%%2Fdomain%%2Fpath%%2Fdtls/.well-known/core\n"
           "\tcoap-client -m get coaps+tcp://%%2Funix%%2Fdomain%%2Fpath%%2Ftls/.well-known/core\n"
           "\tcoap-client -m get -T cafe coap://[::1]/time\n"
diff --git a/man/coap-client.txt.in b/man/coap-client.txt.in
index 0c4424445e..d57ffad489 100644
--- a/man/coap-client.txt.in
+++ b/man/coap-client.txt.in
@@ -319,6 +319,13 @@ coap-client -m get coap://[::1]/.well-known/core
 Query on the resource '.well-known/core' on localhost to get back a list of
 the known resources along with their attribute definitions.
 
+* Example
+----
+coap-client -m get -N coap://[ff02::fd%ens32]/.well-known/core
+----
+Discover the available resources along with their attribute definitions using
+a multicast IP sent out over the ethernet interface ens32.
+
 * Example
 ----
 echo -n "mode=on" | coap-client -m put \
diff --git a/src/coap_address.c b/src/coap_address.c
index 857ca847df..f2864f67ae 100644
--- a/src/coap_address.c
+++ b/src/coap_address.c
@@ -547,7 +547,7 @@ coap_resolve_address_info(const coap_str_const_t *address,
   error = getaddrinfo(addrstr, NULL, &hints, &res);
 
   if (error != 0) {
-    coap_log_warn("getaddrinfo: %s\n", gai_strerror(error));
+    coap_log_warn("getaddrinfo: %s: %s\n", addrstr, gai_strerror(error));
     return NULL;
   }
 
diff --git a/src/coap_io.c b/src/coap_io.c
index 8b36a4c294..882a31ca60 100644
--- a/src/coap_io.c
+++ b/src/coap_io.c
@@ -312,6 +312,11 @@ coap_socket_connect_udp(coap_socket_t *sock,
 
       coap_address_init(&bind_addr);
       bind_addr.addr.sa.sa_family = connect_addr.addr.sa.sa_family;
+#if COAP_IPV6_SUPPORT
+      if (connect_addr.addr.sa.sa_family == AF_INET6) {
+        bind_addr.addr.sin6.sin6_scope_id = connect_addr.addr.sin6.sin6_scope_id;
+      }
+#endif /* COAP_IPV6_SUPPORT */
       if (bind(sock->fd, &bind_addr.addr.sa,
 #if COAP_IPV4_SUPPORT
                bind_addr.addr.sa.sa_family == AF_INET ?
@@ -323,6 +328,16 @@ coap_socket_connect_udp(coap_socket_t *sock,
         goto error;
       }
     }
+#if COAP_IPV6_SUPPORT
+    if (connect_addr.addr.sa.sa_family == AF_INET6 && !coap_is_bcast(&connect_addr)) {
+      int value = connect_addr.addr.sin6.sin6_scope_id;
+
+      if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, OPTVAL_T(&value),
+                     sizeof(value)) == COAP_SOCKET_ERROR)
+        coap_log_warn("coap_socket_connect_udp: getsockopt IPV6_MULTICAST_IF: %d: %s\n",
+                      value, coap_socket_strerror());
+    }
+#endif /* COAP_IPV6_SUPPORT */
     if (getsockname(sock->fd, &local_addr->addr.sa, &local_addr->size) == COAP_SOCKET_ERROR) {
       coap_log_warn("coap_socket_connect_udp: getsockname for multicast socket: %s\n",
                     coap_socket_strerror());
@@ -841,6 +856,19 @@ coap_socket_send(coap_socket_t *sock, coap_session_t *session,
 #else
     bytes_written = send(sock->fd, data, datalen, 0);
 #endif
+#if COAP_IPV6_SUPPORT
+  } else if (session->addr_info.remote.addr.sa.sa_family == AF_INET6 &&
+             coap_is_mcast(&session->addr_info.remote)) {
+#ifdef _WIN32
+    bytes_written = sendto(sock->fd, (const void *)data, (int)datalen, 0,
+                           &session->addr_info.remote.addr.sa,
+                           (int)session->addr_info.remote.size);
+#else
+    bytes_written = sendto(sock->fd, (const void *)data, datalen, 0,
+                           &session->addr_info.remote.addr.sa,
+                           session->addr_info.remote.size);
+#endif
+#endif /* COAP_IPV6_SUPPORT */
   } else {
 #if defined(_WIN32)
     DWORD dwNumberOfBytesSent = 0;