[FFmpeg-devel] [PATCH] avformat: enable UDP IPv6 multicast interface selection

Ignjatović, Lazar (RS) Lazar.Ignjatovic at cubic.com
Thu Mar 14 11:22:40 EET 2024


avformat: enable UDP IPv6 multicast interface selection

localaddr option now properly works with IPv6 addresses. Properly resolved
interface index in places where default 0 interface index is used (marked with
TODO: within udp.c). Added SO_BINDTODEVICE for mcast sockets that are used for
reading from the network. Need for this arises from the fact that [ffx1::*] and
[ffx2::*] mcast addresses need to have a defined interface for binding to avoid
ambiguity between multiple link-local networks on the same host. Failing to set
this option causes errors on Linux systems for interface and link-local scopes.

For mcast addresses, bind to mcast address is attempted as before. In case
that this fails, which will happen on Windows, socket is bound to
INADDR_ANY/IN6ADDR_ANY_INIT depending on address family. Actual interface
selection is performed using udp_set_multicast_interface to point to the
desired interface for sending/listening.

Utilization of sin6_scope_id field enables usage and adequate resolving of
IPv6 addresses that utilize zone index (e.g. fe80::1ff:fe23:4567:890a%eth2 )
This is not fully supported on Windows, thus relying on this field is not done
on Windows systems.

Closes: #368

Signed-off-by: Lazar Ignjatovic <Lazar.Ignjatovic at cubic.com>
---
 configure             |  3 ++
 libavformat/ip.c      | 45 ++++++++++++++++++++++++
 libavformat/ip.h      |  6 ++++
 libavformat/network.h |  6 ++++
 libavformat/udp.c     | 80 ++++++++++++++++++++++++++++++++++++++-----
 5 files changed, 132 insertions(+), 8 deletions(-)

diff --git a/configure b/configure
index c34bdd13f5..77f03948ce 100755
--- a/configure
+++ b/configure
@@ -2256,6 +2256,7 @@ HEADERS_LIST="
     valgrind_valgrind_h
     windows_h
     winsock2_h
+    iphlpapi_h
 "

 INTRINSICS_LIST="
@@ -6408,6 +6409,8 @@ if ! disabled network; then
         check_struct winsock2.h "struct sockaddr" sa_len
         check_type ws2tcpip.h "struct sockaddr_in6"
         check_type ws2tcpip.h "struct sockaddr_storage"
+        check_headers iphlpapi.h && network_extralibs+=" -liphlpapi" || disable iphlpapi_h
+        check_func_headers iphlpapi.h GetBestInterfaceEx
     else
         disable network
     fi
diff --git a/libavformat/ip.c b/libavformat/ip.c
index b2c7ef07e5..4f2d998c34 100644
--- a/libavformat/ip.c
+++ b/libavformat/ip.c
@@ -18,6 +18,9 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */

+#define _DEFAULT_SOURCE
+#define _SVID_SOURCE
+
 #include <string.h>
 #include "ip.h"
 #include "libavutil/avstring.h"
@@ -159,3 +162,45 @@ void ff_ip_reset_filters(IPSourceFilters *filters)
     filters->nb_include_addrs = 0;
     filters->nb_exclude_addrs = 0;
 }
+
+unsigned int ff_ip_resolve_interface_index(struct sockaddr_storage *local_addr)
+{
+#if HAVE_WINSOCK2_H && HAVE_IPHLPAPI_H
+    DWORD retval;
+    unsigned long iface;
+
+    if (local_addr == NULL)
+        return 0;
+
+    retval = GetBestInterfaceEx((struct sockaddr*)local_addr, &iface);
+    if (retval == NO_ERROR)
+        return iface;
+
+    return 0;
+#elif !HAVE_WINSOCK2_H
+    struct ifaddrs *ifaddr, *ifa;
+
+    if (local_addr == NULL)
+        return 0;
+
+    /* Special case for link-local addresses, relevant interface is stored in sin6_scope_id */
+#if HAVE_STRUCT_SOCKADDR_IN6 && defined(IN6_IS_ADDR_LINKLOCAL)
+    if (local_addr->ss_family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6*)local_addr)->sin6_addr)) {
+        unsigned int interface;
+        interface = ((struct sockaddr_in6*)local_addr)->sin6_scope_id;
+
+        if (interface != 0)
+            return interface;
+    }
+#endif
+    if (getifaddrs(&ifaddr) == -1)
+        return 0;
+
+    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+        if (ifa->ifa_addr != NULL && compare_addr((struct sockaddr_storage*)ifa->ifa_addr, local_addr) == 0)
+            return if_nametoindex(ifa->ifa_name);
+    }
+
+    return 0;
+#endif
+}
diff --git a/libavformat/ip.h b/libavformat/ip.h
index b76cdab91c..4085e96f08 100644
--- a/libavformat/ip.h
+++ b/libavformat/ip.h
@@ -69,4 +69,10 @@ int ff_ip_parse_blocks(void *log_ctx, const char *buf, IPSourceFilters *filters)
  */
 void ff_ip_reset_filters(IPSourceFilters *filters);

