#include "lib/net/socket.h"
#include "lib/net/socketpair.h"
#include "lib/net/address.h"
#include "lib/cc/compat_compiler.h"
#include "lib/err/torerr.h"
#include "lib/lock/compat_mutex.h"
#include "lib/log/log.h"
#include "lib/log/util_bug.h"
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <stddef.h>
#include <string.h>
#ifdef __FreeBSD__
#include <sys/sysctl.h>
#endif
int
network_init(void)
{
#ifdef _WIN32
WSADATA WSAData;
int r;
r = WSAStartup(0x101,&WSAData);
if (r) {
log_warn(LD_NET,"Error initializing windows network layer: code was %d",r);
return -1;
}
if (sizeof(SOCKET) != sizeof(tor_socket_t)) {
log_warn(LD_BUG,"The tor_socket_t type does not match SOCKET in size; Tor "
"might not work. (Sizes are %d and %d respectively.)",
(int)sizeof(tor_socket_t), (int)sizeof(SOCKET));
}
#endif
return 0;
}
void
check_network_configuration(bool server_mode)
{
#ifdef __FreeBSD__
if (server_mode) {
int random_id_state;
size_t state_size = sizeof(random_id_state);
if (sysctlbyname("net.inet.ip.random_id", &random_id_state,
&state_size, NULL, 0)) {
log_warn(LD_CONFIG,
"Failed to figure out if IP ids are randomized.");
} else if (random_id_state == 0) {
log_warn(LD_CONFIG, "Looks like IP ids are not randomized. "
"Please consider setting the net.inet.ip.random_id sysctl, "
"so your relay makes it harder to figure out how busy it is.");
}
}
#else
(void) server_mode;
#endif
}
static int max_sockets = 1024;
int
get_max_sockets(void)
{
return max_sockets;
}
void
set_max_sockets(int n)
{
max_sockets = n;
}
#undef DEBUG_SOCKET_COUNTING
#ifdef DEBUG_SOCKET_COUNTING
#include "lib/container/bitarray.h"
static bitarray_t *open_sockets = NULL;
static int max_socket = -1;
#endif
static int n_sockets_open = 0;
static tor_mutex_t *socket_accounting_mutex = NULL;
static inline void
socket_accounting_lock(void)
{
if (PREDICT_UNLIKELY(!socket_accounting_mutex))
socket_accounting_mutex = tor_mutex_new();
tor_mutex_acquire(socket_accounting_mutex);
}
static inline void
socket_accounting_unlock(void)
{
tor_mutex_release(socket_accounting_mutex);
}
int
tor_close_socket_simple(tor_socket_t s)
{
int r = 0;
#if defined(_WIN32)
r = closesocket(s);
#else
r = close(s);
#endif
if (r != 0) {
int err = tor_socket_errno(-1);
log_info(LD_NET, "Close returned an error: %s", tor_socket_strerror(err));
return err;
}
return r;
}
#ifdef DEBUG_SOCKET_COUNTING
static inline void
mark_socket_open(tor_socket_t s)
{
if (s > max_socket) {
if (max_socket == -1) {
open_sockets = bitarray_init_zero(s+128);
max_socket = s+128;
} else {
open_sockets = bitarray_expand(open_sockets, max_socket, s+128);
max_socket = s+128;
}
}
if (bitarray_is_set(open_sockets, s)) {
log_warn(LD_BUG, "I thought that %d was already open, but socket() just "
"gave it to me!", s);
}
bitarray_set(open_sockets, s);
}
static inline void
mark_socket_closed(tor_socket_t s)
{
if (s > max_socket || ! bitarray_is_set(open_sockets, s)) {
log_warn(LD_BUG, "Closing a socket (%d) that wasn't returned by tor_open_"
"socket(), or that was already closed or something.", s);
} else {
tor_assert(open_sockets && s <= max_socket);
bitarray_clear(open_sockets, s);
}
}
#else
#define mark_socket_open(s) ((void) (s))
#define mark_socket_closed(s) ((void) (s))
#endif
MOCK_IMPL(int,
tor_close_socket,(tor_socket_t s))
{
int r = tor_close_socket_simple(s);
socket_accounting_lock();
mark_socket_closed(s);
if (r == 0) {
--n_sockets_open;
} else {
#ifdef _WIN32
if (r != WSAENOTSOCK)
--n_sockets_open;
#else
if (r != EBADF)
--n_sockets_open; #endif
r = -1;
}
tor_assert_nonfatal(n_sockets_open >= 0);
socket_accounting_unlock();
return r;
}
MOCK_IMPL(tor_socket_t,
tor_open_socket,(int domain, int type, int protocol))
{
return tor_open_socket_with_extensions(domain, type, protocol, 1, 0);
}
MOCK_IMPL(tor_socket_t,
tor_connect_socket,(tor_socket_t sock, const struct sockaddr *address,
socklen_t address_len))
{
return connect(sock,address,address_len);
}
tor_socket_t
tor_open_socket_nonblocking(int domain, int type, int protocol)
{
return tor_open_socket_with_extensions(domain, type, protocol, 1, 1);
}
tor_socket_t
tor_open_socket_with_extensions(int domain, int type, int protocol,
int cloexec, int nonblock)
{
tor_socket_t s;
if (get_n_open_sockets() >= max_sockets - 1) {
#ifdef _WIN32
WSASetLastError(WSAEMFILE);
#else
errno = EMFILE;
#endif
return TOR_INVALID_SOCKET;
}
#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK)
int ext_flags = (cloexec ? SOCK_CLOEXEC : 0) |
(nonblock ? SOCK_NONBLOCK : 0);
s = socket(domain, type|ext_flags, protocol);
if (SOCKET_OK(s))
goto socket_ok;
if (errno != EINVAL)
return s;
#endif
s = socket(domain, type, protocol);
if (! SOCKET_OK(s))
return s;
#if defined(FD_CLOEXEC)
if (cloexec) {
if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) {
log_warn(LD_FS,"Couldn't set FD_CLOEXEC: %s", strerror(errno));
tor_close_socket_simple(s);
return TOR_INVALID_SOCKET;
}
}
#else
(void)cloexec;
#endif
if (nonblock) {
if (set_socket_nonblocking(s) == -1) {
tor_close_socket_simple(s);
return TOR_INVALID_SOCKET;
}
}
goto socket_ok;
socket_ok:
tor_take_socket_ownership(s);
return s;
}
void
tor_take_socket_ownership(tor_socket_t s)
{
socket_accounting_lock();
++n_sockets_open;
mark_socket_open(s);
socket_accounting_unlock();
}
void
tor_release_socket_ownership(tor_socket_t s)
{
socket_accounting_lock();
--n_sockets_open;
mark_socket_closed(s);
socket_accounting_unlock();
}
tor_socket_t
tor_accept_socket(tor_socket_t sockfd, struct sockaddr *addr, socklen_t *len)
{
return tor_accept_socket_with_extensions(sockfd, addr, len, 1, 0);
}
tor_socket_t
tor_accept_socket_nonblocking(tor_socket_t sockfd, struct sockaddr *addr,
socklen_t *len)
{
return tor_accept_socket_with_extensions(sockfd, addr, len, 1, 1);
}
tor_socket_t
tor_accept_socket_with_extensions(tor_socket_t sockfd, struct sockaddr *addr,
socklen_t *len, int cloexec, int nonblock)
{
tor_socket_t s;
if (get_n_open_sockets() >= max_sockets - 1) {
#ifdef _WIN32
WSASetLastError(WSAEMFILE);
#else
errno = EMFILE;
#endif
return TOR_INVALID_SOCKET;
}
#if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) \
&& defined(SOCK_NONBLOCK)
int ext_flags = (cloexec ? SOCK_CLOEXEC : 0) |
(nonblock ? SOCK_NONBLOCK : 0);
s = accept4(sockfd, addr, len, ext_flags);
if (SOCKET_OK(s))
goto socket_ok;
if (errno != EINVAL && errno != ENOSYS)
return s;
#endif
s = accept(sockfd, addr, len);
if (!SOCKET_OK(s))
return s;
#if defined(FD_CLOEXEC)
if (cloexec) {
if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) {
log_warn(LD_NET, "Couldn't set FD_CLOEXEC: %s", strerror(errno));
tor_close_socket_simple(s);
return TOR_INVALID_SOCKET;
}
}
#else
(void)cloexec;
#endif
if (nonblock) {
if (set_socket_nonblocking(s) == -1) {
tor_close_socket_simple(s);
return TOR_INVALID_SOCKET;
}
}
goto socket_ok;
socket_ok:
tor_take_socket_ownership(s);
return s;
}
int
get_n_open_sockets(void)
{
int n;
socket_accounting_lock();
n = n_sockets_open;
socket_accounting_unlock();
return n;
}
int
tor_socketpair(int family, int type, int protocol, tor_socket_t fd[2])
{
int r;
#if defined(HAVE_SOCKETPAIR) && !defined(_WIN32)
#ifdef SOCK_CLOEXEC
r = socketpair(family, type|SOCK_CLOEXEC, protocol, fd);
if (r == 0)
goto sockets_ok;
if (errno != EINVAL)
return -errno;
#endif
r = socketpair(family, type, protocol, fd);
if (r < 0)
return -errno;
#else
r = tor_ersatz_socketpair(family, type, protocol, fd);
if (r < 0)
return -r;
#endif
#if defined(FD_CLOEXEC)
if (SOCKET_OK(fd[0])) {
r = fcntl(fd[0], F_SETFD, FD_CLOEXEC);
if (r == -1) {
close(fd[0]);
close(fd[1]);
return -errno;
}
}
if (SOCKET_OK(fd[1])) {
r = fcntl(fd[1], F_SETFD, FD_CLOEXEC);
if (r == -1) {
close(fd[0]);
close(fd[1]);
return -errno;
}
}
#endif
goto sockets_ok;
sockets_ok:
socket_accounting_lock();
if (SOCKET_OK(fd[0])) {
++n_sockets_open;
mark_socket_open(fd[0]);
}
if (SOCKET_OK(fd[1])) {
++n_sockets_open;
mark_socket_open(fd[1]);
}
socket_accounting_unlock();
return 0;
}
MOCK_IMPL(int,
tor_getsockname,(tor_socket_t sock, struct sockaddr *address,
socklen_t *address_len))
{
return getsockname(sock, address, address_len);
}
int
tor_addr_from_getsockname(struct tor_addr_t *addr_out, tor_socket_t sock)
{
struct sockaddr_storage ss;
socklen_t ss_len = sizeof(ss);
memset(&ss, 0, sizeof(ss));
if (tor_getsockname(sock, (struct sockaddr *) &ss, &ss_len) < 0)
return -1;
return tor_addr_from_sockaddr(addr_out, (struct sockaddr *)&ss, NULL);
}
int
set_socket_nonblocking(tor_socket_t sock)
{
#if defined(_WIN32)
unsigned long nonblocking = 1;
ioctlsocket(sock, FIONBIO, (unsigned long*) &nonblocking);
#else
int flags;
flags = fcntl(sock, F_GETFL, 0);
if (flags == -1) {
log_warn(LD_NET, "Couldn't get file status flags: %s", strerror(errno));
return -1;
}
flags |= O_NONBLOCK;
if (fcntl(sock, F_SETFL, flags) == -1) {
log_warn(LD_NET, "Couldn't set file status flags: %s", strerror(errno));
return -1;
}
#endif
return 0;
}
ssize_t
read_all_from_socket(tor_socket_t sock, char *buf, size_t count)
{
size_t numread = 0;
ssize_t result;
if (count > SIZE_T_CEILING || count > SSIZE_MAX) {
errno = EINVAL;
return -1;
}
while (numread < count) {
result = tor_socket_recv(sock, buf+numread, count-numread, 0);
if (result<0)
return -1;
else if (result == 0)
break;
numread += result;
}
return (ssize_t)numread;
}
ssize_t
write_all_to_socket(tor_socket_t fd, const char *buf, size_t count)
{
size_t written = 0;
ssize_t result;
raw_assert(count < SSIZE_MAX);
while (written != count) {
result = tor_socket_send(fd, buf+written, count-written, 0);
if (result<0)
return -1;
written += result;
}
return (ssize_t)count;
}
#if defined(_WIN32)
int
tor_socket_errno(tor_socket_t sock)
{
int optval, optvallen=sizeof(optval);
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK && SOCKET_OK(sock)) {
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*)&optval, &optvallen))
return err;
if (optval)
return optval;
}
return err;
}
#endif
#if defined(_WIN32)
#define E(code, s) { code, (s " [" #code " ]") }
struct { int code; const char *msg; } windows_socket_errors[] = {
E(WSAEINTR, "Interrupted function call"),
E(WSAEACCES, "Permission denied"),
E(WSAEFAULT, "Bad address"),
E(WSAEINVAL, "Invalid argument"),
E(WSAEMFILE, "Too many open files"),
E(WSAEWOULDBLOCK, "Resource temporarily unavailable"),
E(WSAEINPROGRESS, "Operation now in progress"),
E(WSAEALREADY, "Operation already in progress"),
E(WSAENOTSOCK, "Socket operation on nonsocket"),
E(WSAEDESTADDRREQ, "Destination address required"),
E(WSAEMSGSIZE, "Message too long"),
E(WSAEPROTOTYPE, "Protocol wrong for socket"),
E(WSAENOPROTOOPT, "Bad protocol option"),
E(WSAEPROTONOSUPPORT, "Protocol not supported"),
E(WSAESOCKTNOSUPPORT, "Socket type not supported"),
E(WSAEOPNOTSUPP, "Operation not supported"),
E(WSAEPFNOSUPPORT, "Protocol family not supported"),
E(WSAEAFNOSUPPORT, "Address family not supported by protocol family"),
E(WSAEADDRINUSE, "Address already in use"),
E(WSAEADDRNOTAVAIL, "Cannot assign requested address"),
E(WSAENETDOWN, "Network is down"),
E(WSAENETUNREACH, "Network is unreachable"),
E(WSAENETRESET, "Network dropped connection on reset"),
E(WSAECONNABORTED, "Software caused connection abort"),
E(WSAECONNRESET, "Connection reset by peer"),
E(WSAENOBUFS, "No buffer space available"),
E(WSAEISCONN, "Socket is already connected"),
E(WSAENOTCONN, "Socket is not connected"),
E(WSAESHUTDOWN, "Cannot send after socket shutdown"),
E(WSAETIMEDOUT, "Connection timed out"),
E(WSAECONNREFUSED, "Connection refused"),
E(WSAEHOSTDOWN, "Host is down"),
E(WSAEHOSTUNREACH, "No route to host"),
E(WSAEPROCLIM, "Too many processes"),
E(WSASYSNOTREADY, "Network subsystem is unavailable"),
E(WSAVERNOTSUPPORTED, "Winsock.dll out of range"),
E(WSANOTINITIALISED, "Successful WSAStartup not yet performed"),
E(WSAEDISCON, "Graceful shutdown now in progress"),
#ifdef WSATYPE_NOT_FOUND
E(WSATYPE_NOT_FOUND, "Class type not found"),
#endif
E(WSAHOST_NOT_FOUND, "Host not found"),
E(WSATRY_AGAIN, "Nonauthoritative host not found"),
E(WSANO_RECOVERY, "This is a nonrecoverable error"),
E(WSANO_DATA, "Valid name, no data record of requested type)"),
{ -1, NULL },
};
const char *
tor_socket_strerror(int e)
{
int i;
for (i=0; windows_socket_errors[i].code >= 0; ++i) {
if (e == windows_socket_errors[i].code)
return windows_socket_errors[i].msg;
}
return strerror(e);
}
#endif