blob: edfcf12ec459f6922fefe00f5a3d874d15700351 [file] [log] [blame]
/*
* socket.c
*
* Copyright (C) 2012-2020 Nikias Bassen <nikias@gmx.li>
* Copyright (C) 2012 Martin Szulecki <m.szulecki@libimobiledevice.org>
*
* This library 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 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/stat.h>
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#ifndef HAVE_GETIFADDRS
#include <iphlpapi.h>
#endif
static int wsa_init = 0;
#ifndef IFF_RUNNING
#define IFF_RUNNING IFF_UP
#endif
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV 0
#endif
#else
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <fcntl.h>
#ifdef AF_INET6
#include <net/if.h>
#include <ifaddrs.h>
#ifdef __APPLE__
#include <net/if_dl.h>
#endif
#ifdef __linux__
#include <netpacket/packet.h>
#endif
#endif
#endif
#include "socket.h"
#define RECV_TIMEOUT 20000
#define SEND_TIMEOUT 10000
#define CONNECT_TIMEOUT 5000
#ifndef EAFNOSUPPORT
#define EAFNOSUPPORT 102
#endif
#ifndef ECONNRESET
#define ECONNRESET 108
#endif
#ifndef ETIMEDOUT
#define ETIMEDOUT 138
#endif
static int verbose = 0;
void socket_set_verbose(int level)
{
verbose = level;
}
const char *socket_addr_to_string(struct sockaddr *addr, char *addr_out, size_t addr_out_size)
{
#ifdef WIN32
WSADATA wsa_data;
if (!wsa_init) {
if (WSAStartup(MAKEWORD(2,2), &wsa_data) != ERROR_SUCCESS) {
fprintf(stderr, "WSAStartup failed!\n");
ExitProcess(-1);
}
wsa_init = 1;
}
DWORD addr_out_len = addr_out_size;
DWORD addrlen = 0;
if (addr->sa_family == AF_INET) {
addrlen = sizeof(struct sockaddr_in);
}
#ifdef AF_INET6
else if (addr->sa_family == AF_INET6) {
addrlen = sizeof(struct sockaddr_in6);
}
#endif
else {
errno = EAFNOSUPPORT;
return NULL;
}
if (WSAAddressToString(addr, addrlen, NULL, addr_out, &addr_out_len) == 0) {
return addr_out;
}
#else
const void *addrdata = NULL;
if (addr->sa_family == AF_INET) {
addrdata = &((struct sockaddr_in*)addr)->sin_addr;
}
#ifdef AF_INET6
else if (addr->sa_family == AF_INET6) {
addrdata = &((struct sockaddr_in6*)addr)->sin6_addr;
}
#endif
else {
errno = EAFNOSUPPORT;
return NULL;
}
if (inet_ntop(addr->sa_family, addrdata, addr_out, addr_out_size)) {
return addr_out;
}
#endif
return NULL;
}
#ifndef WIN32
int socket_create_unix(const char *filename)
{
struct sockaddr_un name;
int sock;
#ifdef SO_NOSIGPIPE
int yes = 1;
#endif
// remove if still present
unlink(filename);
/* Create the socket. */
sock = socket(PF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
return -1;
}
#ifdef SO_NOSIGPIPE
if (setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (void*)&yes, sizeof(int)) == -1) {
perror("setsockopt()");
socket_close(sock);
return -1;
}
#endif
/* Bind a name to the socket. */
name.sun_family = AF_UNIX;
strncpy(name.sun_path, filename, sizeof(name.sun_path));
name.sun_path[sizeof(name.sun_path) - 1] = '\0';
if (bind(sock, (struct sockaddr*)&name, sizeof(name)) < 0) {
perror("bind");
socket_close(sock);
return -1;
}
if (listen(sock, 100) < 0) {
perror("listen");
socket_close(sock);
return -1;
}
return sock;
}
int socket_connect_unix(const char *filename)
{
struct sockaddr_un name;
int sfd = -1;
struct stat fst;
#ifdef SO_NOSIGPIPE
int yes = 1;
#endif
int bufsize = 0x20000;
// check if socket file exists...
if (stat(filename, &fst) != 0) {
if (verbose >= 2)
fprintf(stderr, "%s: stat '%s': %s\n", __func__, filename,
strerror(errno));
return -1;
}
// ... and if it is a unix domain socket
if (!S_ISSOCK(fst.st_mode)) {
if (verbose >= 2)
fprintf(stderr, "%s: File '%s' is not a socket!\n", __func__,
filename);
return -1;
}
// make a new socket
if ((sfd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
if (verbose >= 2)
fprintf(stderr, "%s: socket: %s\n", __func__, strerror(errno));
return -1;
}
if (setsockopt(sfd, SOL_SOCKET, SO_SNDBUF, (void*)&bufsize, sizeof(int)) == -1) {
perror("Could not set send buffer for socket");
}
if (setsockopt(sfd, SOL_SOCKET, SO_RCVBUF, (void*)&bufsize, sizeof(int)) == -1) {
perror("Could not set receive buffer for socket");
}
#ifdef SO_NOSIGPIPE
if (setsockopt(sfd, SOL_SOCKET, SO_NOSIGPIPE, (void*)&yes, sizeof(int)) == -1) {
perror("setsockopt()");
socket_close(sfd);
return -1;
}
#endif
// and connect to 'filename'
name.sun_family = AF_UNIX;
strncpy(name.sun_path, filename, sizeof(name.sun_path));
name.sun_path[sizeof(name.sun_path) - 1] = 0;
int flags = fcntl(sfd, F_GETFL, 0);
fcntl(sfd, F_SETFL, flags | O_NONBLOCK);
do {
if (connect(sfd, (struct sockaddr*)&name, sizeof(name)) != -1) {
break;
}
if (errno == EINPROGRESS) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(sfd, &fds);
struct timeval timeout;
timeout.tv_sec = CONNECT_TIMEOUT / 1000;
timeout.tv_usec = (CONNECT_TIMEOUT - (timeout.tv_sec * 1000)) * 1000;
if (select(sfd + 1, NULL, &fds, NULL, &timeout) == 1) {
int so_error;
socklen_t len = sizeof(so_error);
getsockopt(sfd, SOL_SOCKET, SO_ERROR, (void*)&so_error, &len);
if (so_error == 0) {
break;
}
}
}
socket_close(sfd);
sfd = -1;
} while (0);
if (sfd < 0) {
if (verbose >= 2)
fprintf(stderr, "%s: connect: %s\n", __func__, strerror(errno));
return -1;
}
return sfd;
}
#endif
int socket_create(const char* addr, uint16_t port)
{
int sfd = -1;
int yes = 1;
struct addrinfo hints;
struct addrinfo *result, *rp;
char portstr[8];
int res;
#ifdef WIN32
WSADATA wsa_data;
if (!wsa_init) {
if (WSAStartup(MAKEWORD(2,2), &wsa_data) != ERROR_SUCCESS) {
fprintf(stderr, "WSAStartup failed!\n");
ExitProcess(-1);
}
wsa_init = 1;
}
#endif
memset(&hints, '\0', sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
hints.ai_protocol = IPPROTO_TCP;
sprintf(portstr, "%d", port);
if (!addr) {
addr = "localhost";
}
res = getaddrinfo(addr, portstr, &hints, &result);
if (res != 0) {
fprintf(stderr, "%s: getaddrinfo: %s\n", __func__, gai_strerror(res));
return -1;
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sfd == -1) {
continue;
}
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(int)) == -1) {
perror("setsockopt()");
socket_close(sfd);
continue;
}
#ifdef SO_NOSIGPIPE
if (setsockopt(sfd, SOL_SOCKET, SO_NOSIGPIPE, (void*)&yes, sizeof(int)) == -1) {
perror("setsockopt()");
socket_close(sfd);
continue;
}
#endif
#if defined(AF_INET6) && defined(IPV6_V6ONLY)
if (rp->ai_family == AF_INET6) {
if (setsockopt(sfd, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&yes, sizeof(int)) == -1) {
perror("setsockopt() IPV6_V6ONLY");
}
}
#endif
if (bind(sfd, rp->ai_addr, rp->ai_addrlen) < 0) {
perror("bind()");
socket_close(sfd);
continue;
}
if (listen(sfd, 100) < 0) {
perror("listen()");
socket_close(sfd);
continue;
}
break;
}
freeaddrinfo(result);
if (rp == NULL) {
return -1;
}
return sfd;
}
#ifdef AF_INET6
static uint32_t _in6_addr_scope(struct in6_addr* addr)
{
uint32_t scope = 0;
if (IN6_IS_ADDR_MULTICAST(addr)) {
if (IN6_IS_ADDR_MC_NODELOCAL(addr)) {
scope = 1;
} else if (IN6_IS_ADDR_MC_LINKLOCAL(addr)) {
scope = 2;
} else if (IN6_IS_ADDR_MC_SITELOCAL(addr)) {
scope = 5;
}
return scope;
}
if (IN6_IS_ADDR_LINKLOCAL(addr)) {
scope = 2;
} else if (IN6_IS_ADDR_LOOPBACK(addr)) {
scope = 2;
} else if (IN6_IS_ADDR_SITELOCAL(addr)) {
scope = 5;
} else if (IN6_IS_ADDR_UNSPECIFIED(addr)) {
scope = 0;
}
return scope;
}
#ifndef HAVE_GETIFADDRS
#ifdef WIN32
struct ifaddrs {
struct ifaddrs *ifa_next; /* Next item in list */
char *ifa_name; /* Name of interface */
unsigned int ifa_flags; /* Flags from SIOCGIFFLAGS */
struct sockaddr *ifa_addr; /* Address of interface */
struct sockaddr *ifa_netmask; /* Netmask of interface */
union {
struct sockaddr *ifu_broadaddr; /* Broadcast address of interface */
struct sockaddr *ifu_dstaddr; /* Point-to-point destination address */
} ifa_ifu;
#define ifa_broadaddr ifa_ifu.ifu_broadaddr
#define ifa_dstaddr ifa_ifu.ifu_dstaddr
void *ifa_data; /* Address-specific data */
};
#define WORKING_BUFFER_SIZE 15000
#define MAX_TRIES 3
static void freeifaddrs(struct ifaddrs *ifa)
{
if (!ifa) {
return;
}
free(ifa->ifa_name);
free(ifa->ifa_addr);
free(ifa->ifa_netmask);
free(ifa->ifa_dstaddr);
free(ifa->ifa_data);
freeifaddrs(ifa->ifa_next);
free(ifa);
}
/*
* getifaddrs() reference implementation for win32.
* Heavily based on openpgm's implementation found here:
* https://github.com/steve-o/openpgm/blob/master/openpgm/pgm/getifaddrs.c
*/
static int getifaddrs(struct ifaddrs** ifap)
{
struct ifaddrs* ifa = NULL;
DWORD dwRetVal = 0;
PIP_ADAPTER_ADDRESSES pAddresses = NULL;
ULONG outBufLen = 0;
ULONG Iterations = 0;
ULONG flags = GAA_FLAG_INCLUDE_PREFIX |
GAA_FLAG_SKIP_ANYCAST |
GAA_FLAG_SKIP_DNS_SERVER |
GAA_FLAG_SKIP_FRIENDLY_NAME |
GAA_FLAG_SKIP_MULTICAST;
PIP_ADAPTER_ADDRESSES adapter = NULL;
if (!ifap) {
errno = EINVAL;
return -1;
}
*ifap = NULL;
outBufLen = WORKING_BUFFER_SIZE;
do {
pAddresses = (IP_ADAPTER_ADDRESSES*)malloc(outBufLen);
if (pAddresses == NULL) {
printf("Memory allocation failed for IP_ADAPTER_ADDRESSES struct\n");
return -1;
}
dwRetVal = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, pAddresses, &outBufLen);
if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
free(pAddresses);
pAddresses = NULL;
} else {
break;
}
Iterations++;
} while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iterations < MAX_TRIES));
if (dwRetVal != NO_ERROR) {
free(pAddresses);
return -1;
}
for (adapter = pAddresses; adapter; adapter = adapter->Next) {
int unicastIndex = 0;
for (IP_ADAPTER_UNICAST_ADDRESS *unicast = adapter->FirstUnicastAddress; unicast; unicast = unicast->Next, ++unicastIndex) {
/* ensure IP adapter */
if (AF_INET != unicast->Address.lpSockaddr->sa_family && AF_INET6 != unicast->Address.lpSockaddr->sa_family) {
continue;
}
if (!ifa) {
ifa = malloc(sizeof(struct ifaddrs));
if (!ifa) {
errno = ENOMEM;
free(pAddresses);
return -1;
}
*ifap = ifa;
ifa->ifa_next = NULL;
} else {
struct ifaddrs* ifanew = malloc(sizeof(struct ifaddrs));
if (!ifanew) {
freeifaddrs(*ifap);
free(pAddresses);
errno = ENOMEM;
return -1;
}
ifa->ifa_next = ifanew;
ifa = ifanew;
ifa->ifa_next = NULL;
}
ifa->ifa_data = NULL;
/* name */
ifa->ifa_name = strdup(adapter->AdapterName);
/* flags */
ifa->ifa_flags = 0;
if (IfOperStatusUp == adapter->OperStatus)
ifa->ifa_flags |= IFF_UP;
if (IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType)
ifa->ifa_flags |= IFF_LOOPBACK;
if (!(adapter->Flags & IP_ADAPTER_NO_MULTICAST))
ifa->ifa_flags |= IFF_MULTICAST;
/* address */
ifa->ifa_addr = (struct sockaddr*)malloc(sizeof(struct sockaddr_storage));
memcpy(ifa->ifa_addr, unicast->Address.lpSockaddr, unicast->Address.iSockaddrLength);
/* netmask */
ifa->ifa_netmask = (struct sockaddr*)malloc(sizeof(struct sockaddr_storage));
memset(ifa->ifa_netmask, 0, sizeof(struct sockaddr_storage));
/* store mac address */
if (adapter->PhysicalAddressLength == 6) {
ifa->ifa_data = malloc(6);
memcpy(ifa->ifa_data, adapter->PhysicalAddress, 6);
}
/* pre-Vista must hunt for matching prefix in linked list, otherwise use
* OnLinkPrefixLength from IP_ADAPTER_UNICAST_ADDRESS structure.
* FirstPrefix requires Windows XP SP1, from SP1 to pre-Vista provides a
* single adapter prefix for each IP address. Vista and later provides
* host IP address prefix, subnet IP address, and subnet broadcast IP
* address. In addition there is a multicast and broadcast address prefix.
*/
ULONG prefixLength = 0;
#if defined( _WIN32 ) && ( _WIN32_WINNT >= 0x0600 )
/* For a unicast IPv4 address, any value greater than 32 is an illegal
* value. For a unicast IPv6 address, any value greater than 128 is an
* illegal value. A value of 255 is commonly used to represent an illegal
* value.
*
* Windows 7 SP1 returns 64 for Teredo links which is incorrect.
*/
#define IN6_IS_ADDR_TEREDO(addr) \
(((const uint32_t *)(addr))[0] == ntohl (0x20010000))
if (AF_INET6 == unicast->Address.lpSockaddr->sa_family &&
/* TunnelType only applies to one interface on the adapter and no
* convenient method is provided to determine which.
*/
TUNNEL_TYPE_TEREDO == adapter->TunnelType &&
/* Test the interface with the known Teredo network prefix.
*/
IN6_IS_ADDR_TEREDO( &((struct sockaddr_in6*)(unicast->Address.lpSockaddr))->sin6_addr) &&
/* Test that this version is actually wrong, subsequent releases from Microsoft
* may resolve the issue.
*/
32 != unicast->OnLinkPrefixLength)
{
prefixLength = 32;
}
else
prefixLength = unicast->OnLinkPrefixLength;
#else
/* The order of linked IP_ADAPTER_UNICAST_ADDRESS structures pointed to by
* the FirstUnicastAddress member does not have any relationship with the
* order of linked IP_ADAPTER_PREFIX structures pointed to by the FirstPrefix
* member.
*
* Example enumeration:
* [ no subnet ]
* ::1/128 - address
* ff00::%1/8 - multicast (no IPv6 broadcast)
* 127.0.0.0/8 - subnet
* 127.0.0.1/32 - address
* 127.255.255.255/32 - subnet broadcast
* 224.0.0.0/4 - multicast
* 255.255.255.255/32 - broadcast
*
* Which differs from most adapters listing three IPv6:
* fe80::%10/64 - subnet
* fe80::51e9:5fe5:4202:325a%10/128 - address
* ff00::%10/8 - multicast
*
* !IfOperStatusUp IPv4 addresses are skipped:
* fe80::%13/64 - subnet
* fe80::d530:946d:e8df:8c91%13/128 - address
* ff00::%13/8 - multicast
* [ no subnet ]
* [ no address ]
* 224.0.0.0/4 - multicast
* 255.255.255.255/32 - broadcast
*
* On PTP links no multicast or broadcast addresses are returned:
* [ no subnet ]
* fe80::5efe:10.203.9.30/128 - address
* [ no multicast ]
* [ no multicast ]
* [ no broadcast ]
*
* Active primary IPv6 interfaces are a bit overloaded:
* ::/0 - default route
* 2001::/32 - global subnet
* 2001:0:4137:9e76:2443:d6:ba87:1a2a/128 - global address
* fe80::/64 - link-local subnet
* fe80::2443:d6:ba87:1a2a/128 - link-local address
* ff00::/8 - multicast
*/
#define IN_LINKLOCAL(a) ((((uint32_t) (a)) & 0xaffff0000) == 0xa9fe0000)
for (IP_ADAPTER_PREFIX *prefix = adapter->FirstPrefix; prefix; prefix = prefix->Next) {
LPSOCKADDR lpSockaddr = prefix->Address.lpSockaddr;
if (lpSockaddr->sa_family != unicast->Address.lpSockaddr->sa_family)
continue;
/* special cases */
/* RFC2863: IPv4 interface not up */
if (AF_INET == lpSockaddr->sa_family && adapter->OperStatus != IfOperStatusUp) {
/* RFC3927: link-local IPv4 always has 16-bit CIDR */
if (IN_LINKLOCAL( ntohl (((struct sockaddr_in*)(unicast->Address.lpSockaddr))->sin_addr.s_addr))) {
prefixLength = 16;
}
break;
}
/* default IPv6 route */
if (AF_INET6 == lpSockaddr->sa_family && 0 == prefix->PrefixLength && IN6_IS_ADDR_UNSPECIFIED( &((struct sockaddr_in6*)(lpSockaddr))->sin6_addr)) {
continue;
}
/* Assume unicast address for first prefix of operational adapter */
if (AF_INET == lpSockaddr->sa_family)
if (IN_MULTICAST( ntohl (((struct sockaddr_in*)(lpSockaddr))->sin_addr.s_addr))) {
fprintf(stderr, "FATAL: first prefix is non a unicast address\n");
break;
}
if (AF_INET6 == lpSockaddr->sa_family)
if (IN6_IS_ADDR_MULTICAST( &((struct sockaddr_in6*)(lpSockaddr))->sin6_addr)) {
fprintf(stderr, "FATAL: first prefix is not a unicast address\n");
break;
}
/* Assume subnet or host IP address for XP backward compatibility */
prefixLength = prefix->PrefixLength;
break;
}
#endif /* defined( _WIN32 ) && ( _WIN32_WINNT >= 0x0600 ) */
/* map prefix to netmask */
ifa->ifa_netmask->sa_family = unicast->Address.lpSockaddr->sa_family;
switch (unicast->Address.lpSockaddr->sa_family) {
case AF_INET:
if (0 == prefixLength || prefixLength > 32) {
prefixLength = 32;
}
#if defined( _WIN32) && ( _WIN32_WINNT >= 0x0600 )
/* Added in Vista, but no IPv6 equivalent. */
{
ULONG Mask;
ConvertLengthToIpv4Mask (prefixLength, &Mask);
((struct sockaddr_in*)ifa->ifa_netmask)->sin_addr.s_addr = Mask; /* network order */
}
#else
/* NB: left-shift of full bit-width is undefined in C standard. */
((struct sockaddr_in*)ifa->ifa_netmask)->sin_addr.s_addr = htonl( 0xffffffffU << ( 32 - prefixLength ) );
#endif
break;
case AF_INET6:
if (0 == prefixLength || prefixLength > 128) {
prefixLength = 128;
}
for (LONG i = prefixLength, j = 0; i > 0; i -= 8, ++j) {
((struct sockaddr_in6*)ifa->ifa_netmask)->sin6_addr.s6_addr[ j ] = i >= 8 ? 0xff : (ULONG)(( 0xffU << ( 8 - i ) ) & 0xffU );
}
break;
default:
break;
}
}
}
free(pAddresses);
return 0;
}
#else
#error No reference implementation for getifaddrs available for this platform.
#endif
#endif
int get_primary_mac_address(unsigned char mac_addr_buf[6])
{
int result = -1;
struct ifaddrs *ifaddr = NULL, *ifa = NULL;
if (getifaddrs(&ifaddr) != -1) {
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) {
continue;
}
if ((ifa->ifa_flags & IFF_UP) == 0) {
continue;
}
if (ifa->ifa_flags & IFF_LOOPBACK) {
continue;
}
#if defined(__APPLE__)
if (ifa->ifa_addr->sa_family != AF_LINK) {
continue;
}
if (!strcmp(ifa->ifa_name, "en0")) {
memcpy(mac_addr_buf, (unsigned char *)LLADDR((struct sockaddr_dl *)(ifa)->ifa_addr), 6);
result = 0;
break;
}
#elif defined (__linux__)
if (ifa->ifa_addr->sa_family != AF_PACKET) {
continue;
}
if (strcmp(ifa->ifa_name, "lo") != 0) {
memcpy(mac_addr_buf, ((struct sockaddr_ll*)ifa->ifa_addr)->sll_addr, 6);
result = 0;
break;
}
#elif defined (WIN32)
if (ifa->ifa_data) {
memcpy(mac_addr_buf, ifa->ifa_data, 6);
result = 0;
break;
}
#else
#error get_primary_mac_address is not supported on this platform.
#endif
}
freeifaddrs(ifaddr);
}
return result;
}
static int32_t _sockaddr_in6_scope_id(struct sockaddr_in6* addr)
{
int32_t res = -1;
struct ifaddrs *ifaddr = NULL, *ifa = NULL;
uint32_t addr_scope;
/* get scope for requested address */
addr_scope = _in6_addr_scope(&addr->sin6_addr);
if (addr_scope == 0) {
/* global scope doesn't need a specific scope id */
return addr_scope;
}
/* get interfaces */
if (getifaddrs(&ifaddr) == -1) {
perror("getifaddrs");
return res;
}
/* loop over interfaces */
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
/* skip if no address is available */
if (ifa->ifa_addr == NULL) {
continue;
}
/* skip if wrong family */
if (ifa->ifa_addr->sa_family != AF_INET6) {
continue;
}
/* skip if not up */
if ((ifa->ifa_flags & IFF_UP) == 0) {
continue;
}
/* skip if not running */
if ((ifa->ifa_flags & IFF_RUNNING) == 0) {
continue;
}
struct sockaddr_in6* addr_in = (struct sockaddr_in6*)ifa->ifa_addr;
/* skip if scopes do not match */
if (_in6_addr_scope(&addr_in->sin6_addr) != addr_scope) {
continue;
}
/* use if address is equal */
if (memcmp(&addr->sin6_addr.s6_addr, &addr_in->sin6_addr.s6_addr, sizeof(addr_in->sin6_addr.s6_addr)) == 0) {
/* if scope id equals the requested one then assume it was valid */
if (addr->sin6_scope_id == addr_in->sin6_scope_id) {
res = addr_in->sin6_scope_id;
break;
}
if ((addr_in->sin6_scope_id > addr->sin6_scope_id) && (res >= 0)) {
// use last valid scope id as we're past the requested scope id
break;
}
res = addr_in->sin6_scope_id;
continue;
}
/* skip loopback interface if not already matched exactly above */
if ((ifa->ifa_flags & IFF_LOOPBACK) != 0) {
continue;
}
if ((addr_in->sin6_scope_id > addr->sin6_scope_id) && (res >= 0)) {
// use last valid scope id as we're past the requested scope id
break;
}
res = addr_in->sin6_scope_id;
/* if scope id equals the requested one then assume it was valid */
if (addr->sin6_scope_id == addr_in->sin6_scope_id) {
/* set the scope id of this interface as most likely candidate */
break;
}
}
freeifaddrs(ifaddr);
return res;
}
#endif
int socket_connect_addr(struct sockaddr* addr, uint16_t port)
{
int sfd = -1;
int yes = 1;
int bufsize = 0x20000;
int addrlen = 0;
#ifdef WIN32
u_long l_yes = 1;
WSADATA wsa_data;
if (!wsa_init) {
if (WSAStartup(MAKEWORD(2,2), &wsa_data) != ERROR_SUCCESS) {
fprintf(stderr, "WSAStartup failed!\n");
ExitProcess(-1);
}
wsa_init = 1;
}
#endif
if (addr->sa_family == AF_INET) {
struct sockaddr_in* addr_in = (struct sockaddr_in*)addr;
addr_in->sin_port = htons(port);
addrlen = sizeof(struct sockaddr_in);
}
#ifdef AF_INET6
else if (addr->sa_family == AF_INET6) {
struct sockaddr_in6* addr_in = (struct sockaddr_in6*)addr;
addr_in->sin6_port = htons(port);
/*
* IPv6 Routing Magic:
*
* If the scope of the address is a link-local one, IPv6 requires the
* scope id set to an interface number to allow proper routing. However,
* as the provided sockaddr might contain a wrong scope id, we must find
* a scope id from a suitable interface on this system or routing might
* fail. An IPv6 guru should have another look though...
*/
addr_in->sin6_scope_id = _sockaddr_in6_scope_id(addr_in);
addrlen = sizeof(struct sockaddr_in6);
}
#endif
else {
fprintf(stderr, "ERROR: Unsupported address family");
return -1;
}
sfd = socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP);
if (sfd == -1) {
perror("socket()");
return -1;
}
#ifdef SO_NOSIGPIPE
if (setsockopt(sfd, SOL_SOCKET, SO_NOSIGPIPE, (void*)&yes, sizeof(int)) == -1) {
perror("setsockopt()");
socket_close(sfd);
return -1;
}
#endif
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(int)) == -1) {
perror("setsockopt()");
socket_close(sfd);
return -1;
}
#ifdef WIN32
ioctlsocket(sfd, FIONBIO, &l_yes);
#else
int flags = fcntl(sfd, F_GETFL, 0);
fcntl(sfd, F_SETFL, flags | O_NONBLOCK);
#endif
do {
if (connect(sfd, addr, addrlen) != -1) {
break;
}
#ifdef WIN32
if (WSAGetLastError() == WSAEWOULDBLOCK)
#else
if (errno == EINPROGRESS)
#endif
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(sfd, &fds);
struct timeval timeout;
timeout.tv_sec = CONNECT_TIMEOUT / 1000;
timeout.tv_usec = (CONNECT_TIMEOUT - (timeout.tv_sec * 1000)) * 1000;
if (select(sfd + 1, NULL, &fds, NULL, &timeout) == 1) {
int so_error;
socklen_t len = sizeof(so_error);
getsockopt(sfd, SOL_SOCKET, SO_ERROR, (void*)&so_error, &len);
if (so_error == 0) {
errno = 0;
break;
}
errno = so_error;
}
}
socket_close(sfd);
sfd = -1;
} while (0);
if (sfd < 0) {
if (verbose >= 2) {
char addrtxt[48];
socket_addr_to_string(addr, addrtxt, sizeof(addrtxt));
fprintf(stderr, "%s: Could not connect to %s port %d\n", __func__, addrtxt, port);
}
return -1;
}
if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, (void*)&yes, sizeof(int)) == -1) {
perror("Could not set TCP_NODELAY on socket");
}
if (setsockopt(sfd, SOL_SOCKET, SO_SNDBUF, (void*)&bufsize, sizeof(int)) == -1) {
perror("Could not set send buffer for socket");
}
if (setsockopt(sfd, SOL_SOCKET, SO_RCVBUF, (void*)&bufsize, sizeof(int)) == -1) {
perror("Could not set receive buffer for socket");
}
return sfd;
}
int socket_connect(const char *addr, uint16_t port)
{
int sfd = -1;
int yes = 1;
int bufsize = 0x20000;
struct addrinfo hints;
struct addrinfo *result, *rp;
char portstr[8];
int res;
#ifdef WIN32
u_long l_yes = 1;
WSADATA wsa_data;
if (!wsa_init) {
if (WSAStartup(MAKEWORD(2,2), &wsa_data) != ERROR_SUCCESS) {
fprintf(stderr, "WSAStartup failed!\n");
ExitProcess(-1);
}
wsa_init = 1;
}
#else
int flags = 0;
#endif
if (!addr) {
errno = EINVAL;
return -1;
}
memset(&hints, '\0', sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_NUMERICSERV;
hints.ai_protocol = IPPROTO_TCP;
sprintf(portstr, "%d", port);
res = getaddrinfo(addr, portstr, &hints, &result);
if (res != 0) {
fprintf(stderr, "%s: getaddrinfo: %s\n", __func__, gai_strerror(res));
return -1;
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sfd == -1) {
continue;
}
#ifdef SO_NOSIGPIPE
if (setsockopt(sfd, SOL_SOCKET, SO_NOSIGPIPE, (void*)&yes, sizeof(int)) == -1) {
perror("setsockopt()");
socket_close(sfd);
return -1;
}
#endif
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(int)) == -1) {
perror("setsockopt()");
socket_close(sfd);
continue;
}
#ifdef WIN32
ioctlsocket(sfd, FIONBIO, &l_yes);
#else
flags = fcntl(sfd, F_GETFL, 0);
fcntl(sfd, F_SETFL, flags | O_NONBLOCK);
#endif
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) {
break;
}
#ifdef WIN32
if (WSAGetLastError() == WSAEWOULDBLOCK)
#else
if (errno == EINPROGRESS)
#endif
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(sfd, &fds);
struct timeval timeout;
timeout.tv_sec = CONNECT_TIMEOUT / 1000;
timeout.tv_usec = (CONNECT_TIMEOUT - (timeout.tv_sec * 1000)) * 1000;
if (select(sfd + 1, NULL, &fds, NULL, &timeout) == 1) {
int so_error;
socklen_t len = sizeof(so_error);
getsockopt(sfd, SOL_SOCKET, SO_ERROR, (void*)&so_error, &len);
if (so_error == 0) {
break;
}
}
}
socket_close(sfd);
}
freeaddrinfo(result);
if (rp == NULL) {
if (verbose >= 2)
fprintf(stderr, "%s: Could not connect to %s:%d\n", __func__, addr, port);
return -1;
}
if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, (void*)&yes, sizeof(int)) == -1) {
perror("Could not set TCP_NODELAY on socket");
}
if (setsockopt(sfd, SOL_SOCKET, SO_SNDBUF, (void*)&bufsize, sizeof(int)) == -1) {
perror("Could not set send buffer for socket");
}
if (setsockopt(sfd, SOL_SOCKET, SO_RCVBUF, (void*)&bufsize, sizeof(int)) == -1) {
perror("Could not set receive buffer for socket");
}
return sfd;
}
int socket_check_fd(int fd, fd_mode fdm, unsigned int timeout)
{
fd_set fds;
int sret;
int eagain;
struct timeval to;
struct timeval *pto;
if (fd < 0) {
if (verbose >= 2)
fprintf(stderr, "ERROR: invalid fd in check_fd %d\n", fd);
return -1;
}
FD_ZERO(&fds);
FD_SET(fd, &fds);
sret = -1;
do {
if (timeout > 0) {
to.tv_sec = (time_t) (timeout / 1000);
to.tv_usec = (time_t) ((timeout - (to.tv_sec * 1000)) * 1000);
pto = &to;
} else {
pto = NULL;
}
eagain = 0;
switch (fdm) {
case FDM_READ:
sret = select(fd + 1, &fds, NULL, NULL, pto);
break;
case FDM_WRITE:
sret = select(fd + 1, NULL, &fds, NULL, pto);
break;
case FDM_EXCEPT:
sret = select(fd + 1, NULL, NULL, &fds, pto);
break;
default:
return -1;
}
if (sret < 0) {
switch (errno) {
case EINTR:
// interrupt signal in select
if (verbose >= 2)
fprintf(stderr, "%s: EINTR\n", __func__);
eagain = 1;
break;
case EAGAIN:
if (verbose >= 2)
fprintf(stderr, "%s: EAGAIN\n", __func__);
break;
default:
if (verbose >= 2)
fprintf(stderr, "%s: select failed: %s\n", __func__,
strerror(errno));
return -1;
}
} else if (sret == 0) {
return -ETIMEDOUT;
}
} while (eagain);
return sret;
}
int socket_accept(int fd, uint16_t port)
{
#ifdef WIN32
int addr_len;
#else
socklen_t addr_len;
#endif
int result;
struct sockaddr_storage addr;
addr_len = sizeof(addr);
result = accept(fd, (struct sockaddr*)&addr, &addr_len);
return result;
}
int socket_shutdown(int fd, int how)
{
return shutdown(fd, how);
}
int socket_close(int fd) {
#ifdef WIN32
return closesocket(fd);
#else
return close(fd);
#endif
}
int socket_receive(int fd, void *data, size_t length)
{
return socket_receive_timeout(fd, data, length, 0, RECV_TIMEOUT);
}
int socket_peek(int fd, void *data, size_t length)
{
return socket_receive_timeout(fd, data, length, MSG_PEEK, RECV_TIMEOUT);
}
int socket_receive_timeout(int fd, void *data, size_t length, int flags,
unsigned int timeout)
{
int res;
int result;
// check if data is available
res = socket_check_fd(fd, FDM_READ, timeout);
if (res <= 0) {
return res;
}
// if we get here, there _is_ data available
result = recv(fd, data, length, flags);
if (res > 0 && result == 0) {
// but this is an error condition
if (verbose >= 3)
fprintf(stderr, "%s: fd=%d recv returned 0\n", __func__, fd);
return -ECONNRESET;
}
if (result < 0) {
return -errno;
}
return result;
}
int socket_send(int fd, void *data, size_t length)
{
int flags = 0;
int res = socket_check_fd(fd, FDM_WRITE, SEND_TIMEOUT);
if (res <= 0) {
return res;
}
#ifdef MSG_NOSIGNAL
flags |= MSG_NOSIGNAL;
#endif
return send(fd, data, length, flags);
}