/***************************************************************************** * io.c: network I/O functions ***************************************************************************** * Copyright (C) 2004-2005, 2007 VLC authors and VideoLAN * Copyright © 2005-2006 Rémi Denis-Courmont * * Authors: Laurent Aimar * Rémi Denis-Courmont * Christophe Mutricy * * 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 * (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 * 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 License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ /***************************************************************************** * Preamble *****************************************************************************/ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #ifdef HAVE_POLL # include #endif #ifdef HAVE_LINUX_DCCP_H /* TODO: use glibc instead of linux-kernel headers */ # include # define SOL_DCCP 269 #endif #include #include #include #if defined (_WIN32) # undef EINPROGRESS # define EINPROGRESS WSAEWOULDBLOCK # undef EWOULDBLOCK # define EWOULDBLOCK WSAEWOULDBLOCK # undef EAGAIN # define EAGAIN WSAEWOULDBLOCK #endif extern int rootwrap_bind (int family, int socktype, int protocol, const struct sockaddr *addr, size_t alen); int net_Socket (vlc_object_t *p_this, int family, int socktype, int protocol) { int fd = vlc_socket (family, socktype, protocol, true); if (fd == -1) { if (net_errno != EAFNOSUPPORT) msg_Err (p_this, "cannot create socket: %s", vlc_strerror_c(net_errno)); return -1; } setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof (int)); #ifdef IPV6_V6ONLY /* * Accepts only IPv6 connections on IPv6 sockets. * If possible, we should open two sockets, but it is not always possible. */ if (family == AF_INET6) setsockopt (fd, IPPROTO_IPV6, IPV6_V6ONLY, &(int){ 1 }, sizeof (int)); #endif #if defined (_WIN32) # ifndef IPV6_PROTECTION_LEVEL # warning Please update your C library headers. # define IPV6_PROTECTION_LEVEL 23 # define PROTECTION_LEVEL_UNRESTRICTED 10 # endif if (family == AF_INET6) setsockopt (fd, IPPROTO_IPV6, IPV6_PROTECTION_LEVEL, &(int){ PROTECTION_LEVEL_UNRESTRICTED }, sizeof (int)); #endif #ifdef DCCP_SOCKOPT_SERVICE if (socktype == SOL_DCCP) { char *dccps = var_InheritString (p_this, "dccp-service"); if (dccps != NULL) { setsockopt (fd, SOL_DCCP, DCCP_SOCKOPT_SERVICE, dccps, (strlen (dccps) + 3) & ~3); free (dccps); } } #endif return fd; } 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; } int *net_Listen (vlc_object_t *p_this, const char *psz_host, unsigned i_port, int type, int protocol) { struct addrinfo hints = { .ai_socktype = type, .ai_protocol = protocol, .ai_flags = AI_PASSIVE | AI_NUMERICSERV | AI_IDN, }, *res; msg_Dbg (p_this, "net: listening to %s port %u", (psz_host != NULL) ? psz_host : "*", i_port); int i_val = vlc_getaddrinfo (psz_host, i_port, &hints, &res); if (i_val) { msg_Err (p_this, "Cannot resolve %s port %u : %s", (psz_host != NULL) ? psz_host : "", i_port, gai_strerror (i_val)); return NULL; } int *sockv = NULL; unsigned sockc = 0; for (struct addrinfo *ptr = res; ptr != NULL; ptr = ptr->ai_next) { int fd = net_Socket (p_this, ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); if (fd == -1) { msg_Dbg (p_this, "socket error: %s", vlc_strerror_c(net_errno)); continue; } /* Bind the socket */ if (bind (fd, ptr->ai_addr, ptr->ai_addrlen)) { int err = net_errno; net_Close (fd); #if !defined(_WIN32) fd = rootwrap_bind (ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol, ptr->ai_addr, ptr->ai_addrlen); if (fd != -1) { msg_Dbg (p_this, "got socket %d from rootwrap", fd); } else #endif { msg_Err (p_this, "socket bind error: %s", vlc_strerror_c(err)); continue; } } /* Listen */ if (listen(fd, INT_MAX)) { msg_Err(p_this, "socket listen error: %s", vlc_strerror_c(net_errno)); net_Close(fd); continue; } int *nsockv = (int *)realloc (sockv, (sockc + 2) * sizeof (int)); if (nsockv != NULL) { nsockv[sockc++] = fd; sockv = nsockv; } else net_Close (fd); } freeaddrinfo (res); if (sockv != NULL) sockv[sockc] = -1; return sockv; } 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; int fd = vlc_accept(sfd, NULL, NULL, true); if (fd == -1) { 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)); continue; } msg_Dbg(obj, "accepted socket %d (from socket %d)", fd, sfd); setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof (int)); /* * 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; } ssize_t (net_Read)(vlc_object_t *restrict obj, int fd, void *restrict buf, size_t len) { size_t rd = 0; do { if (vlc_killed()) { vlc_testcancel(); errno = EINTR; return -1; } ssize_t val = vlc_recv_i11e(fd, buf, len, 0); if (val < 0) { if (errno == EINTR || errno == EAGAIN) continue; #ifdef _WIN32 else if (WSAGetLastError() == WSAEMSGSIZE) /* datagram too big */ { msg_Warn(obj, "read truncated to %zu bytes", len); val = len; } #endif else { msg_Err(obj, "read error: %s", vlc_strerror_c(errno)); return rd ? (ssize_t)rd : -1; } } rd += val; if (val == 0) break; assert(len >= (size_t)val); len -= val; buf = ((char *)buf) + val; } while (len > 0); return rd; } ssize_t (net_Write)(vlc_object_t *obj, int fd, const void *buf, size_t len) { size_t written = 0; do { if (vlc_killed()) { vlc_testcancel(); errno = EINTR; return -1; } ssize_t val = vlc_send_i11e (fd, buf, len, MSG_NOSIGNAL); if (val == -1) { if (errno == EINTR || errno == EAGAIN) continue; msg_Err(obj, "write error: %s", vlc_strerror_c(errno)); return written ? (ssize_t)written : -1; } if (val == 0) break; written += val; assert(len >= (size_t)val); len -= val; buf = ((const char *)buf) + val; } while (len > 0); return written; }