diff mbox series

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

Message ID AS8P193MB1997BA7773FAC87C2A1216308D332@AS8P193MB1997.EURP193.PROD.OUTLOOK.COM
State New
Headers show
Series [FFmpeg-devel,v2] avformat: enable UDP IPv6 multicast interface selection | 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 20, 2024, 9:28 a.m. UTC
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). Adjusted binding for multicast
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.

Utilization of sin6_scope_id field enables usage and adequate resolving
of localaddr 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@cubic.com>
---
V1 -> V2: SO_BINDTODEVICE -> sin6_scope_id, addressed comments

 configure             |  3 ++
 libavformat/ip.c      | 49 ++++++++++++++++++++++++++
 libavformat/ip.h      |  6 ++++
 libavformat/network.h |  6 ++++
 libavformat/udp.c     | 81 ++++++++++++++++++++++++++++++++++++++-----
 5 files changed, 137 insertions(+), 8 deletions(-)

--
2.41.0.windows.2


This message has been marked as Public on 03/20/2024 09:28Z.

Comments

Rémi Denis-Courmont March 20, 2024, 10:51 a.m. UTC | #1
Same fundamental problem as previous version, AFAICT.

-1
Ignjatović, Lazar (RS) March 20, 2024, 11:09 a.m. UTC | #2
This message has been marked as Public on 03/20/2024 11:09Z.
On Wednesday, March 20, 2024 11:51 AM Rémi Denis-Courmont wrote:

> Same fundamental problem as previous version, AFAICT.
>
> -1

Would you mind being more specific?
There are 2 problems you pointed out, one about SO_BINDTODEVICE, and another in respect to casting `sockaddr* ` to `sockaddr_in6*` and `sockaddr_storage*` to `sockaddr_in6*`.
Assuming you are reffering to the cast comment, I've addressed this in the other thread. That should not be an issue to my knowledge, and is done on multiple places throughout the codebase.
Also I've assked you for additional comments, which I'm doing again.

Lazar Ignjatović
Associate Software Engineer

Cubic Defense
cubic.com
Rémi Denis-Courmont March 20, 2024, 11:29 a.m. UTC | #3
Le 20 mars 2024 13:09:15 GMT+02:00, "Ignjatović, Lazar (RS)" <Lazar.Ignjatovic@cubic.com> a écrit :
>
>This message has been marked as Public on 03/20/2024 11:09Z.
>On Wednesday, March 20, 2024 11:51 AM Rémi Denis-Courmont wrote:
>
>> Same fundamental problem as previous version, AFAICT.
>>
>> -1
>
>Would you mind being more specific?

You're not supposed to guess the link ID from the local address. This is counter-sensical. And it's entirely possible to have the same LL address assigned to two interfaces (e.g. using a software bridge) anyway.

Either you know the interface you want to use and you should pass it on directly. Or you don't and using link-local doesn't make sense.
Ignjatović, Lazar (RS) March 20, 2024, 11:45 a.m. UTC | #4
This message has been marked as Public on 03/20/2024 11:45Z.
On Wednesday, March 20, 2024 12:30 PM Rémi Denis-Courmont wrote:

> You're not supposed to guess the link ID from the local address. This is counter-sensical. And it's entirely possible to have the same LL address assigned to two interfaces (e.g. using a software bridge) anyway.

You are right about the same LL on multiple interfaces, that’s why the `sin6_scope_id` is used. For specifying LL address, a scoped address format should be used to avoid ambiguity. In all other cases, determining the interface ID by address should be ok, while keeping the same functionality that `localaddr` param has with IPv6. Currently, I don't see any other way of specifying IPv6 multicast interface without the introduction of another query param like `iface` or something.

Let me know what you think.

Sincirely,
Lazar Ignjatović
Associate Software Engineer

Cubic Defense
cubic.com
Rémi Denis-Courmont March 20, 2024, 12:17 p.m. UTC | #5
Le 20 mars 2024 13:45:10 GMT+02:00, "Ignjatović, Lazar (RS)" <Lazar.Ignjatovic@cubic.com> a écrit :
>
>This message has been marked as Public on 03/20/2024 11:45Z.
>On Wednesday, March 20, 2024 12:30 PM Rémi Denis-Courmont wrote:
>
>> You're not supposed to guess the link ID from the local address. This is counter-sensical. And it's entirely possible to have the same LL address assigned to two interfaces (e.g. using a software bridge) anyway.
>
>You are right about the same LL on multiple interfaces, that’s why the `sin6_scope_id` is used. For specifying LL address, a scoped address format should be used to avoid ambiguity.

Yes, the whole point is that LL addresses are ambiguous.

> In all other cases, determining the interface ID by address should be ok,

No! The only other case is non-LL addressing, which does not require link ID at all.

