io.c 12 KB
Newer Older
Laurent Aimar's avatar
 
Laurent Aimar committed
1
/*****************************************************************************
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
2
 * io.c: network I/O functions
Laurent Aimar's avatar
 
Laurent Aimar committed
3
 *****************************************************************************
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2004-2005, 2007 VLC authors and VideoLAN
5
 * Copyright © 2005-2006 Rémi Denis-Courmont
Laurent Aimar's avatar
 
Laurent Aimar committed
6
7
 *
 * Authors: Laurent Aimar <fenrir@videolan.org>
8
 *          Rémi Denis-Courmont
9
 *          Christophe Mutricy <xtophe at videolan dot org>
Laurent Aimar's avatar
 
Laurent Aimar committed
10
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
11
12
13
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
Laurent Aimar's avatar
 
Laurent Aimar committed
14
15
16
17
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
18
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
Laurent Aimar's avatar
 
Laurent Aimar committed
20
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
21
22
23
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
Laurent Aimar's avatar
 
Laurent Aimar committed
24
25
26
27
28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
29

30
31
32
33
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

34
35
#include <stdlib.h>
#include <stdio.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
36
#include <limits.h>
37
#include <errno.h>
38
#include <assert.h>
Laurent Aimar's avatar
 
Laurent Aimar committed
39

40
#include <unistd.h>
41
#ifdef HAVE_POLL_H
42
43
# include <poll.h>
#endif
44
45
46
47
48
49
#ifdef HAVE_LINUX_DCCP_H
/* TODO: use glibc instead of linux-kernel headers */
# include <linux/dccp.h>
# define SOL_DCCP 269
#endif

50
51
52
#include <vlc_common.h>
#include <vlc_network.h>
#include <vlc_interrupt.h>
53
54
55
56
57
58
59
60
#if defined (_WIN32)
#   undef EINPROGRESS
#   define EINPROGRESS WSAEWOULDBLOCK
#   undef EWOULDBLOCK
#   define EWOULDBLOCK WSAEWOULDBLOCK
#   undef EAGAIN
#   define EAGAIN WSAEWOULDBLOCK
#endif
61

62
63
64
extern int rootwrap_bind (int family, int socktype, int protocol,
                          const struct sockaddr *addr, size_t alen);

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
65
66
67
int net_Socket (vlc_object_t *p_this, int family, int socktype,
                int protocol)
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
68
69
    int fd = vlc_socket (family, socktype, protocol, true);
    if (fd == -1)
70
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
71
        if (net_errno != EAFNOSUPPORT)
72
73
            msg_Err (p_this, "cannot create socket: %s",
                     vlc_strerror_c(net_errno));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
74
        return -1;
75
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
76

77
78
79
80
#ifdef _WIN32
    // Windows expects a BOOL for some getsockopt/setsockopt options
    static_assert(sizeof(int)==sizeof(BOOL), "mismatching type for setsockopt");
#endif
81
    setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof (int));
82
83
84

#ifdef IPV6_V6ONLY
    /*
85
     * Accepts only IPv6 connections on IPv6 sockets.
86
     * If possible, we should open two sockets, but it is not always possible.
87
     */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
88
    if (family == AF_INET6)
89
        setsockopt (fd, IPPROTO_IPV6, IPV6_V6ONLY, &(int){ 1 }, sizeof (int));
90
91
#endif

92
#if defined (_WIN32)
93
# ifndef IPV6_PROTECTION_LEVEL
94
#  warning Please update your C library headers.
95
#  define IPV6_PROTECTION_LEVEL 23
96
#  define PROTECTION_LEVEL_UNRESTRICTED 10
97
# endif
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
98
99
100
    if (family == AF_INET6)
        setsockopt (fd, IPPROTO_IPV6, IPV6_PROTECTION_LEVEL,
                    &(int){ PROTECTION_LEVEL_UNRESTRICTED }, sizeof (int));
101
#endif
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
102

103
#ifdef DCCP_SOCKOPT_SERVICE
104
    if (socktype == SOL_DCCP)
105
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
106
        char *dccps = var_InheritString (p_this, "dccp-service");
107
108
109
110
111
112
        if (dccps != NULL)
        {
            setsockopt (fd, SOL_DCCP, DCCP_SOCKOPT_SERVICE, dccps,
                        (strlen (dccps) + 3) & ~3);
            free (dccps);
        }
113
114
115
    }
#endif

116
117
118
    return fd;
}

