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