tcp.c 6.32 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
 * This file is part of Libav.
6
 *
7
 * Libav 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
 * Libav 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 Libav; 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"
Luca Barbato's avatar
Luca Barbato committed
23 24
#include "libavutil/opt.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 {
Luca Barbato's avatar
Luca Barbato committed
34
    const AVClass *class;
Fabrice Bellard's avatar
Fabrice Bellard committed
35
    int fd;
Luca Barbato's avatar
Luca Barbato committed
36 37 38
    int listen;
    int timeout;
    int listen_timeout;
Fabrice Bellard's avatar
Fabrice Bellard committed
39 40
} TCPContext;

Luca Barbato's avatar
Luca Barbato committed
41 42 43 44 45
#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 for incoming connections",  OFFSET(listen),         AV_OPT_TYPE_INT, { .i64 = 0 },     0,       1,       .flags = D|E },
46
    { "timeout",         "Connection timeout (in milliseconds)", OFFSET(timeout),    AV_OPT_TYPE_INT, { .i64 = 0 }, INT_MIN, INT_MAX, .flags = D|E },
47
    { "listen_timeout",  "Bind timeout (in milliseconds)",   OFFSET(listen_timeout), AV_OPT_TYPE_INT, { .i64 = -1 },    INT_MIN, INT_MAX, .flags = D|E },
Luca Barbato's avatar
Luca Barbato committed
48 49 50 51 52 53 54 55 56 57
    { NULL }
};

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

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

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

109 110 111
    cur_ai = ai;

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

Luca Barbato's avatar
Luca Barbato committed
120
    if (s->listen) {
121 122
        if ((ret = ff_listen_bind(fd, cur_ai->ai_addr, cur_ai->ai_addrlen,
                                  s->listen_timeout, h)) < 0) {
123 124
            goto fail1;
        }
125
        fd = ret;
126
    } else {
127
        if ((ret = ff_listen_connect(fd, cur_ai->ai_addr, cur_ai->ai_addrlen,
Luca Barbato's avatar
Luca Barbato committed
128
                                     s->timeout, h, !!cur_ai->ai_next)) < 0) {
Fabrice Bellard's avatar
Fabrice Bellard committed
129

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

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

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

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

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

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

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

186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
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
202 203 204
static int tcp_close(URLContext *h)
{
    TCPContext *s = h->priv_data;
205
    closesocket(s->fd);
Fabrice Bellard's avatar
Fabrice Bellard committed
206 207 208
    return 0;
}

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

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