/* * Copyright (C) 2010-2015 Mamadou DIOP. * * This file is part of Open Source Doubango Framework. * * DOUBANGO is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * DOUBANGO 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with DOUBANGO. * */ /**@file tnet.h * @brief Protocol agnostic socket. * */ #include "tnet_socket.h" #include "tnet_utils.h" #include "tsk_string.h" #include "tsk_debug.h" #include /**@defgroup tnet_socket_group Protocol agnostic socket * * *

10.1 Sockets

* For performance reason, all sockets created using tinyNET are non-blocking by default. * The newly created socket will be automatically bound to associate it with an IP address and port number. @ref tnet_socket_create() function is used to create and bind a * non-blocking socket. You should use @ref tnet_socket_create_2() function to control whether the socket should be bound or not. The same function is used to force the stack to create a blocking socket.
* A socket object is defined like this:
* * @code typedef struct tnet_socket_s { TSK_DECLARE_OBJECT; tnet_socket_type_t type; tnet_fd_t fd; tnet_ip_t ip; uint16_t port; tnet_tls_socket_handle_t* tlshandle; } tnet_socket_t; * @endcode * To create a socket: * @code // (create udp ipv4 or ipv6 socket) tnet_socket_t* socket = tnet_socket_create( TNET_SOCKET_HOST_ANY, // local ip address/hostname to bind to TNET_SOCKET_PORT_ANY, // local port number to bind to tnet_socket_type_udp_ipv46 // the socket type (IPv4 or IPv6) ); // TNET_SOCKET_HOST_ANY --> bind to "0.0.0.0" or "::" // TNET_SOCKET_PORT_ANY --> bind to any available port * @endcode * TNET_SOCKET_TYPE_IS_*() macros are used to determine: * - The socket type (stream, dgram), * - The socket protocol (udp, tcp, tls, sctp, ipsec), * - The IP version (ipv6, ipv4), * - … *
* A socket is a well-defined object and should be destroyed using @a TSK_DECLARE_SAFE_FREE() macro. * A socket will be automatically closed when destroyed. * */ static int tnet_socket_close(tnet_socket_t *sock); /**@ingroup tnet_socket_group * Creates a new socket. * To check that the returned socket is valid use @ref TNET_SOCKET_IS_VALID function. * @param host FQDN (e.g. www.doubango.org) or IPv4/IPv6 IP string. * @param port The local/remote port used to receive/send data. Set the port value to @ref TNET_SOCKET_PORT_ANY to bind to a random port. * @param type The type of the socket. See @ref tnet_socket_type_t. * @param nonblocking Indicates whether to create non-blocking socket. * @param bindsocket Indicates whether to bind the newly created socket or not. * @retval @ref tnet_socket_t object. * @sa @ref tnet_socket_create. */ tnet_socket_t* tnet_socket_create_2(const char* host, tnet_port_t port_, tnet_socket_type_t type, tsk_bool_t nonblocking, tsk_bool_t bindsocket) { tnet_socket_t *sock; if ((sock = tsk_object_new(tnet_socket_def_t))) { int status; tsk_istr_t port; struct addrinfo *result = tsk_null; struct addrinfo *ptr = tsk_null; struct addrinfo hints; tnet_host_t local_hostname; sock->port = port_; tsk_itoa(sock->port, &port); sock->type = type; memset(local_hostname, 0, sizeof(local_hostname)); /* Get the local host name */ if (host != TNET_SOCKET_HOST_ANY && !tsk_strempty(host)) { memcpy(local_hostname, host, tsk_strlen(host) > sizeof(local_hostname) - 1 ? sizeof(local_hostname) - 1 : tsk_strlen(host)); } else { if (TNET_SOCKET_TYPE_IS_IPV6(sock->type)) { memcpy(local_hostname, "::", 2); } else { memcpy(local_hostname, "0.0.0.0", 7); } } /* hints address info structure */ memset(&hints, 0, sizeof(hints)); hints.ai_family = TNET_SOCKET_TYPE_IS_IPV46(sock->type) ? AF_UNSPEC : (TNET_SOCKET_TYPE_IS_IPV6(sock->type) ? AF_INET6 : AF_INET); hints.ai_socktype = TNET_SOCKET_TYPE_IS_STREAM(sock->type) ? SOCK_STREAM : SOCK_DGRAM; hints.ai_protocol = TNET_SOCKET_TYPE_IS_STREAM(sock->type) ? IPPROTO_TCP : IPPROTO_UDP; hints.ai_flags = AI_PASSIVE #if !TNET_UNDER_WINDOWS || _WIN32_WINNT>=0x600 | AI_ADDRCONFIG #endif ; /* Performs getaddrinfo */ if ((status = tnet_getaddrinfo(local_hostname, port, &hints, &result))) { TNET_PRINT_LAST_ERROR("tnet_getaddrinfo(family=%d, hostname=%s and port=%s) failed: [%s]", hints.ai_family, local_hostname, port, tnet_gai_strerror(status)); goto bail; } /* Find our address. */ for (ptr = result; ptr; ptr = ptr->ai_next) { sock->fd = (tnet_fd_t)tnet_soccket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); if (ptr->ai_family != AF_INET6 && ptr->ai_family != AF_INET) { continue; } /* To avoid "Address already in use" error * Check issue 368 (https://code.google.com/p/doubango/issues/detail?id=368) to understand why it's not used for UDP/DTLS. */ // if (TNET_SOCKET_TYPE_IS_STREAM(sock->type)) { if ((status = tnet_sockfd_reuseaddr(sock->fd, 1))) { // do not break...continue } } if (bindsocket) { /* Bind the socket */ if ((status = bind(sock->fd, ptr->ai_addr, (int)ptr->ai_addrlen))) { TNET_PRINT_LAST_ERROR("bind to [%s:%s]have failed", local_hostname, port); tnet_socket_close(sock); continue; } /* Get local IP string. */ if ((status = tnet_get_ip_n_port(sock->fd, tsk_true/*local*/, &sock->ip, &sock->port))) /* % */ //if((status = tnet_getnameinfo(ptr->ai_addr, ptr->ai_addrlen, sock->ip, sizeof(sock->ip), 0, 0, NI_NUMERICHOST))) { TNET_PRINT_LAST_ERROR("Failed to get local IP and port."); tnet_socket_close(sock); continue; } } /* sets the real socket type (if ipv46) */ if (ptr->ai_family == AF_INET6) { TNET_SOCKET_TYPE_SET_IPV6Only(sock->type); } else { TNET_SOCKET_TYPE_SET_IPV4Only(sock->type); } break; } /* Check socket validity. */ if (!TNET_SOCKET_IS_VALID(sock)) { TNET_PRINT_LAST_ERROR("Invalid socket."); goto bail; } #if TNET_UNDER_IPHONE || TNET_UNDER_IPHONE_SIMULATOR /* disable SIGPIPE signal */ { int yes = 1; if (setsockopt(sock->fd, SOL_SOCKET, SO_NOSIGPIPE, (char*)&yes, sizeof(int))) { TNET_PRINT_LAST_ERROR("setsockopt(SO_NOSIGPIPE) have failed."); } } #endif /* TNET_UNDER_IPHONE */ /* Sets the socket to nonblocking mode */ if(nonblocking) { if((status = tnet_sockfd_set_nonblocking(sock->fd))) { goto bail; } } bail: /* Free addrinfo */ tnet_freeaddrinfo(result); /* Close socket if failed. */ if (status) { if (TNET_SOCKET_IS_VALID(sock)) { tnet_socket_close(sock); } return tsk_null; } } return sock; } /**@ingroup tnet_socket_group * Creates a non-blocking socket and bind it. * To check that the returned socket is valid use @ref TNET_SOCKET_IS_VALID function. * @param host FQDN (e.g. www.doubango.org) or IPv4/IPv6 IP string. * @param port The local/remote port used to receive/send data. Set the port value to @ref TNET_SOCKET_PORT_ANY to bind to a random port. * @param type The type of the socket. See @ref tnet_socket_type_t. * @retval @ref tnet_socket_t object. */ tnet_socket_t* tnet_socket_create(const char* host, tnet_port_t port, tnet_socket_type_t type) { return tnet_socket_create_2(host, port, type, tsk_true, tsk_true); } /**@ingroup tnet_socket_group * Returns the number of bytes sent (or negative value on error) */ int tnet_socket_send_stream(tnet_socket_t* self, const void* data, tsk_size_t size) { if (!self || self->fd == TNET_INVALID_FD || !data || !size || !TNET_SOCKET_TYPE_IS_STREAM(self->type)) { TSK_DEBUG_ERROR("Invalid parameter"); return -1; } if (self->tlshandle && (TNET_SOCKET_TYPE_IS_TLS(self->type) || TNET_SOCKET_TYPE_IS_WSS(self->type))) { return tnet_tls_socket_send(self->tlshandle, data, size) == 0 ? (int)size : -1; // returns zero on success } return (int)tnet_sockfd_send(self->fd, data, size, 0); } /**@ingroup tnet_socket_group * @retval Zero if succeed and nonzero error code otherwise. */ int tnet_socket_handle_brokenpipe(tnet_socket_t* self) { int ret; tnet_fd_t fd_old, fd_new; if (!self || !TNET_SOCKET_TYPE_IS_DGRAM(self->type)) { // Must be UDP TSK_DEBUG_ERROR("Invalid parameter"); return -1; } fd_old = self->fd; fd_new = TNET_INVALID_FD; // close old fd ret = tnet_sockfd_close(&self->fd); // try to create an fd binding to the same address if ((ret = tnet_sockfd_init(self->ip, self->port, self->type, &fd_new)) != 0) { TNET_PRINT_LAST_ERROR("Find to bind to %s:%d", self->ip, self->port); // TODO: Create completly new socket? return ret; } #if TNET_UNDER_IPHONE || TNET_UNDER_IPHONE_SIMULATOR /* disable SIGPIPE signal */ { int yes = 1; if (setsockopt(fd_new, SOL_SOCKET, SO_NOSIGPIPE, (char*)&yes, sizeof(int))) { TNET_PRINT_LAST_ERROR("setsockopt(%d, SO_NOSIGPIPE) have failed", fd_new); } } #endif /* TNET_UNDER_IPHONE || TNET_UNDER_IPHONE_SIMULATOR */ TSK_DEBUG_INFO("Broken pipe result for {%s:%d}: %d -> %d", self->ip, self->port, fd_old, fd_new); self->fd = fd_new; return 0; } /**@ingroup tnet_socket_group * Closes a socket. * @param sock The socket to close. * @retval Zero if succeed and nonzero error code otherwise. **/ static int tnet_socket_close(tnet_socket_t *sock) { return tnet_sockfd_close(&(sock->fd)); } //================================================================================================= // SOCKET object definition // static tsk_object_t* tnet_socket_ctor(tsk_object_t * self, va_list * app) { tnet_socket_t *sock = self; if (sock) { } return self; } static tsk_object_t* tnet_socket_dtor(tsk_object_t * self) { tnet_socket_t *sock = self; if (sock) { /* Close the socket */ if (TNET_SOCKET_IS_VALID(sock)) { tnet_socket_close(sock); } /* Clean up TLS handle */ TSK_OBJECT_SAFE_FREE(sock->tlshandle); /* Clean up DTLS handle */ TSK_OBJECT_SAFE_FREE(sock->dtlshandle); } return self; } static const tsk_object_def_t tnet_socket_def_s = { sizeof(tnet_socket_t), tnet_socket_ctor, tnet_socket_dtor, tsk_null, }; const tsk_object_def_t *tnet_socket_def_t = &tnet_socket_def_s;