diff mbox series

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

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

Checks

Context Check Description
yinshiyou/configure_loongarch64 warning Failed to apply patch
andriy/configure_x86 warning Failed to apply patch

Commit Message

Ignjatović, Lazar (RS) April 11, 2024, 7:45 a.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.

Closes: #368

Signed-off-by: Lazar Ignjatovic <Lazar.Ignjatovic@cubic.com>
---
V1 -> V2 reverted iface resolution for IPv4 MCAST_JOIN_SOURCE_GROUP
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/network.h |  6 +++
 libavformat/udp.c     | 85 +++++++++++++++++++++++++++++++++++++++----
 4 files changed, 88 insertions(+), 8 deletions(-)

--
2.41.0.windows.2


This message has been marked as Public on 04/11/2024 07:45Z.

Comments

Lynne April 11, 2024, 7:50 a.m. UTC | #1
Apr 11, 2024, 09:45 by Lazar.Ignjatovic@cubic.com:

> 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.
>
> Closes: #368
>
> Signed-off-by: Lazar Ignjatovic <Lazar.Ignjatovic@cubic.com>
> ---
> V1 -> V2 reverted iface resolution for IPv4 MCAST_JOIN_SOURCE_GROUP
> 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/network.h |  6 +++
>  libavformat/udp.c     | 85 +++++++++++++++++++++++++++++++++++++++----
>  4 files changed, 88 insertions(+), 8 deletions(-)
>
> 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/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..00c73f9ec9 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;
> @@ -284,6 +316,9 @@ static int udp_set_multicast_sources(URLContext *h,
>
>  //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;
> +
>  memcpy(&mreqs.gsr_group, addr, addr_len);
>  memcpy(&mreqs.gsr_source, &sources[i], sizeof(*sources));
>
> @@ -340,9 +375,24 @@ 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 HAVE_IPHLPAPI_H || !HAVE_WINSOCK2_H
> +    if (res0->ai_family== AF_INET6 && strlen(scope) > 0) {
> +        ((struct sockaddr_in6*)res0->ai_addr)->sin6_scope_id = if_nametoindex(scope);
> +    }
> +#endif
>  memcpy(addr, res0->ai_addr, res0->ai_addrlen);
>  addr_len = res0->ai_addrlen;
>  freeaddrinfo(res0);
> @@ -841,8 +891,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 +920,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
>

Is there a reason why we can't switch to IPv4 addresses mapped
in IPv6 and just use the IPv6 API everywhere?
Ignjatović, Lazar (RS) April 11, 2024, 7:58 a.m. UTC | #2
This message has been marked as Public on 04/11/2024 07:58Z.
On Thursday, April 11, 2024 9:50 AM Lynne wrote:

> Is there a reason why we can't switch to IPv4 addresses mapped in IPv6 and just use the IPv6 API everywhere?

I'm not the person to give you that answer.
My guess is that due to API discrepancies between v4 and v6 it would
require a _lot_ of work to make that jump. Also, different levels of IPv6
support across kernels doesn't make it any easier.

Lazar Ignjatović
Associate Software Engineer

Cubic Defense
cubic.com
Rémi Denis-Courmont April 28, 2024, 6:15 p.m. UTC | #3
Le torstaina 11. huhtikuuta 2024, 10.50.01 EEST Lynne a écrit :
> Is there a reason why we can't switch to IPv4 addresses mapped
> in IPv6 and just use the IPv6 API everywhere?

IPv6-mapped IPv4 addresses are pretty much deprecated, if supported anymore. 
Some people consider them insecure. But even if you don't agree with that 
assessment, the fact of the matter is that they are not portable.
Lynne April 28, 2024, 8:11 p.m. UTC | #4
Apr 28, 2024, 20:15 by remi@remlab.net:

> Le torstaina 11. huhtikuuta 2024, 10.50.01 EEST Lynne a écrit :
>
>> Is there a reason why we can't switch to IPv4 addresses mapped
>> in IPv6 and just use the IPv6 API everywhere?
>>
>
> IPv6-mapped IPv4 addresses are pretty much deprecated, if supported anymore. 
> Some people consider them insecure. But even if you don't agree with that 
> assessment, the fact of the matter is that they are not portable.
>

They were they deprecated? Why would they not be portable?
They seem to work fine in libavtransport.
Brad Smith April 29, 2024, 12:01 a.m. UTC | #5
On 2024-04-28 2:15 p.m., Rémi Denis-Courmont wrote:
> Le torstaina 11. huhtikuuta 2024, 10.50.01 EEST Lynne a écrit :
>> Is there a reason why we can't switch to IPv4 addresses mapped
>> in IPv6 and just use the IPv6 API everywhere?
> IPv6-mapped IPv4 addresses are pretty much deprecated, if supported anymore.
> Some people consider them insecure. But even if you don't agree with that
> assessment, the fact of the matter is that they are not portable.

OpenBSD does not support IPv4-mapped IPv6 addresses and has a split stack.
Rémi Denis-Courmont April 29, 2024, 7:33 a.m. UTC | #6
Le 28 avril 2024 23:11:48 GMT+03:00, Lynne <dev@lynne.ee> a écrit :
>Apr 28, 2024, 20:15 by remi@remlab.net:
>
>> Le torstaina 11. huhtikuuta 2024, 10.50.01 EEST Lynne a écrit :
>>
>>> Is there a reason why we can't switch to IPv4 addresses mapped
>>> in IPv6 and just use the IPv6 API everywhere?
>>>
>>
>> IPv6-mapped IPv4 addresses are pretty much deprecated, if supported anymore. 
>> Some people consider them insecure. But even if you don't agree with that 
>> assessment, the fact of the matter is that they are not portable.
>>
>
>They were they deprecated?

They caused more bugs than they solved problems (because what we need is to add IPv6 to IPv4 apps, not IPv4 to IPv6 apps).

> Why would they not be portable?

Some OS just don't support them, or have a privileged setting to disable them globally. As a user process, you can't rely on them.
Lynne April 29, 2024, 8:20 a.m. UTC | #7
Apr 29, 2024, 09:34 by remi@remlab.net:

>
>
> Le 28 avril 2024 23:11:48 GMT+03:00, Lynne <dev@lynne.ee> a écrit :
> >Apr 28, 2024, 20:15 by remi@remlab.net:
>
>>> Le torstaina 11. huhtikuuta 2024, 10.50.01 EEST Lynne a écrit :
>>>
>>>> Is there a reason why we can't switch to IPv4 addresses mapped
>>>> in IPv6 and just use the IPv6 API everywhere?
>>>>
>>>
>>> IPv6-mapped IPv4 addresses are pretty much deprecated, if supported anymore. 
>>> Some people consider them insecure. But even if you don't agree with that 
>>> assessment, the fact of the matter is that they are not portable.
>>>
> >They were they deprecated?
>
> They caused more bugs than they solved problems (because what we need is to add IPv6 to IPv4 apps, not IPv4 to IPv6 apps).
>

What bugs did they cause? I know they're not very well used, because the
documentation is very lacking, but given that it's a feature of the standard
network stack of both Linux and Windows, surely it's fuzzed and unit tested
more than enough.


>> Why would they not be portable?
>>
>
> Some OS just don't support them, or have a privileged setting to disable them globally. As a user process, you can't rely on them.
>

Which ones have a setting?
Some have a setting to disable IPv6 globally which must be fairly more used,
but I haven't heard ones which have that setting in.
Rémi Denis-Courmont April 29, 2024, 9:56 a.m. UTC | #8
Le 29 avril 2024 11:20:24 GMT+03:00, Lynne <dev@lynne.ee> a écrit :
>> >They were they deprecated?
>>
>> They caused more bugs than they solved problems (because what we need is to add IPv6 to IPv4 apps, not IPv4 to IPv6 apps).
>>
>
>What bugs did they cause?

Obviously anything that assumes IPv6 semantics is prone to breaking.

> i know they're not very well used, because the
>documentation is very lacking,

No, they're not used because they are officially deprecated for 15+ years (see RFC5156).
Lynne April 29, 2024, 10:32 a.m. UTC | #9
Apr 29, 2024, 11:56 by remi@remlab.net:

>
>
> Le 29 avril 2024 11:20:24 GMT+03:00, Lynne <dev@lynne.ee> a écrit :
>
>>> >They were they deprecated?
>>>
>>> They caused more bugs than they solved problems (because what we need is to add IPv6 to IPv4 apps, not IPv4 to IPv6 apps).
>>>
> >What bugs did they cause?
>
> Obviously anything that assumes IPv6 semantics is prone to breaking.
>
>> i know they're not very well used, because the
>>
> >documentation is very lacking,
>
> No, they're not used because they are officially deprecated for 15+ years (see RFC5156).
>

The RFC says that IPv4-Compatible addresses are deprecated,
but doesn't say that about IPv4-Mapped addresses.

The kernel would have dropped support for them if that had happened,
they started doing a cleanup of the whole stack a year ago and even
dropped UDP-Lite.
Rémi Denis-Courmont April 29, 2024, 11:34 a.m. UTC | #10
Le 29 avril 2024 13:32:41 GMT+03:00, Lynne <dev@lynne.ee> a écrit :
>Apr 29, 2024, 11:56 by remi@remlab.net:
>
>>
>>
>> Le 29 avril 2024 11:20:24 GMT+03:00, Lynne <dev@lynne.ee> a écrit :
>>
>>>> >They were they deprecated?
>>>>
>>>> They caused more bugs than they solved problems (because what we need is to add IPv6 to IPv4 apps, not IPv4 to IPv6 apps).
>>>>
>> >What bugs did they cause?
>>
>> Obviously anything that assumes IPv6 semantics is prone to breaking.
>>
>>> i know they're not very well used, because the
>>>
>> >documentation is very lacking,
>>
>> No, they're not used because they are officially deprecated for 15+ years (see RFC5156).
>>
>
>The RFC says that IPv4-Compatible addresses are deprecated,
>but doesn't say that about IPv4-Mapped addresses.

Ah, yeah, the case against mapped addresses did not make it to RFC status, likely because the main author tragically died, see draft-itojun-v6ops-v4mapped-harmful.

But anyway, I am not a vendor of IP stacks. I am just the messenger here: mapped addresses are not portable and so you should not rely on them in portable code.
Lynne April 29, 2024, 12:03 p.m. UTC | #11
Apr 29, 2024, 13:34 by remi@remlab.net:

>
>
> Le 29 avril 2024 13:32:41 GMT+03:00, Lynne <dev@lynne.ee> a écrit :
> >Apr 29, 2024, 11:56 by remi@remlab.net:
>
>>>
>>>
>>> Le 29 avril 2024 11:20:24 GMT+03:00, Lynne <dev@lynne.ee> a écrit :
>>>
>>>>> >They were they deprecated?
>>>>>
>>>>> They caused more bugs than they solved problems (because what we need is to add IPv6 to IPv4 apps, not IPv4 to IPv6 apps).
>>>>>
>>> >What bugs did they cause?
>>>
>>> Obviously anything that assumes IPv6 semantics is prone to breaking.
>>>
>>>> i know they're not very well used, because the
>>>>
>>> >documentation is very lacking,
>>>
>>> No, they're not used because they are officially deprecated for 15+ years (see RFC5156).
>>>
> >The RFC says that IPv4-Compatible addresses are deprecated,
> >but doesn't say that about IPv4-Mapped addresses.
>
> Ah, yeah, the case against mapped addresses did not make it to RFC status, likely because the main author tragically died, see draft-itojun-v6ops-v4mapped-harmful.
>

The draft recommends that their use on the wire is forbidden,
which is a niche use-case some vendors did.


> But anyway, I am not a vendor of IP stacks. I am just the messenger here: mapped addresses are not portable and so you should not rely on them in portable code.
>

Fair enough, thanks for looking into it, will keep it in mind that
OpenBSD doesn't implement this yet.
I'm sure IPv4 will be deprecated soon. The Czech government is
so optimistic it announced it'll stop hosting its website under it
in just 8 years from now.
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/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..00c73f9ec9 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;
@@ -284,6 +316,9 @@  static int udp_set_multicast_sources(URLContext *h,

             //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;
+
             memcpy(&mreqs.gsr_group, addr, addr_len);
             memcpy(&mreqs.gsr_source, &sources[i], sizeof(*sources));

@@ -340,9 +375,24 @@  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 HAVE_IPHLPAPI_H || !HAVE_WINSOCK2_H
+    if (res0->ai_family== AF_INET6 && strlen(scope) > 0) {
+        ((struct sockaddr_in6*)res0->ai_addr)->sin6_scope_id = if_nametoindex(scope);
+    }
+#endif
     memcpy(addr, res0->ai_addr, res0->ai_addrlen);
     addr_len = res0->ai_addrlen;
     freeaddrinfo(res0);
@@ -841,8 +891,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 +920,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)