tcp.c 6.65 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"
Luca Barbato's avatar
Luca Barbato committed
25

26
#include "internal.h"
27
#include "network.h"
28
#include "os_support.h"
29
#include "url.h"
30 31
#if HAVE_POLL_H
#include <poll.h>
32
#endif
Fabrice Bellard's avatar
Fabrice Bellard committed
33 34

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

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[] = {
Luca Barbato's avatar
Luca Barbato committed
47
    { "listen",          "Listen for incoming connections",  OFFSET(listen),         AV_OPT_TYPE_INT, { .i64 = 0 },     0,       1,       .flags = D|E },
48
    { "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 },
49
    { "listen_timeout",  "Connection awaiting timeout (in milliseconds)",      OFFSET(listen_timeout), AV_OPT_TYPE_INT, { .i64 = -1 },         -1, INT_MAX, .flags = D|E },
Luca Barbato's avatar
Luca Barbato committed
50
    { NULL }
51 52
};

Luca Barbato's avatar
Luca Barbato committed
53
static const AVClass tcp_class = {
54 55 56 57 58 59
    .class_name = "tcp",
    .item_name  = av_default_item_name,
    .option     = options,
    .version    = LIBAVUTIL_VERSION_INT,
};

Fabrice Bellard's avatar
Fabrice Bellard committed
60 61 62
/* return non zero if error */
static int tcp_open(URLContext *h, const char *uri, int flags)
{
63
    struct addrinfo hints = { 0 }, *ai, *cur_ai;
Fabrice Bellard's avatar
Fabrice Bellard committed
64
    int port, fd = -1;
65
    TCPContext *s = h->priv_data;
66 67
    const char *p;
    char buf[256];
68
    int ret;
69
    char hostname[1024],proto[1024],path[1024];
70
    char portstr[10];
71
    s->open_timeout = 5000000;
72

73
    av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname),
74
        &port, path, sizeof(path), uri);
75
    if (strcmp(proto, "tcp"))
76
        return AVERROR(EINVAL);
77 78 79 80
    if (port <= 0 || port >= 65536) {
        av_log(h, AV_LOG_ERROR, "Port missing in uri\n");
        return AVERROR(EINVAL);
    }
81 82
    p = strchr(uri, '?');
    if (p) {
83 84 85 86 87 88 89
        if (av_find_info_tag(buf, sizeof(buf), "listen", p)) {
            char *endptr = NULL;
            s->listen = strtol(buf, &endptr, 10);
            /* assume if no digits were found it is a request to enable it */
            if (buf == endptr)
                s->listen = 1;
        }
90
        if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) {
91
            s->rw_timeout = strtol(buf, NULL, 10);
92
        }
93
        if (av_find_info_tag(buf, sizeof(buf), "listen_timeout", p)) {
94
            s->listen_timeout = strtol(buf, NULL, 10);
95
        }
96
    }
97 98 99 100
    if (s->rw_timeout >= 0) {
        s->open_timeout =
        h->rw_timeout   = s->rw_timeout;
    }
101
    hints.ai_family = AF_UNSPEC;
102 103
    hints.ai_socktype = SOCK_STREAM;
    snprintf(portstr, sizeof(portstr), "%d", port);
104
    if (s->listen)
105
        hints.ai_flags |= AI_PASSIVE;
106 107 108 109
    if (!hostname[0])
        ret = getaddrinfo(NULL, portstr, &hints, &ai);
    else
        ret = getaddrinfo(hostname, portstr, &hints, &ai);
110
    if (ret) {
111
        av_log(h, AV_LOG_ERROR,
112 113
               "Failed to resolve hostname %s: %s\n",
               hostname, gai_strerror(ret));
114
        return AVERROR(EIO);
115
    }
Fabrice Bellard's avatar
Fabrice Bellard committed
116

117 118 119
    cur_ai = ai;

 restart:
