/*
NetLink Sockets: Networking C++ library
Copyright 2012 Pedro Francisco Pareja Ruiz (PedroPareja@Gmail.com)
This file is part of NetLink Sockets.
NetLink Sockets 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.
NetLink Sockets 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 NetLink Sockets. If not, see .
*/
#include "socket.h"
#include
#include
NL_NAMESPACE
#ifdef OS_WIN32
static void close(int socketHandler) {
closesocket(socketHandler);
}
static void freeaddrinfo(struct addrinfo* addrInfo) {
(void)::freeaddrinfo(addrInfo);
}
static const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt)
{
if (af == AF_INET)
{
struct sockaddr_in in;
memset(&in, 0, sizeof(in));
in.sin_family = AF_INET;
memcpy(&in.sin_addr, src, sizeof(struct in_addr));
getnameinfo((struct sockaddr *)&in, sizeof(struct
sockaddr_in), dst, cnt, NULL, 0, NI_NUMERICHOST);
return dst;
}
else if (af == AF_INET6)
{
struct sockaddr_in6 in;
memset(&in, 0, sizeof(in));
in.sin6_family = AF_INET6;
memcpy(&in.sin6_addr, src, sizeof(struct in_addr6));
getnameinfo((struct sockaddr *)&in, sizeof(struct
sockaddr_in6), dst, cnt, NULL, 0, NI_NUMERICHOST);
return dst;
}
return NULL;
}
#endif
static unsigned getInPort(struct sockaddr* sa) {
if (sa->sa_family == AF_INET)
return ntohs(((struct sockaddr_in*)sa)->sin_port);
return ntohs(((struct sockaddr_in6*)sa)->sin6_port);
}
static int getSocketErrorCode() {
#ifdef OS_WIN32
return WSAGetLastError();
#else
return errno;
#endif
}
static unsigned getLocalPort(int socketHandler) {
struct sockaddr_storage sin;
#ifdef OS_WIN32
int size;
#else
socklen_t size;
#endif
size = sizeof(sin);
if(getsockname(socketHandler, (struct sockaddr*)&sin, &size) == 0)
return getInPort((struct sockaddr*)&sin);
else
throw Exception(Exception::ERROR_GET_ADDR_INFO, "Socket::(static)getLocalPort: error getting socket info", getSocketErrorCode());
}
static void checkReadError(const string& functionName) {
#ifdef OS_WIN32
if(WSAGetLastError() != WSAEWOULDBLOCK)
throw Exception(Exception::ERROR_READ, string("Socket::") + functionName + ": error detected", getSocketErrorCode());
#else
if(errno != EAGAIN && errno != EWOULDBLOCK)
throw Exception(Exception::ERROR_READ, string("Socket::") + functionName + ": error detected", getSocketErrorCode());
#endif
}
void Socket::initSocket() {
struct addrinfo conf, *res = NULL;
memset(&conf, 0, sizeof(conf));
if(_type == SERVER || _protocol == UDP)
conf.ai_flags = AI_PASSIVE;
switch(_protocol) {
case TCP:
conf.ai_socktype = SOCK_STREAM;
break;
case UDP:
conf.ai_socktype = SOCK_DGRAM;
break;
default:
throw Exception(Exception::BAD_PROTOCOL, "Socket::initSocket: bad protocol");
}
switch(_ipVer) {
case IP4:
conf.ai_family = AF_INET;
break;
case IP6:
conf.ai_family = AF_INET6;
break;
case ANY:
conf.ai_family = AF_UNSPEC;
break;
default:
throw Exception(Exception::BAD_IP_VER, "Socket::initSocket: bad ip version parameter");
}
char portStr[10];
const char* host;
if(_type == CLIENT && _protocol == TCP) {
host = _hostTo.c_str();
snprintf(portStr, 10, "%u", _portTo);
}
else {
if(!_hostFrom.compare("") || !_hostFrom.compare("*"))
host = NULL;
else
host = _hostFrom.c_str();
snprintf(portStr, 10, "%u", _portFrom);
}
int status = getaddrinfo(host, portStr, &conf, &res);
ReleaseManager addrInfoReleaser(freeaddrinfo);
addrInfoReleaser.add(res);
if(status != 0) {
string errorMsg = "Socket::initSocket: Error setting addrInfo: ";
#ifndef _MSC_VER
errorMsg += gai_strerror(status);
#endif
throw Exception(Exception::ERROR_SET_ADDR_INFO, errorMsg, getSocketErrorCode());
}
bool connected = false;
while(!connected && res) {
_socketHandler = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if(_socketHandler != -1)
switch(_type) {
case CLIENT:
if(_protocol == UDP) {
if (bind(_socketHandler, res->ai_addr, res->ai_addrlen) == -1)
close(_socketHandler);
else
connected = true;
}
else {
status = connect(_socketHandler, res->ai_addr, res->ai_addrlen);
if(status != -1)
connected = true;
else
close(_socketHandler);
}
break;
case SERVER:
#ifdef OS_WIN32
char yes = 1;
#else
int yes = 1;
#endif
if (setsockopt(_socketHandler, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
throw Exception(Exception::ERROR_SET_SOCK_OPT, "Socket::initSocket: Error establishing socket options");
if (bind(_socketHandler, res->ai_addr, res->ai_addrlen) == -1)
close(_socketHandler);
else
connected = true;
if (_protocol == TCP && listen(_socketHandler, _listenQueue) == -1)
throw Exception(Exception::ERROR_CAN_NOT_LISTEN, "Socket::initSocket: could not start listening", getSocketErrorCode());
break;
}
if(connected && _ipVer == ANY)
switch(res->ai_family) {
case AF_INET:
_ipVer = IP4;
break;
case AF_INET6:
_ipVer = IP6;
break;
}
res = res->ai_next;
}
if(!connected)
throw Exception(Exception::ERROR_CONNECT_SOCKET, "Socket::initSocket: error in socket connection/bind", getSocketErrorCode());
if(!_portFrom)
_portFrom = getLocalPort(_socketHandler);
//freeaddrinfo(res);
}
/**
* CLIENT Socket constructor
*
* Creates a socket, connect it (if TCP) and sets it ready to send to hostTo:portTo.
* The local port of the socket is choosen by OS.
*
* @param hostTo the target/remote host
* @param portTo the target/remote port
* @param protocol the protocol to be used (TCP or UDP). TCP by default.
* @param ipVer the IP version to be used (IP4, IP6 or ANY). ANY by default.
* @throw Exception BAD_PROTOCOL, BAD_IP_VER, ERROR_SET_ADDR_INFO*, ERROR_CONNECT_SOCKET*,
* ERROR_GET_ADDR_INFO*
*/
Socket::Socket(const string& hostTo, unsigned portTo, Protocol protocol, IPVer ipVer) :
_hostTo(hostTo), _portTo(portTo), _portFrom(0), _protocol(protocol),
_ipVer(ipVer), _type(CLIENT), _blocking(true), _listenQueue(0)
{
initSocket();
}
/**
* SERVER Socket constructor
*
* Creates a socket, binds it to portFrom port and listens for connections (if TCP).
*
* @param portFrom the local port the socket will be bound to
* @param protocol the protocol to be used (TCP or UDP). TCP by default.
* @param ipVer the IP version to be used (IP4, IP6 or ANY). IP4 by default.
* @param hostFrom the local address to be binded to (example: "localhost" or "127.0.0.1"). Empty (by default) or "*" means all avariable addresses.
* @param listenQueue the size of the internal buffer of the SERVER TCP socket where the connection requests are stored until accepted
* @throw Exception BAD_PROTOCOL, BAD_IP_VER, ERROR_SET_ADDR_INFO*, ERROR_SET_SOCK_OPT*,
* ERROR_CAN_NOT_LISTEN*, ERROR_CONNECT_SOCKET*
*/
Socket::Socket(unsigned portFrom, Protocol protocol, IPVer ipVer, const string& hostFrom, unsigned listenQueue):
_hostFrom(hostFrom), _portTo(0), _portFrom(portFrom), _protocol(protocol),
_ipVer(ipVer), _type(SERVER), _blocking(true), _listenQueue(listenQueue)
{
initSocket();
}
/**
* UDP CLIENT Socket Constructor
*
* This client constructor for UDP Sockets allows to expecify the local port the socket
* will be bound to. It sets the socket ready to send data to hostTo:portTo.
*
* @param hostTo the target/remote host
* @param portTo the target/remote port
* @param portFrom the local port the socket will be bound to
* @param ipVer the IP version to be used (IP4, IP6 or ANY). ANY by default.
* @throw Exception BAD_PROTOCOL, BAD_IP_VER, ERROR_SET_ADDR_INFO*, ERROR_SET_SOCK_OPT*,
* ERROR_CAN_NOT_LISTEN*, ERROR_CONNECT_SOCKET*
*/
Socket::Socket(const string& hostTo, unsigned portTo, unsigned portFrom, IPVer ipVer):
_hostTo(hostTo), _portTo(portTo), _portFrom(portFrom), _protocol(UDP),
_ipVer(ipVer), _type(CLIENT), _blocking(true), _listenQueue(0)
{
initSocket();
}
Socket::Socket() : _blocking(true), _socketHandler(-1) {};
/**
* Socket Destructor
*
* Closes (disconnects) the socket
*/
Socket::~Socket() {
if(_socketHandler != -1)
close(_socketHandler);
}
// get sockaddr, IPv4 or IPv6:
// This function is from Brian “Beej Jorgensen” Hall: Beej's Guide to Network Programming.
static void *get_in_addr(struct sockaddr *sa)
{
if (sa->sa_family == AF_INET) {
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
/**
* Accepts a new incoming connection (SERVER Socket).
*
* Creates a new CLIENT socket to handle the communication (send/recieve data) of
* this accepted connection. Requires the socket to be a SERVER TCP socket. Throws an
* exception otherwise.
*
* @pre Socket must be SERVER
* @return A CLIENT socket that handles the new connection
* @throw Exception EXPECTED_TCP_SOCKET, EXPECTED_SERVER_SOCKET
*/
Socket* Socket::accept() {
if(_protocol != TCP)
throw Exception(Exception::EXPECTED_TCP_SOCKET, "Socket::accept: non-tcp socket can not accept connections");
if(_type != SERVER)
throw Exception(Exception::EXPECTED_SERVER_SOCKET, "Socket::accept: non-server socket can not accept connections");
struct sockaddr_storage incoming_addr;
#ifdef OS_WIN32
int addrSize = sizeof(incoming_addr);
#else
unsigned addrSize = sizeof(incoming_addr);
#endif
int new_handler = ::accept(_socketHandler, (struct sockaddr *)&incoming_addr, &addrSize);
if(new_handler == -1)
return NULL;
char hostChar[INET6_ADDRSTRLEN];
inet_ntop(incoming_addr.ss_family, get_in_addr((struct sockaddr *)&incoming_addr), hostChar, sizeof hostChar);
int localPort = getLocalPort(new_handler);
Socket* acceptSocket = new Socket();
acceptSocket->_socketHandler = new_handler;
acceptSocket->_hostTo = hostChar;
acceptSocket->_portTo = getInPort((struct sockaddr *)&incoming_addr);
acceptSocket->_portFrom = localPort;
acceptSocket->_protocol = _protocol;
acceptSocket->_ipVer = _ipVer;
acceptSocket->_type = CLIENT;
acceptSocket->_listenQueue = 0;
acceptSocket->blocking(_blocking);
return acceptSocket;
}
/**
* Sends data to an expecific host:port
*
* Sends the data contained in buffer to a given host:port. Requires the socket to be an UDP socket,
* throws an exception otherwise.
*
* @pre Socket must be UDP
* @param buffer A pointer to the data we want to send
* @param size Size of the data to send (bytes)
* @param hostTo Target/remote host
* @param portTo Target/remote port
* @throw Exception EXPECTED_UDP_SOCKET, BAD_IP_VER, ERROR_SET_ADDR_INFO*, ERROR_SEND*
*/
void Socket::sendTo(const void* buffer, size_t size, const string& hostTo, unsigned portTo) {
if(_protocol != UDP)
throw Exception(Exception::EXPECTED_UDP_SOCKET, "Socket::sendTo: non-UDP socket can not 'sendTo'");
struct addrinfo conf, *res;
memset(&conf, 0, sizeof(conf));
conf.ai_socktype = SOCK_DGRAM;
switch(_ipVer) {
case IP4:
conf.ai_family = AF_INET;
break;
case IP6:
conf.ai_family = AF_INET6;
break;
default:
throw Exception(Exception::BAD_IP_VER, "Socket::sendTo: bad ip version.");
}
char portStr[10];
snprintf(portStr, 10, "%u", portTo);
int status = getaddrinfo(hostTo.c_str(), portStr, &conf, &res);
ReleaseManager addrInfoReleaser(freeaddrinfo);
addrInfoReleaser.add(&res);
if(status != 0) {
string errorMsg = "Socket::sendTo: error setting addrInfo: ";
#ifndef _MSC_VER
errorMsg += gai_strerror(status);
#endif
throw Exception(Exception::ERROR_SET_ADDR_INFO, "Socket::sendTo: error setting addr info", getSocketErrorCode());
}
size_t sentBytes = 0;
while(sentBytes < size) {
int status = ::sendto(_socketHandler, (const char*)buffer + sentBytes, size - sentBytes, 0, res->ai_addr, res->ai_addrlen);
if(status == -1)
throw Exception(Exception::ERROR_SEND, "Socket::sendTo: could not send the data", getSocketErrorCode());
sentBytes += status;
}
}
/**
* Receive data and get the source host and port
*
* Requires the socket to be UDP. Source host address and port are returned in hostFrom and
* portFrom parameters. Data recieved is written in buffer address up to bufferSize.
*
* @pre Socket must be UDP
* @param buffer Pointer to a buffer where received data will be stored
* @param bufferSize Size of the buffer
* @param[out] hostFrom Here the function will store the address of the remote host
* @param[out] portFrom Here the function will store the remote port
* @return the length of the data recieved
* @throw Exception EXPECTED_UDP_SOCKET, ERROR_READ*
*/
int Socket::readFrom(void* buffer, size_t bufferSize, string* hostFrom, unsigned* portFrom) {
if(_protocol != UDP)
throw Exception(Exception::EXPECTED_UDP_SOCKET, "Socket::readFrom: non-UDP socket can not 'readFrom'");
struct sockaddr_storage addr;
socklen_t addrSize = sizeof(addr);
int status = recvfrom(_socketHandler, (char*)buffer, bufferSize, 0, (struct sockaddr *)&addr, &addrSize);
if(status == -1) {
checkReadError("readFrom");
if(hostFrom)
*hostFrom = "";
if(portFrom)
*portFrom = 0;
}
else {
if(portFrom)
*portFrom = getInPort((struct sockaddr*)&addr);
if(hostFrom) {
char hostChar[INET6_ADDRSTRLEN];
inet_ntop(addr.ss_family, get_in_addr((struct sockaddr *)&addr), hostChar, sizeof hostChar);
*hostFrom = hostChar;
}
}
return status;
}
/**
* Sends data
*
* Sends the data contained in buffer. Requires the Socket to be a CLIENT socket.
*
* @pre Socket must be CLIENT
* @param buffer A pointer to the data we want to send
* @param size Length of the data to be sent (bytes)
* @throw Exception EXPECTED_CLIENT_SOCKET, ERROR_SEND*
*/
void Socket::send(const void* buffer, size_t size) {
if(_type != CLIENT)
throw Exception(Exception::EXPECTED_CLIENT_SOCKET, "Socket::send: Expected client socket (socket with host and port target)");
if(_protocol == UDP)
return sendTo(buffer, size, _hostTo, _portTo);
size_t sentData = 0;
while (sentData < size) {
int status = ::send(_socketHandler, (const char*)buffer + sentData, size - sentData, 0);
if(status == -1)
throw Exception(Exception::ERROR_SEND, "Error sending data", getSocketErrorCode());
sentData += status;
}
}
/**
* Receives data
*
* Receives data and stores it in buffer until bufferSize reached.
*
* @param buffer A pointer to a buffer where received data will be stored
* @param bufferSize Size of the buffer
* @return Size of received data or (-1) if Socket is non-blocking and there's no data received.
* @throw Exception ERROR_READ*
*/
int Socket::read(void* buffer, size_t bufferSize) {
int status = recv(_socketHandler, (char*)buffer, bufferSize, 0);
if(status == -1)
checkReadError("read");
return status;
}
/**
* Get next read() data size
*
* Get the size of the data (bytes) a call to read() or readFrom() can process
*
* @return size of data the next call to read/readFrom will receive
* @throw Exception ERROR_IOCTL*
*/
int Socket::nextReadSize() const {
#ifdef OS_WIN32
u_long result = -1;
#else
long int result = -1;
#endif
int status;
#ifdef OS_WIN32
status = ioctlsocket(_socketHandler, FIONREAD, &result);
#else
status = ioctl(_socketHandler, FIONREAD, &result);
#endif
if(status)
throw Exception(Exception::ERROR_IOCTL, "Socket::nextReadSize: error ioctl", getSocketErrorCode());
return result;
}
/**
* Sets the blocking nature of the Socket
*
* Sets the Socket as blocking (if blocking is true) or as non-blocking (otherwise)
*
* @param blocking true to set the Socket as blocking; false to set the Socket as non-blocking
* @throw Exception ERROR_IOCTL*
*/
void Socket::blocking(bool blocking) {
_blocking = blocking;
int result = -1;
#ifdef OS_WIN32
u_long non_blocking = !blocking;
result = ioctlsocket(_socketHandler, FIONBIO, &non_blocking);
if(result!=0)
result = -1;
#else
int flags = fcntl(_socketHandler, F_GETFL);
if(blocking)
result = fcntl(_socketHandler, F_SETFL, flags & ~O_NONBLOCK);
else
result = fcntl(_socketHandler, F_SETFL, flags | O_NONBLOCK);
#endif
if (result == -1)
throw Exception(Exception::ERROR_IOCTL, "Socket::blocking: ioctl error", getSocketErrorCode());
}
/**
* Closes (disconnects) the socket. After this call the socket can not be used.
*
* @warning Any use of the Socket after disconnection leads to undefined behaviour.
*/
void Socket::disconnect() {
close(_socketHandler);
_socketHandler = -1;
}
/**
* @include socket.inline.h
*/
NL_NAMESPACE_END