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