120 121 122
    fd = ff_socket(cur_ai->ai_family,
                   cur_ai->ai_socktype,
                   cur_ai->ai_protocol);
123 124
    if (fd < 0) {
        ret = ff_neterrno();
125
        goto fail;
126
    }
127

128
    if (s->listen) {
129 130
        if ((ret = ff_listen_bind(fd, cur_ai->ai_addr, cur_ai->ai_addrlen,
                                  s->listen_timeout, h)) < 0) {
131 132
            goto fail1;
        }
133
        fd = ret;
134
    } else {
135
        if ((ret = ff_listen_connect(fd, cur_ai->ai_addr, cur_ai->ai_addrlen,
136
                                     s->open_timeout / 1000, h, !!cur_ai->ai_next)) < 0) {
137

138
            if (ret == AVERROR_EXIT)
139
                goto fail1;
140 141
            else
                goto fail;
142
        }
143
    }
144

145
    h->is_streamed = 1;
Fabrice Bellard's avatar
Fabrice Bellard committed
146
    s->fd = fd;
147
    freeaddrinfo(ai);
Fabrice Bellard's avatar
Fabrice Bellard committed
148 149 150
    return 0;

 fail:
151 152 153 154 155
    if (cur_ai->ai_next) {
        /* Retry with the next sockaddr */
        cur_ai = cur_ai->ai_next;
        if (fd >= 0)
            closesocket(fd);
156
        ret = 0;
157 158
        goto restart;
    }
159
 fail1:
Fabrice Bellard's avatar
Fabrice Bellard committed
160
    if (fd >= 0)
161
        closesocket(fd);
162
    freeaddrinfo(ai);
163
    return ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
164 165
}

166
static int tcp_read(URLContext *h, uint8_t *buf, int size)
Fabrice Bellard's avatar
Fabrice Bellard committed
167 168
{
    TCPContext *s = h->priv_data;
169
    int ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
170

171
    if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
172 173
        ret = ff_network_wait_fd_timeout(s->fd, 0, h->rw_timeout, &h->interrupt_callback);
        if (ret)
174
            return ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
175
    }
176 177
    ret = recv(s->fd, buf, size, 0);
    return ret < 0 ? ff_neterrno() : ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
178 179
}

180
static int tcp_write(URLContext *h, const uint8_t *buf, int size)
Fabrice Bellard's avatar
Fabrice Bellard committed
181 182
{
    TCPContext *s = h->priv_data;
183
    int ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
184

185
    if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
186 187
        ret = ff_network_wait_fd_timeout(s->fd, 1, h->rw_timeout, &h->interrupt_callback);
        if (ret)
188
            return ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
189
    }
190
    ret = send(s->fd, buf, size, MSG_NOSIGNAL);
191
    return ret < 0 ? ff_neterrno() : ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
192 193
}

194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
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
210 211 212
static int tcp_close(URLContext *h)
{
    TCPContext *s = h->priv_data;
213
    closesocket(s->fd);
Fabrice Bellard's avatar
Fabrice Bellard committed
214 215 216
    return 0;
}

217 218 219 220 221 222
static int tcp_get_file_handle(URLContext *h)
{
    TCPContext *s = h->priv_data;
    return s->fd;
}

223
URLProtocol ff_tcp_protocol = {
224 225 226 227 228
    .name                = "tcp",
    .url_open            = tcp_open,
    .url_read            = tcp_read,
    .url_write           = tcp_write,
    .url_close           = tcp_close,
229
    .url_get_file_handle = tcp_get_file_handle,
230
    .url_shutdown        = tcp_shutdown,
231
    .priv_data_size      = sizeof(TCPContext),
232
    .flags               = URL_PROTOCOL_FLAG_NETWORK,
Luca Barbato's avatar
Luca Barbato committed
233
    .priv_data_class     = &tcp_class,
Fabrice Bellard's avatar
Fabrice Bellard committed
234
};