+/**
+ * Resolves IP address to an associated interface index
+ * @return interface index, 0 as default interface value on error
+ */
+unsigned int ff_ip_resolve_interface_index(struct sockaddr_storage *local_addr);
+
 #endif /* AVFORMAT_IP_H */
diff --git a/libavformat/network.h b/libavformat/network.h
index ca214087fc..2461b651d4 100644
--- a/libavformat/network.h
+++ b/libavformat/network.h
@@ -38,6 +38,10 @@
 #include <winsock2.h>
 #include <ws2tcpip.h>

+#if HAVE_IPHLPAPI_H
+#include <iphlpapi.h>
+#endif
+
 #ifndef EPROTONOSUPPORT
 #define EPROTONOSUPPORT WSAEPROTONOSUPPORT
 #endif
@@ -64,6 +68,8 @@ int ff_neterrno(void);
 #include <netinet/in.h>
 #include <netinet/tcp.h>
 #include <netdb.h>
+#include <net/if.h>
+#include <ifaddrs.h>

 #define ff_neterrno() AVERROR(errno)
 #endif /* HAVE_WINSOCK2_H */
diff --git a/libavformat/udp.c b/libavformat/udp.c
index d9514f5026..9b8ca74282 100644
--- a/libavformat/udp.c
+++ b/libavformat/udp.c
@@ -220,8 +220,7 @@ static int udp_join_multicast_group(int sockfd, struct sockaddr *addr,
         struct ipv6_mreq mreq6;

         memcpy(&mreq6.ipv6mr_multiaddr, &(((struct sockaddr_in6 *)addr)->sin6_addr), sizeof(struct in6_addr));
-        //TODO: Interface index should be looked up from local_addr
-        mreq6.ipv6mr_interface = 0;
+        mreq6.ipv6mr_interface = ff_ip_resolve_interface_index((struct sockaddr_storage *)local_addr);
         if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6)) < 0) {
             ff_log_net_error(logctx, AV_LOG_ERROR, "setsockopt(IPV6_ADD_MEMBERSHIP)");
             return ff_neterrno();
@@ -231,6 +230,38 @@ static int udp_join_multicast_group(int sockfd, struct sockaddr *addr,
     return 0;
 }

+static int udp_set_multicast_interface(int sockfd, struct sockaddr *addr,
+                                    struct sockaddr *local_addr, void *logctx)
+{
+#ifdef IP_MULTICAST_IF
+    if (addr->sa_family == AF_INET) {
+        struct ip_mreq mreq;
+
+        mreq.imr_multiaddr.s_addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr;
+        if (local_addr)
+            mreq.imr_interface = ((struct sockaddr_in *)local_addr)->sin_addr;
+        else
+            mreq.imr_interface.s_addr = INADDR_ANY;
+
+        if (setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, (const void *)&mreq, sizeof(mreq)) < 0) {
+            ff_log_net_error(logctx, AV_LOG_ERROR, "setsockopt(IP_MULTICAST_IF)");
+            return ff_neterrno();
+        }
+    }
+#endif
+#if defined(IPV6_MULTICAST_IF) && defined(IPPROTO_IPV6)
+    if (addr->sa_family == AF_INET6) {
+        unsigned int iface = ff_ip_resolve_interface_index((struct sockaddr_storage *)local_addr);
+
+        if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &iface, sizeof(unsigned int)) < 0) {
+            ff_log_net_error(logctx, AV_LOG_ERROR, "setsockopt(IPV6_MULTICAST_IF)");
+            return ff_neterrno();
+        }
+    }
+#endif
+    return 0;
+}
+
 static int udp_leave_multicast_group(int sockfd, struct sockaddr *addr,
                                      struct sockaddr *local_addr, void *logctx)
 {
@@ -254,8 +285,7 @@ static int udp_leave_multicast_group(int sockfd, struct sockaddr *addr,
         struct ipv6_mreq mreq6;

         memcpy(&mreq6.ipv6mr_multiaddr, &(((struct sockaddr_in6 *)addr)->sin6_addr), sizeof(struct in6_addr));
-        //TODO: Interface index should be looked up from local_addr
-        mreq6.ipv6mr_interface = 0;
+        mreq6.ipv6mr_interface = ff_ip_resolve_interface_index((struct sockaddr_storage *)local_addr);
         if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq6, sizeof(mreq6)) < 0) {
             ff_log_net_error(logctx, AV_LOG_ERROR, "setsockopt(IPV6_DROP_MEMBERSHIP)");
             return -1;
@@ -282,8 +312,7 @@ static int udp_set_multicast_sources(URLContext *h,
             struct group_source_req mreqs;
             int level = addr->sa_family == AF_INET ? IPPROTO_IP : IPPROTO_IPV6;

-            //TODO: Interface index should be looked up from local_addr
-            mreqs.gsr_interface = 0;
+            mreqs.gsr_interface = ff_ip_resolve_interface_index(local_addr);
             memcpy(&mreqs.gsr_group, addr, addr_len);
             memcpy(&mreqs.gsr_source, &sources[i], sizeof(*sources));

@@ -839,10 +868,39 @@ static int udp_open(URLContext *h, const char *uri, int flags)
      * port. This fails on windows. This makes sending to the same address
      * using sendto() fail, so only do it if we're opened in read-only mode. */
     if (s->is_multicast && (h->flags & AVIO_FLAG_READ)) {
+#if defined(SO_BINDTODEVICE) && !HAVE_WINSOCK2_H
+        {
+            struct ifreq ifr;
+            unsigned int iface;
+            iface = ff_ip_resolve_interface_index((struct sockaddr_storage *)&s->local_addr_storage);
+            memset(&ifr, 0, sizeof(ifr));
+
+            if (if_indextoname(iface, ifr.ifr_name)) {
+                if (setsockopt(udp_fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)) < 0) {
+                    perror("setsockopt SO_BINDTODEVICE");
+                }
+            }
+        }
+#endif // defined(SO_BINDTODEVICE) && !HAVE_WINSOCK2_H
         bind_ret = bind(udp_fd,(struct sockaddr *)&s->dest_addr, len);
     }
-    /* bind to the local address if not multicast or if the multicast
-     * bind failed */
+
+    /* bind to ADDR_ANY if the multicast bind failed */
+    if (s->is_multicast && bind_ret < 0) {
+        struct addrinfo *res;
+
+        if (s->dest_addr.ss_family == AF_INET)
+            res = ff_ip_resolve_host(h, "0.0.0.0", 0, SOCK_DGRAM, AF_UNSPEC, 0);
+        else if (s->dest_addr.ss_family == AF_INET6)
+            res = ff_ip_resolve_host(h, "::", 0, SOCK_DGRAM, AF_UNSPEC, 0);
+
+        if (res && res->ai_addr) {
+            bind_ret = bind(udp_fd, res->ai_addr, res->ai_addrlen);
+        }
+
+        freeaddrinfo(res);
+    }
+
     /* the bind is needed to give a port to the socket now */
     if (bind_ret < 0 && bind(udp_fd,(struct sockaddr *)&my_addr, len) < 0) {
         ff_log_net_error(h, AV_LOG_ERROR, "bind failed");
@@ -855,6 +913,12 @@ static int udp_open(URLContext *h, const char *uri, int flags)
     s->local_port = udp_port(&my_addr, len);

     if (s->is_multicast) {
+        if ((ret = udp_set_multicast_interface(udp_fd,
+                                        (struct sockaddr *)&s->dest_addr,
+                                        (struct sockaddr *)&s->local_addr_storage,
+                                        h)) < 0)
+            goto fail;
+
         if (h->flags & AVIO_FLAG_WRITE) {
             /* output */
             if ((ret = udp_set_multicast_ttl(udp_fd, s->ttl, (struct sockaddr *)&s->dest_addr, h)) < 0)
--
2.41.0.windows.2


This message has been marked as Public on 03/14/2024 09:22Z.


More information about the ffmpeg-devel mailing list