/* -*- c-basic-offset: 4; -*- */ /* sock.c: General Socket Functions * * Copyright (C) 2014 Michael Smith <msmith@icecast.org>, * Brendan Cully <brendan@xiph.org>, * Karl Heyes <karl@xiph.org>, * Jack Moffitt <jack@icecast.org>, * Ed "oddsock" Zaleski <oddsock@xiph.org>, * Copyright (C) 2012-2018 Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <sys/types.h> #include <ctype.h> #include <string.h> #include <fcntl.h> #include <errno.h> #ifdef HAVE_POLL #include <poll.h> #endif #ifdef HAVE_SYS_SELECT_H #include <sys/select.h> #endif #ifndef _WIN32 #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <arpa/inet.h> #include <sys/time.h> #include <netdb.h> #else #include <winsock2.h> #endif #include <igloo/sock.h> #include <igloo/resolver.h> #include "../src/private.h" /* for older C libraries */ #ifndef AI_NUMERICSERV # define AI_NUMERICSERV 0 #endif #ifndef AI_ADDRCONFIG # define AI_ADDRCONFIG 0 #endif /* igloo_sock_initialize ** ** initializes the socket library. you must call this ** before using the library! */ void igloo_sock_initialize(void) { #ifdef _WIN32 WSADATA wsad; WSAStartup(0x0101, &wsad); #endif igloo_resolver_initialize(); } /* igloo_sock_shutdown ** ** shutdown the socket library. remember to call this when you're ** through using the lib */ void igloo_sock_shutdown(void) { #ifdef _WIN32 WSACleanup(); #endif igloo_resolver_shutdown(); } /* igloo_sock_error ** ** returns the last socket error */ int igloo_sock_error(void) { #ifdef _WIN32 return WSAGetLastError(); #else return errno; #endif } static void igloo_sock_set_error(int val) { #ifdef _WIN32 WSASetLastError (val); #else errno = val; #endif } /* igloo_sock_recoverable ** ** determines if the socket error is recoverable ** in terms of non blocking sockets */ int igloo_sock_recoverable(int error) { switch (error) { case 0: case EAGAIN: case EINTR: case EINPROGRESS: #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif #if defined (WSAEWOULDBLOCK) && WSAEWOULDBLOCK != EWOULDBLOCK case WSAEWOULDBLOCK: #endif #if defined (WSAEINPROGRESS) && WSAEINPROGRESS != EINPROGRESS case WSAEINPROGRESS: #endif #ifdef ERESTART case ERESTART: #endif return 1; default: return 0; } } static int sock_connect_pending (int error) { return error == EINPROGRESS || error == EALREADY; } /* igloo_sock_valid_socket ** ** determines if a igloo_sock_t represents a valid socket */ static int igloo_sock_valid_socket(igloo_sock_t sock) { int ret; int optval; socklen_t optlen; optlen = sizeof(int); /* apparently on windows getsockopt.optval is a char * */ ret = getsockopt(sock, SOL_SOCKET, SO_TYPE, (void*) &optval, &optlen); return (ret == 0); } /* determines if the passed socket is still connected */ int igloo_sock_active (igloo_sock_t sock) { char c; int l; l = recv (sock, &c, 1, MSG_PEEK); if (l == 0) return 0; if (l == igloo_SOCK_ERROR && igloo_sock_recoverable (igloo_sock_error())) return 1; return 0; } /* inet_aton ** ** turns an ascii ip address into a binary representation */ #ifdef _WIN32 int inet_aton(const char *s, struct in_addr *a) { int lsb, b2, b3, msb; if (sscanf(s, "%d.%d.%d.%d", &lsb, &b2, &b3, &msb) < 4) { return 0; } a->s_addr = inet_addr(s); return (a->s_addr != INADDR_NONE); } #endif /* _WIN32 */ /* igloo_sock_set_blocking * * set the sock blocking or nonblocking * 1 for blocking * 0 for nonblocking */ int igloo_sock_set_blocking(igloo_sock_t sock, int block) { #ifdef _WIN32 #ifdef __MINGW32__ u_long varblock = 1; #else int varblock = 1; #endif #endif if ((!igloo_sock_valid_socket(sock)) || (block < 0) || (block > 1)) return igloo_SOCK_ERROR; #ifdef _WIN32 if (block) varblock = 0; return ioctlsocket(sock, FIONBIO, &varblock); #else return fcntl(sock, F_SETFL, (block) ? 0 : O_NONBLOCK); #endif } static int igloo_sock_set_nolinger(igloo_sock_t sock) { struct linger lin = { 0, 0 }; return setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *)&lin, sizeof(struct linger)); } int igloo_sock_set_nodelay(igloo_sock_t sock) { int nodelay = 1; return setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&nodelay, sizeof(int)); } static int igloo_sock_set_keepalive(igloo_sock_t sock) { int keepalive = 1; return setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive, sizeof(int)); } /* igloo_sock_close ** ** close the socket */ int igloo_sock_close(igloo_sock_t sock) { #ifdef _WIN32 return closesocket(sock); #else return close(sock); #endif } /* igloo_sock_write_bytes ** ** write bytes to the socket ** this function will _NOT_ block */ int igloo_sock_write_bytes(igloo_sock_t sock, const void *buff, size_t len) { /* sanity check */ if (!buff) { return igloo_SOCK_ERROR; } else if (len <= 0) { return igloo_SOCK_ERROR; } /*else if (!igloo_sock_valid_socket(sock)) { return igloo_SOCK_ERROR; } */ return send(sock, buff, len, 0); } /* igloo_sock_write ** ** write a formatted string to the socket ** this function must only be called with a blocking socket. ** will truncate the string if it's greater than 1024 chars. */ int igloo_sock_write(igloo_sock_t sock, const char *fmt, ...) { int rc; va_list ap; va_start (ap, fmt); rc = igloo_sock_write_fmt (sock, fmt, ap); va_end (ap); return rc; } #ifdef HAVE_OLD_VSNPRINTF int igloo_sock_write_fmt(igloo_sock_t sock, const char *fmt, va_list ap) { va_list ap_local; unsigned int len = 1024; char *buff = NULL; int ret; /* don't go infinite, but stop at some huge limit */ while (len < 2*1024*1024) { char *tmp = realloc (buff, len); ret = -1; if (tmp == NULL) break; buff = tmp; va_copy (ap_local, ap); ret = vsnprintf (buff, len, fmt, ap_local); if (ret > 0) { ret = igloo_sock_write_bytes (sock, buff, ret); break; } len += 8192; } free (buff); return ret; } #else int igloo_sock_write_fmt(igloo_sock_t sock, const char *fmt, va_list ap) { char buffer [1024], *buff = buffer; int len; int rc = igloo_SOCK_ERROR; va_list ap_retry; va_copy (ap_retry, ap); len = vsnprintf (buff, sizeof (buffer), fmt, ap); if (len > 0) { if ((size_t)len < sizeof (buffer)) /* common case */ rc = igloo_sock_write_bytes(sock, buff, (size_t)len); else { /* truncated */ buff = malloc (++len); if (buff) { len = vsnprintf (buff, len, fmt, ap_retry); if (len > 0) rc = igloo_sock_write_bytes (sock, buff, len); free (buff); } } } va_end (ap_retry); return rc; } #endif int igloo_sock_read_bytes(igloo_sock_t sock, char *buff, size_t len) { /*if (!igloo_sock_valid_socket(sock)) return 0; */ if (!buff) return 0; if (len <= 0) return 0; return recv(sock, buff, len, 0); } /* igloo_sock_read_line ** ** Read one line of at max len bytes from sock into buff. ** If ok, return 1 and nullterminate buff. Otherwize return 0. ** Terminating \n is not put into the buffer. ** ** this function will probably not work on sockets in nonblocking mode */ int igloo_sock_read_line(igloo_sock_t sock, char *buff, const int len) { char c = '\0'; int read_bytes, pos; /*if (!igloo_sock_valid_socket(sock)) { return 0; } else*/ if (!buff) { return 0; } else if (len <= 0) { return 0; } pos = 0; read_bytes = recv(sock, &c, 1, 0); if (read_bytes < 0) { return 0; } while ((c != '\n') && (pos < len) && (read_bytes == 1)) { if (c != '\r') buff[pos++] = c; read_bytes = recv(sock, &c, 1, 0); } if (read_bytes == 1) { buff[pos] = '\0'; return 1; } else { return 0; } } /* see if a connection has been established. If timeout is < 0 then wait * indefinitely, else wait for the stated number of seconds. * return igloo_SOCK_TIMEOUT for timeout * return igloo_SOCK_ERROR for failure * return 0 for try again, interrupted * return 1 for ok */ #ifdef HAVE_POLL int igloo_sock_connected (igloo_sock_t sock, int timeout) { struct pollfd check; int val = igloo_SOCK_ERROR; socklen_t size = sizeof val; check.fd = sock; check.events = POLLOUT; switch (poll (&check, 1, timeout*1000)) { case 0: return igloo_SOCK_TIMEOUT; default: /* on windows getsockopt.val is defined as char* */ if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*) &val, &size) == 0) { if (val == 0) return 1; igloo_sock_set_error (val); } /* fall through */ case -1: if (igloo_sock_recoverable (igloo_sock_error())) return 0; return igloo_SOCK_ERROR; } } #else int igloo_sock_connected (igloo_sock_t sock, int timeout) { fd_set wfds; int val = igloo_SOCK_ERROR; socklen_t size = sizeof val; struct timeval tv, *timeval = NULL; /* make a timeout of <0 be indefinite */ if (timeout >= 0) { tv.tv_sec = timeout; tv.tv_usec = 0; timeval = &tv; } FD_ZERO(&wfds); FD_SET(sock, &wfds); switch (select(sock + 1, NULL, &wfds, NULL, timeval)) { case 0: return igloo_SOCK_TIMEOUT; default: /* on windows getsockopt.val is defined as char* */ if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*) &val, &size) == 0) { if (val == 0) return 1; igloo_sock_set_error (val); } /* fall through */ case -1: if (igloo_sock_recoverable (igloo_sock_error())) return 0; return igloo_SOCK_ERROR; } } #endif igloo_sock_t igloo_sock_connect_wto (const char *hostname, int port, int timeout) { return igloo_sock_connect_wto_bind(hostname, port, NULL, timeout); } #ifdef HAVE_GETADDRINFO igloo_sock_t igloo_sock_connect_non_blocking (const char *hostname, unsigned port) { int sock = igloo_SOCK_ERROR; struct addrinfo *ai, *head, hints; char service[8]; memset (&hints, 0, sizeof (hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; snprintf (service, sizeof (service), "%u", port); if (getaddrinfo (hostname, service, &hints, &head)) return igloo_SOCK_ERROR; ai = head; while (ai) { if ((sock = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol)) > -1) { igloo_sock_set_blocking (sock, 0); if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0 && !sock_connect_pending(igloo_sock_error())) { igloo_sock_close (sock); sock = igloo_SOCK_ERROR; } else break; } ai = ai->ai_next; } if (head) freeaddrinfo (head); return sock; } /* issue a connect, but return after the timeout (seconds) is reached. If * timeout is 0 or less then we will wait until the OS gives up on the connect * The socket is returned */ igloo_sock_t igloo_sock_connect_wto_bind (const char *hostname, int port, const char *bnd, int timeout) { igloo_sock_t sock = igloo_SOCK_ERROR; struct addrinfo *ai, *head, *b_head=NULL, hints; char service[8]; memset (&hints, 0, sizeof (hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; snprintf (service, sizeof (service), "%u", port); if (getaddrinfo (hostname, service, &hints, &head)) return igloo_SOCK_ERROR; ai = head; while (ai) { if ((sock = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol)) >= 0) { if (timeout > 0) igloo_sock_set_blocking (sock, 0); if (bnd) { struct addrinfo b_hints; memset (&b_hints, 0, sizeof(b_hints)); b_hints.ai_family = ai->ai_family; b_hints.ai_socktype = ai->ai_socktype; b_hints.ai_protocol = ai->ai_protocol; if (getaddrinfo (bnd, NULL, &b_hints, &b_head) || bind (sock, b_head->ai_addr, b_head->ai_addrlen) < 0) { igloo_sock_close (sock); sock = igloo_SOCK_ERROR; break; } } if (connect (sock, ai->ai_addr, ai->ai_addrlen) == 0) break; /* loop as the connect maybe async */ while (sock != igloo_SOCK_ERROR) { if (igloo_sock_recoverable (igloo_sock_error())) { int connected = igloo_sock_connected (sock, timeout); if (connected == 0) /* try again, interrupted */ continue; if (connected == 1) /* connected */ { if (timeout >= 0) igloo_sock_set_blocking(sock, 1); break; } } igloo_sock_close (sock); sock = igloo_SOCK_ERROR; } if (sock != igloo_SOCK_ERROR) break; } ai = ai->ai_next; } if (b_head) freeaddrinfo (b_head); freeaddrinfo (head); return sock; } igloo_sock_t igloo_sock_get_server_socket (int port, const char *sinterface) { struct sockaddr_storage sa; struct addrinfo hints, *res, *ai; char service [10]; int sock; if (port < 0) return igloo_SOCK_ERROR; memset (&sa, 0, sizeof(sa)); memset (&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG | AI_NUMERICSERV | AI_NUMERICHOST; hints.ai_socktype = SOCK_STREAM; snprintf (service, sizeof (service), "%d", port); if (getaddrinfo (sinterface, service, &hints, &res)) return igloo_SOCK_ERROR; ai = res; do { int on = 1; sock = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock < 0) continue; setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (const void *)&on, sizeof(on)); on = 0; #ifdef IPV6_V6ONLY setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof on); #endif if (bind (sock, ai->ai_addr, ai->ai_addrlen) < 0) { igloo_sock_close (sock); continue; } freeaddrinfo (res); return sock; } while ((ai = ai->ai_next)); freeaddrinfo (res); return igloo_SOCK_ERROR; } #else int sock_try_connection (igloo_sock_t sock, const char *hostname, unsigned int port) { struct sockaddr_in sin, server; char ip[MAX_ADDR_LEN]; if (!hostname || !hostname[0] || port == 0) return -1; memset(&sin, 0, sizeof(struct sockaddr_in)); memset(&server, 0, sizeof(struct sockaddr_in)); if (!igloo_resolver_getip(hostname, ip, MAX_ADDR_LEN)) { igloo_sock_close (sock); return -1; } if (inet_aton(ip, (struct in_addr *)&sin.sin_addr) == 0) { igloo_sock_close(sock); return -1; } memcpy(&server.sin_addr, &sin.sin_addr, sizeof(struct sockaddr_in)); server.sin_family = AF_INET; server.sin_port = htons((short)port); return connect(sock, (struct sockaddr *)&server, sizeof(server)); } igloo_sock_t igloo_sock_connect_non_blocking (const char *hostname, unsigned port) { igloo_sock_t sock; sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == igloo_SOCK_ERROR) return igloo_SOCK_ERROR; igloo_sock_set_blocking (sock, 0); sock_try_connection (sock, hostname, port); return sock; } igloo_sock_t igloo_sock_connect_wto_bind (const char *hostname, int port, const char *bnd, int timeout) { igloo_sock_t sock; sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == igloo_SOCK_ERROR) return igloo_SOCK_ERROR; if (bnd) { struct sockaddr_in sa; memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; if (inet_aton (bnd, &sa.sin_addr) == 0 || bind (sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) { igloo_sock_close (sock); return igloo_SOCK_ERROR; } } if (timeout) { igloo_sock_set_blocking (sock, 0); if (sock_try_connection (sock, hostname, port) < 0) { int ret = igloo_sock_connected (sock, timeout); if (ret <= 0) { igloo_sock_close (sock); return igloo_SOCK_ERROR; } } igloo_sock_set_blocking(sock, 1); } else { if (sock_try_connection (sock, hostname, port) < 0) { igloo_sock_close (sock); sock = igloo_SOCK_ERROR; } } return sock; } /* igloo_sock_get_server_socket ** ** create a socket for incoming requests on a specified port and ** interface. if interface is null, listen on all interfaces. ** returns the socket, or igloo_SOCK_ERROR on failure */ igloo_sock_t igloo_sock_get_server_socket(int port, const char *sinterface) { struct sockaddr_in sa; int error, opt; igloo_sock_t sock; char ip[MAX_ADDR_LEN]; if (port < 0) return igloo_SOCK_ERROR; /* defaults */ memset(&sa, 0, sizeof(sa)); /* set the interface to bind to if specified */ if (sinterface != NULL) { if (!igloo_resolver_getip(sinterface, ip, sizeof (ip))) return igloo_SOCK_ERROR; if (!inet_aton(ip, &sa.sin_addr)) { return igloo_SOCK_ERROR; } else { sa.sin_family = AF_INET; sa.sin_port = htons((short)port); } } else { sa.sin_addr.s_addr = INADDR_ANY; sa.sin_family = AF_INET; sa.sin_port = htons((short)port); } /* get a socket */ sock = socket (AF_INET, SOCK_STREAM, 0); if (sock == -1) return igloo_SOCK_ERROR; /* reuse it if we can */ opt = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(int)); /* bind socket to port */ error = bind(sock, (struct sockaddr *)&sa, sizeof (struct sockaddr_in)); if (error == -1) return igloo_SOCK_ERROR; return sock; } #endif void igloo_sock_set_send_buffer (igloo_sock_t sock, int win_size) { setsockopt (sock, SOL_SOCKET, SO_SNDBUF, (char *) &win_size, sizeof(win_size)); } int igloo_sock_listen(igloo_sock_t serversock, int backlog) { if (!igloo_sock_valid_socket(serversock)) return 0; if (backlog <= 0) backlog = 10; return (listen(serversock, backlog) == 0); } igloo_sock_t igloo_sock_accept(igloo_sock_t serversock, char *ip, size_t len) { #ifdef HAVE_GETNAMEINFO struct sockaddr_storage sa; #else struct sockaddr_in sa; #endif igloo_sock_t ret; socklen_t slen; if (ip == NULL || len == 0 || !igloo_sock_valid_socket(serversock)) return igloo_SOCK_ERROR; slen = sizeof(sa); ret = accept(serversock, (struct sockaddr *)&sa, &slen); if (ret != igloo_SOCK_ERROR) { #ifdef HAVE_GETNAMEINFO if (getnameinfo ((struct sockaddr *)&sa, slen, ip, len, NULL, 0, NI_NUMERICHOST)) snprintf (ip, len, "unknown"); #else /* inet_ntoa is not reentrant, we should protect this */ strncpy(ip, inet_ntoa(sa.sin_addr), len); #endif igloo_sock_set_nolinger(ret); igloo_sock_set_keepalive(ret); } return ret; }