FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/tcp.c
Date: 2024-04-18 20:30:25
Exec Total Coverage
Lines: 0 165 0.0%
Functions: 0 9 0.0%
Branches: 0 116 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 #if !HAVE_WINSOCK2_H
48 int tcp_mss;
49 #endif /* !HAVE_WINSOCK2_H */
50 } TCPContext;
51
52 #define OFFSET(x) offsetof(TCPContext, x)
53 #define D AV_OPT_FLAG_DECODING_PARAM
54 #define E AV_OPT_FLAG_ENCODING_PARAM
55 static const AVOption options[] = {
56 { "listen", "Listen for incoming connections", OFFSET(listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 2, .flags = D|E },
57 { "local_port", "Local port", OFFSET(local_port), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, .flags = D|E },
58 { "local_addr", "Local address", OFFSET(local_addr), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, .flags = D|E },
59 { "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 },
60 { "listen_timeout", "Connection awaiting timeout (in milliseconds)", OFFSET(listen_timeout), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E },
61 { "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 },
62 { "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 },
63 { "tcp_nodelay", "Use TCP_NODELAY to disable nagle's algorithm", OFFSET(tcp_nodelay), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, .flags = D|E },
64 #if !HAVE_WINSOCK2_H
65 { "tcp_mss", "Maximum segment size for outgoing TCP packets", OFFSET(tcp_mss), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E },
66 #endif /* !HAVE_WINSOCK2_H */
67 { NULL }
68 };
69
70 static const AVClass tcp_class = {
71 .class_name = "tcp",
72 .item_name = av_default_item_name,
73 .option = options,
74 .version = LIBAVUTIL_VERSION_INT,
75 };
76
77 static int customize_fd(void *ctx, int fd, int family)
78 {
79 TCPContext *s = ctx;
80
81 if (s->local_addr || s->local_port) {
82 struct addrinfo hints = { 0 }, *ai, *cur_ai;
83 int ret;
84
85 hints.ai_family = family;
86 hints.ai_socktype = SOCK_STREAM;
87
88 ret = getaddrinfo(s->local_addr, s->local_port, &hints, &ai);
89 if (ret) {
90 av_log(ctx, AV_LOG_ERROR,
91 "Failed to getaddrinfo local addr: %s port: %s err: %s\n",
92 s->local_addr, s->local_port, gai_strerror(ret));
93 return ret;
94 }
95
96 cur_ai = ai;
97 while (cur_ai) {
98 ret = bind(fd, (struct sockaddr *)cur_ai->ai_addr, (int)cur_ai->ai_addrlen);
99 if (ret)
100 cur_ai = cur_ai->ai_next;
101 else
102 break;
103 }
104 freeaddrinfo(ai);
105
106 if (ret) {
107 ff_log_net_error(ctx, AV_LOG_ERROR, "bind local failed");
108 return ret;
109 }
110 }
111 /* Set the socket's send or receive buffer sizes, if specified.
112 If unspecified or setting fails, system default is used. */
113 if (s->recv_buffer_size > 0) {
114 if (setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &s->recv_buffer_size, sizeof (s->recv_buffer_size))) {
115 ff_log_net_error(ctx, AV_LOG_WARNING, "setsockopt(SO_RCVBUF)");
116 }
117 }
118 if (s->send_buffer_size > 0) {
119 if (setsockopt (fd, SOL_SOCKET, SO_SNDBUF, &s->send_buffer_size, sizeof (s->send_buffer_size))) {
120 ff_log_net_error(ctx, AV_LOG_WARNING, "setsockopt(SO_SNDBUF)");
121 }
122 }
123 if (s->tcp_nodelay > 0) {
124 if (setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, &s->tcp_nodelay, sizeof (s->tcp_nodelay))) {
125 ff_log_net_error(ctx, AV_LOG_WARNING, "setsockopt(TCP_NODELAY)");
126 }
127 }
128 #if !HAVE_WINSOCK2_H
129 if (s->tcp_mss > 0) {
130 if (setsockopt (fd, IPPROTO_TCP, TCP_MAXSEG, &s->tcp_mss, sizeof (s->tcp_mss))) {
131 ff_log_net_error(ctx, AV_LOG_WARNING, "setsockopt(TCP_MAXSEG)");
132 }
133 }
134 #endif /* !HAVE_WINSOCK2_H */
135
136 return 0;
137 }
138
139 /* return non zero if error */
140 static int tcp_open(URLContext *h, const char *uri, int flags)
141 {
142 struct addrinfo hints = { 0 }, *ai, *cur_ai;
143 int port, fd = -1;
144 TCPContext *s = h->priv_data;
145 const char *p;
146 char buf[256];
147 int ret;
148 char hostname[1024],proto[1024],path[1024];
149 char portstr[10];
150 s->open_timeout = 5000000;
151
152 av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname),
153 &port, path, sizeof(path), uri);
154 if (strcmp(proto, "tcp"))
155 return AVERROR(EINVAL);
156 if (port <= 0 || port >= 65536) {
157 av_log(h, AV_LOG_ERROR, "Port missing in uri\n");
158 return AVERROR(EINVAL);
159 }
160 p = strchr(uri, '?');
161 if (p) {
162 if (av_find_info_tag(buf, sizeof(buf), "listen", p)) {
163 char *endptr = NULL;
164 s->listen = strtol(buf, &endptr, 10);
165 /* assume if no digits were found it is a request to enable it */
166 if (buf == endptr)
167 s->listen = 1;
168 }
169 if (av_find_info_tag(buf, sizeof(buf), "local_port", p)) {
170 av_freep(&s->local_port);
171 s->local_port = av_strdup(buf);
172 if (!s->local_port)
173 return AVERROR(ENOMEM);
174 }
175 if (av_find_info_tag(buf, sizeof(buf), "local_addr", p)) {
176 av_freep(&s->local_addr);
177 s->local_addr = av_strdup(buf);
178 if (!s->local_addr)
179 return AVERROR(ENOMEM);
180 }
181 if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) {
182 s->rw_timeout = strtol(buf, NULL, 10);
183 }
184 if (av_find_info_tag(buf, sizeof(buf), "listen_timeout", p)) {
185 s->listen_timeout = strtol(buf, NULL, 10);
186 }
187 if (av_find_info_tag(buf, sizeof(buf), "tcp_nodelay", p)) {
188 s->tcp_nodelay = strtol(buf, NULL, 10);
189 }
190 }
191 if (s->rw_timeout >= 0) {
192 s->open_timeout =
193 h->rw_timeout = s->rw_timeout;
194 }
195 hints.ai_family = AF_UNSPEC;
196 hints.ai_socktype = SOCK_STREAM;
197 snprintf(portstr, sizeof(portstr), "%d", port);
198 if (s->listen)
199 hints.ai_flags |= AI_PASSIVE;
200 if (!hostname[0])
201 ret = getaddrinfo(NULL, portstr, &hints, &ai);
202 else
203 ret = getaddrinfo(hostname, portstr, &hints, &ai);
204 if (ret) {
205 av_log(h, AV_LOG_ERROR,
206 "Failed to resolve hostname %s: %s\n",
207 hostname, gai_strerror(ret));
208 return AVERROR(EIO);
209 }
210
211 cur_ai = ai;
212
213 #if HAVE_STRUCT_SOCKADDR_IN6
214 // workaround for IOS9 getaddrinfo in IPv6 only network use hardcode IPv4 address can not resolve port number.
215 if (cur_ai->ai_family == AF_INET6){
216 struct sockaddr_in6 * sockaddr_v6 = (struct sockaddr_in6 *)cur_ai->ai_addr;
217 if (!sockaddr_v6->sin6_port){
218 sockaddr_v6->sin6_port = htons(port);
219 }
220 }
221 #endif
222
223 if (s->listen > 0) {
224 while (cur_ai && fd < 0) {
225 fd = ff_socket(cur_ai->ai_family,
226 cur_ai->ai_socktype,
227 cur_ai->ai_protocol, h);
228 if (fd < 0) {
229 ret = ff_neterrno();
230 cur_ai = cur_ai->ai_next;
231 }
232 }
233 if (fd < 0)
234 goto fail1;
235 customize_fd(s, fd, cur_ai->ai_family);
236 }
237
238 if (s->listen == 2) {
239 // multi-client
240 if ((ret = ff_listen(fd, cur_ai->ai_addr, cur_ai->ai_addrlen, h)) < 0)
241 goto fail1;
242 } else if (s->listen == 1) {
243 // single client
244 if ((ret = ff_listen_bind(fd, cur_ai->ai_addr, cur_ai->ai_addrlen,
245 s->listen_timeout, h)) < 0)
246 goto fail1;
247 // Socket descriptor already closed here. Safe to overwrite to client one.
248 fd = ret;
249 } else {
250 ret = ff_connect_parallel(ai, s->open_timeout / 1000, 3, h, &fd, customize_fd, s);
251 if (ret < 0)
252 goto fail1;
253 }
254
255 h->is_streamed = 1;
256 s->fd = fd;
257
258 freeaddrinfo(ai);
259 return 0;
260
261 fail1:
262 if (fd >= 0)
263 closesocket(fd);
264 freeaddrinfo(ai);
265 return ret;
266 }
267
268 static int tcp_accept(URLContext *s, URLContext **c)
269 {
270 TCPContext *sc = s->priv_data;
271 TCPContext *cc;
272 int ret;
273 av_assert0(sc->listen);
274 if ((ret = ffurl_alloc(c, s->filename, s->flags, &s->interrupt_callback)) < 0)
275 return ret;
276 cc = (*c)->priv_data;
277 ret = ff_accept(sc->fd, sc->listen_timeout, s);
278 if (ret < 0) {
279 ffurl_closep(c);
280 return ret;
281 }
282 cc->fd = ret;
283 return 0;
284 }
285
286 static int tcp_read(URLContext *h, uint8_t *buf, int size)
287 {
288 TCPContext *s = h->priv_data;
289 int ret;
290
291 if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
292 ret = ff_network_wait_fd_timeout(s->fd, 0, h->rw_timeout, &h->interrupt_callback);
293 if (ret)
294 return ret;
295 }
296 ret = recv(s->fd, buf, size, 0);
297 if (ret == 0)
298 return AVERROR_EOF;
299 return ret < 0 ? ff_neterrno() : ret;
300 }
301
302 static int tcp_write(URLContext *h, const uint8_t *buf, int size)
303 {
304 TCPContext *s = h->priv_data;
305 int ret;
306
307 if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
308 ret = ff_network_wait_fd_timeout(s->fd, 1, h->rw_timeout, &h->interrupt_callback);
309 if (ret)
310 return ret;
311 }
312 ret = send(s->fd, buf, size, MSG_NOSIGNAL);
313 return ret < 0 ? ff_neterrno() : ret;
314 }
315
316 static int tcp_shutdown(URLContext *h, int flags)
317 {
318 TCPContext *s = h->priv_data;
319 int how;
320
321 if (flags & AVIO_FLAG_WRITE && flags & AVIO_FLAG_READ) {
322 how = SHUT_RDWR;
323 } else if (flags & AVIO_FLAG_WRITE) {
324 how = SHUT_WR;
325 } else {
326 how = SHUT_RD;
327 }
328
329 return shutdown(s->fd, how);
330 }
331
332 static int tcp_close(URLContext *h)
333 {
334 TCPContext *s = h->priv_data;
335 closesocket(s->fd);
336 return 0;
337 }
338
339 static int tcp_get_file_handle(URLContext *h)
340 {
341 TCPContext *s = h->priv_data;
342 return s->fd;
343 }
344
345 static int tcp_get_window_size(URLContext *h)
346 {
347 TCPContext *s = h->priv_data;
348 int avail;
349 socklen_t avail_len = sizeof(avail);
350
351 #if HAVE_WINSOCK2_H
352 /* SO_RCVBUF with winsock only reports the actual TCP window size when
353 auto-tuning has been disabled via setting SO_RCVBUF */
354 if (s->recv_buffer_size < 0) {
355 return AVERROR(ENOSYS);
356 }
357 #endif
358
359 if (getsockopt(s->fd, SOL_SOCKET, SO_RCVBUF, &avail, &avail_len)) {
360 return ff_neterrno();
361 }
362 return avail;
363 }
364
365 const URLProtocol ff_tcp_protocol = {
366 .name = "tcp",
367 .url_open = tcp_open,
368 .url_accept = tcp_accept,
369 .url_read = tcp_read,
370 .url_write = tcp_write,
371 .url_close = tcp_close,
372 .url_get_file_handle = tcp_get_file_handle,
373 .url_get_short_seek = tcp_get_window_size,
374 .url_shutdown = tcp_shutdown,
375 .priv_data_size = sizeof(TCPContext),
376 .flags = URL_PROTOCOL_FLAG_NETWORK,
377 .priv_data_class = &tcp_class,
378 };
379