Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
From: Peter Enderborg via ffmpeg-devel <ffmpeg-devel@ffmpeg.org>
To: <michael@niedermayer.cc>, <ffmpeg-devel@ffmpeg.org>, <remi@remlab.net>
Cc: Peter Enderborg <peterend@axis.com>
Subject: [FFmpeg-devel] [PATCH v3 3/5] libavformat: udp.c Add support for multi or single join
Date: Fri, 29 Aug 2025 16:04:57 +0200
Message-ID: <20250829140459.3220037-4-peterend@axis.com> (raw)
In-Reply-To: <20250829140459.3220037-1-peterend@axis.com>

A new commandline option to set for joining multiple
interfaces. The default is one, and it is also a
special case falling back to output routing table
for selecting interface on group join. On group
join it search over all interfaces.

Change-Id: Idbf88c3ecd7d27cba0a236a8fa2451a85143d068
Signed-off-by: Peter Enderborg <peterend@axis.com>
---
 libavformat/rtpproto.c |  3 ++
 libavformat/rtsp.c     |  3 +-
 libavformat/rtsp.h     |  1 +
 libavformat/udp.c      | 82 +++++++++++++++++++++++++++++++-----------
 4 files changed, 67 insertions(+), 22 deletions(-)

diff --git a/libavformat/rtpproto.c b/libavformat/rtpproto.c
index 15d0050936..c6abd7a02c 100644
--- a/libavformat/rtpproto.c
+++ b/libavformat/rtpproto.c
@@ -61,6 +61,7 @@ typedef struct RTPContext {
     char *fec_options_str;
     int64_t rw_timeout;
     char *localaddr;
+    int multicast_max_join;
 } RTPContext;
 
 #define OFFSET(x) offsetof(RTPContext, x)
@@ -81,6 +82,7 @@ static const AVOption options[] = {
     { "block",              "Block list",                                                       OFFSET(block),           AV_OPT_TYPE_STRING, { .str = NULL },               .flags = D|E },
     { "fec",                "FEC",                                                              OFFSET(fec_options_str), AV_OPT_TYPE_STRING, { .str = NULL },               .flags = E },
     { "localaddr",          "Local address",                                                    OFFSET(localaddr),       AV_OPT_TYPE_STRING, { .str = NULL },               .flags = D|E },
+    { "multicast_max_join", "Number of ipv6 network intefaces to join multicast group",         OFFSET(multicast_max_join), AV_OPT_TYPE_INT,    { .i64 = 1 }, 1, INT_MAX, .flags = D|E },
     { NULL }
 };
 