119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
int (net_Connect)(vlc_object_t *obj, const char *host, int serv,
                  int type, int proto)
{
    struct addrinfo hints = {
        .ai_socktype = type,
        .ai_protocol = proto,
        .ai_flags = AI_NUMERICSERV | AI_IDN,
    }, *res;
    int ret = -1;

    int val = vlc_getaddrinfo_i11e(host, serv, &hints, &res);
    if (val)
    {
        msg_Err(obj, "cannot resolve %s port %d : %s", host, serv,
                gai_strerror (val));
        return -1;
    }

    vlc_tick_t timeout = VLC_TICK_FROM_MS(var_InheritInteger(obj,
                                                             "ipv4-timeout"));

    for (struct addrinfo *ptr = res; ptr != NULL; ptr = ptr->ai_next)
    {
        int fd = net_Socket(obj, ptr->ai_family,
                            ptr->ai_socktype, ptr->ai_protocol);
        if (fd == -1)
        {
            msg_Dbg(obj, "socket error: %s", vlc_strerror_c(net_errno));
            continue;
        }

        if (connect(fd, ptr->ai_addr, ptr->ai_addrlen))
        {
            if (net_errno != EINPROGRESS && errno != EINTR)
            {
                msg_Err(obj, "connection failed: %s",
                        vlc_strerror_c(net_errno));
                goto next_ai;
            }

            struct pollfd ufd;
            vlc_tick_t deadline = VLC_TICK_INVALID;

            ufd.fd = fd;
            ufd.events = POLLOUT;
            deadline = vlc_tick_now() + timeout;

            do
            {
                vlc_tick_t now = vlc_tick_now();

                if (vlc_killed())
                    goto next_ai;

                if (now > deadline)
                    now = deadline;

                val = vlc_poll_i11e(&ufd, 1, MS_FROM_VLC_TICK(deadline - now));
            }
            while (val == -1 && errno == EINTR);

            switch (val)
            {
                 case -1: /* error */
                     msg_Err(obj, "polling error: %s",
                             vlc_strerror_c(net_errno));
                     goto next_ai;

                 case 0: /* timeout */
                     msg_Warn(obj, "connection timed out");
                     goto next_ai;
            }

            /* There is NO WAY around checking SO_ERROR.
             * Don't ifdef it out!!! */
            if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &val,
                           &(socklen_t){ sizeof (val) }) || val)
            {
                msg_Err(obj, "connection failed: %s", vlc_strerror_c(val));
                goto next_ai;
            }
        }

        msg_Dbg(obj, "connection succeeded (socket = %d)", fd);
        ret = fd; /* success! */
        break;

next_ai: /* failure */
        net_Close(fd);
    }

    freeaddrinfo(res);
    return ret;
}
Laurent Aimar's avatar
Laurent Aimar committed
213

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
214
int *net_Listen (vlc_object_t *p_this, const char *psz_host,
215
                 unsigned i_port, int type, int protocol)
216
{
217
218
219
    struct addrinfo hints = {
        .ai_socktype = type,
        .ai_protocol = protocol,
220
        .ai_flags = AI_PASSIVE | AI_NUMERICSERV | AI_IDN,
221
    }, *res;
222

223
    msg_Dbg (p_this, "net: listening to %s port %u",
224
             (psz_host != NULL) ? psz_host : "*", i_port);
225

226
    int i_val = vlc_getaddrinfo (psz_host, i_port, &hints, &res);
227
228
    if (i_val)
    {
229
        msg_Err (p_this, "Cannot resolve %s port %u : %s",
230
                 (psz_host != NULL) ? psz_host : "", i_port,
231
                 gai_strerror (i_val));
232
233
234
235
236
237
238
239
        return NULL;
    }

    int *sockv = NULL;
    unsigned sockc = 0;

    for (struct addrinfo *ptr = res; ptr != NULL; ptr = ptr->ai_next)
    {
240
241
        int fd = net_Socket (p_this, ptr->ai_family, ptr->ai_socktype,
                             ptr->ai_protocol);
242
243
        if (fd == -1)
        {
244
            msg_Dbg (p_this, "socket error: %s", vlc_strerror_c(net_errno));
245
246
247
248
249
250
            continue;
        }

        /* Bind the socket */
        if (bind (fd, ptr->ai_addr, ptr->ai_addrlen))
        {
251
            int err = net_errno;
252
            net_Close (fd);
253
#if !defined(_WIN32)
254
255
            fd = rootwrap_bind (ptr->ai_family, ptr->ai_socktype,
                                ptr->ai_protocol,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
256
                                ptr->ai_addr, ptr->ai_addrlen);
257
258
259
260
261
262
263
            if (fd != -1)
            {
                msg_Dbg (p_this, "got socket %d from rootwrap", fd);
            }
            else
#endif
            {
264
                msg_Err (p_this, "socket bind error: %s", vlc_strerror_c(err));
265
266
267
268
269
                continue;
            }
        }

        /* Listen */
270
        if (listen(fd, INT_MAX))
271
        {
272
273
274
275
            msg_Err(p_this, "socket listen error: %s",
                    vlc_strerror_c(net_errno));
            net_Close(fd);
            continue;
276
277
278
279
280
281
282
283
284
285
286
287
        }

        int *nsockv = (int *)realloc (sockv, (sockc + 2) * sizeof (int));
        if (nsockv != NULL)
        {
            nsockv[sockc++] = fd;
            sockv = nsockv;
        }
        else
            net_Close (fd);
    }

288
    freeaddrinfo (res);
289
290
291
292
293
294
295

    if (sockv != NULL)
        sockv[sockc] = -1;

    return sockv;
}