Still -1.
Ignjatović, Lazar (RS) March 20, 2024, 12:35 p.m. UTC | #6
This message has been marked as Public on 03/20/2024 12:35Z.
On Wednesday, March 20, 2024 1:18 PM Rémi Denis-Courmont wrote:

>> In all other cases, determining the interface ID by address should be
>> ok,
>
> No! The only other case is non-LL addressing, which does not require link ID at all.

For IPv4 that is certainly true, but it differs for v6. Specifying egress interface is done by setting the setsockopt(IPV6_MULTICAST_IF) to the value of the interface, not address. On the other hand v4 for the similar option setsockopt(IP_MULTICAST_IF) sets it by definig an address (not interface). Without setting IPV6_MULTICAST_IF to the appropriate interface, default interface is chosen. We want to have control over this, and I cant se how this can be achieved without knowing the interface id.

For ingress IPv6 traffic, the way to filter on which interface is being listened on is by binding to a mcast addres, which has sin6_scope_id properly set.

Lazar Ignjatović
Associate Software Engineer

Cubic Defense
cubic.com
Rémi Denis-Courmont March 21, 2024, 7:26 a.m. UTC | #7
Le 20 mars 2024 14:35:28 GMT+02:00, "Ignjatović, Lazar (RS)" <Lazar.Ignjatovic@cubic.com> a écrit :
> Specifying egress interface is done by setting the setsockopt(IPV6_MULTICAST_IF) to the value of the interface, not address. On the other hand v4 for the similar option setsockopt(IP_MULTICAST_IF) sets it by definig an address (not interface). Without setting IPV6_MULTICAST_IF to the appropriate interface, default interface is chosen. We want to have control over this, and I cant se how this can be achieved without knowing the interface id.

>For ingress IPv6 traffic, the way to filter on which interface is being listened on is by binding to a mcast addres, which has sin6_scope_id properly set.

Your MR makes even less sense for multicast. For multicast there is no local address to match to an interface. So you just have to have the interface specified explicitly in addition to the LL group address. This is true for both egress and ingress.
Ignjatović, Lazar (RS) March 21, 2024, 1:01 p.m. UTC | #8
> Your MR makes even less sense for multicast. For multicast there is no local address to match to an interface. So you just have to have the interface specified explicitly in addition to the LL group address. This is true for both egress and ingress.

I've compared ffmpeg 5.1.2 against my MR on IPv6 multicast. Here are the results:

Recv (on interface other than default)

|     Address    |     localaddr     | 5.1.2 | MR  |
| -------------- | ----------------- | ----- | --- |
| FF11::1        | null              | NO    | NO  |
| FF11::1        | iface addr        | NO    | YES |
| FF11::1%eth0   | null              | NO    | YES |
| FF12::1        | null              | NO    | NO  |
| FF12::1        | iface addr        | NO    | YES |
| FF12::1%eth0   | null              | NO    | YES |
| FF14::1        | null              | NO    | NO  |
| FF14::1        | iface addr        | NO    | YES |
| FF14::1%eth0   | null              | NO    | NO  |
| FF15::1        | null              | NO    | NO  |
| FF15::1        | iface addr        | NO    | YES |
| FF15::1%eth0   | null              | NO    | NO  |
| FF18::1        | null              | NO    | NO  |
| FF18::1        | iface addr        | NO    | YES |
| FF18::1%eth0   | null              | NO    | NO  |

Send (on interface other than default)

|     Address    |     localaddr     | 5.1.2 | MR  |
| -------------- | ----------------- | ----- | --- |
| FF11::1        | null              | NO    | NO  |
| FF11::1        | iface addr        | NO    | YES |
| FF11::1%eth0   | null              | YES   | YES |
| FF12::1        | null              | NO    | NO  |
| FF12::1        | iface addr        | NO    | YES |
| FF12::1%eth0   | null              | YES   | YES |
| FF14::1        | null              | NO    | NO  |
| FF14::1        | iface addr        | NO    | YES |
| FF14::1%eth0   | null              | NO    | NO  |
| FF15::1        | null              | NO    | NO  |
| FF15::1        | iface addr        | NO    | YES |
| FF15::1%eth0   | null              | NO    | NO  |
| FF18::1        | null              | NO    | NO  |
| FF18::1        | iface addr        | NO    | YES |
| FF18::1%eth0   | null              | NO    | NO  |

Here are the reasons why the combinations marked NO under my MR don’t work:

FF11::1      -> ambiguous interface on which to listen/send
FF12::1      -> ambiguous interface on which to listen/send
FF14::1      -> ambiguous interface on which to listen/send, uses only default iface
FF14::1%eth0 -> getaddrinfo errors as it supports %scope only for link-local
FF15::1      -> ambiguous interface on which to listen/send, uses only default iface
FF15::1%eth0 -> getaddrinfo errors as it supports %scope only for link-local
FF18::1      -> ambiguous interface on which to listen/send, uses only default iface
FF18::1%eth0 -> getaddrinfo errors as it supports %scope only for link-local