@@ -199,6 +201,7 @@ static void build_udp_url(RTPContext *s,
         url_add_option(buf, buf_size, "block=%s", exclude_sources);
     if (localaddr && localaddr[0])
         url_add_option(buf, buf_size, "localaddr=%s", localaddr);
+    url_add_option(buf, buf_size, "multicast_max_join=%d", s->multicast_max_join);
 }
 
 /**
diff --git a/libavformat/rtsp.c b/libavformat/rtsp.c
index 10355b89b8..08ce628b0c 100644
--- a/libavformat/rtsp.c
+++ b/libavformat/rtsp.c
@@ -81,6 +81,7 @@
 #define COMMON_OPTS() \
     { "reorder_queue_size", "set number of packets to buffer for handling of reordered packets", OFFSET(reordering_queue_size), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, DEC }, \
     { "buffer_size",        "Underlying protocol send/receive buffer size",                  OFFSET(buffer_size),           AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, DEC|ENC }, \
+    { "multicast_max_join", "Number of ipv6 network intefaces to join multicast group",      OFFSET(multicast_max_join),           AV_OPT_TYPE_INT, { .i64 = 1 }, 1, INT_MAX, ENC }, \
     { "pkt_size",           "Underlying protocol send packet size",                          OFFSET(pkt_size),              AV_OPT_TYPE_INT, { .i64 = 1472 }, -1, INT_MAX, ENC } \
 
 
@@ -139,7 +140,7 @@ static AVDictionary *map_to_opts(RTSPState *rt)
     av_dict_set_int(&opts, "pkt_size",    rt->pkt_size,    0);
     if (rt->localaddr && rt->localaddr[0])
         av_dict_set(&opts, "localaddr", rt->localaddr, 0);
-
+    av_dict_set_int(&opts, "multicast_max_join", rt->multicast_max_join, 0);
     return opts;
 }
 
diff --git a/libavformat/rtsp.h b/libavformat/rtsp.h
index ca278acd43..626114fb5a 100644
--- a/libavformat/rtsp.h
+++ b/libavformat/rtsp.h
@@ -419,6 +419,7 @@ typedef struct RTSPState {
     int buffer_size;
     int pkt_size;
     char *localaddr;
+    int multicast_max_join;
 
     /**
      * Options used for TLS based RTSP streams.
diff --git a/libavformat/udp.c b/libavformat/udp.c
index 9ffd55e017..9c2f904e50 100644
--- a/libavformat/udp.c
+++ b/libavformat/udp.c
@@ -74,6 +74,7 @@
 #include <sys/ioctl.h>
 #include <ifaddrs.h>
 #endif
+#define IPV6_DEFAULT_JOIN 1
 
 #ifndef IPV6_ADD_MEMBERSHIP
 #define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
@@ -124,6 +125,7 @@ typedef struct UDPContext {
     uint8_t tmp[UDP_MAX_PKT_SIZE + sizeof(UDPQueuedPacketHeader)];
     int remaining_in_dg;
     char *localaddr;
+    int multicast_max_join;
     int timeout;
     struct sockaddr_storage local_addr_storage;
     char *sources;
@@ -143,6 +145,7 @@ static const AVOption options[] = {
     { "localport",      "Local port",                                      OFFSET(local_port),     AV_OPT_TYPE_INT,    { .i64 = -1 },    -1, INT_MAX, D|E },
     { "local_port",     "Local port",                                      OFFSET(local_port),     AV_OPT_TYPE_INT,    { .i64 = -1 },    -1, INT_MAX, .flags = D|E },
     { "localaddr",      "Local address",                                   OFFSET(localaddr),      AV_OPT_TYPE_STRING, { .str = NULL },               .flags = D|E },
+    { "multicast_max_join", "Number of ipv6 network intefaces to join multicast group", OFFSET(multicast_max_join), AV_OPT_TYPE_INT, { .i64 = IPV6_DEFAULT_JOIN }, -1, INT_MAX, .flags = D|E },
     { "udplite_coverage", "choose UDPLite head size which should be validated by checksum", OFFSET(udplite_coverage), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, D|E },
     { "pkt_size",       "Maximum UDP packet size",                         OFFSET(pkt_size),       AV_OPT_TYPE_INT,    { .i64 = 1472 },  -1, INT_MAX, .flags = D|E },
     { "reuse",          "explicitly allow reusing UDP sockets",            OFFSET(reuse_socket),   AV_OPT_TYPE_BOOL,   { .i64 = -1 },    -1, 1,       D|E },
@@ -255,7 +258,8 @@ static int udp_ipv6_mc_devname_membership(int mop, const char *name,
 }
 
 static int udp_ipv6_multicast_iterate(int sockfd, struct sockaddr_in6 *addr,
-                                      struct sockaddr_in6 *local_addr, int mop, void *logctx)
+                                      struct sockaddr_in6 *local_addr, int mop,
+                                      int multicast_max_join, void *logctx)
 {
     struct in6_addr anyipv6 = IN6ADDR_ANY_INIT;
     struct ifaddrs *ifal=NULL,*ife=NULL;
@@ -267,13 +271,16 @@ static int udp_ipv6_multicast_iterate(int sockfd, struct sockaddr_in6 *addr,
             if (ife->ifa_addr &&
                 (ife->ifa_addr->sa_family == AF_INET6) &&
                 (ife->ifa_flags & IFF_MULTICAST) &&
-                (ife->ifa_flags & IFF_UP)) {
+                (ife->ifa_flags & IFF_UP) &&
+                multicast_max_join ) {
                 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)))) {
                     if (udp_ipv6_mc_devname_membership(mop,ife->ifa_name,
                                                        iindex_fd, &addr->sin6_addr,
-                                                       sockfd, logctx) == 1)
+                                                       sockfd, logctx) == 1) {
                         membership_changed = 1;
+                        multicast_max_join--;
+                    }
                 }
             }
         }
@@ -290,7 +297,8 @@ static int udp_ipv6_multicast_iterate(int sockfd, struct sockaddr_in6 *addr,
 #endif
 
 static int udp_join_multicast_group(int sockfd, struct sockaddr *addr,
-                                    struct sockaddr *local_addr, void *logctx)
+                                    struct sockaddr *local_addr,
+                                    int multicast_max_join, void *logctx)
 {
 #ifdef IP_ADD_MEMBERSHIP
     if (addr->sa_family == AF_INET) {
@@ -305,19 +313,33 @@ static int udp_join_multicast_group(int sockfd, struct sockaddr *addr,
             ff_log_net_error(logctx, AV_LOG_ERROR, "setsockopt(IP_ADD_MEMBERSHIP)");
             return ff_neterrno();
         }
+        if (multicast_max_join)
+            ff_log_net_error(logctx, AV_LOG_WARNING, "multicast_max_join is not used in ip4");
     }
 #endif
 #if HAVE_STRUCT_IPV6_MREQ && defined(IPPROTO_IPV6)
     if (addr->sa_family == AF_INET6) {
+        struct ipv6_mreq mreq6;
 #if HAVE_IOCTL_GIFINDEX
-        return udp_ipv6_multicast_iterate(sockfd,
-                                          (struct sockaddr_in6 *)addr,
-                                          (struct sockaddr_in6 *)local_addr,
-                                          IPV6_JOIN_GROUP,
-                                          logctx);
+        /* Fallback to use multicast output routing if we do not have
+           multiple interface join or a interface name or a local address */
+        if (multicast_max_join == IPV6_DEFAULT_JOIN && !local_addr) {
+            memcpy(&mreq6.ipv6mr_multiaddr, &(((struct sockaddr_in6 *)addr)->sin6_addr), sizeof(struct in6_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();
+            }
+        }
+        else {
+            return udp_ipv6_multicast_iterate(sockfd,
+                                              (struct sockaddr_in6 *)addr,
+                                              (struct sockaddr_in6 *)local_addr,
+                                              IPV6_JOIN_GROUP,
+                                              multicast_max_join,
+                                              logctx);
+        }
 #else
-        struct ipv6_mreq mreq6;
-
         memcpy(&mreq6.ipv6mr_multiaddr, &(((struct sockaddr_in6 *)addr)->sin6_addr), sizeof(struct in6_addr));
         mreq6.ipv6mr_interface = 0;
         if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6)) < 0) {
@@ -331,7 +353,8 @@ static int udp_join_multicast_group(int sockfd, struct sockaddr *addr,
 }
 
 static int udp_leave_multicast_group(int sockfd, struct sockaddr *addr,
-                                     struct sockaddr *local_addr, void *logctx)
+                                     struct sockaddr *local_addr,
+                                     int multicast_max_join, void *logctx)
 {
 #ifdef IP_DROP_MEMBERSHIP
     if (addr->sa_family == AF_INET) {
@@ -350,15 +373,27 @@ 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;
 #if HAVE_IOCTL_GIFINDEX
-        return udp_ipv6_multicast_iterate(sockfd,
-                                          (struct sockaddr_in6 *)addr,
-                                          (struct sockaddr_in6 *)local_addr,
-                                          IPV6_LEAVE_GROUP,
-                                          logctx);
+        /* Fallback to use multicast output routing if we do not have
+           multiple interface join or a interface name or a local address */
+        if (multicast_max_join == IPV6_DEFAULT_JOIN && !local_addr) {
+            memcpy(&mreq6.ipv6mr_multiaddr, &(((struct sockaddr_in6 *)addr)->sin6_addr), sizeof(struct in6_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();
+            }
+        }
+        else {
+            return udp_ipv6_multicast_iterate(sockfd,
+                                              (struct sockaddr_in6 *)addr,
+                                              (struct sockaddr_in6 *)local_addr,
+                                              IPV6_LEAVE_GROUP,
+                                              multicast_max_join,
+                                              logctx);
+        }
 #else
-        struct ipv6_mreq mreq6;
-
         memcpy(&mreq6.ipv6mr_multiaddr, &(((struct sockaddr_in6 *)addr)->sin6_addr), sizeof(struct in6_addr));
         mreq6.ipv6mr_interface = 0;
         if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq6, sizeof(mreq6)) < 0) {
@@ -847,6 +882,9 @@ static int udp_open(URLContext *h, const char *uri, int flags)
         if (av_find_info_tag(buf, sizeof(buf), "localport", p)) {
             s->local_port = strtol(buf, NULL, 10);
         }
+        if (av_find_info_tag(buf, sizeof(buf), "multicast_max_join", p)) {
+            s->multicast_max_join = strtol(buf, NULL, 10);
+        }
         if (av_find_info_tag(buf, sizeof(buf), "pkt_size", p)) {
             s->pkt_size = strtol(buf, NULL, 10);
         }
@@ -1012,7 +1050,8 @@ static int udp_open(URLContext *h, const char *uri, int flags)
                     goto fail;
             } else {
                 if ((ret = udp_join_multicast_group(udp_fd, (struct sockaddr *)&s->dest_addr,
-                                                    (struct sockaddr *)&s->local_addr_storage, h)) < 0)
+                                                    (struct sockaddr *)&s->local_addr_storage,
+                                                    s->multicast_max_join, h)) < 0)
                     goto fail;
             }
             if (s->filters.nb_exclude_addrs) {
@@ -1266,7 +1305,8 @@ static int udp_close(URLContext *h)
 
     if (s->is_multicast && (h->flags & AVIO_FLAG_READ))
         udp_leave_multicast_group(s->udp_fd, (struct sockaddr *)&s->dest_addr,
-                                  (struct sockaddr *)&s->local_addr_storage, h);
+                                  (struct sockaddr *)&s->local_addr_storage,
+                                  s->multicast_max_join, h);
 #if HAVE_PTHREAD_CANCEL
     if (s->thread_started) {
         int ret;
-- 
2.34.1

_______________________________________________
ffmpeg-devel mailing list -- ffmpeg-devel@ffmpeg.org
To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org

  parent reply	other threads:[~2025-08-29 14:07 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <0250811223419.GF29660@pb2>
2025-08-29 14:04 ` [FFmpeg-devel] [PATCH v3 0/5] Select output interfaces for ipv6 multicast Peter Enderborg via ffmpeg-devel
2025-08-29 14:04   ` [FFmpeg-devel] [PATCH v3 1/5] configure: Add test_ioctl and test for SIOCGIFINDEX Peter Enderborg via ffmpeg-devel
2025-08-29 17:38     ` [FFmpeg-devel] " Rémi Denis-Courmont via ffmpeg-devel
2025-08-29 14:04   ` [FFmpeg-devel] [PATCH v3 2/5] avformat/udp: Select output interfaces for ipv6 multicast Peter Enderborg via ffmpeg-devel
2025-08-29 14:04   ` Peter Enderborg via ffmpeg-devel [this message]
2025-08-29 14:04   ` [FFmpeg-devel] [PATCH v3 4/5] libavformat: add multicast interface Peter Enderborg via ffmpeg-devel
2025-08-29 14:04   ` [FFmpeg-devel] [PATCH v3 5/5] doc/protocols: Add command-line description for ipv6 options Peter Enderborg via ffmpeg-devel
2025-08-30  0:06     ` [FFmpeg-devel] " Marton Balint via ffmpeg-devel

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250829140459.3220037-4-peterend@axis.com \
    --to=ffmpeg-devel@ffmpeg.org \
    --cc=michael@niedermayer.cc \
    --cc=peterend@axis.com \
    --cc=remi@remlab.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git