296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
void net_ListenClose(int *fds)
{
    if (fds != NULL)
    {
        for (int *p = fds; *p != -1; p++)
            net_Close(*p);

        free(fds);
    }
}

#undef net_Accept
int net_Accept(vlc_object_t *obj, int *fds)
{
    assert(fds != NULL);

    unsigned n = 0;
    while (fds[n] != -1)
        n++;

    struct pollfd ufd[n];
    /* Initialize file descriptor set */
    for (unsigned i = 0; i < n; i++)
    {
        ufd[i].fd = fds[i];
        ufd[i].events = POLLIN;
    }

    for (;;)
    {
        while (poll(ufd, n, -1) == -1)
        {
            if (net_errno != EINTR)
            {
                msg_Err(obj, "poll error: %s", vlc_strerror_c(net_errno));
                return -1;
            }
        }

        for (unsigned i = 0; i < n; i++)
        {
            if (ufd[i].revents == 0)
                continue;

            int sfd = ufd[i].fd;
341
            int fd = vlc_accept(sfd, NULL, NULL, true);
342
            if (fd == -1)
343
344
345
346
347
348
349
            {
                if (net_errno != EAGAIN)
#if (EAGAIN != EWOULDBLOCK)
                if (net_errno != EWOULDBLOCK)
#endif
                    msg_Err(obj, "accept failed (from socket %d): %s", sfd,
                            vlc_strerror_c(net_errno));
350
                continue;
351
            }
352

353
354
355
            msg_Dbg(obj, "accepted socket %d (from socket %d)", fd, sfd);
            setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
                       &(int){ 1 }, sizeof (int));
356
357
358
359
360
361
362
363
364
365
366
367
            /*
             * Move listening socket to the end to let the others in the
             * set a chance next time.
             */
            memmove(fds + i, fds + i + 1, n - (i + 1));
            fds[n - 1] = sfd;
            return fd;
        }
    }
    return -1;
}

368
369
ssize_t (net_Read)(vlc_object_t *restrict obj, int fd,
                   void *restrict buf, size_t len)
370
{
371
    size_t rd = 0;
372

373
    do
374
    {
375
        if (vlc_killed())
376
377
378
379
380
381
382
383
        {
            vlc_testcancel();
            errno = EINTR;
            return -1;
        }

        ssize_t val = vlc_recv_i11e(fd, buf, len, 0);
        if (val < 0)
384
        {
385
386
387
388
            if (errno == EINTR || errno == EAGAIN)
                continue;
#ifdef _WIN32
            else if (WSAGetLastError() == WSAEMSGSIZE) /* datagram too big */
389
            {
390
391
392
                msg_Warn(obj, "read truncated to %zu bytes", len);
                val = len;
            }
393
#endif
394
395
396
397
            else
            {
                msg_Err(obj, "read error: %s", vlc_strerror_c(errno));
                return rd ? (ssize_t)rd : -1;
398
            }
399
400
        }

401
        rd += val;
402

403
404
        if (val == 0)
            break;
405

406
407
408
        assert(len >= (size_t)val);
        len -= val;
        buf = ((char *)buf) + val;
409
    }
410
    while (len > 0);
411

412
    return rd;
413
414
}

415
ssize_t (net_Write)(vlc_object_t *obj, int fd, const void *buf, size_t len)
Laurent Aimar's avatar
 
Laurent Aimar committed
416
{
417
    size_t written = 0;
Laurent Aimar's avatar
 
Laurent Aimar committed
418

419
    do
Laurent Aimar's avatar
 
Laurent Aimar committed
420
    {
421
        if (vlc_killed())
Laurent Aimar's avatar
 
Laurent Aimar committed
422
        {
423
424
            vlc_testcancel();
            errno = EINTR;
425
            return -1;
Laurent Aimar's avatar
 
Laurent Aimar committed
426
427
        }

428
        ssize_t val = vlc_send_i11e(fd, buf, len, 0);
429
        if (val == -1)
430
        {
431
            if (errno == EINTR || errno == EAGAIN)
432
                continue;
433

434
435
436
            msg_Err(obj, "write error: %s", vlc_strerror_c(errno));
            return written ? (ssize_t)written : -1;
        }
437

438
439
        if (val == 0)
            break;
440

441
442
443
444
445
446
        written += val;
        assert(len >= (size_t)val);
        len -= val;
        buf = ((const char *)buf) + val;
    }
    while (len > 0);
447

448
    return written;
Laurent Aimar's avatar
 
Laurent Aimar committed
449
}