So the problem to overcome here is controlling on which interface we listen/send multicat IPv6.
For link local yes, %scope is the way to go.

I proposed this MR as the solution to the problem, keepeng the `localaddr` parameter semantics the same as with IPv4.

Other solution could be introduction of a new parameter, as I said before. In this case then, the documentation should
be changed to reflect that `localaddr` is only used for IPv4 addresses, and that same functionality can be obtained with
this new parameter under IPv6.

There will certainly be V3 of the patch, as I've identified minor changes needed for this approach.
If you have any other ideas, I'm open to hearing them.

Lazar Ignjatović
Associate Software Engineer

Cubic Defense
cubic.com

This message has been marked as Public on 03/21/2024 13:01Z.
Rémi Denis-Courmont March 21, 2024, 7:44 p.m. UTC | #9
Le torstaina 21. maaliskuuta 2024, 15.01.09 EET Ignjatović, Lazar (RS) a écrit 
:
> > Your MR makes even less sense for multicast. For multicast there is no
> > local address to match to an interface. So you just have to have the
> > interface specified explicitly in addition to the LL group address. This
> > is true for both egress and ingress.
> 
> I've compared ffmpeg 5.1.2 against my MR on IPv6 multicast. Here are the
> results:

I don't care. That does not make your MR any less counter-sensical.

If you could infer the scope ID from the IPv6 address, there wouldn't be a 
scope ID field in the socket address in the first place. Is it that hard to 
understand?

If you tests show anything, it's that LL addressing is not intended for 
application protocols. This is consistent with the fact that nobody cared to 
support this combination in FFmpeg after 28 years of IPv6. Now I don't mind 
adding proper support, but not kludges.

> So the problem to overcome here is controlling on which interface we
> listen/send multicat IPv6.

That "problem" was solved before FFmpeg existed by adding a separate parameter 
(ping6) or prepending the interface name after a percent sign (glibc).
Ignjatović, Lazar (RS) March 22, 2024, 9:31 a.m. UTC | #10
>> I've compared ffmpeg 5.1.2 against my MR on IPv6 multicast. Here are
>> the
>> results:
>
> I don't care. That does not make your MR any less counter-sensical.

If I may cite Marton Balint
```
d3bda871f033be4825ecb69d444b3396bf2a2eb7
avformat/udp: specify the local address for some source filtered multicast joins

We already use localaddr for the multicast joins without source filters, so we
should use them for source filters as well. This patch only fixes the
IP_ADD_SOURCE_MEMBERSHIP and the IP_BLOCK_SOURCE case.

Unless we do this, the kernel automatically selects an interface based on the
source address, and that interface might be different from the one set in
localaddr. For blocked sources this even casues EINVAL because we joined the
multicast group on a different interface.

Signed-off-by: Marton Balint <cus@passwd.hu>
---
 ...
//TODO: Interface index should be looked up from local_addr
...
```

And another commit
```
ab0812c1a8925a95315354b88b41256faad5faa8
avformat/udp: always use IP_ADD_SOURCE_MEMBERSHIP for subscribing to an UDP multicast source group in IPv4

That alone supports specifying the interface based on its address. Getting the
interface index from the local address seems quite a bit of work in a platform
independent way...

Obviously for IPv6 we still always use MCAST_JOIN_SOURCE_GROUP.

As a side effect this also fixes ticket #7459.

Signed-off-by: Marton Balint <cus@passwd.hu>
```

From this I understand that the intention is to still use localaddr parameter
with the same semantics as with IPv4

> If you could infer the scope ID from the IPv6 address, there wouldn't be a
> scope ID field in the socket address in the first place. Is it that hard to
> understand?

Well, it's not that simple. Here I have to cite ipv6(7) — Linux manual page
```
       sin6_scope_id
       is an ID depending on the scope of the address.  It is new in
       Linux 2.4.  Linux supports it only for link-local addresses, in
       that case sin6_scope_id contains the interface index (see
       netdevice(7))
```

> That "problem" was solved before FFmpeg existed by adding a separate parameter
> (ping6) or prepending the interface name after a percent sign (glibc).

And this is the closest you've got to providing an alternate solution to the proposed.
Which is adding another parameter, or relying on % for every type of multicast.

> If you tests show anything, it's that LL addressing is not intended for application protocols.

And just to clarify
FF11::1%eth0 is an interface-local multicast, thus supported
FF12::1%eth0 is a link-local multicast, thus supported

All other multicast scopes are not supported by Linux in respect to %scope.

