mistserver/lib/socket.cpp
Marco van Dijk 8ac486b815 Completed new sessions system
Co-authored-by: Thulinma <jaron@vietors.com>
2022-10-05 03:13:52 +02:00

2079 lines
69 KiB
C++

/// \file socket.cpp
/// A handy Socket wrapper library.
/// Written by Jaron Vietor in 2010 for DDVTech
#include "defines.h"
#include "socket.h"
#include "timing.h"
#include "json.h"
#include <cstdlib>
#include <ifaddrs.h>
#include <netdb.h>
#include <netinet/in.h>
#include <poll.h>
#include <sstream>
#include <sys/socket.h>
#include <sys/stat.h>
#define BUFFER_BLOCKSIZE 4096 // set buffer blocksize to 4KiB
#ifdef __CYGWIN__
#define SOCKETSIZE 8092ul
#else
#define SOCKETSIZE 51200ul
#endif
/// Local-scope only helper function that prints address families
static const char *addrFam(int f){
switch (f){
case AF_UNSPEC: return "Unspecified";
case AF_INET: return "IPv4";
case AF_INET6: return "IPv6";
case PF_UNIX: return "Unix";
default: return "???";
}
}
/// Calls gai_strerror with the given argument, calling regular strerror on the global errno as needed
static const char *gai_strmagic(int errcode){
if (errcode == EAI_SYSTEM){
return strerror(errno);
}else{
return gai_strerror(errcode);
}
}
static std::string getIPv6BinAddr(const struct sockaddr_in6 &remoteaddr){
char tmpBuffer[17] = "\000\000\000\000\000\000\000\000\000\000\377\377\000\000\000\000";
switch (remoteaddr.sin6_family){
case AF_INET:
memcpy(tmpBuffer + 12, &(reinterpret_cast<const sockaddr_in *>(&remoteaddr)->sin_addr.s_addr), 4);
break;
case AF_INET6: memcpy(tmpBuffer, &(remoteaddr.sin6_addr.s6_addr), 16); break;
default: return ""; break;
}
return std::string(tmpBuffer, 16);
}
bool Socket::isLocalhost(const std::string &remotehost){
std::string tmpInput = remotehost;
std::string bf = Socket::getBinForms(tmpInput);
std::string tmpAddr;
while (bf.size() >= 16){
Socket::hostBytesToStr(bf.data(), 16, tmpAddr);
if (isLocal(tmpAddr)){return true;}
bf.erase(0, 17);
}
return false;
}
/// Checks if the given file descriptor is actually socket or not.
bool Socket::checkTrueSocket(int sock){
struct stat sBuf;
if (sock != -1 && !fstat(sock, &sBuf)){return S_ISSOCK(sBuf.st_mode);}
return false;
}
bool Socket::isLocal(const std::string &remotehost){
struct ifaddrs *ifAddrStruct = NULL;
struct ifaddrs *ifa = NULL;
void *tmpAddrPtr = NULL;
bool ret = false;
char addressBuffer[INET6_ADDRSTRLEN];
getifaddrs(&ifAddrStruct);
for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next){
if (!ifa->ifa_addr){continue;}
if (ifa->ifa_addr->sa_family == AF_INET){// check it is IP4
tmpAddrPtr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
INSANE_MSG("Comparing '%s' to '%s'", remotehost.c_str(), addressBuffer);
if (remotehost == addressBuffer){
ret = true;
break;
}
INSANE_MSG("Comparing '%s' to '::ffff:%s'", remotehost.c_str(), addressBuffer);
if (remotehost == std::string("::ffff:") + addressBuffer){
ret = true;
break;
}
}else if (ifa->ifa_addr->sa_family == AF_INET6){// check it is IP6
tmpAddrPtr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
INSANE_MSG("Comparing '%s' to '%s'", remotehost.c_str(), addressBuffer);
if (remotehost == addressBuffer){
ret = true;
break;
}
}
}
if (ifAddrStruct != NULL) freeifaddrs(ifAddrStruct);
return ret;
}
/// Helper function that matches two binary-format IPv6 addresses with prefix bits of prefix.
bool Socket::matchIPv6Addr(const std::string &A, const std::string &B, uint8_t prefix){
if (!prefix){prefix = 128;}
if (Util::printDebugLevel >= DLVL_MEDIUM){
std::string Astr, Bstr;
Socket::hostBytesToStr(A.data(), 16, Astr);
Socket::hostBytesToStr(B.data(), 16, Bstr);
MEDIUM_MSG("Matching: %s to %s with %u prefix", Astr.c_str(), Bstr.c_str(), prefix);
}
if (memcmp(A.data(), B.data(), prefix / 8)){return false;}
if ((prefix % 8) && ((A.data()[prefix / 8] & (0xFF << (8 - (prefix % 8)))) !=
(B.data()[prefix / 8] & (0xFF << (8 - (prefix % 8)))))){
return false;
}
return true;
}
/// Attempts to match the given address with optional subnet to the given binary-form IPv6 address.
/// Returns true if match could be made, false otherwise.
bool Socket::isBinAddress(const std::string &binAddr, std::string addr){
// Check if we need to do prefix matching
uint8_t prefixLen = 0;
if (addr.find('/') != std::string::npos){
prefixLen = atoi(addr.c_str() + addr.find('/') + 1);
addr.erase(addr.find('/'), std::string::npos);
}
// Loops over all IPs for the given address and matches them in IPv6 form.
struct addrinfo *result, *rp, hints;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED;
hints.ai_protocol = 0;
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
int s = getaddrinfo(addr.c_str(), 0, &hints, &result);
if (s != 0){return false;}
for (rp = result; rp != NULL; rp = rp->ai_next){
std::string tBinAddr = getIPv6BinAddr(*((sockaddr_in6 *)rp->ai_addr));
if (rp->ai_family == AF_INET){
if (matchIPv6Addr(tBinAddr, binAddr, prefixLen ? prefixLen + 96 : 0)){return true;}
}else{
if (matchIPv6Addr(tBinAddr, binAddr, prefixLen)){return true;}
}
}
freeaddrinfo(result);
return false;
}
/// Converts the given address with optional subnet to binary IPv6 form.
/// Returns 16 bytes of address, followed by 1 byte of subnet bits, zero or more times.
std::string Socket::getBinForms(std::string addr){
// Check for empty address
if (!addr.size()){return std::string(17, (char)0);}
// Check if we need to do prefix matching
uint8_t prefixLen = 128;
if (addr.find('/') != std::string::npos){
prefixLen = atoi(addr.c_str() + addr.find('/') + 1);
addr.erase(addr.find('/'), std::string::npos);
}
// Loops over all IPs for the given address and converts to IPv6 binary form.
struct addrinfo *result, *rp, hints;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED;
hints.ai_protocol = 0;
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
int s = getaddrinfo(addr.c_str(), 0, &hints, &result);
if (s != 0){return "";}
std::string ret;
for (rp = result; rp != NULL; rp = rp->ai_next){
ret += getIPv6BinAddr(*((sockaddr_in6 *)rp->ai_addr));
if (rp->ai_family == AF_INET){
ret += (char)(prefixLen <= 32 ? prefixLen + 96 : prefixLen);
}else{
ret += (char)prefixLen;
}
}
freeaddrinfo(result);
return ret;
}
/// Checks bytes (length len) containing a binary-encoded IPv4 or IPv6 IP address, and writes it in
/// human-readable notation to target. Writes "unknown" if it cannot decode to a sensible value.
void Socket::hostBytesToStr(const char *bytes, size_t len, std::string &target){
switch (len){
case 4:
char tmpstr[16];
snprintf(tmpstr, 16, "%hhu.%hhu.%hhu.%hhu", bytes[0], bytes[1], bytes[2], bytes[3]);
target = tmpstr;
break;
case 16:
if (memcmp(bytes, "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", 15) == 0){
if (bytes[15] == 0){
target = "::";
return;
}
char tmpstr[6];
snprintf(tmpstr, 6, "::%hhu", bytes[15]);
target = tmpstr;
return;
}
if (memcmp(bytes, "\000\000\000\000\000\000\000\000\000\000\377\377", 12) == 0){
char tmpstr[16];
snprintf(tmpstr, 16, "%hhu.%hhu.%hhu.%hhu", bytes[12], bytes[13], bytes[14], bytes[15]);
target = tmpstr;
}else{
char tmpstr[40];
snprintf(tmpstr, 40, "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x",
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]);
target = tmpstr;
}
break;
default: target = "unknown"; break;
}
}
/// Resolves a hostname into a human-readable address that is the best guess for external address matching this host.
/// The optional family can force IPv4/IPv6 resolving, while the optional hint will allow forcing resolving to a
/// specific address if it is a match for this host.
/// Returns empty string if no reasonable match could be made.
std::string Socket::resolveHostToBestExternalAddrGuess(const std::string &host, int family,
const std::string &hint){
struct addrinfo *result, *rp, hints;
std::string newaddr;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = family;
hints.ai_socktype = 0;
hints.ai_flags = AI_ADDRCONFIG;
int s = getaddrinfo(host.c_str(), 0, &hints, &result);
if (s != 0){
FAIL_MSG("Could not resolve %s! Error: %s", host.c_str(), gai_strmagic(s));
return "";
}
for (rp = result; rp != NULL; rp = rp->ai_next){
static char addrconv[INET6_ADDRSTRLEN];
if (rp->ai_family == AF_INET6){
newaddr = inet_ntop(rp->ai_family, &((const sockaddr_in6 *)rp->ai_addr)->sin6_addr, addrconv, INET6_ADDRSTRLEN);
}
if (rp->ai_family == AF_INET){
newaddr = inet_ntop(rp->ai_family, &((const sockaddr_in *)rp->ai_addr)->sin_addr, addrconv, INET6_ADDRSTRLEN);
}
if (newaddr.substr(0, 7) == "::ffff:"){newaddr = newaddr.substr(7);}
HIGH_MSG("Resolved to %s addr [%s]", addrFam(rp->ai_family), newaddr.c_str());
// if not a local address, we can't bind, so don't bother trying it
if (!isLocal(newaddr)){continue;}
// we match the hint, done!
if (newaddr == hint){break;}
}
freeaddrinfo(result);
return newaddr;
}
/// Gets bound host and port for a socket and returns them by reference.
/// Returns true on success and false on failure.
bool Socket::getSocketName(int fd, std::string &host, uint32_t &port){
struct sockaddr_in6 tmpaddr;
socklen_t len = sizeof(tmpaddr);
if (getsockname(fd, (sockaddr *)&tmpaddr, &len)){return false;}
static char addrconv[INET6_ADDRSTRLEN];
if (tmpaddr.sin6_family == AF_INET6){
host = inet_ntop(AF_INET6, &(tmpaddr.sin6_addr), addrconv, INET6_ADDRSTRLEN);
if (host.substr(0, 7) == "::ffff:"){host = host.substr(7);}
port = ntohs(tmpaddr.sin6_port);
HIGH_MSG("Local IPv6 addr [%s:%" PRIu32 "]", host.c_str(), port);
return true;
}
if (tmpaddr.sin6_family == AF_INET){
host = inet_ntop(AF_INET, &(((sockaddr_in *)&tmpaddr)->sin_addr), addrconv, INET6_ADDRSTRLEN);
port = ntohs(((sockaddr_in *)&tmpaddr)->sin_port);
HIGH_MSG("Local IPv4 addr [%s:%" PRIu32 "]", host.c_str(), port);
return true;
}
return false;
}
/// Gets peer host and port for a socket and returns them by reference.
/// Returns true on success and false on failure.
bool Socket::getPeerName(int fd, std::string &host, uint32_t &port, sockaddr * tmpaddr, socklen_t * addrlen){
if (getpeername(fd, tmpaddr, addrlen)){return false;}
static char addrconv[INET6_ADDRSTRLEN];
if (tmpaddr->sa_family == AF_INET6){
host = inet_ntop(AF_INET6, &(((sockaddr_in6*)tmpaddr)->sin6_addr), addrconv, INET6_ADDRSTRLEN);
if (host.substr(0, 7) == "::ffff:"){host = host.substr(7);}
port = ntohs(((sockaddr_in6 *)&tmpaddr)->sin6_port);
HIGH_MSG("Peer IPv6 addr [%s:%" PRIu32 "]", host.c_str(), port);
return true;
}
if (tmpaddr->sa_family == AF_INET){
host = inet_ntop(AF_INET, &(((sockaddr_in *)tmpaddr)->sin_addr), addrconv, INET6_ADDRSTRLEN);
port = ntohs(((sockaddr_in *)&tmpaddr)->sin_port);
HIGH_MSG("Peer IPv4 addr [%s:%" PRIu32 "]", host.c_str(), port);
return true;
}
return false;
}
/// Gets peer host and port for a socket and returns them by reference.
/// Returns true on success and false on failure.
bool Socket::getPeerName(int fd, std::string &host, uint32_t &port){
struct sockaddr_in6 tmpaddr;
socklen_t addrLen = sizeof(tmpaddr);
return getPeerName(fd, host, port, (sockaddr*)&tmpaddr, &addrLen);
}
std::string uint2string(unsigned int i){
std::stringstream st;
st << i;
return st.str();
}
Socket::Buffer::Buffer(){
splitter = "\n";
}
/// Returns the amount of elements in the internal std::deque of std::string objects.
/// The back is popped as long as it is empty, first - this way this function is
/// guaranteed to return 0 if the buffer is empty.
unsigned int Socket::Buffer::size(){
while (data.size() > 0 && data.back().empty()){data.pop_back();}
return data.size();
}
/// Returns either the amount of total bytes available in the buffer or max, whichever is smaller.
unsigned int Socket::Buffer::bytes(unsigned int max){
unsigned int i = 0;
for (std::deque<std::string>::iterator it = data.begin(); it != data.end(); ++it){
i += (*it).size();
if (i >= max){return max;}
}
return i;
}
/// Returns how many bytes to read until the next splitter, or 0 if none found.
unsigned int Socket::Buffer::bytesToSplit(){
unsigned int i = 0;
for (std::deque<std::string>::reverse_iterator it = data.rbegin(); it != data.rend(); ++it){
i += (*it).size();
if ((*it).size() >= splitter.size() && (*it).substr((*it).size() - splitter.size()) == splitter){
return i;
}
}
return 0;
}
/// Appends this string to the internal std::deque of std::string objects.
/// It is automatically split every BUFFER_BLOCKSIZE bytes and when the splitter string is
/// encountered.
void Socket::Buffer::append(const std::string &newdata){
append(newdata.data(), newdata.size());
}
/// Helper function that does a short-circuiting string compare
inline bool string_compare(const char *a, const char *b, const size_t len){
for (size_t i = 0; i < len; ++i){
if (a[i] != b[i]){return false;}
}
return true;
}
/// Appends this data block to the internal std::deque of std::string objects.
/// It is automatically split every BUFFER_BLOCKSIZE bytes and when the splitter string is
/// encountered.
void Socket::Buffer::append(const char *newdata, const unsigned int newdatasize){
uint32_t i = 0;
while (i < newdatasize){
uint32_t j = 0;
if (!splitter.size()){
if (newdatasize - i > BUFFER_BLOCKSIZE){
j = BUFFER_BLOCKSIZE;
}else{
j = newdatasize - i;
}
}else{
while (j + i < newdatasize && j < BUFFER_BLOCKSIZE){
j++;
if (j >= splitter.size()){
if (string_compare(newdata + i + j - splitter.size(), splitter.data(), splitter.size())){
break;
}
}
}
}
if (j){
data.push_front("");
data.front().assign(newdata + i, (size_t)j);
i += j;
}else{
FAIL_MSG("Appended an empty string to buffer: aborting!");
break;
}
}
if (data.size() > 5000){
WARN_MSG("Warning: After %d new bytes, buffer has %d parts containing over %u bytes!",
newdatasize, (int)data.size(), bytes(9000));
}
}
/// Prepends this data block to the internal std::deque of std::string objects.
/// It is _not_ automatically split every BUFFER_BLOCKSIZE bytes.
void Socket::Buffer::prepend(const std::string &newdata){
data.push_back(newdata);
}
/// Prepends this data block to the internal std::deque of std::string objects.
/// It is _not_ automatically split every BUFFER_BLOCKSIZE bytes.
void Socket::Buffer::prepend(const char *newdata, const unsigned int newdatasize){
data.push_back(std::string(newdata, (size_t)newdatasize));
}
/// Returns true if at least count bytes are available in this buffer.
bool Socket::Buffer::available(unsigned int count){
size();
unsigned int i = 0;
for (std::deque<std::string>::iterator it = data.begin(); it != data.end(); ++it){
i += (*it).size();
if (i >= count){return true;}
}
return false;
}
/// Returns true if at least count bytes are available in this buffer.
bool Socket::Buffer::available(unsigned int count) const{
unsigned int i = 0;
for (std::deque<std::string>::const_iterator it = data.begin(); it != data.end(); ++it){
i += (*it).size();
if (i >= count){return true;}
}
return false;
}
/// Removes count bytes from the buffer, returning them by value.
/// Returns an empty string if not all count bytes are available.
std::string Socket::Buffer::remove(unsigned int count){
size();
if (!available(count)){return "";}
unsigned int i = 0;
std::string ret;
ret.reserve(count);
for (std::deque<std::string>::reverse_iterator it = data.rbegin(); it != data.rend(); ++it){
if (i + (*it).size() < count){
ret.append(*it);
i += (*it).size();
(*it).clear();
}else{
ret.append(*it, 0, count - i);
(*it).erase(0, count - i);
break;
}
}
return ret;
}
/// Copies count bytes from the buffer, returning them by value.
/// Returns an empty string if not all count bytes are available.
std::string Socket::Buffer::copy(unsigned int count){
size();
if (!available(count)){return "";}
unsigned int i = 0;
std::string ret;
ret.reserve(count);
for (std::deque<std::string>::reverse_iterator it = data.rbegin(); it != data.rend(); ++it){
if (i + (*it).size() < count){
ret.append(*it);
i += (*it).size();
}else{
ret.append(*it, 0, count - i);
break;
}
}
return ret;
}
/// Gets a reference to the back of the internal std::deque of std::string objects.
std::string &Socket::Buffer::get(){
size();
static std::string empty;
if (data.size() > 0){
return data.back();
}else{
return empty;
}
}
/// Completely empties the buffer
void Socket::Buffer::clear(){
data.clear();
}
void Socket::Connection::setBoundAddr(){
boundaddr.clear();
// If a bound address was set through environment (e.g. HTTPS output), restore it from there.
char *envbound = getenv("MIST_BOUND_ADDR");
if (envbound){boundaddr = envbound;}
// If we can't read the address, don't try
if (!isTrueSocket){return;}
// Otherwise, read from socket pointer. Works for both SSL and non-SSL sockets, and real sockets passed as fd's, but not for non-sockets (duh)
uint32_t boundport = 0;
getSocketName(getSocket(), boundaddr, boundport);
socklen_t aLen = sizeof(remoteaddr);
getPeerName(getSocket(), remotehost, boundport, (sockaddr *)&remoteaddr, &aLen);
}
// Cleans up the socket by dropping the connection.
// Does not call close because it calls shutdown, which would destroy any copies of this socket too.
Socket::Connection::~Connection(){
drop();
}
/// Create a new base socket. This is a basic constructor for converting any valid socket to a
/// Socket::Connection. \param sockNo Integer representing the socket to convert.
Socket::Connection::Connection(int sockNo){
clear();
open(sockNo, sockNo);
}// Socket::Connection basic constructor
/// Open from existing socket connection.
/// Closes any existing connections and resets all internal values beforehand.
/// Simply calls open(sockNo, sockNo) internally.
void Socket::Connection::open(int sockNo){
open(sockNo, sockNo);
}
/// Simulate a socket using two file descriptors.
/// \param write The filedescriptor to write to.
/// \param read The filedescriptor to read from.
Socket::Connection::Connection(int write, int read){
clear();
open(write, read);
}// Socket::Connection basic constructor
/// Open from two existing file descriptors.
/// Closes any existing connections and resets all internal values beforehand.
void Socket::Connection::open(int write, int read){
drop();
clear();
sSend = write;
if (write != read){
sRecv = read;
}else{
sRecv = -1;
}
isTrueSocket = Socket::checkTrueSocket(sSend);
setBoundAddr();
}
void Socket::Connection::clear(){
sSend = -1;
sRecv = -1;
isTrueSocket = false;
up = 0;
down = 0;
conntime = Util::bootSecs();
Error = false;
Blocking = false;
skipCount = 0;
#ifdef SSL
sslConnected = false;
server_fd = 0;
ssl = 0;
conf = 0;
ctr_drbg = 0;
entropy = 0;
#endif
}
/// Create a new disconnected base socket. This is a basic constructor for placeholder purposes.
/// A socket created like this is always disconnected and should/could be overwritten at some point.
Socket::Connection::Connection(){
clear();
}// Socket::Connection basic constructor
void Socket::Connection::resetCounter(){
up = 0;
down = 0;
}
void Socket::Connection::addUp(const uint32_t i){
up += i;
}
void Socket::Connection::addDown(const uint32_t i){
down += i;
}
/// Internally used call to make an file descriptor blocking or not.
void setFDBlocking(int FD, bool blocking){
int flags = fcntl(FD, F_GETFL, 0);
if (!blocking){
flags |= O_NONBLOCK;
}else{
flags &= !O_NONBLOCK;
}
fcntl(FD, F_SETFL, flags);
}
/// Internally used call to make an file descriptor blocking or not.
bool isFDBlocking(int FD){
int flags = fcntl(FD, F_GETFL, 0);
return !(flags & O_NONBLOCK);
}
/// Set this socket to be blocking (true) or nonblocking (false).
void Socket::Connection::setBlocking(bool blocking){
#ifdef SSL
if (sslConnected){
if (blocking == Blocking){return;}
if (blocking){
mbedtls_net_set_block(server_fd);
Blocking = true;
}else{
mbedtls_net_set_nonblock(server_fd);
Blocking = false;
}
return;
}
#endif
if (sSend >= 0){setFDBlocking(sSend, blocking);}
if (sRecv >= 0 && sSend != sRecv){setFDBlocking(sRecv, blocking);}
}
/// Set this socket to be blocking (true) or nonblocking (false).
bool Socket::Connection::isBlocking(){
#ifdef SSL
if (sslConnected){return Blocking;}
#endif
if (sSend >= 0){return isFDBlocking(sSend);}
if (sRecv >= 0){return isFDBlocking(sRecv);}
return false;
}
/// Close connection. The internal socket is closed and then set to -1.
/// If the connection is already closed, nothing happens.
/// This function calls shutdown, thus making the socket unusable in all other
/// processes as well. Do not use on shared sockets that are still in use.
void Socket::Connection::close(){
if (sSend != -1){shutdown(sSend, SHUT_RDWR);}
drop();
}// Socket::Connection::close
/// Close connection. The internal socket is closed and then set to -1.
/// If the connection is already closed, nothing happens.
/// This function does *not* call shutdown, allowing continued use in other
/// processes.
void Socket::Connection::drop(){
#ifdef SSL
if (sslConnected){
DONTEVEN_MSG("SSL close");
if (ssl){mbedtls_ssl_close_notify(ssl);}
if (server_fd){
mbedtls_net_free(server_fd);
delete server_fd;
server_fd = 0;
}
if (ssl){
mbedtls_ssl_free(ssl);
delete ssl;
ssl = 0;
}
if (conf){
mbedtls_ssl_config_free(conf);
delete conf;
conf = 0;
}
if (ctr_drbg){
mbedtls_ctr_drbg_free(ctr_drbg);
delete ctr_drbg;
ctr_drbg = 0;
}
if (entropy){
mbedtls_entropy_free(entropy);
delete entropy;
entropy = 0;
}
sslConnected = false;
return;
}
#endif
if (connected()){
if (sSend != -1){
HIGH_MSG("Socket %d closed", sSend);
errno = EINTR;
while (::close(sSend) != 0 && errno == EINTR){}
if (sRecv == sSend){sRecv = -1;}
sSend = -1;
}
if (sRecv != -1){
errno = EINTR;
while (::close(sRecv) != 0 && errno == EINTR){}
sRecv = -1;
}
}
}// Socket::Connection::drop
/// Returns internal socket number.
int Socket::Connection::getSocket(){
#ifdef SSL
if (sslConnected){return server_fd->fd;}
#endif
if (sSend != -1){return sSend;}
return sRecv;
}
/// Returns non-piped internal socket number.
int Socket::Connection::getPureSocket(){
#ifdef SSL
if (sslConnected){return server_fd->fd;}
#endif
if (!isTrueSocket){return -1;}
return sSend;
}
/// Returns a string describing the last error that occured.
/// Only reports errors if an error actually occured - returns the host address or empty string
/// otherwise.
std::string Socket::Connection::getError(){
return lastErr;
}
/// Create a new Unix Socket. This socket will (try to) connect to the given address right away.
/// \param address String containing the location of the Unix socket to connect to.
/// \param nonblock Whether the socket should be nonblocking. False by default.
Socket::Connection::Connection(std::string address, bool nonblock){
clear();
open(address, nonblock);
}// Socket::Connection Unix Constructor
/// Open Unix connection.
/// Closes any existing connections and resets all internal values beforehand.
void Socket::Connection::open(std::string address, bool nonblock){
drop();
clear();
isTrueSocket = true;
sSend = socket(PF_UNIX, SOCK_STREAM, 0);
if (sSend < 0){
lastErr = strerror(errno);
FAIL_MSG("Could not create socket! Error: %s", lastErr.c_str());
return;
}
sockaddr_un addr;
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, address.c_str(), address.size() + 1);
int r = connect(sSend, (sockaddr *)&addr, sizeof(addr));
if (r == 0){
if (nonblock){
int flags = fcntl(sSend, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(sSend, F_SETFL, flags);
}
}else{
lastErr = strerror(errno);
FAIL_MSG("Could not connect to %s! Error: %s", address.c_str(), lastErr.c_str());
close();
}
}
#ifdef SSL
/// Local-only function for debugging SSL sockets
static void my_debug(void *ctx, int level, const char *file, int line, const char *str){
((void)level);
fprintf((FILE *)ctx, "%s:%04d: %s", file, line, str);
fflush((FILE *)ctx);
}
#endif
/// Create a new TCP Socket. This socket will (try to) connect to the given host/port right away.
/// \param host String containing the hostname to connect to.
/// \param port String containing the port to connect to.
/// \param nonblock Whether the socket should be nonblocking.
Socket::Connection::Connection(std::string host, int port, bool nonblock, bool with_ssl){
clear();
open(host, port, nonblock, with_ssl);
}
/// Open TCP connection.
/// Closes any existing connections and resets all internal values beforehand.
void Socket::Connection::open(std::string host, int port, bool nonblock, bool with_ssl){
drop();
clear();
if (with_ssl){
#ifdef SSL
mbedtls_debug_set_threshold(0);
server_fd = new mbedtls_net_context;
ssl = new mbedtls_ssl_context;
conf = new mbedtls_ssl_config;
ctr_drbg = new mbedtls_ctr_drbg_context;
entropy = new mbedtls_entropy_context;
mbedtls_net_init(server_fd);
mbedtls_ssl_init(ssl);
mbedtls_ssl_config_init(conf);
mbedtls_ctr_drbg_init(ctr_drbg);
mbedtls_entropy_init(entropy);
DONTEVEN_MSG("SSL init");
if (mbedtls_ctr_drbg_seed(ctr_drbg, mbedtls_entropy_func, entropy, (const unsigned char *)"meow", 4) != 0){
lastErr = "SSL socket init failed";
FAIL_MSG("SSL socket init failed");
close();
return;
}
DONTEVEN_MSG("SSL connect");
int ret = 0;
if ((ret = mbedtls_net_connect(server_fd, host.c_str(), JSON::Value(port).asString().c_str(),
MBEDTLS_NET_PROTO_TCP)) != 0){
char estr[200];
mbedtls_strerror(ret, estr, 200);
lastErr = estr;
FAIL_MSG("SSL connect failed: %d: %s", ret, lastErr.c_str());
close();
return;
}
if ((ret = mbedtls_ssl_config_defaults(conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT)) != 0){
char estr[200];
mbedtls_strerror(ret, estr, 200);
lastErr = estr;
FAIL_MSG("SSL config failed: %d: %s", ret, lastErr.c_str());
close();
return;
}
mbedtls_ssl_conf_authmode(conf, MBEDTLS_SSL_VERIFY_NONE);
mbedtls_ssl_conf_rng(conf, mbedtls_ctr_drbg_random, ctr_drbg);
mbedtls_ssl_conf_dbg(conf, my_debug, stderr);
if ((ret = mbedtls_ssl_setup(ssl, conf)) != 0){
char estr[200];
mbedtls_strerror(ret, estr, 200);
lastErr = estr;
FAIL_MSG("SSL setup error %d: %s", ret, lastErr.c_str());
close();
return;
}
if ((ret = mbedtls_ssl_set_hostname(ssl, host.c_str())) != 0){
char estr[200];
mbedtls_strerror(ret, estr, 200);
lastErr = estr;
FAIL_MSG("SSL setup error %d: %s", ret, lastErr.c_str());
close();
return;
}
mbedtls_ssl_set_bio(ssl, server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);
while ((ret = mbedtls_ssl_handshake(ssl)) != 0){
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE){
char estr[200];
mbedtls_strerror(ret, estr, 200);
lastErr = estr;
FAIL_MSG("SSL handshake error %d: %s", ret, lastErr.c_str());
close();
return;
}
}
sslConnected = true;
isTrueSocket = true;
setBoundAddr();
Blocking = true;
if (nonblock){setBlocking(false);}
DONTEVEN_MSG("SSL connect success");
return;
#endif
FAIL_MSG("Attempted to open SSL socket without SSL support compiled in!");
return;
}
isTrueSocket = true;
struct addrinfo *result, *rp, hints;
std::stringstream ss;
ss << port;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG;
int s = getaddrinfo(host.c_str(), ss.str().c_str(), &hints, &result);
if (s != 0){
lastErr = gai_strmagic(s);
FAIL_MSG("Could not connect to %s:%i! Error: %s", host.c_str(), port, lastErr.c_str());
close();
return;
}
lastErr = "";
for (rp = result; rp != NULL; rp = rp->ai_next){
sSend = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sSend < 0){continue;}
//Ensure we can handle interrupted system call case
int ret = 0;
do{
ret = connect(sSend, rp->ai_addr, rp->ai_addrlen);
}while(ret && errno == EINTR);
if (!ret){
memcpy(&remoteaddr, rp->ai_addr, rp->ai_addrlen);
break;
}
lastErr += strerror(errno);
::close(sSend);
}
freeaddrinfo(result);
if (rp == 0){
FAIL_MSG("Could not connect to %s! Error: %s", host.c_str(), lastErr.c_str());
close();
}else{
if (nonblock){
int flags = fcntl(sSend, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(sSend, F_SETFL, flags);
}
int optval = 1;
int optlen = sizeof(optval);
setsockopt(sSend, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen);
setBoundAddr();
}
}
/// Returns the connected-state for this socket.
/// Note that this function might be slightly behind the real situation.
/// The connection status is updated after every read/write attempt, when errors occur
/// and when the socket is closed manually.
/// \returns True if socket is connected, false otherwise.
bool Socket::Connection::connected() const{
#ifdef SSL
if (sslConnected){return true;}
#endif
return (sSend >= 0) || (sRecv >= 0);
}
/// Returns the time this socket has been connected.
unsigned int Socket::Connection::connTime(){
return conntime;
}
/// Returns total amount of bytes sent.
uint64_t Socket::Connection::dataUp(){
return up;
}
/// Returns total amount of bytes received.
uint64_t Socket::Connection::dataDown(){
return down;
}
/// Updates the downbuffer internal variable.
/// Returns true if new data was received, false otherwise.
bool Socket::Connection::spool(bool strictMode){
/// \todo Provide better mechanism to prevent overbuffering.
if (!strictMode && downbuffer.size() > 10000){
return true;
}else{
return iread(downbuffer);
}
}
bool Socket::Connection::peek(){
/// clear buffer
downbuffer.clear();
return iread(downbuffer, MSG_PEEK);
}
/// Returns a reference to the download buffer.
Socket::Buffer &Socket::Connection::Received(){
return downbuffer;
}
/// Returns a reference to the download buffer.
const Socket::Buffer &Socket::Connection::Received() const{
return downbuffer;
}
/// Will not buffer anything but always send right away. Blocks.
/// Any data that could not be send will block until it can be send or the connection is severed.
void Socket::Connection::SendNow(const char *data, size_t len){
bool bing = isBlocking();
if (!bing){setBlocking(true);}
unsigned int i = iwrite(data, std::min((long unsigned int)len, SOCKETSIZE));
while (i < len && connected()){
i += iwrite(data + i, std::min((long unsigned int)(len - i), SOCKETSIZE));
}
if (!bing){setBlocking(false);}
}
/// Will not buffer anything but always send right away. Blocks.
/// Any data that could not be send will block until it can be send or the connection is severed.
void Socket::Connection::SendNow(const char *data){
int len = strlen(data);
SendNow(data, len);
}
/// Will not buffer anything but always send right away. Blocks.
/// Any data that could not be send will block until it can be send or the connection is severed.
void Socket::Connection::SendNow(const std::string &data){
SendNow(data.data(), data.size());
}
void Socket::Connection::skipBytes(uint32_t byteCount){
INFO_MSG("Skipping first %" PRIu32 " bytes going to socket", byteCount);
skipCount = byteCount;
}
/// Incremental write call. This function tries to write len bytes to the socket from the buffer,
/// returning the amount of bytes it actually wrote.
/// \param buffer Location of the buffer to write from.
/// \param len Amount of bytes to write.
/// \returns The amount of bytes actually written.
unsigned int Socket::Connection::iwrite(const void *buffer, int len){
#ifdef SSL
if (sslConnected){
DONTEVEN_MSG("SSL iwrite");
if (!connected() || len < 1){return 0;}
int r;
r = mbedtls_ssl_write(ssl, (const unsigned char *)buffer, len);
if (r < 0){
switch (errno){
case MBEDTLS_ERR_SSL_WANT_WRITE: return 0; break;
case MBEDTLS_ERR_SSL_WANT_READ: return 0; break;
case EWOULDBLOCK: return 0; break;
case EINTR: return 0; break;
default:
Error = true;
lastErr = strerror(errno);
INSANE_MSG("Could not iwrite data! Error: %s", lastErr.c_str());
close();
return 0;
break;
}
}
if (r == 0 && (sSend >= 0)){
DONTEVEN_MSG("Socket closed by remote");
close();
}
up += r;
return r;
}
#endif
if (!connected() || len < 1){return 0;}
if (skipCount){
// We have bytes to skip writing.
// Pretend we write them, but don't really.
if (len <= skipCount){
skipCount -= len;
return len;
}else{
unsigned int toSkip = skipCount;
skipCount = 0;
return iwrite((((char *)buffer) + toSkip), len - toSkip) + toSkip;
}
}
int r;
if (isTrueSocket){
r = send(sSend, buffer, len, 0);
}else{
r = write(sSend, buffer, len);
}
if (r < 0){
switch (errno){
case EWOULDBLOCK: return 0; break;
case EINTR: return 0; break;
default:
Error = true;
lastErr = strerror(errno);
INSANE_MSG("Could not iwrite data! Error: %s", lastErr.c_str());
close();
return 0;
break;
}
}
if (r == 0 && (sSend >= 0)){
DONTEVEN_MSG("Socket closed by remote");
close();
}
up += r;
return r;
}// Socket::Connection::iwrite
/// Incremental read call. This function tries to read len bytes to the buffer from the socket,
/// returning the amount of bytes it actually read.
/// \param buffer Location of the buffer to read to.
/// \param len Amount of bytes to read.
/// \param flags Flags to use in the recv call. Ignored on fake sockets.
/// \returns The amount of bytes actually read.
int Socket::Connection::iread(void *buffer, int len, int flags){
#ifdef SSL
if (sslConnected){
DONTEVEN_MSG("SSL iread");
if (!connected() || len < 1){return 0;}
int r;
/// \TODO Flags ignored... Bad.
r = mbedtls_ssl_read(ssl, (unsigned char *)buffer, len);
if (r < 0){
switch (errno){
case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY:
close();
return 0;
break;
case MBEDTLS_ERR_SSL_WANT_WRITE: return 0; break;
case MBEDTLS_ERR_SSL_WANT_READ: return 0; break;
case EWOULDBLOCK: return 0; break;
case EINTR: return 0; break;
default:
Error = true;
char estr[200];
mbedtls_strerror(r, estr, 200);
lastErr = estr;
INFO_MSG("Read returns %d: %s (%s)", r, estr, lastErr.c_str());
close();
return 0;
break;
}
}
if (r == 0){
DONTEVEN_MSG("Socket closed by remote");
close();
}
down += r;
return r;
}
#endif
if (!connected() || len < 1){return 0;}
int r;
if (sRecv != -1 || !isTrueSocket){
r = read(sRecv, buffer, len);
}else{
r = recv(sSend, buffer, len, flags);
}
if (r < 0){
switch (errno){
case EWOULDBLOCK: return 0; break;
case EINTR: return 0; break;
default:
Error = true;
lastErr = strerror(errno);
INSANE_MSG("Could not iread data! Error: %s", lastErr.c_str());
close();
return 0;
break;
}
}
if (r == 0){
DONTEVEN_MSG("Socket closed by remote");
close();
}
down += r;
return r;
}// Socket::Connection::iread
/// Read call that is compatible with Socket::Buffer.
/// Data is read using iread (which is nonblocking if the Socket::Connection itself is),
/// then appended to end of buffer.
/// \param buffer Socket::Buffer to append data to.
/// \param flags Flags to use in the recv call. Ignored on fake sockets.
/// \return True if new data arrived, false otherwise.
bool Socket::Connection::iread(Buffer &buffer, int flags){
char cbuffer[BUFFER_BLOCKSIZE];
int num = iread(cbuffer, BUFFER_BLOCKSIZE, flags);
if (num < 1){return false;}
buffer.append(cbuffer, num);
return true;
}// iread
/// Incremental write call that is compatible with std::string.
/// Data is written using iwrite (which is nonblocking if the Socket::Connection itself is),
/// then removed from front of buffer.
/// \param buffer std::string to remove data from.
/// \return True if more data was sent, false otherwise.
bool Socket::Connection::iwrite(std::string &buffer){
if (buffer.size() < 1){return false;}
unsigned int tmp = iwrite((void *)buffer.c_str(), buffer.size());
if (!tmp){return false;}
buffer = buffer.substr(tmp);
return true;
}// iwrite
/// Gets hostname for connection, if available.
std::string Socket::Connection::getHost() const{
return remotehost;
}
/// Gets locally bound host for connection, if available.
std::string Socket::Connection::getBoundAddress() const{
return boundaddr;
}
/// Gets binary IPv6 address for connection, if available.
/// Guaranteed to be either empty or 16 bytes long.
std::string Socket::Connection::getBinHost(){
return getIPv6BinAddr(remoteaddr);
}
/// Sets hostname for connection manually.
/// Overwrites the detected host, thus possibily making it incorrect.
void Socket::Connection::setHost(std::string host){
remotehost = host;
struct addrinfo *result, hints;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED;
hints.ai_protocol = 0;
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
int s = getaddrinfo(host.c_str(), 0, &hints, &result);
if (s != 0){return;}
if (result){memcpy(&remoteaddr, result->ai_addr, result->ai_addrlen);}
freeaddrinfo(result);
}
/// Returns true if these sockets are the same socket.
/// Does not check the internal stats - only the socket itself.
bool Socket::Connection::operator==(const Connection &B) const{
return sSend == B.sSend && sRecv == B.sRecv;
}
/// Returns true if these sockets are not the same socket.
/// Does not check the internal stats - only the socket itself.
bool Socket::Connection::operator!=(const Connection &B) const{
return sSend != B.sSend || sRecv != B.sRecv;
}
/// Returns true if the socket is valid.
/// Aliases for Socket::Connection::connected()
Socket::Connection::operator bool() const{
return connected();
}
// Copy constructor
Socket::Connection::Connection(const Connection &rhs){
clear();
if (!rhs){return;}
#if DEBUG >= DLVL_DEVEL
#ifdef SSL
HIGH_MSG("Copying %s socket", rhs.sslConnected ? "SSL" : "regular");
#else
HIGH_MSG("Copying regular socket");
#endif
#endif
conntime = rhs.conntime;
isTrueSocket = rhs.isTrueSocket;
remotehost = rhs.remotehost;
boundaddr = rhs.boundaddr;
remoteaddr = rhs.remoteaddr;
lastErr = rhs.lastErr;
up = rhs.up;
down = rhs.down;
downbuffer = rhs.downbuffer;
#ifdef SSL
if (!rhs.sslConnected){
#endif
if (rhs.sSend >= 0){sSend = dup(rhs.sSend);}
if (rhs.sRecv >= 0){sRecv = dup(rhs.sRecv);}
#if DEBUG >= DLVL_DEVEL
HIGH_MSG("Socket original = (%d / %d), copy = (%d / %d)", rhs.sSend, rhs.sRecv, sSend, sRecv);
#endif
#ifdef SSL
}
#endif
}
// Assignment constructor
Socket::Connection &Socket::Connection::operator=(const Socket::Connection &rhs){
drop();
clear();
if (!rhs){return *this;}
#if DEBUG >= DLVL_DEVEL
#ifdef SSL
HIGH_MSG("Assigning %s socket", rhs.sslConnected ? "SSL" : "regular");
#else
HIGH_MSG("Assigning regular socket");
#endif
#endif
conntime = rhs.conntime;
isTrueSocket = rhs.isTrueSocket;
remotehost = rhs.remotehost;
boundaddr = rhs.boundaddr;
remoteaddr = rhs.remoteaddr;
lastErr = rhs.lastErr;
up = rhs.up;
down = rhs.down;
downbuffer = rhs.downbuffer;
#ifdef SSL
if (!rhs.sslConnected){
#endif
if (rhs.sSend >= 0){sSend = dup(rhs.sSend);}
if (rhs.sRecv >= 0){sRecv = dup(rhs.sRecv);}
#if DEBUG >= DLVL_DEVEL
HIGH_MSG("Socket original = (%d / %d), copy = (%d / %d)", rhs.sSend, rhs.sRecv, sSend, sRecv);
#endif
#ifdef SSL
}
#endif
return *this;
}
/// Returns true if the given address can be matched with the remote host.
/// Can no longer return true after any socket error have occurred.
bool Socket::Connection::isAddress(const std::string &addr){
// Retrieve current socket binary address
std::string myBinAddr = getBinHost();
return isBinAddress(myBinAddr, addr);
}
bool Socket::Connection::isLocal(){
return Socket::isLocal(remotehost);
}
/// Create a new base Server. The socket is never connected, and a placeholder for later
/// connections.
Socket::Server::Server(){
sock = -1;
}// Socket::Server base Constructor
/// Create a new Server from existing socket.
Socket::Server::Server(int fromSock){
sock = fromSock;
}
/// Create a new TCP Server. The socket is immediately bound and set to listen.
/// A maximum of 100 connections will be accepted between accept() calls.
/// Any further connections coming in will be dropped.
/// \param port The TCP port to listen on
/// \param hostname (optional) The interface to bind to. The default is 0.0.0.0 (all interfaces).
/// \param nonblock (optional) Whether accept() calls will be nonblocking. Default is false
/// (blocking).
Socket::Server::Server(int port, std::string hostname, bool nonblock){
if (!IPv6bind(port, hostname, nonblock) && !IPv4bind(port, hostname, nonblock)){
FAIL_MSG("Could not create socket %s:%i! Error: %s", hostname.c_str(), port, errors.c_str());
sock = -1;
}
}// Socket::Server TCP Constructor
/// Attempt to bind an IPv6 socket.
/// \param port The TCP port to listen on
/// \param hostname The interface to bind to. The default is 0.0.0.0 (all interfaces).
/// \param nonblock Whether accept() calls will be nonblocking. Default is false (blocking).
/// \return True if successful, false otherwise.
bool Socket::Server::IPv6bind(int port, std::string hostname, bool nonblock){
sock = socket(AF_INET6, SOCK_STREAM, 0);
if (sock < 0){
errors = strerror(errno);
ERROR_MSG("Could not create IPv6 socket %s:%i! Error: %s", hostname.c_str(), port, errors.c_str());
return false;
}
int on = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
#ifdef __CYGWIN__
on = 0;
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
#endif
if (nonblock){
int flags = fcntl(sock, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(sock, F_SETFL, flags);
}
struct sockaddr_in6 addr;
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(port); // set port
// set interface, 0.0.0.0 (default) is all
if (hostname == "0.0.0.0" || hostname.length() == 0){
addr.sin6_addr = in6addr_any;
}else{
if (inet_pton(AF_INET6, hostname.c_str(), &addr.sin6_addr) != 1){
errors = strerror(errno);
HIGH_MSG("Could not convert '%s' to a valid IPv6 address", hostname.c_str());
close();
return false;
}
}
int ret = bind(sock, (sockaddr *)&addr, sizeof(addr)); // do the actual bind
if (ret == 0){
ret = listen(sock, 100); // start listening, backlog of 100 allowed
if (ret == 0){
DEVEL_MSG("IPv6 socket success @ %s:%i", hostname.c_str(), port);
return true;
}else{
errors = strerror(errno);
ERROR_MSG("IPv6 listen failed! Error: %s", errors.c_str());
close();
return false;
}
}else{
errors = strerror(errno);
ERROR_MSG("IPv6 Binding %s:%i failed (%s)", hostname.c_str(), port, errors.c_str());
close();
return false;
}
}
/// Attempt to bind an IPv4 socket.
/// \param port The TCP port to listen on
/// \param hostname The interface to bind to. The default is 0.0.0.0 (all interfaces).
/// \param nonblock Whether accept() calls will be nonblocking. Default is false (blocking).
/// \return True if successful, false otherwise.
bool Socket::Server::IPv4bind(int port, std::string hostname, bool nonblock){
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0){
errors = strerror(errno);
ERROR_MSG("Could not create IPv4 socket %s:%i! Error: %s", hostname.c_str(), port, errors.c_str());
return false;
}
int on = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (nonblock){
int flags = fcntl(sock, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(sock, F_SETFL, flags);
}
struct sockaddr_in addr4;
memset(&addr4, 0, sizeof(addr4));
addr4.sin_family = AF_INET;
addr4.sin_port = htons(port); // set port
// set interface, 0.0.0.0 (default) is all
if (hostname == "0.0.0.0" || hostname.length() == 0){
addr4.sin_addr.s_addr = INADDR_ANY;
}else{
if (inet_pton(AF_INET, hostname.c_str(), &addr4.sin_addr) != 1){
errors = strerror(errno);
HIGH_MSG("Could not convert '%s' to a valid IPv4 address", hostname.c_str());
close();
return false;
}
}
int ret = bind(sock, (sockaddr *)&addr4, sizeof(addr4)); // do the actual bind
if (ret == 0){
ret = listen(sock, 100); // start listening, backlog of 100 allowed
if (ret == 0){
DEVEL_MSG("IPv4 socket success @ %s:%i", hostname.c_str(), port);
return true;
}else{
errors = strerror(errno);
ERROR_MSG("IPv4 listen failed! Error: %s", errors.c_str());
close();
return false;
}
}else{
errors = strerror(errno);
ERROR_MSG("IPv4 Binding %s:%i failed (%s)", hostname.c_str(), port, errors.c_str());
close();
return false;
}
}
/// Create a new Unix Server. The socket is immediately bound and set to listen.
/// A maximum of 100 connections will be accepted between accept() calls.
/// Any further connections coming in will be dropped.
/// The address used will first be unlinked - so it succeeds if the Unix socket already existed.
/// Watch out for this behaviour - it will delete any file located at address! \param address The
/// location of the Unix socket to bind to. \param nonblock (optional) Whether accept() calls will
/// be nonblocking. Default is false (blocking).
Socket::Server::Server(std::string address, bool nonblock){
unlink(address.c_str());
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0){
errors = strerror(errno);
ERROR_MSG("Could not create unix socket %s! Error: %s", address.c_str(), errors.c_str());
return;
}
if (nonblock){
int flags = fcntl(sock, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(sock, F_SETFL, flags);
}
sockaddr_un addr;
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, address.c_str(), address.size() + 1);
int ret = bind(sock, (sockaddr *)&addr, sizeof(addr));
if (ret == 0){
ret = listen(sock, 100); // start listening, backlog of 100 allowed
if (ret == 0){
return;
}else{
errors = strerror(errno);
ERROR_MSG("Unix listen failed! Error: %s", errors.c_str());
close();
return;
}
}else{
errors = strerror(errno);
ERROR_MSG("Unix Binding %s failed (%s)", address.c_str(), errors.c_str());
close();
return;
}
}// Socket::Server Unix Constructor
/// Accept any waiting connections. If the Socket::Server is blocking, this function will block
/// until there is an incoming connection. If the Socket::Server is nonblocking, it might return a
/// Socket::Connection that is not connected, so check for this. \param nonblock (optional) Whether
/// the newly connected socket should be nonblocking. Default is false (blocking). \returns A
/// Socket::Connection, which may or may not be connected, depending on settings and circumstances.
Socket::Connection Socket::Server::accept(bool nonblock){
if (sock < 0){return Socket::Connection(-1);}
struct sockaddr_in6 tmpaddr;
socklen_t len = sizeof(tmpaddr);
int r = ::accept(sock, (sockaddr *)&tmpaddr, &len);
// set the socket to be nonblocking, if requested.
// we could do this through accept4 with a flag, but that call is non-standard...
if (r < 0){
if ((errno != EWOULDBLOCK) && (errno != EAGAIN) && (errno != EINTR)){
if (errno != EINVAL){
FAIL_MSG("Error during accept: %s. Closing server socket %d.", strerror(errno), sock);
}
close();
}
return Socket::Connection();
}
if (nonblock){
int flags = fcntl(r, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(r, F_SETFL, flags);
}
int optval = 1;
int optlen = sizeof(optval);
setsockopt(r, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen);
return Socket::Connection(r);
}
/// Set this socket to be blocking (true) or nonblocking (false).
void Socket::Server::setBlocking(bool blocking){
if (sock >= 0){setFDBlocking(sock, blocking);}
}
/// Set this socket to be blocking (true) or nonblocking (false).
bool Socket::Server::isBlocking(){
if (sock >= 0){return isFDBlocking(sock);}
return false;
}
/// Close connection. The internal socket is closed and then set to -1.
/// If the connection is already closed, nothing happens.
/// This function calls shutdown, thus making the socket unusable in all other
/// processes as well. Do not use on shared sockets that are still in use.
void Socket::Server::close(){
if (sock != -1){shutdown(sock, SHUT_RDWR);}
drop();
}// Socket::Server::close
/// Close connection. The internal socket is closed and then set to -1.
/// If the connection is already closed, nothing happens.
/// This function does *not* call shutdown, allowing continued use in other
/// processes.
void Socket::Server::drop(){
if (connected()){
if (sock != -1){
HIGH_MSG("ServerSocket %d closed", sock);
errno = EINTR;
while (::close(sock) != 0 && errno == EINTR){}
sock = -1;
}
}
}// Socket::Server::drop
/// Returns the connected-state for this socket.
/// Note that this function might be slightly behind the real situation.
/// The connection status is updated after every accept attempt, when errors occur
/// and when the socket is closed manually.
/// \returns True if socket is connected, false otherwise.
bool Socket::Server::connected() const{
return (sock >= 0);
}// Socket::Server::connected
/// Returns internal socket number.
int Socket::Server::getSocket(){
return sock;
}
/// Create a new UDP Socket.
/// Will attempt to create an IPv6 UDP socket, on fail try a IPV4 UDP socket.
/// If both fail, prints an DLVL_FAIL debug message.
/// \param nonblock Whether the socket should be nonblocking.
Socket::UDPConnection::UDPConnection(bool nonblock){
boundPort = 0;
family = AF_INET6;
sock = socket(AF_INET6, SOCK_DGRAM, 0);
if (sock == -1){
sock = socket(AF_INET, SOCK_DGRAM, 0);
family = AF_INET;
}
if (sock == -1){
FAIL_MSG("Could not create UDP socket: %s", strerror(errno));
}else{
if (nonblock){setBlocking(!nonblock);}
checkRecvBuf();
}
up = 0;
down = 0;
destAddr = 0;
destAddr_size = 0;
#ifdef __CYGWIN__
data.allocate(SOCKETSIZE);
#else
data.allocate(2048);
#endif
}// Socket::UDPConnection UDP Contructor
///Checks if the UDP receive buffer is at least 1 mbyte, attempts to increase and warns user through log message on failure.
void Socket::UDPConnection::checkRecvBuf(){
if (sock == -1){return;}
int recvbuf = 0;
int origbuf = 0;
socklen_t slen = sizeof(recvbuf);
getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&recvbuf, &slen);
origbuf = recvbuf;
if (recvbuf < 1024*1024){
recvbuf = 1024*1024;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&recvbuf, sizeof(recvbuf));
slen = sizeof(recvbuf);
getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&recvbuf, &slen);
#ifndef __CYGWIN__
if (recvbuf < 1024*1024){
recvbuf = 1024*1024;
setsockopt(sock, SOL_SOCKET, SO_RCVBUFFORCE, (void*)&recvbuf, sizeof(recvbuf));
slen = sizeof(recvbuf);
getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&recvbuf, &slen);
}
#endif
if (recvbuf < 200*1024){
recvbuf = 200*1024;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&recvbuf, sizeof(recvbuf));
slen = sizeof(recvbuf);
getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&recvbuf, &slen);
#ifndef __CYGWIN__
if (recvbuf < 200*1024){
recvbuf = 200*1024;
setsockopt(sock, SOL_SOCKET, SO_RCVBUFFORCE, (void*)&recvbuf, sizeof(recvbuf));
slen = sizeof(recvbuf);
getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&recvbuf, &slen);
}
#endif
}
if (recvbuf < 200*1024){
WARN_MSG("Your UDP receive buffer is set < 200 kbyte (%db) and the kernel denied our request for an increase. It's recommended to set your net.core.rmem_max setting to at least 200 kbyte for best results.", origbuf);
}
}
}
/// Copies a UDP socket, re-allocating local copies of any needed structures.
/// The data/data_size/data_len variables are *not* copied over.
Socket::UDPConnection::UDPConnection(const UDPConnection &o){
boundPort = 0;
family = AF_INET6;
sock = socket(AF_INET6, SOCK_DGRAM, 0);
if (sock == -1){
sock = socket(AF_INET, SOCK_DGRAM, 0);
family = AF_INET;
}
if (sock == -1){FAIL_MSG("Could not create UDP socket: %s", strerror(errno));}
checkRecvBuf();
up = 0;
down = 0;
if (o.destAddr && o.destAddr_size){
destAddr = malloc(o.destAddr_size);
destAddr_size = o.destAddr_size;
if (destAddr){memcpy(destAddr, o.destAddr, o.destAddr_size);}
}else{
destAddr = 0;
destAddr_size = 0;
}
data.allocate(2048);
}
/// Close the UDP socket
void Socket::UDPConnection::close(){
if (sock != -1){
errno = EINTR;
while (::close(sock) != 0 && errno == EINTR){}
sock = -1;
}
}
/// Closes the UDP socket, cleans up any memory allocated by the socket.
Socket::UDPConnection::~UDPConnection(){
close();
if (destAddr){
free(destAddr);
destAddr = 0;
}
}
// Sets socket family type (to IPV4 or IPV6) (AF_INET=2, AF_INET6=10)
void Socket::UDPConnection::setSocketFamily(int AF_TYPE){\
INFO_MSG("Switching UDP socket from %s to %s", addrFam(family), addrFam(AF_TYPE));
family = AF_TYPE;
}
/// Stores the properties of the receiving end of this UDP socket.
/// This will be the receiving end for all SendNow calls.
void Socket::UDPConnection::SetDestination(std::string destIp, uint32_t port){
DONTEVEN_MSG("Setting destination to %s:%u", destIp.c_str(), port);
// UDP sockets can switch between IPv4 and IPv6 on demand.
// We change IPv4-mapped IPv6 addresses into IPv4 addresses for Windows-sillyness reasons.
if (destIp.substr(0, 7) == "::ffff:"){destIp = destIp.substr(7);}
struct addrinfo *result, *rp, hints;
std::stringstream ss;
ss << port;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = family;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_ADDRCONFIG | AI_ALL;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
int s = getaddrinfo(destIp.c_str(), ss.str().c_str(), &hints, &result);
if (s != 0){
hints.ai_family = AF_UNSPEC;
s = getaddrinfo(destIp.c_str(), ss.str().c_str(), &hints, &result);
if (s != 0){
FAIL_MSG("Could not connect UDP socket to %s:%i! Error: %s", destIp.c_str(), port, gai_strmagic(s));
return;
}
}
for (rp = result; rp != NULL; rp = rp->ai_next){
// assume success
if (destAddr){
free(destAddr);
destAddr = 0;
}
destAddr_size = rp->ai_addrlen;
destAddr = malloc(destAddr_size);
if (!destAddr){return;}
memcpy(destAddr, rp->ai_addr, rp->ai_addrlen);
if (family != rp->ai_family){
INFO_MSG("Switching UDP socket from %s to %s", addrFam(family), addrFam(rp->ai_family));
close();
family = rp->ai_family;
sock = socket(family, SOCK_DGRAM, 0);
checkRecvBuf();
if (boundPort){
INFO_MSG("Rebinding to %s:%d %s", boundAddr.c_str(), boundPort, boundMulti.c_str());
bind(boundPort, boundAddr, boundMulti);
}
}
{
std::string trueDest;
uint32_t truePort;
GetDestination(trueDest, truePort);
HIGH_MSG("Set UDP destination: %s:%d => %s:%d (%s)", destIp.c_str(), port, trueDest.c_str(), truePort, addrFam(family));
}
freeaddrinfo(result);
return;
//\todo Possibly detect and handle failure
}
freeaddrinfo(result);
free(destAddr);
destAddr = 0;
FAIL_MSG("Could not set destination for UDP socket: %s:%d", destIp.c_str(), port);
}// Socket::UDPConnection SetDestination
/// Gets the properties of the receiving end of this UDP socket.
/// This will be the receiving end for all SendNow calls.
void Socket::UDPConnection::GetDestination(std::string &destIp, uint32_t &port){
if (!destAddr || !destAddr_size){
destIp = "";
port = 0;
return;
}
char addr_str[INET6_ADDRSTRLEN + 1];
addr_str[INET6_ADDRSTRLEN] = 0; // set last byte to zero, to prevent walking out of the array
if (((struct sockaddr_in *)destAddr)->sin_family == AF_INET6){
if (inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)destAddr)->sin6_addr), addr_str, INET6_ADDRSTRLEN) != 0){
destIp = addr_str;
port = ntohs(((struct sockaddr_in6 *)destAddr)->sin6_port);
return;
}
}
if (((struct sockaddr_in *)destAddr)->sin_family == AF_INET){
if (inet_ntop(AF_INET, &(((struct sockaddr_in *)destAddr)->sin_addr), addr_str, INET6_ADDRSTRLEN) != 0){
destIp = addr_str;
port = ntohs(((struct sockaddr_in *)destAddr)->sin_port);
return;
}
}
destIp = "";
port = 0;
FAIL_MSG("Could not get destination for UDP socket");
}// Socket::UDPConnection GetDestination
/// Gets the properties of the receiving end of this UDP socket.
/// This will be the receiving end for all SendNow calls.
std::string Socket::UDPConnection::getBinDestination(){
std::string binList = getIPv6BinAddr(*(sockaddr_in6*)destAddr);
if (binList.size() < 16){ return std::string("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", 16); }
return binList.substr(0, 16);
}// Socket::UDPConnection GetDestination
/// Returns the port number of the receiving end of this socket.
/// Returns 0 on error.
uint32_t Socket::UDPConnection::getDestPort() const{
if (!destAddr || !destAddr_size){return 0;}
if (((struct sockaddr_in *)destAddr)->sin_family == AF_INET6){
return ntohs(((struct sockaddr_in6 *)destAddr)->sin6_port);
}
if (((struct sockaddr_in *)destAddr)->sin_family == AF_INET){
return ntohs(((struct sockaddr_in *)destAddr)->sin_port);
}
return 0;
}
/// Sets the socket to be blocking if the parameters is true.
/// Sets the socket to be non-blocking otherwise.
void Socket::UDPConnection::setBlocking(bool blocking){
if (sock >= 0){setFDBlocking(sock, blocking);}
}
/// Sends a UDP datagram using the buffer sdata.
/// This function simply calls SendNow(const char*, size_t)
void Socket::UDPConnection::SendNow(const std::string &sdata){
SendNow(sdata.c_str(), sdata.size());
}
/// Sends a UDP datagram using the buffer sdata.
/// sdata is required to be NULL-terminated.
/// This function simply calls SendNow(const char*, size_t)
void Socket::UDPConnection::SendNow(const char *sdata){
int len = strlen(sdata);
SendNow(sdata, len);
}
/// Sends a UDP datagram using the buffer sdata of length len.
/// Does not do anything if len < 1.
/// Prints an DLVL_FAIL level debug message if sending failed.
void Socket::UDPConnection::SendNow(const char *sdata, size_t len){
if (len < 1){return;}
int r = sendto(sock, sdata, len, 0, (sockaddr *)destAddr, destAddr_size);
if (r > 0){
up += r;
}else{
FAIL_MSG("Could not send UDP data through %d: %s", sock, strerror(errno));
}
}
std::string Socket::UDPConnection::getBoundAddress(){
std::string boundaddr;
uint32_t boundport;
Socket::getSocketName(sock, boundaddr, boundport);
return boundaddr;
}
/// Bind to a port number, returning the bound port.
/// If that fails, returns zero.
/// \arg port Port to bind to, required.
/// \arg iface Interface address to listen for packets on (may be multicast address)
/// \arg multicastInterfaces Comma-separated list of interfaces to listen on for multicast packets.
/// Optional, left out means automatically chosen by kernel. \return Actually bound port number, or
/// zero on error.
uint16_t Socket::UDPConnection::bind(int port, std::string iface, const std::string &multicastInterfaces){
close(); // we open a new socket for each attempt
int addr_ret;
bool multicast = false;
struct addrinfo hints, *addr_result, *rp;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE | AI_V4MAPPED;
if (destAddr && destAddr_size){
hints.ai_family = ((struct sockaddr_in *)destAddr)->sin_family;
}else{
hints.ai_family = AF_UNSPEC;
}
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
std::stringstream ss;
ss << port;
if (iface == "0.0.0.0" || iface.length() == 0){
if ((addr_ret = getaddrinfo(0, ss.str().c_str(), &hints, &addr_result)) != 0){
FAIL_MSG("Could not resolve %s for UDP: %s", iface.c_str(), gai_strmagic(addr_ret));
return 0;
}
}else{
if ((addr_ret = getaddrinfo(iface.c_str(), ss.str().c_str(), &hints, &addr_result)) != 0){
FAIL_MSG("Could not resolve %s for UDP: %s", iface.c_str(), gai_strmagic(addr_ret));
return 0;
}
}
std::string err_str;
uint16_t portNo = 0;
for (rp = addr_result; rp != NULL; rp = rp->ai_next){
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sock == -1){continue;}
if (rp->ai_family == AF_INET6){
const int optval = 0;
if (setsockopt(sock, SOL_SOCKET, IPV6_V6ONLY, &optval, sizeof(optval)) < 0){
WARN_MSG("Could not set IPv6 UDP socket to be dual-stack! %s", strerror(errno));
}
}
checkRecvBuf();
char human_addr[INET6_ADDRSTRLEN];
char human_port[16];
getnameinfo(rp->ai_addr, rp->ai_addrlen, human_addr, INET6_ADDRSTRLEN, human_port, 16,
NI_NUMERICHOST | NI_NUMERICSERV);
MEDIUM_MSG("Attempting bind to %s:%s (%s)", human_addr, human_port, addrFam(rp->ai_family));
family = rp->ai_family;
hints.ai_family = family;
if (family == AF_INET6){
sockaddr_in6 *addr6 = (sockaddr_in6 *)(rp->ai_addr);
if (memcmp((char *)&(addr6->sin6_addr), "\000\000\000\000\000\000\000\000\000\000\377\377", 12) == 0){
// IPv6-mapped IPv4 address - 13th byte ([12]) holds the first IPv4 byte
multicast = (((char *)&(addr6->sin6_addr))[12] & 0xF0) == 0xE0;
}else{
//"normal" IPv6 address - prefix ff00::/8
multicast = (((char *)&(addr6->sin6_addr))[0] == 0xFF);
}
}else{
sockaddr_in *addr4 = (sockaddr_in *)(rp->ai_addr);
// multicast has a "1110" bit prefix
multicast = (((char *)&(addr4->sin_addr))[0] & 0xF0) == 0xE0;
#ifdef __CYGWIN__
if (multicast){((sockaddr_in *)rp->ai_addr)->sin_addr.s_addr = htonl(INADDR_ANY);}
#endif
}
if (multicast){
const int optval = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0){
WARN_MSG("Could not set multicast UDP socket re-use! %s", strerror(errno));
}
}
if (::bind(sock, rp->ai_addr, rp->ai_addrlen) == 0){
// get port number
struct sockaddr_storage fin_addr;
socklen_t alen = sizeof(fin_addr);
if (getsockname(sock, (struct sockaddr *)&fin_addr, &alen) == 0){
if (family == AF_INET6){
portNo = ntohs(((struct sockaddr_in6 *)&fin_addr)->sin6_port);
}else{
portNo = ntohs(((struct sockaddr_in *)&fin_addr)->sin_port);
}
}
boundAddr = iface;
boundMulti = multicastInterfaces;
boundPort = portNo;
INFO_MSG("UDP bind success on %s:%u (%s)", human_addr, portNo, addrFam(rp->ai_family));
break;
}
if (err_str.size()){err_str += ", ";}
err_str += human_addr;
err_str += ":";
err_str += strerror(errno);
close(); // we open a new socket for each attempt
}
freeaddrinfo(addr_result);
if (sock == -1){
FAIL_MSG("Could not open %s for UDP: %s", iface.c_str(), err_str.c_str());
return 0;
}
// handle multicast membership(s)
if (multicast){
struct ipv6_mreq mreq6;
struct ip_mreq mreq4;
memset(&mreq4, 0, sizeof(mreq4));
memset(&mreq6, 0, sizeof(mreq6));
struct addrinfo *reslocal, *resmulti;
if ((addr_ret = getaddrinfo(iface.c_str(), 0, &hints, &resmulti)) != 0){
WARN_MSG("Unable to parse multicast address: %s", gai_strmagic(addr_ret));
close();
return 0;
}
if (!multicastInterfaces.length()){
if (family == AF_INET6){
memcpy(&mreq6.ipv6mr_multiaddr, &((sockaddr_in6 *)resmulti->ai_addr)->sin6_addr,
sizeof(mreq6.ipv6mr_multiaddr));
if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&mreq6, sizeof(mreq6)) != 0){
FAIL_MSG("Unable to register for IPv6 multicast on all interfaces: %s", strerror(errno));
close();
}
}else{
mreq4.imr_multiaddr = ((sockaddr_in *)resmulti->ai_addr)->sin_addr;
mreq4.imr_interface.s_addr = INADDR_ANY;
if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq4, sizeof(mreq4)) != 0){
FAIL_MSG("Unable to register for IPv4 multicast on all interfaces: %s", strerror(errno));
close();
}
}
}else{
size_t nxtPos = std::string::npos;
bool atLeastOne = false;
for (size_t loc = 0; loc != std::string::npos; loc = (nxtPos == std::string::npos ? nxtPos : nxtPos + 1)){
nxtPos = multicastInterfaces.find(',', loc);
std::string curIface =
multicastInterfaces.substr(loc, (nxtPos == std::string::npos ? nxtPos : nxtPos - loc));
// do a bit of filtering for IPv6, removing the []-braces, if any
if (curIface[0] == '['){
if (curIface[curIface.size() - 1] == ']'){
curIface = curIface.substr(1, curIface.size() - 2);
}else{
curIface = curIface.substr(1, curIface.size() - 1);
}
}
if (family == AF_INET6){
INFO_MSG("Registering for IPv6 multicast on interface %s", curIface.c_str());
if ((addr_ret = getaddrinfo(curIface.c_str(), 0, &hints, &reslocal)) != 0){
WARN_MSG("Unable to resolve IPv6 interface address %s: %s", curIface.c_str(), gai_strmagic(addr_ret));
continue;
}
memcpy(&mreq6.ipv6mr_multiaddr, &((sockaddr_in6 *)resmulti->ai_addr)->sin6_addr,
sizeof(mreq6.ipv6mr_multiaddr));
mreq6.ipv6mr_interface = ((sockaddr_in6 *)reslocal->ai_addr)->sin6_scope_id;
if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&mreq6, sizeof(mreq6)) != 0){
FAIL_MSG("Unable to register for IPv6 multicast on interface %s (%u): %s", curIface.c_str(),
((sockaddr_in6 *)reslocal->ai_addr)->sin6_scope_id, strerror(errno));
}else{
atLeastOne = true;
}
}else{
INFO_MSG("Registering for IPv4 multicast on interface %s", curIface.c_str());
if ((addr_ret = getaddrinfo(curIface.c_str(), 0, &hints, &reslocal)) != 0){
WARN_MSG("Unable to resolve IPv4 interface address %s: %s", curIface.c_str(), gai_strmagic(addr_ret));
continue;
}
mreq4.imr_multiaddr = ((sockaddr_in *)resmulti->ai_addr)->sin_addr;
mreq4.imr_interface = ((sockaddr_in *)reslocal->ai_addr)->sin_addr;
if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq4, sizeof(mreq4)) != 0){
FAIL_MSG("Unable to register for IPv4 multicast on interface %s: %s", curIface.c_str(),
strerror(errno));
}else{
atLeastOne = true;
}
}
if (!atLeastOne){close();}
freeaddrinfo(reslocal); // free resolved interface addr
}// loop over all interfaces
}
freeaddrinfo(resmulti); // free resolved multicast addr
}
return portNo;
}
/// Attempt to receive a UDP packet.
/// This will automatically allocate or resize the internal data buffer if needed.
/// If a packet is received, it will be placed in the "data" member, with it's length in "data_len".
/// \return True if a packet was received, false otherwise.
bool Socket::UDPConnection::Receive(){
if (sock == -1){return false;}
data.truncate(0);
socklen_t destsize = destAddr_size;
int r = recvfrom(sock, data, data.rsize(), MSG_TRUNC | MSG_DONTWAIT, (sockaddr *)destAddr, &destsize);
if (r == -1){
if (errno != EAGAIN){INFO_MSG("UDP receive: %d (%s)", errno, strerror(errno));}
return false;
}
data.append(0, r);
down += r;
//Handle UDP packets that are too large
if (data.rsize() < (unsigned int)r){
INFO_MSG("Doubling UDP socket buffer from %" PRIu32 " to %" PRIu32, data.rsize(), data.rsize()*2);
data.allocate(data.rsize()*2);
}
return (r > 0);
}
int Socket::UDPConnection::getSock(){
return sock;
}