[FFmpeg-devel] [PATCH] avformat/udp: Select output interfaces for ipv6 multicast
Rémi Denis-Courmont
remi at remlab.net
Wed Aug 6 18:28:59 EEST 2025
Le 5 août 2025 14:56:18 GMT+07:00, Peter Enderborg <peterend at axis.com> a écrit :
>This fixes two old TODO's in ipv6 multicast handling.
>A helper function to iterate over all interfaces added to
>help join the multicast group on interface if approperite.
>
>The default value is IN6ADDR_ANY (::) and it then joins all interfaces
>that are up and have multicast support. Local address can be
>specified and then it join ALL interfaces that use that specific
>local address.
>
>Limitations (TODO's)
>Handling when network configuration is changed. if up/down etc.
>
>Signed-off-by: Peter Enderborg <peterend at axis.com>
>---
> libavformat/udp.c | 108 ++++++++++++++++++++++++++++++++++++++--------
> 1 file changed, 90 insertions(+), 18 deletions(-)
>
>diff --git a/libavformat/udp.c b/libavformat/udp.c
>index 035db785c2..0a981447a5 100644
>--- a/libavformat/udp.c
>+++ b/libavformat/udp.c
>@@ -68,6 +68,13 @@
> #include "libavutil/thread.h"
> #endif
>
>+#if HAVE_STRUCT_IPV6_MREQ && defined(IPPROTO_IPV6)
>+#include <sys/types.h>
>+#include <net/if.h>
>+#include <sys/ioctl.h>
>+#include <ifaddrs.h>
>+#endif
>+
> #ifndef IPV6_ADD_MEMBERSHIP
> #define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
> #define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP
>@@ -205,6 +212,79 @@ static int udp_set_multicast_ttl(int sockfd, int mcastTTL,
>
> return 0;
> }
>+#if HAVE_STRUCT_IPV6_MREQ && defined(IPPROTO_IPV6)
>+static int udp_ipv6_multicast_iterate(int sockfd, struct sockaddr_in6 *addr,
>+ struct sockaddr_in6 *local_addr, int mop, void *logctx)
>+{
>+ struct in6_addr anyipv6 = IN6ADDR_ANY_INIT;
>+ struct ifaddrs *ifal=NULL,*ife=NULL;
>+ int iindex_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
>+ int membership_changed = 0;
>+
>+ if (iindex_fd >= 0 && !getifaddrs(&ifal)) {
>+ for(ife=ifal; ife!=NULL; ife=ife->ifa_next) {
>+ if (ife->ifa_addr &&
>+ (ife->ifa_addr->sa_family == AF_INET6) &&
>+ (ife->ifa_flags & IFF_MULTICAST) &&
>+ (ife->ifa_flags & IFF_UP)) {
>+ if ((!memcmp(&local_addr->sin6_addr, &anyipv6, sizeof(struct in6_addr))) ||
>+ (!memcmp(&local_addr->sin6_addr, &((struct sockaddr_in6 *)ife->ifa_addr)->sin6_addr, sizeof(struct in6_addr)))) {
>+ struct ifreq if_req;
>+ strncpy(if_req.ifr_name, ife->ifa_name, IFNAMSIZ);
>+ if (ioctl(iindex_fd, SIOCGIFINDEX, &if_req) != -1) {
>+ struct ipv6_mreq mreq6;
>+ memcpy(&mreq6.ipv6mr_multiaddr, &addr->sin6_addr, sizeof(struct in6_addr));
>+ mreq6.ipv6mr_interface = if_req.ifr_ifindex;
>+ switch (mop) {
>+ case IPV6_JOIN_GROUP:
>+ if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6)) == 0) {
>+ membership_changed = 1;
>+ } else {
>+ /* Subscribe for all ipv6 addresses belong to a
>+ interface.It can generate EADDRINUSE but
>+ is accaptable. */
>+ if (errno != EADDRINUSE) {
>+ ff_log_net_error(logctx, AV_LOG_ERROR, "setsockopt(IPV6_ADD_MEMBERSHIP)");
>+ goto error;
>+ }
>+ }
>+ break;
>+ case IPV6_LEAVE_GROUP:
>+ if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq6, sizeof(mreq6)) == 0) {
>+ membership_changed = 1;
You can't leave a group you didn't join and you can't assume that the set of interfaces is constant over time.
Besides joining a group on more than one interface is generally a terrible idea, as you would receive the same data multiple time (+ reordering).
It makes sense to track interfaces and subscribe to all of them for discovery protocols, but it's way more involved (and less portable) than this.
>+ } else {
>+ /* Unsubscribe for all ipv6 addresses belong to a
>+ interface. Ignore EADDRNOTAVAIL. */
>+ if (errno != EADDRNOTAVAIL) {
>+ ff_log_net_error(logctx, AV_LOG_ERROR, "setsockopt(IPV6_DROP_MEMBERSHIP)");
>+ goto error;
>+ }
>+ }
>+ break;
>+ default:
>+ av_log(logctx, AV_LOG_ERROR, "unknown multicast operation\n");
>+ goto error;
>+ }
>+ }
>+ }
>+ }
>+ }
>+ }
>+ freeifaddrs(ifal);
>+ close(iindex_fd);
>+
>+ if (!membership_changed) {
>+ av_log(logctx, AV_LOG_ERROR, "no valid interfaces found\n");
>+ return AVERROR(EINVAL);
>+ }
>+ return 0;
>+error:
>+ freeifaddrs(ifal);
>+ close(iindex_fd);
>+ av_log(logctx, AV_LOG_ERROR, "udp_ipv6_multicast_iterate failed\n");
>+ return AVERROR(EINVAL);
>+}
>+#endif
>
> static int udp_join_multicast_group(int sockfd, struct sockaddr *addr,
> struct sockaddr *local_addr, void *logctx)
>@@ -226,15 +306,11 @@ static int udp_join_multicast_group(int sockfd, struct sockaddr *addr,
> #endif
> #if HAVE_STRUCT_IPV6_MREQ && defined(IPPROTO_IPV6)
> if (addr->sa_family == AF_INET6) {
>- 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;
>- 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();
>- }
>+ return udp_ipv6_multicast_iterate(sockfd,
>+ (struct sockaddr_in6 *)addr,
>+ (struct sockaddr_in6 *)local_addr,
>+ IPV6_JOIN_GROUP,
>+ logctx);
> }
> #endif
> return 0;
>@@ -260,15 +336,11 @@ static int udp_leave_multicast_group(int sockfd, struct sockaddr *addr,
> #endif
> #if HAVE_STRUCT_IPV6_MREQ && defined(IPPROTO_IPV6)
> if (addr->sa_family == AF_INET6) {
>- 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;
>- 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;
>- }
>+ return udp_ipv6_multicast_iterate(sockfd,
>+ (struct sockaddr_in6 *)addr,
>+ (struct sockaddr_in6 *)local_addr,
>+ IPV6_LEAVE_GROUP,
>+ logctx);
> }
> #endif
> return 0;
More information about the ffmpeg-devel
mailing list