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/parseutils.h" 23#include "libavutil/opt.h" 24#include "libavutil/time.h" 25#include "internal.h" 26#include "network.h" 27#include "os_support.h" 28#include "url.h" 29#if HAVE_POLL_H 30#include <poll.h> 31#endif 32 33typedef struct TCPContext { 34 const AVClass *class; 35 int fd; 36 int listen; 37 int open_timeout; 38 int rw_timeout; 39 int listen_timeout; 40} TCPContext; 41 42#define OFFSET(x) offsetof(TCPContext, x) 43#define D AV_OPT_FLAG_DECODING_PARAM 44#define E AV_OPT_FLAG_ENCODING_PARAM 45static const AVOption options[] = { 46{"listen", "listen on port instead of connecting", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, D|E }, 47{"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E }, 48{"listen_timeout", "set connection awaiting timeout", OFFSET(listen_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E }, 49{NULL} 50}; 51 52static const AVClass tcp_context_class = { 53 .class_name = "tcp", 54 .item_name = av_default_item_name, 55 .option = options, 56 .version = LIBAVUTIL_VERSION_INT, 57}; 58 59/* return non zero if error */ 60static int tcp_open(URLContext *h, const char *uri, int flags) 61{ 62 struct addrinfo hints = { 0 }, *ai, *cur_ai; 63 int port, fd = -1; 64 TCPContext *s = h->priv_data; 65 const char *p; 66 char buf[256]; 67 int ret; 68 char hostname[1024],proto[1024],path[1024]; 69 char portstr[10]; 70 s->open_timeout = 5000000; 71 72 av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), 73 &port, path, sizeof(path), uri); 74 if (strcmp(proto, "tcp")) 75 return AVERROR(EINVAL); 76 if (port <= 0 || port >= 65536) { 77 av_log(h, AV_LOG_ERROR, "Port missing in uri\n"); 78 return AVERROR(EINVAL); 79 } 80 p = strchr(uri, '?'); 81 if (p) { 82 if (av_find_info_tag(buf, sizeof(buf), "listen", p)) { 83 char *endptr = NULL; 84 s->listen = strtol(buf, &endptr, 10); 85 /* assume if no digits were found it is a request to enable it */ 86 if (buf == endptr) 87 s->listen = 1; 88 } 89 if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) { 90 s->rw_timeout = strtol(buf, NULL, 10); 91 } 92 if (av_find_info_tag(buf, sizeof(buf), "listen_timeout", p)) { 93 s->listen_timeout = strtol(buf, NULL, 10); 94 } 95 } 96 if (s->rw_timeout >= 0) { 97 s->open_timeout = 98 h->rw_timeout = s->rw_timeout; 99 } 100 hints.ai_family = AF_UNSPEC; 101 hints.ai_socktype = SOCK_STREAM; 102 snprintf(portstr, sizeof(portstr), "%d", port); 103 if (s->listen) 104 hints.ai_flags |= AI_PASSIVE; 105 if (!hostname[0]) 106 ret = getaddrinfo(NULL, portstr, &hints, &ai); 107 else 108 ret = getaddrinfo(hostname, portstr, &hints, &ai); 109 if (ret) { 110 av_log(h, AV_LOG_ERROR, 111 "Failed to resolve hostname %s: %s\n", 112 hostname, gai_strerror(ret)); 113 return AVERROR(EIO); 114 } 115 116 cur_ai = ai; 117 118 restart: 119 fd = ff_socket(cur_ai->ai_family, 120 cur_ai->ai_socktype, 121 cur_ai->ai_protocol); 122 if (fd < 0) { 123 ret = ff_neterrno(); 124 goto fail; 125 } 126 127 if (s->listen) { 128 if ((fd = ff_listen_bind(fd, cur_ai->ai_addr, cur_ai->ai_addrlen, 129 s->listen_timeout, h)) < 0) { 130 ret = fd; 131 goto fail1; 132 } 133 } else { 134 if ((ret = ff_listen_connect(fd, cur_ai->ai_addr, cur_ai->ai_addrlen, 135 s->open_timeout / 1000, h, !!cur_ai->ai_next)) < 0) { 136 137 if (ret == AVERROR_EXIT) 138 goto fail1; 139 else 140 goto fail; 141 } 142 } 143 144 h->is_streamed = 1; 145 s->fd = fd; 146 freeaddrinfo(ai); 147 return 0; 148 149 fail: 150 if (cur_ai->ai_next) { 151 /* Retry with the next sockaddr */ 152 cur_ai = cur_ai->ai_next; 153 if (fd >= 0) 154 closesocket(fd); 155 ret = 0; 156 goto restart; 157 } 158 fail1: 159 if (fd >= 0) 160 closesocket(fd); 161 freeaddrinfo(ai); 162 return ret; 163} 164 165static int tcp_read(URLContext *h, uint8_t *buf, int size) 166{ 167 TCPContext *s = h->priv_data; 168 int ret; 169 170 if (!(h->flags & AVIO_FLAG_NONBLOCK)) { 171 ret = ff_network_wait_fd_timeout(s->fd, 0, h->rw_timeout, &h->interrupt_callback); 172 if (ret) 173 return ret; 174 } 175 ret = recv(s->fd, buf, size, 0); 176 return ret < 0 ? ff_neterrno() : ret; 177} 178 179static int tcp_write(URLContext *h, const uint8_t *buf, int size) 180{ 181 TCPContext *s = h->priv_data; 182 int ret; 183 184 if (!(h->flags & AVIO_FLAG_NONBLOCK)) { 185 ret = ff_network_wait_fd_timeout(s->fd, 1, h->rw_timeout, &h->interrupt_callback); 186 if (ret) 187 return ret; 188 } 189 ret = send(s->fd, buf, size, 0); 190 return ret < 0 ? ff_neterrno() : ret; 191} 192 193static int tcp_shutdown(URLContext *h, int flags) 194{ 195 TCPContext *s = h->priv_data; 196 int how; 197 198 if (flags & AVIO_FLAG_WRITE && flags & AVIO_FLAG_READ) { 199 how = SHUT_RDWR; 200 } else if (flags & AVIO_FLAG_WRITE) { 201 how = SHUT_WR; 202 } else { 203 how = SHUT_RD; 204 } 205 206 return shutdown(s->fd, how); 207} 208 209static int tcp_close(URLContext *h) 210{ 211 TCPContext *s = h->priv_data; 212 closesocket(s->fd); 213 return 0; 214} 215 216static int tcp_get_file_handle(URLContext *h) 217{ 218 TCPContext *s = h->priv_data; 219 return s->fd; 220} 221 222URLProtocol ff_tcp_protocol = { 223 .name = "tcp", 224 .url_open = tcp_open, 225 .url_read = tcp_read, 226 .url_write = tcp_write, 227 .url_close = tcp_close, 228 .url_get_file_handle = tcp_get_file_handle, 229 .url_shutdown = tcp_shutdown, 230 .priv_data_size = sizeof(TCPContext), 231 .priv_data_class = &tcp_context_class, 232 .flags = URL_PROTOCOL_FLAG_NETWORK, 233}; 234