diff mbox series

[FFmpeg-devel] libavformat/tcp: add local_addr/local_port for network option

Message ID 20230301154456.3407862-1-jack.wgm@gmail.com
State New
Headers show
Series [FFmpeg-devel] libavformat/tcp: add local_addr/local_port for network option | 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

jackarain March 1, 2023, 3:44 p.m. UTC
Signed-off-by: jackarain <jack.wgm@gmail.com>
---
 doc/protocols.texi    |  6 +++++
 libavformat/network.c | 14 ++++++++----
 libavformat/network.h |  2 +-
 libavformat/tcp.c     | 53 ++++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 69 insertions(+), 6 deletions(-)

Comments

Rémi Denis-Courmont March 2, 2023, 6:47 p.m. UTC | #1
Le keskiviikkona 1. maaliskuuta 2023, 17.44.56 EET jackarain a écrit :
> Signed-off-by: jackarain <jack.wgm@gmail.com>
> ---
>  doc/protocols.texi    |  6 +++++
>  libavformat/network.c | 14 ++++++++----
>  libavformat/network.h |  2 +-
>  libavformat/tcp.c     | 53 ++++++++++++++++++++++++++++++++++++++++++-
>  4 files changed, 69 insertions(+), 6 deletions(-)
> 
> diff --git a/doc/protocols.texi b/doc/protocols.texi
> index 21ae6181a0..b3fad55591 100644
> --- a/doc/protocols.texi
> +++ b/doc/protocols.texi
> @@ -1882,6 +1882,12 @@ The list of supported options follows.
>  Listen for an incoming connection. 0 disables listen, 1 enables listen in
>  single client mode, 2 enables listen in multi-client mode. Default value is
> 0.
> 
> +@item local_addr=@var{addr}
> +Local IP address of a network interface used for tcp socket connect.
> +
> +@item local_port=@var{port}
> +Local port used for tcp socket connect.
> +
>  @item timeout=@var{microseconds}
>  Set raise error timeout, expressed in microseconds.
> 
> diff --git a/libavformat/network.c b/libavformat/network.c
> index 21e20b3e9a..de8b14be82 100644
> --- a/libavformat/network.c
> +++ b/libavformat/network.c
> @@ -356,7 +356,7 @@ struct ConnectionAttempt {
>  static int start_connect_attempt(struct ConnectionAttempt *attempt,
>                                   struct addrinfo **ptr, int timeout_ms,
>                                   URLContext *h,
> -                                 void (*customize_fd)(void *, int), void
> *customize_ctx) +                                 int (*customize_fd)(void
> *, int), void *customize_ctx) {
>      struct addrinfo *ai = *ptr;
>      int ret;
> @@ -371,8 +371,14 @@ static int start_connect_attempt(struct
> ConnectionAttempt *attempt,
> 
>      ff_socket_nonblock(attempt->fd, 1);
> 
> -    if (customize_fd)
> -        customize_fd(customize_ctx, attempt->fd);
> +    if (customize_fd) {
> +        ret = customize_fd(customize_ctx, attempt->fd);
> +        if (ret) {
> +            closesocket(attempt->fd);
> +            attempt->fd = -1;
> +            return ret;
> +        }
> +    }
> 
>      while ((ret = connect(attempt->fd, ai->ai_addr, ai->ai_addrlen))) {
>          ret = ff_neterrno();
> @@ -402,7 +408,7 @@ static int start_connect_attempt(struct
> ConnectionAttempt *attempt,
> 
>  int ff_connect_parallel(struct addrinfo *addrs, int timeout_ms_per_address,
> int parallel, URLContext *h, int *fd,
> -                        void (*customize_fd)(void *, int), void
> *customize_ctx) +                        int (*customize_fd)(void *, int),
> void *customize_ctx) {
>      struct ConnectionAttempt attempts[3];
>      struct pollfd pfd[3];
> diff --git a/libavformat/network.h b/libavformat/network.h
> index 71c49a73fb..8a8cbe672e 100644
> --- a/libavformat/network.h
> +++ b/libavformat/network.h
> @@ -336,6 +336,6 @@ void ff_log_net_error(void *ctx, int level, const char*
> prefix); */
>  int ff_connect_parallel(struct addrinfo *addrs, int timeout_ms_per_address,
> int parallel, URLContext *h, int *fd,
> -                        void (*customize_fd)(void *, int), void
> *customize_ctx); +                        int (*customize_fd)(void *, int),
> void *customize_ctx);
> 
>  #endif /* AVFORMAT_NETWORK_H */
> diff --git a/libavformat/tcp.c b/libavformat/tcp.c
> index a11ccbb913..a897174f6c 100644
> --- a/libavformat/tcp.c
> +++ b/libavformat/tcp.c
> @@ -36,6 +36,8 @@ typedef struct TCPContext {
>      const AVClass *class;
>      int fd;
>      int listen;
> +    char *local_port;
> +    char *local_addr;
>      int open_timeout;
>      int rw_timeout;
>      int listen_timeout;
> @@ -52,6 +54,8 @@ typedef struct TCPContext {
>  #define E AV_OPT_FLAG_ENCODING_PARAM
>  static const AVOption options[] = {
>      { "listen",          "Listen for incoming connections", 
> OFFSET(listen),         AV_OPT_TYPE_INT, { .i64 = 0 },     0,       2,     
>  .flags = D|E }, +    { "local_port",      "Local port",                   
>                      OFFSET(local_port),     AV_OPT_TYPE_STRING, { .str =
> NULL },     0,       0, .flags = D|E }, +    { "local_addr",      "Local
> address",                                      OFFSET(local_addr),    
> AV_OPT_TYPE_STRING, { .str = NULL },     0,       0, .flags = D|E }, {
> "timeout",     "set timeout (in microseconds) of socket I/O operations",
> OFFSET(rw_timeout),     AV_OPT_TYPE_INT, { .i64 = -1 },         -1,
> INT_MAX, .flags = D|E }, { "listen_timeout",  "Connection awaiting timeout
> (in milliseconds)",      OFFSET(listen_timeout), AV_OPT_TYPE_INT, { .i64 =
> -1 },         -1, INT_MAX, .flags = D|E }, { "send_buffer_size", "Socket
> send buffer size (in bytes)",                OFFSET(send_buffer_size),
> AV_OPT_TYPE_INT, { .i64 = -1 },         -1, INT_MAX, .flags = D|E }, @@
> -70,9 +74,42 @@ static const AVClass tcp_class = {
>      .version    = LIBAVUTIL_VERSION_INT,
>  };
> 
> -static void customize_fd(void *ctx, int fd)
> +static int customize_fd(void *ctx, int fd)
>  {
>      TCPContext *s = ctx;
> +
> +    if (s->local_addr || s->local_port) {
> +        struct addrinfo hints = { 0 }, *ai, *cur_ai;
> +        int ret;
> +
> +        hints.ai_family = AF_UNSPEC;

That needs to match the socket protocol family, or bind() will fail.

> +        hints.ai_socktype = SOCK_STREAM;
> +
> +        ret = getaddrinfo(s->local_addr, s->local_port, &hints, &ai);
> +        if (ret) {
> +            av_log(ctx, AV_LOG_ERROR,
> +               "Failed to getaddrinfo local addr: %s port: %s err: %s\n",
> +                s->local_addr, s->local_port, gai_strerror(ret));
> +            return ret;
> +        }
> +
> +        cur_ai = ai;
> +        while (cur_ai) {
> +            ret = bind(fd, (struct sockaddr *)cur_ai->ai_addr,
> (int)cur_ai->ai_addrlen); +            if (ret)
> +                cur_ai = cur_ai->ai_next;
> +            else
> +                break;
> +        }
> +        freeaddrinfo(ai);
> +
> +        if (ret) {
> +            av_log(ctx, AV_LOG_ERROR,
> +                "Failed to bind local addr: %s port: %s err: %s\n",
> +                s->local_addr, s->local_port, gai_strerror(ret));
> +            return ret;
> +        }
> +    }
>      /* Set the socket's send or receive buffer sizes, if specified.
>         If unspecified or setting fails, system default is used. */
>      if (s->recv_buffer_size > 0) {
> @@ -97,6 +134,8 @@ static void customize_fd(void *ctx, int fd)
>          }
>      }
>  #endif /* !HAVE_WINSOCK2_H */
> +
> +    return 0;
>  }
> 
>  /* return non zero if error */
> @@ -129,6 +168,18 @@ static int tcp_open(URLContext *h, const char *uri, int
> flags) if (buf == endptr)
>                  s->listen = 1;
>          }
> +        if (av_find_info_tag(buf, sizeof(buf), "local_port", p)) {
> +            av_freep(&s->local_port);
> +            s->local_port = av_strdup(buf);
> +            if (!s->local_addr)
> +                return AVERROR(ENOMEM);
> +        }
> +        if (av_find_info_tag(buf, sizeof(buf), "local_addr", p)) {
> +            av_freep(&s->local_addr);
> +            s->local_addr = av_strdup(buf);
> +            if (!s->local_addr)
> +                return AVERROR(ENOMEM);
> +        }
>          if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) {
>              s->rw_timeout = strtol(buf, NULL, 10);
>          }
Nicolas George March 2, 2023, 6:51 p.m. UTC | #2
Rémi Denis-Courmont (12023-03-02):
> That needs to match the socket protocol family, or bind() will fail.

Oh, right! The proper way to work with getaddrinfo is to create the
socket afterwards based on its return, but staying within a family is a
good second best choice. Thanks for noticing.

Regards,
diff mbox series

Patch

diff --git a/doc/protocols.texi b/doc/protocols.texi
index 21ae6181a0..b3fad55591 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -1882,6 +1882,12 @@  The list of supported options follows.
 Listen for an incoming connection. 0 disables listen, 1 enables listen in
 single client mode, 2 enables listen in multi-client mode. Default value is 0.
 
+@item local_addr=@var{addr}
+Local IP address of a network interface used for tcp socket connect.
+
+@item local_port=@var{port}
+Local port used for tcp socket connect.
+
 @item timeout=@var{microseconds}
 Set raise error timeout, expressed in microseconds.
 
diff --git a/libavformat/network.c b/libavformat/network.c
index 21e20b3e9a..de8b14be82 100644
--- a/libavformat/network.c
+++ b/libavformat/network.c
@@ -356,7 +356,7 @@  struct ConnectionAttempt {
 static int start_connect_attempt(struct ConnectionAttempt *attempt,
                                  struct addrinfo **ptr, int timeout_ms,
                                  URLContext *h,
-                                 void (*customize_fd)(void *, int), void *customize_ctx)
+                                 int (*customize_fd)(void *, int), void *customize_ctx)
 {
     struct addrinfo *ai = *ptr;
     int ret;
@@ -371,8 +371,14 @@  static int start_connect_attempt(struct ConnectionAttempt *attempt,
 
     ff_socket_nonblock(attempt->fd, 1);
 
-    if (customize_fd)
-        customize_fd(customize_ctx, attempt->fd);
+    if (customize_fd) {
+        ret = customize_fd(customize_ctx, attempt->fd);
+        if (ret) {
+            closesocket(attempt->fd);
+            attempt->fd = -1;
+            return ret;
+        }
+    }
 
     while ((ret = connect(attempt->fd, ai->ai_addr, ai->ai_addrlen))) {
         ret = ff_neterrno();
@@ -402,7 +408,7 @@  static int start_connect_attempt(struct ConnectionAttempt *attempt,
 
 int ff_connect_parallel(struct addrinfo *addrs, int timeout_ms_per_address,
                         int parallel, URLContext *h, int *fd,
-                        void (*customize_fd)(void *, int), void *customize_ctx)
+                        int (*customize_fd)(void *, int), void *customize_ctx)
 {
     struct ConnectionAttempt attempts[3];
     struct pollfd pfd[3];
diff --git a/libavformat/network.h b/libavformat/network.h
index 71c49a73fb..8a8cbe672e 100644
--- a/libavformat/network.h
+++ b/libavformat/network.h
@@ -336,6 +336,6 @@  void ff_log_net_error(void *ctx, int level, const char* prefix);
  */
 int ff_connect_parallel(struct addrinfo *addrs, int timeout_ms_per_address,
                         int parallel, URLContext *h, int *fd,
-                        void (*customize_fd)(void *, int), void *customize_ctx);
+                        int (*customize_fd)(void *, int), void *customize_ctx);
 
 #endif /* AVFORMAT_NETWORK_H */
diff --git a/libavformat/tcp.c b/libavformat/tcp.c
index a11ccbb913..a897174f6c 100644
--- a/libavformat/tcp.c
+++ b/libavformat/tcp.c
@@ -36,6 +36,8 @@  typedef struct TCPContext {
     const AVClass *class;
     int fd;
     int listen;
+    char *local_port;
+    char *local_addr;
     int open_timeout;
     int rw_timeout;
     int listen_timeout;
@@ -52,6 +54,8 @@  typedef struct TCPContext {
 #define E AV_OPT_FLAG_ENCODING_PARAM
 static const AVOption options[] = {
     { "listen",          "Listen for incoming connections",  OFFSET(listen),         AV_OPT_TYPE_INT, { .i64 = 0 },     0,       2,       .flags = D|E },
+    { "local_port",      "Local port",                                         OFFSET(local_port),     AV_OPT_TYPE_STRING, { .str = NULL },     0,       0, .flags = D|E },
+    { "local_addr",      "Local address",                                      OFFSET(local_addr),     AV_OPT_TYPE_STRING, { .str = NULL },     0,       0, .flags = D|E },
     { "timeout",     "set timeout (in microseconds) of socket I/O operations", OFFSET(rw_timeout),     AV_OPT_TYPE_INT, { .i64 = -1 },         -1, INT_MAX, .flags = D|E },
     { "listen_timeout",  "Connection awaiting timeout (in milliseconds)",      OFFSET(listen_timeout), AV_OPT_TYPE_INT, { .i64 = -1 },         -1, INT_MAX, .flags = D|E },
     { "send_buffer_size", "Socket send buffer size (in bytes)",                OFFSET(send_buffer_size), AV_OPT_TYPE_INT, { .i64 = -1 },         -1, INT_MAX, .flags = D|E },
@@ -70,9 +74,42 @@  static const AVClass tcp_class = {
     .version    = LIBAVUTIL_VERSION_INT,
 };
 
-static void customize_fd(void *ctx, int fd)
+static int customize_fd(void *ctx, int fd)
 {
     TCPContext *s = ctx;
+
+    if (s->local_addr || s->local_port) {
+        struct addrinfo hints = { 0 }, *ai, *cur_ai;
+        int ret;
+
+        hints.ai_family = AF_UNSPEC;
+        hints.ai_socktype = SOCK_STREAM;
+
+        ret = getaddrinfo(s->local_addr, s->local_port, &hints, &ai);
+        if (ret) {
+            av_log(ctx, AV_LOG_ERROR,
+               "Failed to getaddrinfo local addr: %s port: %s err: %s\n",
+                s->local_addr, s->local_port, gai_strerror(ret));
+            return ret;
+        }
+
+        cur_ai = ai;
+        while (cur_ai) {
+            ret = bind(fd, (struct sockaddr *)cur_ai->ai_addr, (int)cur_ai->ai_addrlen);
+            if (ret)
+                cur_ai = cur_ai->ai_next;
+            else
+                break;
+        }
+        freeaddrinfo(ai);
+
+        if (ret) {
+            av_log(ctx, AV_LOG_ERROR,
+                "Failed to bind local addr: %s port: %s err: %s\n",
+                s->local_addr, s->local_port, gai_strerror(ret));
+            return ret;
+        }
+    }
     /* Set the socket's send or receive buffer sizes, if specified.
        If unspecified or setting fails, system default is used. */
     if (s->recv_buffer_size > 0) {
@@ -97,6 +134,8 @@  static void customize_fd(void *ctx, int fd)
         }
     }
 #endif /* !HAVE_WINSOCK2_H */
+
+    return 0;
 }
 
 /* return non zero if error */
@@ -129,6 +168,18 @@  static int tcp_open(URLContext *h, const char *uri, int flags)
             if (buf == endptr)
                 s->listen = 1;
         }
+        if (av_find_info_tag(buf, sizeof(buf), "local_port", p)) {
+            av_freep(&s->local_port);
+            s->local_port = av_strdup(buf);
+            if (!s->local_addr)
+                return AVERROR(ENOMEM);
+        }
+        if (av_find_info_tag(buf, sizeof(buf), "local_addr", p)) {
+            av_freep(&s->local_addr);
+            s->local_addr = av_strdup(buf);
+            if (!s->local_addr)
+                return AVERROR(ENOMEM);
+        }
         if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) {
             s->rw_timeout = strtol(buf, NULL, 10);
         }