yading@11: /* yading@11: * TCP protocol yading@11: * Copyright (c) 2002 Fabrice Bellard yading@11: * yading@11: * This file is part of FFmpeg. yading@11: * yading@11: * FFmpeg is free software; you can redistribute it and/or yading@11: * modify it under the terms of the GNU Lesser General Public yading@11: * License as published by the Free Software Foundation; either yading@11: * version 2.1 of the License, or (at your option) any later version. yading@11: * yading@11: * FFmpeg is distributed in the hope that it will be useful, yading@11: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@11: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU yading@11: * Lesser General Public License for more details. yading@11: * yading@11: * You should have received a copy of the GNU Lesser General Public yading@11: * License along with FFmpeg; if not, write to the Free Software yading@11: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@11: */ yading@11: #include "avformat.h" yading@11: #include "libavutil/parseutils.h" yading@11: #include "libavutil/opt.h" yading@11: #include "libavutil/time.h" yading@11: #include "internal.h" yading@11: #include "network.h" yading@11: #include "os_support.h" yading@11: #include "url.h" yading@11: #if HAVE_POLL_H yading@11: #include yading@11: #endif yading@11: yading@11: typedef struct TCPContext { yading@11: const AVClass *class; yading@11: int fd; yading@11: int listen; yading@11: int rw_timeout; yading@11: int listen_timeout; yading@11: } TCPContext; yading@11: yading@11: #define OFFSET(x) offsetof(TCPContext, x) yading@11: #define D AV_OPT_FLAG_DECODING_PARAM yading@11: #define E AV_OPT_FLAG_ENCODING_PARAM yading@11: static const AVOption options[] = { yading@11: {"listen", "listen on port instead of connecting", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, D|E }, yading@11: {"timeout", "timeout of socket i/o operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, D|E }, yading@11: {"listen_timeout", "connection awaiting timeout", OFFSET(listen_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E }, yading@11: {NULL} yading@11: }; yading@11: yading@11: static const AVClass tcp_context_class = { yading@11: .class_name = "tcp", yading@11: .item_name = av_default_item_name, yading@11: .option = options, yading@11: .version = LIBAVUTIL_VERSION_INT, yading@11: }; yading@11: yading@11: /* return non zero if error */ yading@11: static int tcp_open(URLContext *h, const char *uri, int flags) yading@11: { yading@11: struct addrinfo hints = { 0 }, *ai, *cur_ai; yading@11: int port, fd = -1; yading@11: TCPContext *s = h->priv_data; yading@11: const char *p; yading@11: char buf[256]; yading@11: int ret; yading@11: socklen_t optlen; yading@11: char hostname[1024],proto[1024],path[1024]; yading@11: char portstr[10]; yading@11: h->rw_timeout = 5000000; yading@11: yading@11: av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), yading@11: &port, path, sizeof(path), uri); yading@11: if (strcmp(proto, "tcp")) yading@11: return AVERROR(EINVAL); yading@11: if (port <= 0 || port >= 65536) { yading@11: av_log(h, AV_LOG_ERROR, "Port missing in uri\n"); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: p = strchr(uri, '?'); yading@11: if (p) { yading@11: if (av_find_info_tag(buf, sizeof(buf), "listen", p)) yading@11: s->listen = 1; yading@11: if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) { yading@11: s->rw_timeout = strtol(buf, NULL, 10); yading@11: } yading@11: if (av_find_info_tag(buf, sizeof(buf), "listen_timeout", p)) { yading@11: s->listen_timeout = strtol(buf, NULL, 10); yading@11: } yading@11: } yading@11: h->rw_timeout = s->rw_timeout; yading@11: hints.ai_family = AF_UNSPEC; yading@11: hints.ai_socktype = SOCK_STREAM; yading@11: snprintf(portstr, sizeof(portstr), "%d", port); yading@11: if (s->listen) yading@11: hints.ai_flags |= AI_PASSIVE; yading@11: if (!hostname[0]) yading@11: ret = getaddrinfo(NULL, portstr, &hints, &ai); yading@11: else yading@11: ret = getaddrinfo(hostname, portstr, &hints, &ai); yading@11: if (ret) { yading@11: av_log(h, AV_LOG_ERROR, yading@11: "Failed to resolve hostname %s: %s\n", yading@11: hostname, gai_strerror(ret)); yading@11: return AVERROR(EIO); yading@11: } yading@11: yading@11: cur_ai = ai; yading@11: yading@11: restart: yading@11: ret = AVERROR(EIO); yading@11: fd = socket(cur_ai->ai_family, cur_ai->ai_socktype, cur_ai->ai_protocol); yading@11: if (fd < 0) yading@11: goto fail; yading@11: yading@11: if (s->listen) { yading@11: int fd1; yading@11: int reuse = 1; yading@11: struct pollfd lp = { fd, POLLIN, 0 }; yading@11: setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); yading@11: ret = bind(fd, cur_ai->ai_addr, cur_ai->ai_addrlen); yading@11: if (ret) { yading@11: ret = ff_neterrno(); yading@11: goto fail1; yading@11: } yading@11: ret = listen(fd, 1); yading@11: if (ret) { yading@11: ret = ff_neterrno(); yading@11: goto fail1; yading@11: } yading@11: ret = poll(&lp, 1, s->listen_timeout >= 0 ? s->listen_timeout : -1); yading@11: if (ret <= 0) { yading@11: ret = AVERROR(ETIMEDOUT); yading@11: goto fail1; yading@11: } yading@11: fd1 = accept(fd, NULL, NULL); yading@11: if (fd1 < 0) { yading@11: ret = ff_neterrno(); yading@11: goto fail1; yading@11: } yading@11: closesocket(fd); yading@11: fd = fd1; yading@11: ff_socket_nonblock(fd, 1); yading@11: } else { yading@11: redo: yading@11: ff_socket_nonblock(fd, 1); yading@11: ret = connect(fd, cur_ai->ai_addr, cur_ai->ai_addrlen); yading@11: } yading@11: yading@11: if (ret < 0) { yading@11: struct pollfd p = {fd, POLLOUT, 0}; yading@11: int64_t wait_started; yading@11: ret = ff_neterrno(); yading@11: if (ret == AVERROR(EINTR)) { yading@11: if (ff_check_interrupt(&h->interrupt_callback)) { yading@11: ret = AVERROR_EXIT; yading@11: goto fail1; yading@11: } yading@11: goto redo; yading@11: } yading@11: if (ret != AVERROR(EINPROGRESS) && yading@11: ret != AVERROR(EAGAIN)) yading@11: goto fail; yading@11: yading@11: /* wait until we are connected or until abort */ yading@11: wait_started = av_gettime(); yading@11: do { yading@11: if (ff_check_interrupt(&h->interrupt_callback)) { yading@11: ret = AVERROR_EXIT; yading@11: goto fail1; yading@11: } yading@11: ret = poll(&p, 1, 100); yading@11: if (ret > 0) yading@11: break; yading@11: } while (!h->rw_timeout || (av_gettime() - wait_started < h->rw_timeout)); yading@11: if (ret <= 0) { yading@11: ret = AVERROR(ETIMEDOUT); yading@11: goto fail; yading@11: } yading@11: /* test error */ yading@11: optlen = sizeof(ret); yading@11: if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &ret, &optlen)) yading@11: ret = AVUNERROR(ff_neterrno()); yading@11: if (ret != 0) { yading@11: char errbuf[100]; yading@11: ret = AVERROR(ret); yading@11: av_strerror(ret, errbuf, sizeof(errbuf)); yading@11: av_log(h, AV_LOG_ERROR, yading@11: "TCP connection to %s:%d failed: %s\n", yading@11: hostname, port, errbuf); yading@11: goto fail; yading@11: } yading@11: } yading@11: h->is_streamed = 1; yading@11: s->fd = fd; yading@11: freeaddrinfo(ai); yading@11: return 0; yading@11: yading@11: fail: yading@11: if (cur_ai->ai_next) { yading@11: /* Retry with the next sockaddr */ yading@11: cur_ai = cur_ai->ai_next; yading@11: if (fd >= 0) yading@11: closesocket(fd); yading@11: goto restart; yading@11: } yading@11: fail1: yading@11: if (fd >= 0) yading@11: closesocket(fd); yading@11: freeaddrinfo(ai); yading@11: return ret; yading@11: } yading@11: yading@11: static int tcp_read(URLContext *h, uint8_t *buf, int size) yading@11: { yading@11: TCPContext *s = h->priv_data; yading@11: int ret; yading@11: yading@11: if (!(h->flags & AVIO_FLAG_NONBLOCK)) { yading@11: ret = ff_network_wait_fd_timeout(s->fd, 0, h->rw_timeout, &h->interrupt_callback); yading@11: if (ret) yading@11: return ret; yading@11: } yading@11: ret = recv(s->fd, buf, size, 0); yading@11: return ret < 0 ? ff_neterrno() : ret; yading@11: } yading@11: yading@11: static int tcp_write(URLContext *h, const uint8_t *buf, int size) yading@11: { yading@11: TCPContext *s = h->priv_data; yading@11: int ret; yading@11: yading@11: if (!(h->flags & AVIO_FLAG_NONBLOCK)) { yading@11: ret = ff_network_wait_fd_timeout(s->fd, 1, h->rw_timeout, &h->interrupt_callback); yading@11: if (ret) yading@11: return ret; yading@11: } yading@11: ret = send(s->fd, buf, size, 0); yading@11: return ret < 0 ? ff_neterrno() : ret; yading@11: } yading@11: yading@11: static int tcp_shutdown(URLContext *h, int flags) yading@11: { yading@11: TCPContext *s = h->priv_data; yading@11: int how; yading@11: yading@11: if (flags & AVIO_FLAG_WRITE && flags & AVIO_FLAG_READ) { yading@11: how = SHUT_RDWR; yading@11: } else if (flags & AVIO_FLAG_WRITE) { yading@11: how = SHUT_WR; yading@11: } else { yading@11: how = SHUT_RD; yading@11: } yading@11: yading@11: return shutdown(s->fd, how); yading@11: } yading@11: yading@11: static int tcp_close(URLContext *h) yading@11: { yading@11: TCPContext *s = h->priv_data; yading@11: closesocket(s->fd); yading@11: return 0; yading@11: } yading@11: yading@11: static int tcp_get_file_handle(URLContext *h) yading@11: { yading@11: TCPContext *s = h->priv_data; yading@11: return s->fd; yading@11: } yading@11: yading@11: URLProtocol ff_tcp_protocol = { yading@11: .name = "tcp", yading@11: .url_open = tcp_open, yading@11: .url_read = tcp_read, yading@11: .url_write = tcp_write, yading@11: .url_close = tcp_close, yading@11: .url_get_file_handle = tcp_get_file_handle, yading@11: .url_shutdown = tcp_shutdown, yading@11: .priv_data_size = sizeof(TCPContext), yading@11: .priv_data_class = &tcp_context_class, yading@11: .flags = URL_PROTOCOL_FLAG_NETWORK, yading@11: };