tcp.c 6.31 KB
Newer Older
Fabrice Bellard's avatar
Fabrice Bellard committed
1 2
/*
 * TCP protocol
3
 * Copyright (c) 2002 Fabrice Bellard
Fabrice Bellard's avatar
Fabrice Bellard committed
4
 *
5 6 7
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
Fabrice Bellard's avatar
Fabrice Bellard committed
8 9
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
Fabrice Bellard's avatar
Fabrice Bellard committed
11
 *
12
 * FFmpeg is distributed in the hope that it will be useful,
Fabrice Bellard's avatar
Fabrice Bellard committed
13 14 15 16 17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * 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
Fabrice Bellard's avatar
Fabrice Bellard committed
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 30
#if HAVE_POLL_H
#include <poll.h>
31
#endif
Fabrice Bellard's avatar
Fabrice Bellard committed
32 33

typedef struct TCPContext {
34
    const AVClass *class;
Fabrice Bellard's avatar
Fabrice Bellard committed
35
    int fd;
36
    int listen;
37
    int open_timeout;
38 39
    int rw_timeout;
    int listen_timeout;
Fabrice Bellard's avatar
Fabrice Bellard committed
40 41
} TCPContext;

42 43 44 45 46
#define OFFSET(x) offsetof(TCPContext, x)
#define D AV_OPT_FLAG_DECODING_PARAM
#define E AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options[] = {
{"listen", "listen on port instead of connecting", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, D|E },
47
{"timeout", "timeout of socket i/o operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E },
48 49 50 51 52 53 54 55 56 57 58
{"listen_timeout", "connection awaiting timeout", OFFSET(listen_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E },
{NULL}
};

static const AVClass tcp_context_class = {
    .class_name = "tcp",
    .item_name  = av_default_item_name,
    .option     = options,
    .version    = LIBAVUTIL_VERSION_INT,
};

Fabrice Bellard's avatar
Fabrice Bellard committed
59 60 61
/* return non zero if error */
static int tcp_open(URLContext *h, const char *uri, int flags)
{
62
    struct addrinfo hints = { 0 }, *ai, *cur_ai;
Fabrice Bellard's avatar
Fabrice Bellard committed
63
    int port, fd = -1;
64
    TCPContext *s = h->priv_data;
65 66
    const char *p;
    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 77 78 79
    if (port <= 0 || port >= 65536) {
        av_log(h, AV_LOG_ERROR, "Port missing in uri\n");
        return AVERROR(EINVAL);
    }
80 81 82
    p = strchr(uri, '?');
    if (p) {
        if (av_find_info_tag(buf, sizeof(buf), "listen", p))
83
            s->listen = 1;
84
        if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) {
85
            s->rw_timeout = strtol(buf, NULL, 10);
86
        }
87
        if (av_find_info_tag(buf, sizeof(buf), "listen_timeout", p)) {
88
            s->listen_timeout = strtol(buf, NULL, 10);
89
        }
90
    }
91 92 93 94
    if (s->rw_timeout >= 0) {
        s->open_timeout =
        h->rw_timeout   = s->rw_timeout;
    }
95
    hints.ai_family = AF_UNSPEC;
96 97
    hints.ai_socktype = SOCK_STREAM;
    snprintf(portstr, sizeof(portstr), "%d", port);
98
    if (s->listen)
99
        hints.ai_flags |= AI_PASSIVE;
100 101 102 103
    if (!hostname[0])
        ret = getaddrinfo(NULL, portstr, &hints, &ai);
    else
        ret = getaddrinfo(hostname, portstr, &hints, &ai);
104
    if (ret) {
105
        av_log(h, AV_LOG_ERROR,
106 107
               "Failed to resolve hostname %s: %s\n",
               hostname, gai_strerror(ret));
108
        return AVERROR(EIO);
109
    }
Fabrice Bellard's avatar
Fabrice Bellard committed
110

111 112 113
    cur_ai = ai;

 restart:
114 115 116
    fd = ff_socket(cur_ai->ai_family,
                   cur_ai->ai_socktype,
                   cur_ai->ai_protocol);