If we choose to support ff15::1%eth0 for example, we are introducing something that
is not normally supported on Linux, potentially creating confusion among users.
Interface parameter seems like a better option than the %scope for everything.

Can someone else agree with this?

If this is, in fact, the preferred way, then okay. I will propose the v3 of the patch
done this way. Still, I prefer keeping the IPv4 semantics for v6.

Lazar Ignjatović
Associate Software Engineer

Cubic Defense
cubic.com

This message has been marked as Public on 03/22/2024 09:31Z.
Rémi Denis-Courmont March 22, 2024, 9:55 a.m. UTC | #11
Le 22 mars 2024 11:31:28 GMT+02:00, "Ignjatović, Lazar (RS)" <Lazar.Ignjatovic@cubic.com> a écrit :
>>> I've compared ffmpeg 5.1.2 against my MR on IPv6 multicast. Here are
>>> the
>>> results:
>>
>> I don't care. That does not make your MR any less counter-sensical.
>
>If I may cite Marton Balint

I am not answerable for something somebody else did.

And yes, IPv4 selects multicast interfaces by IPv4 address. That was a poor design choice in hindsight and IPv6 was instead designed to select multicast interfaces by local link ID.

You've made it clear up-thread that you know all of this, as well as you know how to specify link IDs (with % or an additional parameter). So why are you arguing now?
Ignjatović, Lazar (RS) March 22, 2024, 10:16 a.m. UTC | #12
This message has been marked as Public on 03/22/2024 10:16Z.
On Friday, March 22, 2024 10:55 AM Rémi Denis-Courmont wrote:

> So why are you arguing now?

Because I still belive that using localaddr is the better approach.
However, I will create and submit v3 of this patch and also create
another patch following your suggestions of either using a new param
or using scope_id.

Both will be available, so the maintainers can choose. I think that’s fair.

Lazar Ignjatović
Associate Software Engineer

Cubic Defense
cubic.com
Rémi Denis-Courmont March 26, 2024, 6:24 p.m. UTC | #13
Le perjantaina 22. maaliskuuta 2024, 12.16.30 EET Ignjatović, Lazar (RS) a 
écrit :
> This message has been marked as Public on 03/22/2024 10:16Z.
> 
> On Friday, March 22, 2024 10:55 AM Rémi Denis-Courmont wrote:
> > So why are you arguing now?
> 
> Because I still belive that using localaddr is the better approach.

You're entitled to your opinion. That won't change the fact that:

1) The rest of the industry disagrees with you. And by that, I mean the IETF 
(see the advanced IPv6 socket API specifications), the OS and IP stacks and as 
well as existing software other than FFmpeg.

2) It is obvjectively far simpler and more user-friendly to request an 
interface name than a link-local address. Back when link-local addresses were 
derived from the MAC address, you would have had to remember the MAC, which 
nobody in its right mind did. But nowadays, link-local addresses are actually 
random, so that's moot.

In other words, this is ridiculous.
diff mbox series

Patch

diff --git a/configure b/configure
index e019d1b996..08f35bbd25 100755
--- a/configure
+++ b/configure
@@ -2258,6 +2258,7 @@  HEADERS_LIST="
     valgrind_valgrind_h
     windows_h
     winsock2_h
+    iphlpapi_h
 "

 INTRINSICS_LIST="
@@ -6406,6 +6407,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/libavformat/ip.c b/libavformat/ip.c
index b2c7ef07e5..4b83e79298 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,49 @@  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 && defined(IN6_IS_ADDR_LINKLOCAL)
+    /* Special case for link-local addresses, relevant interface is stored in sin6_scope_id */
+    if (local_addr->ss_family == AF_INET6) {
+        iface = ((struct sockaddr_in6*)local_addr)->sin6_scope_id;
+        if (iface != 0)
+            return iface;
+    }
+#endif /* HAVE_STRUCT_SOCKADDR_IN6 && defined(IN6_IS_ADDR_LINKLOCAL) */
+    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 */
+}
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..c2420a7f0d 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,43 @@  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;
+
+        if (local_addr)
+            iface = ff_ip_resolve_interface_index((struct sockaddr_storage *)local_addr);
+        else
+            iface = ((struct sockaddr_in6*)local_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 +290,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 +317,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 +873,35 @@  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 HAVE_STRUCT_SOCKADDR_IN6 && !HAVE_WINSOCK2_H
+        if (s->dest_addr.ss_family == AF_INET6) {
+            unsigned int iface;
+            struct sockaddr_in6 *addr;
+            addr = (struct sockaddr_in6 *)&s->dest_addr;
+            iface = ff_ip_resolve_interface_index(&s->local_addr_storage);
+            if (iface)
+                addr->sin6_scope_id = iface;
+        }
+#endif
         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 +914,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)