FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/tcp.c
Date: 2025-09-01 09:01:44
Exec Total Coverage
Lines: 0 170 0.0%
Functions: 0 9 0.0%
Branches: 0 122 0.0%

Line Branch Exec Source
1 /*
2 * TCP protocol
3 * Copyright (c) 2002 Fabrice Bellard
4 *
5 * This file is part of FFmpeg.
6 *
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21 #include "avformat.h"
22 #include "libavutil/avassert.h"
23 #include "libavutil/mem.h"
24 #include "libavutil/parseutils.h"
25 #include "libavutil/opt.h"
26 #include "libavutil/time.h"
27
28 #include "network.h"
29 #include "os_support.h"
30 #include "url.h"
31 #if HAVE_POLL_H
32 #include <poll.h>
33 #endif
34
35 typedef struct TCPContext {
36 const AVClass *class;
37 int fd;
38 int listen;
39 char *local_port;
40 char *local_addr;
41 int open_timeout;
42 int rw_timeout;
43 int listen_timeout;
44 int recv_buffer_size;
45 int send_buffer_size;
46 int tcp_nodelay;
47 int tcp_keepalive;
48 #if !HAVE_WINSOCK2_H
49 int tcp_mss;
50 #endif /* !HAVE_WINSOCK2_H */
51 } TCPContext;
52
53 #define OFFSET(x) offsetof(TCPContext, x)
54 #define D AV_OPT_FLAG_DECODING_PARAM
55 #define E AV_OPT_FLAG_ENCODING_PARAM
56 static const AVOption options[] = {
57 { "listen", "Listen for incoming connections", OFFSET(listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 2, .flags = D|E },
58 { "local_port", "Local port", OFFSET(local_port), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, .flags = D|E },
59 { "local_addr", "Local address", OFFSET(local_addr), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, .flags = D|E },
60 { "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 },
61 { "listen_timeout", "Connection awaiting timeout (in milliseconds)", OFFSET(listen_timeout), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E },
62 { "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 },
63 { "recv_buffer_size", "Socket receive buffer size (in bytes)", OFFSET(recv_buffer_size), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E },
64 { "tcp_nodelay", "Use TCP_NODELAY to disable nagle's algorithm", OFFSET(tcp_nodelay), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, .flags = D|E },
65 { "tcp_keepalive", "Use TCP keepalive to detect dead connections and keep long-lived connections active.", OFFSET(tcp_keepalive), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, .flags = D|E },
66 #if !HAVE_WINSOCK2_H
67 { "tcp_mss", "Maximum segment size for outgoing TCP packets", OFFSET(tcp_mss), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E },
68 #endif /* !HAVE_WINSOCK2_H */
69 { NULL }
70 };
71
72 static const AVClass tcp_class = {
73 .class_name = "tcp",
74 .item_name = av_default_item_name,
75 .option = options,
76 .version = LIBAVUTIL_VERSION_INT,
77 };
78
79 static int customize_fd(void *ctx, int fd, int family)
80 {
81 TCPContext *s = ctx;
82
83 if (s->local_addr || s->local_port) {
84 struct addrinfo hints = { 0 }, *ai, *cur_ai;
85 int ret;
86
87 hints.ai_family = family;
88 hints.ai_socktype = SOCK_STREAM;
89
90 ret = getaddrinfo(s->local_addr, s->local_port, &hints, &ai);
91 if (ret) {
92 av_log(ctx, AV_LOG_ERROR,
93 "Failed to getaddrinfo local addr: %s port: %s err: %s\n",
94 s->local_addr, s->local_port, gai_strerror(ret));
95 return ret;
96 }
97
98 cur_ai = ai;
99 while (cur_ai) {
100 ret = bind(fd, (struct sockaddr *)cur_ai->ai_addr, (int)cur_ai->ai_addrlen);
101 if (ret)
102 cur_ai = cur_ai->ai_next;
103 else
104 break;
105 }
106 freeaddrinfo(ai);
107
108 if (ret) {
109 ff_log_net_error(ctx, AV_LOG_ERROR, "bind local failed");
110 return ret;
111 }
112 }
113 /* Set the socket's send or receive buffer sizes, if specified.
114 If unspecified or setting fails, system default is used. */
115 if (s->recv_buffer_size > 0) {
116 if (setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &s->recv_buffer_size, sizeof (s->recv_buffer_size))) {
117 ff_log_net_error(ctx, AV_LOG_WARNING, "setsockopt(SO_RCVBUF)");
118 }
119 }
120 if (s->send_buffer_size > 0) {
121 if (setsockopt (fd, SOL_SOCKET, SO_SNDBUF, &s->send_buffer_size, sizeof (s->send_buffer_size))) {
122 ff_log_net_error(ctx, AV_LOG_WARNING, "setsockopt(SO_SNDBUF)");
123 }
124 }
125 if (s->tcp_nodelay > 0) {
126 if (setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, &s->tcp_nodelay, sizeof (s->tcp_nodelay))) {
127 ff_log_net_error(ctx, AV_LOG_WARNING, "setsockopt(TCP_NODELAY)");
128 }
129 }
130 if (s->tcp_keepalive > 0) {
131 if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &s->tcp_keepalive, sizeof(s->tcp_keepalive))) {
132 ff_log_net_error(ctx, AV_LOG_WARNING, "setsockopt(SO_KEEPALIVE)");
133 }
134 }
135
136 #if !HAVE_WINSOCK2_H
137 if (s->tcp_mss > 0) {
138 if (setsockopt (fd, IPPROTO_TCP, TCP_MAXSEG, &s->tcp_mss, sizeof (s->tcp_mss))) {
139 ff_log_net_error(ctx, AV_LOG_WARNING, "setsockopt(TCP_MAXSEG)");
140 }
141 }
142 #endif /* !HAVE_WINSOCK2_H */
143
144 return 0;
145 }
146
147 /* return non zero if error */
148 static int tcp_open(URLContext *h, const char *uri, int flags)
149 {
150 struct addrinfo hints = { 0 }, *ai, *cur_ai;
151 int port, fd = -1;
152 TCPContext *s = h->priv_data;
153 const char *p;
154 char buf[256];
155 int ret;
156 char hostname[1024],proto[1024],path[1024];
157 char portstr[10];
158 s->open_timeout = 5000000;
159
160 av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname),
161 &port, path, sizeof(path), uri);
162 if (strcmp(proto, "tcp"))
163 return AVERROR(EINVAL);
164 if (port <= 0 || port >= 65536) {
165 av_log(h, AV_LOG_ERROR, "Port missing in uri\n");
166 return AVERROR(EINVAL);
167 }
168 p = strchr(uri, '?');
169 if (p) {
170 if (av_find_info_tag(buf, sizeof(buf), "listen", p)) {
171 char *endptr = NULL;
172 s->listen = strtol(buf, &endptr, 10);
173 /* assume if no digits were found it is a request to enable it */
174 if (buf == endptr)
175 s->listen = 1;
176 }
177 if (av_find_info_tag(buf, sizeof(buf), "local_port", p)) {
178 av_freep(&s->local_port);
179 s->local_port = av_strdup(buf);
180 if (!s->local_port)
181 return AVERROR(ENOMEM);
182 }
183 if (av_find_info_tag(buf, sizeof(buf), "local_addr", p)) {
184 av_freep(&s->local_addr);
185 s->local_addr = av_strdup(buf);
186 if (!s->local_addr)
187 return AVERROR(ENOMEM);
188 }
189 if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) {
190 s->rw_timeout = strtol(buf, NULL, 10);
191 }
192 if (av_find_info_tag(buf, sizeof(buf), "listen_timeout", p)) {
193 s->listen_timeout = strtol(buf, NULL, 10);
194 }
195 if (av_find_info_tag(buf, sizeof(buf), "tcp_nodelay", p)) {
196 s->tcp_nodelay = strtol(buf, NULL, 10);
197 }
198 if (av_find_info_tag(buf, sizeof(buf), "tcp_keepalive", p)) {
199 s->tcp_keepalive = strtol(buf, NULL, 10);
200 }
201 }
202 if (s->rw_timeout >= 0) {
203 s->open_timeout =
204 h->rw_timeout = s->rw_timeout;
205 }
206 hints.ai_family = AF_UNSPEC;
207 hints.ai_socktype = SOCK_STREAM;
208 snprintf(portstr, sizeof(portstr), "%d", port);
209 if (s->listen)
210 hints.ai_flags |= AI_PASSIVE;
211 if (!hostname[0])
212 ret = getaddrinfo(NULL, portstr, &hints, &ai);
213 else
214 ret = getaddrinfo(hostname, portstr, &hints, &ai);
215 if (ret) {
216 av_log(h, AV_LOG_ERROR,
217 "Failed to resolve hostname %s: %s\n",
218 hostname, gai_strerror(ret));
219 return AVERROR(EIO);
220 }
221
222 cur_ai = ai;
223
224 #if HAVE_STRUCT_SOCKADDR_IN6
225 // workaround for IOS9 getaddrinfo in IPv6 only network use hardcode IPv4 address can not resolve port number.
226 if (cur_ai->ai_family == AF_INET6){
227 struct sockaddr_in6 * sockaddr_v6 = (struct sockaddr_in6 *)cur_ai->ai_addr;
228 if (!sockaddr_v6->sin6_port){
229 sockaddr_v6->sin6_port = htons(port);
230 }
231 }
232 #endif
233
234 if (s->listen > 0) {
235 while (cur_ai && fd < 0) {
236 fd = ff_socket(cur_ai->ai_family,
237 cur_ai->ai_socktype,
238 cur_ai->ai_protocol, h);
239 if (fd < 0) {
240 ret = ff_neterrno();
241 cur_ai = cur_ai->ai_next;
242 }
243 }
244 if (fd < 0)
245 goto fail1;
246 customize_fd(s, fd, cur_ai->ai_family);
247 }
248
249 if (s->listen == 2) {
250 // multi-client
251 if ((ret = ff_listen(fd, cur_ai->ai_addr, cur_ai->ai_addrlen, h)) < 0)
252 goto fail1;
253 } else if (s->listen == 1) {
254 // single client
255 if ((ret = ff_listen_bind(fd, cur_ai->ai_addr, cur_ai->ai_addrlen,
256 s->listen_timeout, h)) < 0)
257 goto fail1;
258 // Socket descriptor already closed here. Safe to overwrite to client one.
259 fd = ret;
260 } else {
261 ret = ff_connect_parallel(ai, s->open_timeout / 1000, 3, h, &fd, customize_fd, s);
262 if (ret < 0)
263 goto fail1;
264 }
265
266 h->is_streamed = 1;
267 s->fd = fd;
268
269 freeaddrinfo(ai);
270 return 0;
271
272 fail1:
273 if (fd >= 0)
274 closesocket(fd);
275 freeaddrinfo(ai);
276 return ret;
277 }
278
279 static int tcp_accept(URLContext *s, URLContext **c)
280 {
281 TCPContext *sc = s->priv_data;
282 TCPContext *cc;
283 int ret;
284 av_assert0(sc->listen);
285 if ((ret = ffurl_alloc(c, s->filename, s->flags, &s->interrupt_callback)) < 0)
286 return ret;
287 cc = (*c)->priv_data;
288 ret = ff_accept(sc->fd, sc->listen_timeout, s);
289 if (ret < 0) {
290 ffurl_closep(c);
291 return ret;
292 }
293 cc->fd = ret;
294 return 0;
295 }
296
297 static int tcp_read(URLContext *h, uint8_t *buf, int size)
298 {
299 TCPContext *s = h->priv_data;
300 int ret;
301
302 if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
303 ret = ff_network_wait_fd_timeout(s->fd, 0, h->rw_timeout, &h->interrupt_callback);
304 if (ret)
305 return ret;
306 }
307 ret = recv(s->fd, buf, size, 0);
308 if (ret == 0)
309 return AVERROR_EOF;
310 return ret < 0 ? ff_neterrno() : ret;
311 }
312
313 static int tcp_write(URLContext *h, const uint8_t *buf, int size)
314 {
315 TCPContext *s = h->priv_data;
316 int ret;
317
318 if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
319 ret = ff_network_wait_fd_timeout(s->fd, 1, h->rw_timeout, &h->interrupt_callback);
320 if (ret)
321 return ret;
322 }
323 ret = send(s->fd, buf, size, MSG_NOSIGNAL);
324 return ret < 0 ? ff_neterrno() : ret;
325 }
326
327 static int tcp_shutdown(URLContext *h, int flags)
328 {
329 TCPContext *s = h->priv_data;
330 int how;
331
332 if (flags & AVIO_FLAG_WRITE && flags & AVIO_FLAG_READ) {
333 how = SHUT_RDWR;
334 } else if (flags & AVIO_FLAG_WRITE) {
335 how = SHUT_WR;
336 } else {
337 how = SHUT_RD;
338 }
339
340 return shutdown(s->fd, how);
341 }
342
343 static int tcp_close(URLContext *h)
344 {
345 TCPContext *s = h->priv_data;
346 closesocket(s->fd);
347 return 0;
348 }
349
350 static int tcp_get_file_handle(URLContext *h)
351 {
352 TCPContext *s = h->priv_data;
353 return s->fd;
354 }
355
356 static int tcp_get_window_size(URLContext *h)
357 {
358 TCPContext *s = h->priv_data;
359 int avail;
360 socklen_t avail_len = sizeof(avail);
361
362 #if HAVE_WINSOCK2_H
363 /* SO_RCVBUF with winsock only reports the actual TCP window size when
364 auto-tuning has been disabled via setting SO_RCVBUF */
365 if (s->recv_buffer_size < 0) {
366 return AVERROR(ENOSYS);
367 }
368 #endif
369
370 if (getsockopt(s->fd, SOL_SOCKET, SO_RCVBUF, &avail, &avail_len)) {
371 return ff_neterrno();
372 }
373 return avail;
374 }
375
376 const URLProtocol ff_tcp_protocol = {
377 .name = "tcp",
378 .url_open = tcp_open,
379 .url_accept = tcp_accept,
380 .url_read = tcp_read,
381 .url_write = tcp_write,
382 .url_close = tcp_close,
383 .url_get_file_handle = tcp_get_file_handle,
384 .url_get_short_seek = tcp_get_window_size,
385 .url_shutdown = tcp_shutdown,
386 .priv_data_size = sizeof(TCPContext),
387 .flags = URL_PROTOCOL_FLAG_NETWORK,
388 .priv_data_class = &tcp_class,
389 };
390