117 118
    if (fd < 0) {
        ret = ff_neterrno();
119
        goto fail;
120
    }
121

122
    if (s->listen) {
123
        if ((fd = ff_listen_bind(fd, cur_ai->ai_addr, cur_ai->ai_addrlen,
124
                                 s->listen_timeout, h)) < 0) {
125
            ret = fd;
126 127
            goto fail1;
        }
128
    } else {
129
        if ((ret = ff_listen_connect(fd, cur_ai->ai_addr, cur_ai->ai_addrlen,
130
                                     s->open_timeout / 1000, h, !!cur_ai->ai_next)) < 0) {
131

132
            if (ret == AVERROR_EXIT)
133
                goto fail1;
134 135
            else
                goto fail;
136
        }
137
    }
138

139
    h->is_streamed = 1;
Fabrice Bellard's avatar
Fabrice Bellard committed
140
    s->fd = fd;
141
    freeaddrinfo(ai);
Fabrice Bellard's avatar
Fabrice Bellard committed
142 143 144
    return 0;

 fail:
145 146 147 148 149
    if (cur_ai->ai_next) {
        /* Retry with the next sockaddr */
        cur_ai = cur_ai->ai_next;
        if (fd >= 0)
            closesocket(fd);
150
        ret = 0;
151 152
        goto restart;
    }
153
 fail1:
Fabrice Bellard's avatar
Fabrice Bellard committed
154
    if (fd >= 0)
155
        closesocket(fd);
156
    freeaddrinfo(ai);
157
    return ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
158 159
}

160
static int tcp_read(URLContext *h, uint8_t *buf, int size)
Fabrice Bellard's avatar
Fabrice Bellard committed
161 162
{
    TCPContext *s = h->priv_data;
163
    int ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
164

165
    if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
166 167
        ret = ff_network_wait_fd_timeout(s->fd, 0, h->rw_timeout, &h->interrupt_callback);
        if (ret)
168
            return ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
169
    }
170 171
    ret = recv(s->fd, buf, size, 0);
    return ret < 0 ? ff_neterrno() : ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
172 173
}

174
static int tcp_write(URLContext *h, const uint8_t *buf, int size)
Fabrice Bellard's avatar
Fabrice Bellard committed
175 176
{
    TCPContext *s = h->priv_data;
177
    int ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
178

179
    if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
180 181
        ret = ff_network_wait_fd_timeout(s->fd, 1, h->rw_timeout, &h->interrupt_callback);
        if (ret)
182
            return ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
183
    }
184 185
    ret = send(s->fd, buf, size, 0);
    return ret < 0 ? ff_neterrno() : ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
186 187
}

188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
static int tcp_shutdown(URLContext *h, int flags)
{
    TCPContext *s = h->priv_data;
    int how;

    if (flags & AVIO_FLAG_WRITE && flags & AVIO_FLAG_READ) {
        how = SHUT_RDWR;
    } else if (flags & AVIO_FLAG_WRITE) {
        how = SHUT_WR;
    } else {
        how = SHUT_RD;
    }

    return shutdown(s->fd, how);
}

Fabrice Bellard's avatar
Fabrice Bellard committed
204 205 206
static int tcp_close(URLContext *h)
{
    TCPContext *s = h->priv_data;
207
    closesocket(s->fd);
Fabrice Bellard's avatar
Fabrice Bellard committed
208 209 210
    return 0;
}

211 212 213 214 215 216
static int tcp_get_file_handle(URLContext *h)
{
    TCPContext *s = h->priv_data;
    return s->fd;
}

217
URLProtocol ff_tcp_protocol = {
218 219 220 221 222
    .name                = "tcp",
    .url_open            = tcp_open,
    .url_read            = tcp_read,
    .url_write           = tcp_write,
    .url_close           = tcp_close,
223
    .url_get_file_handle = tcp_get_file_handle,
224
    .url_shutdown        = tcp_shutdown,
225
    .priv_data_size      = sizeof(TCPContext),
226
    .priv_data_class     = &tcp_context_class,
227
    .flags               = URL_PROTOCOL_FLAG_NETWORK,
Fabrice Bellard's avatar
Fabrice Bellard committed
228
};