diff mbox series

[FFmpeg-devel] avformat: enable UDP IPv6 multicast interface selection using zone index

Message ID AS8P193MB199762E3A2E8FB5E3933F6B38D352@AS8P193MB1997.EURP193.PROD.OUTLOOK.COM
State New
Headers show
Series [FFmpeg-devel] avformat: enable UDP IPv6 multicast interface selection using zone index | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Ignjatović, Lazar (RS) March 26, 2024, 12:34 p.m. UTC
avformat: enable UDP IPv6 multicast interface selection using zone index

Enabled IPv6 interface selection using zone index. Properly resolved
interface index in places where default 0 interface index is used
(marked with TODO: within udp.c). Adjusted binding for multicast sockets
that are used for reading from the network.

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.

IPv6 interfaces are resolved based on sin6_scope_id field, while IPv4
interface resolution is performed based on address.

Closes: #368

Signed-off-by: Lazar Ignjatovic <Lazar.Ignjatovic@cubic.com>
---
NOTE: Due to comments, this patch is proposed as one of two alternatives
The other alternative uses `localaddr` for defining interfaces.

 configure             |  3 ++
 doc/protocols.texi    |  2 +-
 libavformat/ip.c      | 48 ++++++++++++++++++++++++
 libavformat/ip.h      |  6 +++
 libavformat/network.h |  6 +++
 libavformat/udp.c     | 87 ++++++++++++++++++++++++++++++++++++++-----
 6 files changed, 142 insertions(+), 10 deletions(-)

--
2.41.0.windows.2


This message has been marked as Public on 03/26/2024 12:34Z.
diff mbox series

Patch

diff --git a/configure b/configure
index 2a1d22310b..35d6a0b78c 100755
--- a/configure
+++ b/configure
@@ -2307,6 +2307,7 @@  HEADERS_LIST="
     valgrind_valgrind_h
     windows_h
     winsock2_h
+    iphlpapi_h
 "

 INTRINSICS_LIST="
@@ -6475,6 +6476,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 -liphlpapi && network_extralibs="$network_extralibs -liphlpapi" || disable iphlpapi_h
+        check_func_headers iphlpapi.h GetBestInterfaceEx $network_extralibs
     else
         disable network
     fi
diff --git a/doc/protocols.texi b/doc/protocols.texi
index f54600b846..a8892845d3 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -2027,7 +2027,7 @@  packet bursts.
 Override the local UDP port to bind with.

 @item localaddr=@var{addr}
-Local IP address of a network interface used for sending packets or joining
+Local IPv4 address of a network interface used for sending packets or joining
 multicast groups.

 @item pkt_size=@var{size}
diff --git a/libavformat/ip.c b/libavformat/ip.c
index b2c7ef07e5..dc488b12c2 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,48 @@  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
+#if 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;
+#endif /* HAVE_IPHLPAPI_H */
+    return 0;
+#else /* HAVE_WINSOCK2_H */
+    struct ifaddrs *ifaddr, *ifa;
+    unsigned int iface;
+
+    if (local_addr == NULL)
+        return 0;
+
+#if HAVE_STRUCT_SOCKADDR_IN6
+    if (local_addr->ss_family == AF_INET6) {
+        iface = ((struct sockaddr_in6*)local_addr)->sin6_scope_id;
+        if (iface)
+            return iface;
+    }
+#endif /* HAVE_STRUCT_SOCKADDR_IN6 */
+    if (getifaddrs(&ifaddr) == -1)
+        return 0;
+
+    iface = 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) {
+            iface = if_nametoindex(ifa->ifa_name);
+            break;
+        }
+    }
+
+    freeifaddrs(ifaddr);
+    return iface;
+#endif /* HAVE_WINSOCK2_H */
+}
\ No newline at end of file
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..415377e032 100644
--- a/libavformat/udp.c
+++ b/libavformat/udp.c
@@ -35,6 +35,7 @@ 
 #include "libavutil/opt.h"
 #include "libavutil/log.h"
 #include "libavutil/time.h"
+#include "libavutil/avstring.h"
 #include "internal.h"
 #include "network.h"
 #include "os_support.h"
@@ -220,8 +221,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 = ((struct sockaddr_in6 *)addr)->sin6_scope_id;
         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 +231,39 @@  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;
+        iface = ((struct sockaddr_in6 *)addr)->sin6_scope_id;
+
+        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 +287,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 = ((struct sockaddr_in6 *)addr)->sin6_scope_id;
         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 +314,11 @@  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;
+            if (level == IPPROTO_IPV6)
+                mreqs.gsr_interface = ((struct sockaddr_in6 *)addr)->sin6_scope_id;
+            else
+                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));

@@ -340,9 +375,22 @@  static int udp_set_url(URLContext *h,
 {
     struct addrinfo *res0;
     int addr_len;
+    const char *host, *sc;
+    char address[1024], scope[1024];

-    res0 = ff_ip_resolve_host(h, hostname, port, SOCK_DGRAM, AF_UNSPEC, 0);
+    if (sc = strchr(hostname, '%')) {
+        av_strlcpy(address, hostname, FFMIN(1024, sc - hostname + 1));
+        av_strlcpy(scope, sc + 1, FFMIN(1024, strlen(hostname) - (sc - hostname)));
+        host = address;
+    } else {
+        host = hostname;
+    }
+
+    res0 = ff_ip_resolve_host(h, host, port, SOCK_DGRAM, AF_UNSPEC, 0);
     if (!res0) return AVERROR(EIO);
+    if (res0->ai_family== AF_INET6 && strlen(scope) > 0) {
+        ((struct sockaddr_in6*)res0->ai_addr)->sin6_scope_id = if_nametoindex(scope);
+    }
     memcpy(addr, res0->ai_addr, res0->ai_addrlen);
     addr_len = res0->ai_addrlen;
     freeaddrinfo(res0);
@@ -841,8 +889,23 @@  static int udp_open(URLContext *h, const char *uri, int flags)
     if (s->is_multicast && (h->flags & AVIO_FLAG_READ)) {
         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 +918,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)