/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2013, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ #include "C4Include.h" #include "C4NetIO.h" #include "C4Constants.h" #include "C4Config.h" #include #include #include #include #include // platform specifics #ifdef _WIN32 #include #include typedef int socklen_t; int pipe(int *phandles) { return _pipe(phandles, 10, O_BINARY); } #else #include #include #include #include #include #include #define ioctlsocket ioctl #define closesocket close #define SOCKET_ERROR (-1) #endif #ifdef _MSC_VER #pragma warning (disable : 4355) #endif // constants definition const int C4NetIO::TO_INF = -1; const uint16_t C4NetIO::P_NONE = ~0; // simulate packet loss (loss probability in percent) // #define C4NETIO_SIMULATE_PACKETLOSS 10 // *** helpers #ifdef HAVE_WINSOCK const char *GetSocketErrorMsg(int iError) { switch (iError) { case WSAEACCES: return "Permission denied."; case WSAEADDRINUSE: return "Address already in use."; case WSAEADDRNOTAVAIL: return "Cannot assign requested address."; case WSAEAFNOSUPPORT: return "Address family not supported by protocol family."; case WSAEALREADY: return "Operation already in progress."; case WSAECONNABORTED: return "Software caused connection abort."; case WSAECONNREFUSED: return "Connection refused."; case WSAECONNRESET: return "Connection reset by peer."; case WSAEDESTADDRREQ: return "Destination address required."; case WSAEFAULT: return "Bad address."; case WSAEHOSTDOWN: return "Host is down."; case WSAEHOSTUNREACH: return "No route to host."; case WSAEINPROGRESS: return "Operation now in progress."; case WSAEINTR: return "Interrupted function call."; case WSAEINVAL: return "Invalid argument."; case WSAEISCONN: return "Socket is already connected."; case WSAEMFILE: return "Too many open files."; case WSAEMSGSIZE: return "Message too long."; case WSAENETDOWN: return "Network is down."; case WSAENETRESET: return "Network dropped connection on reset."; case WSAENETUNREACH: return "Network is unreachable."; case WSAENOBUFS: return "No buffer space available."; case WSAENOPROTOOPT: return "Bad protocol option."; case WSAENOTCONN: return "Socket is not connected."; case WSAENOTSOCK: return "Socket operation on non-socket."; case WSAEOPNOTSUPP: return "Operation not supported."; case WSAEPFNOSUPPORT: return "Protocol family not supported."; case WSAEPROCLIM: return "Too many processes."; case WSAEPROTONOSUPPORT: return "Protocol not supported."; case WSAEPROTOTYPE: return "Protocol wrong type for socket."; case WSAESHUTDOWN: return "Cannot send after socket shutdown."; case WSAESOCKTNOSUPPORT: return "Socket type not supported."; case WSAETIMEDOUT: return "Connection timed out."; case WSATYPE_NOT_FOUND: return "Class type not found."; case WSAEWOULDBLOCK: return "Resource temporarily unavailable."; case WSAHOST_NOT_FOUND: return "Host not found."; case WSA_INVALID_HANDLE: return "Specified event object handle is invalid."; case WSA_INVALID_PARAMETER: return "One or more parameters are invalid."; case WSA_IO_INCOMPLETE: return "Overlapped I/O event object not in signaled state."; case WSA_IO_PENDING: return "Overlapped operations will complete later."; case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available."; case WSANOTINITIALISED: return "Successful WSAStartup not yet performed."; case WSANO_DATA: return "Valid name, no data record of requested type."; case WSANO_RECOVERY: return "This is a non-recoverable error."; case WSASYSCALLFAILURE: return "System call failure."; case WSASYSNOTREADY: return "Network subsystem is unavailable."; case WSATRY_AGAIN: return "Non-authoritative host not found."; case WSAVERNOTSUPPORTED: return "WINSOCK.DLL version out of range."; case WSAEDISCON: return "Graceful shutdown in progress."; case WSA_OPERATION_ABORTED: return "Overlapped operation aborted."; case 0: return "no error"; default: return "Stupid Error."; } } const char *GetSocketErrorMsg() { return GetSocketErrorMsg(WSAGetLastError()); } bool HaveSocketError() { return !! WSAGetLastError(); } bool HaveWouldBlockError() { return WSAGetLastError() == WSAEWOULDBLOCK; } bool HaveConnResetError() { return WSAGetLastError() == WSAECONNRESET; } void ResetSocketError() { WSASetLastError(0); } static int iWSockUseCounter = 0; bool AcquireWinSock() { if (!iWSockUseCounter) { // initialize winsock WSADATA data; int res = WSAStartup(WINSOCK_VERSION, &data); // success? count if (!res) iWSockUseCounter++; // return result return !res; } // winsock already initialized iWSockUseCounter++; return true; } void ReleaseWinSock() { iWSockUseCounter--; // last use? if (!iWSockUseCounter) WSACleanup(); } #else const char *GetSocketErrorMsg(int iError) { return strerror(iError); } const char *GetSocketErrorMsg() { return GetSocketErrorMsg(errno); } bool HaveSocketError() { return !! errno; } bool HaveWouldBlockError() { return errno == EINPROGRESS || errno == EWOULDBLOCK; } bool HaveConnResetError() { return errno == ECONNRESET; } void ResetSocketError() { errno = 0; } #endif // HAVE_WINSOCK // *** C4NetIO // construction / destruction C4NetIO::C4NetIO() { ResetError(); } C4NetIO::~C4NetIO() { } void C4NetIO::SetError(const char *strnError, bool fSockErr) { fSockErr &= HaveSocketError(); if (fSockErr) Error.Format("%s (%s)", strnError, GetSocketErrorMsg()); else Error.Copy(strnError); } // *** C4NetIOPacket // construction / destruction C4NetIOPacket::C4NetIOPacket() { } C4NetIOPacket::C4NetIOPacket(const void *pnData, size_t inSize, bool fCopy, const C4NetIO::addr_t &naddr) : StdCopyBuf(pnData, inSize, fCopy), addr(naddr) { } C4NetIOPacket::C4NetIOPacket(const StdBuf &Buf, const C4NetIO::addr_t &naddr) : StdCopyBuf(Buf), addr(naddr) { } C4NetIOPacket::C4NetIOPacket(uint8_t cStatusByte, const char *pnData, size_t inSize, const C4NetIO::addr_t &naddr) { // Create buffer New(sizeof(cStatusByte) + inSize); // Write data *getMBufPtr(*this) = cStatusByte; Write(pnData, inSize, sizeof(cStatusByte)); } C4NetIOPacket::~C4NetIOPacket() { Clear(); } void C4NetIOPacket::Clear() { addr = C4NetIO::addr_t(); StdBuf::Clear(); } // *** C4NetIOTCP // construction / destruction C4NetIOTCP::C4NetIOTCP() : pPeerList(NULL), pConnectWaits(NULL), PeerListCSec(this), fInit(false), iListenPort(~0), lsock(INVALID_SOCKET), #ifdef STDSCHEDULER_USE_EVENTS Event(NULL), #endif pCB(NULL) { } C4NetIOTCP::~C4NetIOTCP() { Close(); } bool C4NetIOTCP::Init(uint16_t iPort) { // already init? close first if (fInit) Close(); #ifdef HAVE_WINSOCK // init winsock if (!AcquireWinSock()) { SetError("could not start winsock"); return false; } #endif #ifdef STDSCHEDULER_USE_EVENTS // create event if ((Event = WSACreateEvent()) == WSA_INVALID_EVENT) { SetError("could not create socket event", true); // to do: more error information return false; } #else // create pipe if (pipe(Pipe) != 0) { SetError("could not create pipe", true); return false; } #endif // create listen socket (if necessary) if (iPort != P_NONE) if (!Listen(iPort)) return false; // ok fInit = true; return true; } bool C4NetIOTCP::InitBroadcast(addr_t *pBroadcastAddr) { // ignore return true; } bool C4NetIOTCP::Close() { ResetError(); // not init? if (!fInit) return false; // terminate connections CStdShareLock PeerListLock(&PeerListCSec); for (Peer *pPeer = pPeerList; pPeer; pPeer = pPeer->Next) if (pPeer->Open()) { pPeer->Close(); if (pCB) pCB->OnDisconn(pPeer->GetAddr(), this, "owner class closed"); } ClearConnectWaits(); // close listen socket if (lsock != INVALID_SOCKET) { closesocket(lsock); lsock = INVALID_SOCKET; } #ifdef STDSCHEDULER_USE_EVENTS // close event if (Event != NULL) { WSACloseEvent(Event); Event = NULL; } #else // close pipe close(Pipe[0]); close(Pipe[1]); #endif #ifdef HAVE_WINSOCK // release winsock ReleaseWinSock(); #endif // ok fInit = false; return true; } bool C4NetIOTCP::CloseBroadcast() { return true; } #ifdef __APPLE__ static int fix_poll_timeout(int timeout) { if (timeout < 0 || timeout > 1000) return 1000; else return timeout; } #endif bool C4NetIOTCP::Execute(int iMaxTime, pollfd *fds) // (mt-safe) { // security if (!fInit) return false; #ifdef STDSCHEDULER_USE_EVENTS // wait for something to happen if (WaitForSingleObject(Event, iMaxTime == C4NetIO::TO_INF ? INFINITE : iMaxTime) == WAIT_TIMEOUT) // timeout -> nothing happened return true; WSAResetEvent(Event); WSANETWORKEVENTS wsaEvents; #else #ifdef __APPLE__ iMaxTime = fix_poll_timeout(iMaxTime); #endif std::vector fdvec; std::map fdmap; if (!fds) { // build socket sets GetFDs(fdvec); fds = &fdvec[0]; // wait for something to happen int ret = poll(fds, fdvec.size(), iMaxTime); // error if (ret < 0) { SetError("poll failed"); return false; } // nothing happened if (ret == 0) return true; } else { // We need to know the size of fdvec, so construct the vector GetFDs(fdvec); // Now overwrite with the poll result std::copy(fds, fds + fdvec.size(), fdvec.begin()); } // flush pipe assert(fdvec[0].fd == Pipe[0]); if (fdvec[0].events & fdvec[0].revents) { char c; if (::read(Pipe[0], &c, 1) == -1) SetError("read failed"); } for (std::vector::const_iterator i = fdvec.begin(); i != fdvec.end(); ++i) fdmap[i->fd] = &*i; std::map::const_iterator cur_fd; #endif // check sockets for events // first: the listen socket if (lsock != INVALID_SOCKET) { #ifdef STDSCHEDULER_USE_EVENTS // get event list if (::WSAEnumNetworkEvents(lsock, NULL, &wsaEvents) == SOCKET_ERROR) return false; // a connection waiting for accept? if (wsaEvents.lNetworkEvents & FD_ACCEPT) #else cur_fd = fdmap.find(lsock); // a connection waiting for accept? if (cur_fd != fdmap.end() && (cur_fd->second->events & cur_fd->second->revents)) #endif if (!Accept()) return false; // (note: what happens if there are more connections waiting?) #ifdef STDSCHEDULER_USE_EVENTS // closed? if (wsaEvents.lNetworkEvents & FD_CLOSE) // try to recreate the listen socket Listen(iListenPort); #endif } // second: waited-for connection CStdShareLock PeerListLock(&PeerListCSec); for (ConnectWait *pWait = pConnectWaits, *pNext; pWait; pWait = pNext) { pNext = pWait->Next; // not closed? if (pWait->sock) { #ifdef STDSCHEDULER_USE_EVENTS // get event list if (::WSAEnumNetworkEvents(pWait->sock, NULL, &wsaEvents) == SOCKET_ERROR) return false; if (wsaEvents.lNetworkEvents & FD_CONNECT) #else // got connection? cur_fd = fdmap.find(pWait->sock); if (cur_fd != fdmap.end() && (cur_fd->second->events & cur_fd->second->revents)) #endif { // remove from list SOCKET sock = pWait->sock; pWait->sock = 0; #ifdef STDSCHEDULER_USE_EVENTS // error? if (wsaEvents.iErrorCode[FD_CONNECT_BIT]) { // disconnect-callback if (pCB) pCB->OnDisconn(pWait->addr, this, GetSocketErrorMsg(wsaEvents.iErrorCode[FD_CONNECT_BIT])); } else #else // get error code int iErrCode; socklen_t iErrCodeLen = sizeof(iErrCode); if (getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast(&iErrCode), &iErrCodeLen) != 0) { close(sock); if (pCB) pCB->OnDisconn(pWait->addr, this, GetSocketErrorMsg()); } // error? else if (iErrCode) { close(sock); if (pCB) pCB->OnDisconn(pWait->addr, this, GetSocketErrorMsg(iErrCode)); } else #endif // accept connection, do callback if (!Accept(sock, pWait->addr)) return false; } } } // last: all connected sockets for (Peer *pPeer = pPeerList; pPeer; pPeer = pPeer->Next) if (pPeer->Open()) { SOCKET sock = pPeer->GetSocket(); #ifdef STDSCHEDULER_USE_EVENTS // get event list if (::WSAEnumNetworkEvents(sock, NULL, &wsaEvents) == SOCKET_ERROR) return false; // something to read from socket? if (wsaEvents.lNetworkEvents & FD_READ) #else // something to read from socket? cur_fd = fdmap.find(sock); if (cur_fd != fdmap.end() && (POLLIN & cur_fd->second->revents)) #endif for (;;) { // how much? #ifdef _WIN32 DWORD iBytesToRead; #else int iBytesToRead; #endif if (::ioctlsocket(pPeer->GetSocket(), FIONREAD, &iBytesToRead) == SOCKET_ERROR) { pPeer->Close(); if (pCB) pCB->OnDisconn(pPeer->GetAddr(), this, GetSocketErrorMsg()); break; } // The following two lines of code will make sure that if the variable // "iBytesToRead" is zero, it will be increased by one. // In this case, it will hold the value 1 after the operation. // Note it doesn't do anything for negative values. // (This comment has been sponsored by Sven2) if (!iBytesToRead) ++iBytesToRead; // get buffer void *pBuf = pPeer->GetRecvBuf(iBytesToRead); // read a buffer full of data from socket int iBytesRead; if ((iBytesRead = ::recv(sock, reinterpret_cast(pBuf), iBytesToRead, 0)) == SOCKET_ERROR) { // Would block? Ok, let's try this again later if (HaveWouldBlockError()) { ResetSocketError(); break; } // So he's serious after all... pPeer->Close (); if (pCB) pCB->OnDisconn(pPeer->GetAddr(), this, GetSocketErrorMsg()); break; } // nothing? this means the conection was closed, if you trust in linux manpages. if (!iBytesRead) { pPeer->Close(); if (pCB) pCB->OnDisconn(pPeer->GetAddr(), this, "connection closed"); break; } // pass to Peer::OnRecv pPeer->OnRecv(iBytesRead); } // socket has become writeable? #ifdef STDSCHEDULER_USE_EVENTS if (wsaEvents.lNetworkEvents & FD_WRITE) #else if (cur_fd != fdmap.end() && (POLLOUT & cur_fd->second->revents)) #endif // send remaining data pPeer->Send(); #ifdef STDSCHEDULER_USE_EVENTS // socket was closed? if (wsaEvents.lNetworkEvents & FD_CLOSE) { const char *szReason = wsaEvents.iErrorCode[FD_CLOSE_BIT] ? GetSocketErrorMsg(wsaEvents.iErrorCode[FD_CLOSE_BIT]) : "closed by peer"; // close socket pPeer->Close(); // do callback if (pCB) pCB->OnDisconn(pPeer->GetAddr(), this, szReason); } #endif } // done return true; } bool C4NetIOTCP::Connect(const C4NetIO::addr_t &addr) // (mt-safe) { // create new socket SOCKET nsock = ::socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP); if (nsock == INVALID_SOCKET) { SetError("socket creation failed", true); return false; } #ifdef STDSCHEDULER_USE_EVENTS // set event if (::WSAEventSelect(nsock, Event, FD_CONNECT) == SOCKET_ERROR) { // set error SetError("connect failed: could not set event", true); closesocket(nsock); return false; } // add to list AddConnectWait(nsock, addr); #elif defined(HAVE_WINSOCK) // disable blocking unsigned long iBlock = 1; if (::ioctlsocket(nsock, FIONBIO, &iBlock) == SOCKET_ERROR) { // set error SetError("connect failed: could not disable blocking", true); close(nsock); return false; } #else // disable blocking if (::fcntl(nsock, F_SETFL, fcntl(nsock, F_GETFL) | O_NONBLOCK) == SOCKET_ERROR) { // set error SetError("connect failed: could not disable blocking", true); close(nsock); return false; } #endif // connect (async) if (::connect(nsock, reinterpret_cast(&addr), sizeof addr) == SOCKET_ERROR) { if (!HaveWouldBlockError()) // expected { SetError("socket connection failed", true); closesocket(nsock); return false; } } #ifndef STDSCHEDULER_USE_EVENTS // add to list AddConnectWait(nsock, addr); #endif // ok return true; } bool C4NetIOTCP::Close(const addr_t &addr) // (mt-safe) { CStdShareLock PeerListLock(&PeerListCSec); // find connect wait ConnectWait *pWait = GetConnectWait(addr); if (pWait) { // close socket, do callback closesocket(pWait->sock); pWait->sock = 0; if (pCB) pCB->OnDisconn(pWait->addr, this, "closed"); } else { // find peer Peer *pPeer = GetPeer(addr); if (pPeer) { C4NetIO::addr_t addr = pPeer->GetAddr(); // close peer pPeer->Close(); // do callback if (pCB) pCB->OnDisconn(addr, this, "closed"); } // not found else return false; } // ok return true; } bool C4NetIOTCP::Send(const C4NetIOPacket &rPacket) // (mt-safe) { CStdShareLock PeerListLock(&PeerListCSec); // find peer Peer *pPeer = GetPeer(rPacket.getAddr()); // not found? if (!pPeer) return false; // send return pPeer->Send(rPacket); } bool C4NetIOTCP::SetBroadcast(const addr_t &addr, bool fSet) // (mt-safe) { CStdShareLock PeerListLock(&PeerListCSec); // find peer Peer *pPeer = GetPeer(addr); if (!pPeer) return false; // set flag pPeer->SetBroadcast(fSet); return true; } bool C4NetIOTCP::Broadcast(const C4NetIOPacket &rPacket) // (mt-safe) { CStdShareLock PeerListLock(&PeerListCSec); // just send to all clients bool fSuccess = true; for (Peer *pPeer = pPeerList; pPeer; pPeer = pPeer->Next) if (pPeer->Open() && pPeer->doBroadcast()) fSuccess &= Send(C4NetIOPacket(rPacket.getRef(), pPeer->GetAddr())); return fSuccess; } void C4NetIOTCP::UnBlock() // (mt-safe) { #ifdef STDSCHEDULER_USE_EVENTS // unblock WaitForSingleObject in C4NetIOTCP::Execute manually // by setting the Event WSASetEvent(Event); #else // write one character to the pipe, this will unblock everything that // waits for the FD set returned by GetFDs. char c = 1; if (write(Pipe[1], &c, 1) == -1) SetError("write failed"); #endif } #ifdef STDSCHEDULER_USE_EVENTS HANDLE C4NetIOTCP::GetEvent() // (mt-safe) { return Event; } #else void C4NetIOTCP::GetFDs(std::vector & fds) { pollfd pfd; pfd.revents = 0; // add pipe pfd.fd = Pipe[0]; pfd.events = POLLIN; fds.push_back(pfd); // add listener if (lsock != INVALID_SOCKET) { pfd.fd = lsock; pfd.events = POLLIN; fds.push_back(pfd); } // add connect waits (wait for them to become writeable) CStdShareLock PeerListLock(&PeerListCSec); for (ConnectWait *pWait = pConnectWaits; pWait; pWait = pWait->Next) { pfd.fd = pWait->sock; pfd.events = POLLOUT; fds.push_back(pfd); } // add sockets for (Peer *pPeer = pPeerList; pPeer; pPeer = pPeer->Next) if (pPeer->GetSocket()) { // Wait for socket to become readable pfd.fd = pPeer->GetSocket(); pfd.events = POLLIN; // Wait for socket to become writeable, if there is data waiting if (pPeer->hasWaitingData()) { pfd.events |= POLLOUT; } fds.push_back(pfd); } } #endif bool C4NetIOTCP::GetStatistic(int *pBroadcastRate) // (mt-safe) { // no broadcast if (pBroadcastRate) *pBroadcastRate = 0; return true; } bool C4NetIOTCP::GetConnStatistic(const addr_t &addr, int *pIRate, int *pORate, int *pLoss) // (mt-safe) { CStdShareLock PeerListLock(&PeerListCSec); // find peer Peer *pPeer = GetPeer(addr); if (!pPeer || !pPeer->Open()) return false; // return statistics if (pIRate) *pIRate = pPeer->GetIRate(); if (pORate) *pORate = pPeer->GetORate(); if (pLoss) *pLoss = 0; return true; } void C4NetIOTCP::ClearStatistic() { CStdShareLock PeerListLock(&PeerListCSec); // clear all peer statistics for (Peer *pPeer = pPeerList; pPeer; pPeer = pPeer->Next) pPeer->ClearStatistics(); } C4NetIOTCP::Peer *C4NetIOTCP::Accept(SOCKET nsock, const addr_t &ConnectAddr) // (mt-safe) { addr_t caddr = ConnectAddr; // accept incoming connection? C4NetIO::addr_t addr; socklen_t iAddrSize = sizeof addr; if (nsock == INVALID_SOCKET) { // accept from listener #ifdef __linux__ if ((nsock = ::accept4(lsock, reinterpret_cast(&addr), &iAddrSize, SOCK_CLOEXEC)) == INVALID_SOCKET) #else if ((nsock = ::accept(lsock, reinterpret_cast(&addr), &iAddrSize)) == INVALID_SOCKET) #endif { // set error SetError("socket accept failed", true); return NULL; } // connect address unknown, so zero it ZeroMem(&caddr, sizeof caddr); } else { // get peer address if (::getpeername(nsock, reinterpret_cast(&addr), &iAddrSize) == SOCKET_ERROR) { #ifndef HAVE_WINSOCK // getpeername behaves strangely on exotic platforms. Just ignore it. if (errno != ENOTCONN) { #endif // set error SetError("could not get peer address for connected socket", true); return NULL; #ifndef HAVE_WINSOCK } #endif } } // check address if (iAddrSize != sizeof addr || addr.sin_family != AF_INET) { // set error SetError("socket accept failed: invalid address returned"); closesocket(nsock); return NULL; } // disable nagle (yep, we know what we are doing here - I think) int iNoDelay = 1; ::setsockopt(nsock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&iNoDelay), sizeof(iNoDelay)); #ifdef STDSCHEDULER_USE_EVENTS // set event if (::WSAEventSelect(nsock, Event, FD_READ | FD_WRITE | FD_CLOSE) == SOCKET_ERROR) { // set error SetError("connection accept failed: could not set event", true); closesocket(nsock); return NULL; } #elif defined(HAVE_WINSOCK) // disable blocking unsigned long iBlock = 1; if (::ioctlsocket(nsock, FIONBIO, &iBlock) == SOCKET_ERROR) { // set error SetError("connect failed: could not disable blocking", true); close(nsock); return false; } #else // disable blocking if (::fcntl(nsock, F_SETFL, fcntl(nsock, F_GETFL) | O_NONBLOCK) == SOCKET_ERROR) { // set error SetError("connection accept failed: could not disable blocking", true); close(nsock); return NULL; } #endif // create new peer Peer *pnPeer = new Peer(addr, nsock, this); // get required locks to add item to list CStdShareLock PeerListLock(&PeerListCSec); CStdLock PeerListAddLock(&PeerListAddCSec); // add to list pnPeer->Next = pPeerList; pPeerList = pnPeer; // clear add-lock PeerListAddLock.Clear(); Changed(); // ask callback if connection should be permitted if (pCB && !pCB->OnConn(addr, caddr, NULL, this)) // close socket immediately (will be deleted later) pnPeer->Close(); // ok return pnPeer; } bool C4NetIOTCP::Listen(uint16_t inListenPort) { // already listening? if (lsock != INVALID_SOCKET) // close existing socket closesocket(lsock); iListenPort = P_NONE; // create socket if ((lsock = ::socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP)) == INVALID_SOCKET) { SetError("socket creation failed", true); return false; } // To be able to reuse the port after close #if !defined(_DEBUG) && !defined(_WIN32) int reuseaddr = 1; setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reuseaddr), sizeof(reuseaddr)); #endif // bind listen socket C4NetIO::addr_t addr; addr.sin_family = AF_INET; addr.sin_port = htons(inListenPort); addr.sin_addr.s_addr = INADDR_ANY; memset(addr.sin_zero, 0, sizeof addr.sin_zero); if (::bind(lsock, reinterpret_cast(&addr), sizeof addr) == SOCKET_ERROR) { SetError("socket bind failed", true); closesocket(lsock); lsock = INVALID_SOCKET; return false; } #ifdef STDSCHEDULER_USE_EVENTS // set event callback if (::WSAEventSelect(lsock, Event, FD_ACCEPT | FD_CLOSE) == SOCKET_ERROR) { SetError("could not set event for listen socket", true); closesocket(lsock); lsock = INVALID_SOCKET; return false; } #endif // start listening if (::listen(lsock, SOMAXCONN) == SOCKET_ERROR) { SetError("socket listen failed", true); closesocket(lsock); lsock = INVALID_SOCKET; return false; } // ok iListenPort = inListenPort; Changed(); return true; } C4NetIOTCP::Peer *C4NetIOTCP::GetPeer(const addr_t &addr) // (mt-safe) { CStdShareLock PeerListLock(&PeerListCSec); for (Peer *pPeer = pPeerList; pPeer; pPeer = pPeer->Next) if (pPeer->Open()) if (AddrEqual(pPeer->GetAddr(), addr)) return pPeer; return NULL; } void C4NetIOTCP::OnShareFree(CStdCSecEx *pCSec) { if (pCSec == &PeerListCSec) { // clear up Peer *pPeer = pPeerList, *pLast = NULL; while (pPeer) { // delete? if (!pPeer->Open()) { // unlink Peer *pDelete = pPeer; pPeer = pPeer->Next; (pLast ? pLast->Next : pPeerList) = pPeer; // delete delete pDelete; } else { // next peer pLast = pPeer; pPeer = pPeer->Next; } } ConnectWait *pWait = pConnectWaits, *pWLast = NULL; while (pWait) { // delete? if (!pWait->sock) { // unlink ConnectWait *pDelete = pWait; pWait = pWait->Next; (pWLast ? pWLast->Next : pConnectWaits) = pWait; // delete delete pDelete; } else { // next peer pWLast = pWait; pWait = pWait->Next; } } } } void C4NetIOTCP::AddConnectWait(SOCKET sock, const addr_t &addr) // (mt-safe) { CStdShareLock PeerListLock(&PeerListCSec); CStdLock PeerListAddLock(&PeerListAddCSec); // create new entry, add to list ConnectWait *pnWait = new ConnectWait; pnWait->sock = sock; pnWait->addr = addr; pnWait->Next = pConnectWaits; pConnectWaits = pnWait; #ifndef STDSCHEDULER_USE_EVENTS // unblock, so new FD can be realized UnBlock(); #endif Changed(); } C4NetIOTCP::ConnectWait *C4NetIOTCP::GetConnectWait(const addr_t &addr) // (mt-safe) { CStdShareLock PeerListLock(&PeerListCSec); // search for (ConnectWait *pWait = pConnectWaits; pWait; pWait = pWait->Next) if (AddrEqual(pWait->addr, addr)) return pWait; return NULL; } void C4NetIOTCP::ClearConnectWaits() // (mt-safe) { CStdShareLock PeerListLock(&PeerListCSec); for (ConnectWait *pWait = pConnectWaits; pWait; pWait = pWait->Next) if (pWait->sock) { closesocket(pWait->sock); pWait->sock = 0; } } void C4NetIOTCP::PackPacket(const C4NetIOPacket &rPacket, StdBuf &rOutBuf) { // packet data uint8_t cFirstByte = 0xff; uint32_t iSize = rPacket.getSize(); uint32_t iOASize = sizeof(cFirstByte) + sizeof(iSize) + iSize; // enlarge buffer int iPos = rOutBuf.getSize(); rOutBuf.Grow(iOASize); // write packet at end of outgoing buffer *getMBufPtr(rOutBuf, iPos) = cFirstByte; iPos += sizeof(uint8_t); *getMBufPtr(rOutBuf, iPos) = iSize; iPos += sizeof(uint32_t); rOutBuf.Write(rPacket, iPos); } size_t C4NetIOTCP::UnpackPacket(const StdBuf &IBuf, const C4NetIO::addr_t &addr) { size_t iPos = 0; // check first byte (should be 0xff) if (*getBufPtr(IBuf, iPos) != (uint8_t) 0xff) // clear buffer return IBuf.getSize(); iPos += sizeof(char); // read packet size uint32_t iPacketSize; if (iPos + sizeof(uint32_t) > IBuf.getSize()) return 0; iPacketSize = *getBufPtr(IBuf, iPos); iPos += sizeof(uint32_t); // packet incomplete? if (iPos + iPacketSize > IBuf.getSize()) return 0; // ok, call back if (pCB) pCB->OnPacket(C4NetIOPacket(IBuf.getPart(iPos, iPacketSize), addr), this); // absorbed return iPos + iPacketSize; } // * C4NetIOTCP::Peer const unsigned int C4NetIOTCP::Peer::iTCPHeaderSize = 28 + 24; // (bytes) const unsigned int C4NetIOTCP::Peer::iMinIBufSize = 8192; // (bytes) // construction / destruction C4NetIOTCP::Peer::Peer(const C4NetIO::addr_t &naddr, SOCKET nsock, C4NetIOTCP *pnParent) : pParent(pnParent), addr(naddr), sock(nsock), iIBufUsage(0), iIRate(0), iORate(0), fOpen(true), fDoBroadcast(false), Next(NULL) { } C4NetIOTCP::Peer::~Peer() { // close socket Close(); } // implementation bool C4NetIOTCP::Peer::Send(const C4NetIOPacket &rPacket) // (mt-safe) { CStdLock OLock(&OCSec); // already data pending to be sent? try to sent them first (empty buffer) if (!OBuf.isNull()) Send(); bool fSend = OBuf.isNull(); // pack packet pParent->PackPacket(rPacket, OBuf); // (try to) send return fSend ? Send() : true; } bool C4NetIOTCP::Peer::Send() // (mt-safe) { CStdLock OLock(&OCSec); if (OBuf.isNull()) return true; // send as much as possibile int iBytesSent; if ((iBytesSent = ::send(sock, getBufPtr(OBuf), OBuf.getSize(), 0)) == SOCKET_ERROR) if (!HaveWouldBlockError()) { pParent->SetError("send failed", true); return false; } // nothin sent? if (iBytesSent == SOCKET_ERROR || !iBytesSent) return true; // increase output rate iORate += iBytesSent + iTCPHeaderSize; // data remaining? if (unsigned(iBytesSent) < OBuf.getSize()) { // Shrink buffer OBuf.Move(iBytesSent, OBuf.getSize() - iBytesSent); OBuf.Shrink(iBytesSent); #ifndef STDSCHEDULER_USE_EVENTS // Unblock parent so the FD-list can be refreshed pParent->UnBlock(); #endif } else // just delete buffer OBuf.Clear(); // ok return true; } void *C4NetIOTCP::Peer::GetRecvBuf(int iSize) // (mt-safe) { CStdLock ILock(&ICSec); // Enlarge input buffer? size_t iIBufSize = Max(iMinIBufSize, IBuf.getSize()); while ((size_t)(iIBufUsage + iSize) > iIBufSize) iIBufSize *= 2; if (iIBufSize != IBuf.getSize()) IBuf.SetSize(iIBufSize); // Return the appropriate part of the input buffer return IBuf.getMPtr(iIBufUsage); } void C4NetIOTCP::Peer::OnRecv(int iSize) // (mt-safe) { CStdLock ILock(&ICSec); // increase input rate and input buffer usage iIRate += iTCPHeaderSize + iSize; iIBufUsage += iSize; // a prior call to GetRecvBuf should have ensured this assert(static_cast(iIBufUsage) <= IBuf.getSize()); // read packets size_t iPos = 0, iPacketPos; while ((iPacketPos = iPos) < (size_t)iIBufUsage) { // Try to unpack a packet StdBuf IBufPart = IBuf.getPart(iPos, iIBufUsage - iPos); int32_t iBytes = pParent->UnpackPacket(IBufPart, addr); // Could not unpack? if (!iBytes) break; // Advance iPos += iBytes; } // data left? if (iPacketPos < (size_t) iIBufUsage) { // no packet read? if (!iPacketPos) return; // move data IBuf.Move(iPacketPos, IBuf.getSize() - iPacketPos); iIBufUsage -= iPacketPos; // shrink buffer size_t iIBufSize = IBuf.getSize(); while ((size_t) iIBufUsage <= iIBufSize / 2) iIBufSize /= 2; if (iIBufSize != IBuf.getSize()) IBuf.Shrink(iPacketPos); } else { // the buffer is empty iIBufUsage = 0; // shrink buffer to minimum if (IBuf.getSize() > iMinIBufSize) IBuf.SetSize(iMinIBufSize); } } void C4NetIOTCP::Peer::Close() // (mt-safe) { CStdLock ILock(&ICSec); CStdLock OLock(&OCSec); if (!fOpen) return; // close socket closesocket(sock); // set flag fOpen = false; // clear buffers IBuf.Clear(); OBuf.Clear(); iIBufUsage = 0; // reset statistics iIRate = iORate = 0; } void C4NetIOTCP::Peer::ClearStatistics() // (mt-safe) { CStdLock ILock(&ICSec); CStdLock OLock(&OCSec); iIRate = iORate = 0; } // *** C4NetIOSimpleUDP C4NetIOSimpleUDP::C4NetIOSimpleUDP() : fInit(false), fMultiCast(false), iPort(~0), sock(INVALID_SOCKET), #ifdef STDSCHEDULER_USE_EVENTS hEvent(NULL), #endif fAllowReUse(false) { } C4NetIOSimpleUDP::~C4NetIOSimpleUDP() { Close(); } bool C4NetIOSimpleUDP::Init(uint16_t inPort) { // reset error ResetError(); // already initialized? close first if (fInit) Close(); #ifdef HAVE_WINSOCK // init winsock if (!AcquireWinSock()) { SetError("could not start winsock"); return false; } #endif // create socket if ((sock = ::socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP)) == INVALID_SOCKET) { SetError("could not create socket", true); return false; } // set reuse socket option if (::setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&fAllowReUse), sizeof fAllowReUse) == SOCKET_ERROR) { SetError("could not set reuse options", true); return false; } // bind socket iPort = inPort; C4NetIO::addr_t naddr; naddr.sin_family = AF_INET; naddr.sin_port = (iPort == P_NONE ? 0 : htons(iPort)); naddr.sin_addr.s_addr = INADDR_ANY; memset(naddr.sin_zero, 0, sizeof naddr.sin_zero); if (::bind(sock, reinterpret_cast(&naddr), sizeof naddr) == SOCKET_ERROR) { SetError("could not bind socket", true); return false; } #ifdef STDSCHEDULER_USE_EVENTS // create event if ((hEvent = WSACreateEvent()) == WSA_INVALID_EVENT) { SetError("could not create event", true); return false; } // set event for socket if (WSAEventSelect(sock, hEvent, FD_READ | FD_CLOSE) == SOCKET_ERROR) { SetError("could not select event", true); return false; } #else // disable blocking if (::fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK) == SOCKET_ERROR) { // set error SetError("could not disable blocking", true); return false; } // create pipe if (pipe(Pipe) != 0) { SetError("could not create pipe", true); return false; } #endif // set flags fInit = true; fMultiCast = false; // ok, that's all for know. // call InitBroadcast for more initialization fun return true; } bool C4NetIOSimpleUDP::InitBroadcast(addr_t *pBroadcastAddr) { // no error... yet ResetError(); // security if (!pBroadcastAddr) return false; // Init() has to be called first if (!fInit) return false; // already activated? if (fMultiCast) CloseBroadcast(); // broadcast addr valid? if (pBroadcastAddr->sin_family != AF_INET || in_addr_b(pBroadcastAddr->sin_addr, 0) != 239) { SetError("invalid broadcast address"); return false; } if (pBroadcastAddr->sin_port != htons(iPort)) { SetError("invalid broadcast address (different port)"); return false; } // set mc ttl to somewhat about "same net" int iTTL = 16; if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, reinterpret_cast(&iTTL), sizeof(iTTL)) == SOCKET_ERROR) { SetError("could not set mc ttl", true); return false; } // set up multicast group information this->MCAddr = *pBroadcastAddr; MCGrpInfo.imr_multiaddr = MCAddr.sin_addr; MCGrpInfo.imr_interface.s_addr = INADDR_ANY; // join multicast group if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, reinterpret_cast(&MCGrpInfo), sizeof(MCGrpInfo)) == SOCKET_ERROR) { SetError("could not join multicast group"); // to do: more error information return false; } // (try to) disable loopback (will set fLoopback accordingly) SetMCLoopback(false); // ok fMultiCast = true; return true; } bool C4NetIOSimpleUDP::Close() { // should be initialized if (!fInit) return true; ResetError(); // deactivate multicast if (fMultiCast) CloseBroadcast(); // close sockets if (sock != INVALID_SOCKET) { closesocket(sock); sock = INVALID_SOCKET; } #ifdef STDSCHEDULER_USE_EVENTS // close event if (hEvent != NULL) { WSACloseEvent(hEvent); hEvent = NULL; } #else // close pipes close(Pipe[0]); close(Pipe[1]); #endif #ifdef HAVE_WINSOCK // release winsock ReleaseWinSock(); #endif // ok fInit = false; return false; } bool C4NetIOSimpleUDP::CloseBroadcast() { // multicast not active? if (!fMultiCast) return true; // leave multicast group if (setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, reinterpret_cast(&MCGrpInfo), sizeof(MCGrpInfo)) == SOCKET_ERROR) { SetError("could not join multicast group"); // to do: more error information return false; } // ok fMultiCast = false; return true; } bool C4NetIOSimpleUDP::Execute(int iMaxTime, pollfd *) { if (!fInit) { SetError("not yet initialized"); return false; } ResetError(); #ifdef __APPLE__ iMaxTime = fix_poll_timeout(iMaxTime); #endif // wait for socket / timeout WaitResult eWR = WaitForSocket(iMaxTime); if (eWR == WR_Error) return false; // cancelled / timeout? if (eWR == WR_Cancelled || eWR == WR_Timeout) return true; assert(eWR == WR_Readable); // read packets from socket for (;;) { // how much can be read? #ifdef _WIN32 u_long iMaxMsgSize; #else // The FIONREAD ioctl call takes an int on unix int iMaxMsgSize; #endif if (::ioctlsocket(sock, FIONREAD, &iMaxMsgSize) == SOCKET_ERROR) { SetError("Could not determine the amount of data that can be read from socket", true); return false; } // nothing? if (!iMaxMsgSize) break; // alloc buffer C4NetIOPacket Pkt; Pkt.New(iMaxMsgSize); // read data (note: it is _not_ garantueed that iMaxMsgSize bytes are available) addr_t SrcAddr; socklen_t iSrcAddrLen = sizeof(SrcAddr); int iMsgSize = ::recvfrom(sock, getMBufPtr(Pkt), iMaxMsgSize, 0, reinterpret_cast(&SrcAddr), &iSrcAddrLen); // error? if (iMsgSize == SOCKET_ERROR) { if (HaveConnResetError()) { // this is actually some kind of notification: an ICMP msg (unreachable) // came back, so callback and continue reading if (pCB) pCB->OnDisconn(SrcAddr, this, GetSocketErrorMsg()); continue; } else { // this is the real thing, though SetError("could not receive data from socket", true); return false; } } // invalid address? if (iSrcAddrLen != sizeof(SrcAddr) || SrcAddr.sin_family != AF_INET) { SetError("recvfrom returned an invalid address"); return false; } // again: nothing? if (!iMsgSize) // docs say that the connection has been closed (whatever that means for a connectionless socket...) // let's just pretend it didn't happen, but stop reading. break; // fill in packet information Pkt.SetSize(iMsgSize); Pkt.SetAddr(SrcAddr); // callback if (pCB) pCB->OnPacket(Pkt, this); } // ok return true; } bool C4NetIOSimpleUDP::Send(const C4NetIOPacket &rPacket) { if (!fInit) { SetError("not yet initialized"); return false; } // send it C4NetIO::addr_t addr = rPacket.getAddr(); if (::sendto(sock, getBufPtr(rPacket), rPacket.getSize(), 0, reinterpret_cast(&addr), sizeof(addr)) != int(rPacket.getSize()) && !HaveWouldBlockError()) { SetError("socket sendto failed", true); return false; } // ok ResetError(); return true; } bool C4NetIOSimpleUDP::Broadcast(const C4NetIOPacket &rPacket) { // just set broadcast address and send return C4NetIOSimpleUDP::Send(C4NetIOPacket(rPacket.getRef(), MCAddr)); } #ifdef STDSCHEDULER_USE_EVENTS void C4NetIOSimpleUDP::UnBlock() // (mt-safe) { // unblock WaitForSingleObject in C4NetIOTCP::Execute manually // by setting the Event WSASetEvent(hEvent); } HANDLE C4NetIOSimpleUDP::GetEvent() // (mt-safe) { return hEvent; } enum C4NetIOSimpleUDP::WaitResult C4NetIOSimpleUDP::WaitForSocket(int iTimeout) { // wait for anything to happen DWORD ret = WaitForSingleObject(hEvent, iTimeout == TO_INF ? INFINITE : iTimeout); if (ret == WAIT_TIMEOUT) return WR_Timeout; if (ret == WAIT_FAILED) { SetError("Wait for Event failed"); return WR_Error; } // get socket events (and reset the event) WSANETWORKEVENTS wsaEvents; if (WSAEnumNetworkEvents(sock, hEvent, &wsaEvents) == SOCKET_ERROR) { SetError("could not enumerate network events!"); return WR_Error; } // socket readable? if (wsaEvents.lNetworkEvents | FD_READ) return WR_Readable; // in case the event was set without the socket beeing readable, // the operation has been cancelled (see Unblock()) WSAResetEvent(hEvent); return WR_Cancelled; } #else // STDSCHEDULER_USE_EVENTS void C4NetIOSimpleUDP::UnBlock() // (mt-safe) { // write one character to the pipe, this will unblock everything that // waits for the FD set returned by GetFDs. char c = 42; if (write(Pipe[1], &c, 1) == -1) SetError("write failed"); } void C4NetIOSimpleUDP::GetFDs(std::vector & fds) { // add pipe pollfd pfd = { Pipe[0], POLLIN, 0 }; fds.push_back(pfd); // add socket if (sock != INVALID_SOCKET) { pollfd pfd = { sock, POLLIN, 0 }; fds.push_back(pfd); } } enum C4NetIOSimpleUDP::WaitResult C4NetIOSimpleUDP::WaitForSocket(int iTimeout) { // get file descriptors std::vector fds; GetFDs(fds); // wait for anything to happen int ret = poll(&fds[0], fds.size(), iTimeout); // catch simple cases if (ret < 0) { SetError("poll failed", true); return WR_Error; } if (!ret) return WR_Timeout; // flush pipe, if neccessary if (fds[0].revents & POLLIN) { char c; if (::read(Pipe[0], &c, 1) == -1) SetError("read failed"); } // socket readable? return (sock != INVALID_SOCKET) && (fds[1].revents & POLLIN) ? WR_Readable : WR_Cancelled; } #endif // STDSCHEDULER_USE_EVENTS bool C4NetIOSimpleUDP::SetMCLoopback(int fLoopback) { // enable/disable MC loopback setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, reinterpret_cast(&fLoopback), sizeof fLoopback); // read result socklen_t iSize = sizeof(fLoopback); if (getsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, reinterpret_cast(&fLoopback), &iSize) == SOCKET_ERROR) return false; fMCLoopback = !! fLoopback; return true; } void C4NetIOSimpleUDP::SetReUseAddress(bool fAllow) { fAllowReUse = fAllow; } // *** C4NetIOUDP // * build options / constants / structures // Check immediately when missing packets are detected? #define C4NETIOUDP_OPT_RECV_CHECK_IMMEDIATE // Protocol version const unsigned int C4NetIOUDP::iVersion = 2; // Standard timeout length const unsigned int C4NetIOUDP::iStdTimeout = 1000; // (ms) // Time interval for connection checks // Equals the maximum time that C4NetIOUDP::Execute might block const unsigned int C4NetIOUDP::iCheckInterval = 1000; // (ms) const unsigned int C4NetIOUDP::iMaxOPacketBacklog = 10000; const unsigned int C4NetIOUDP::iUDPHeaderSize = 8 + 24; // (bytes) #pragma pack (push, 1) // packet structures struct C4NetIOUDP::PacketHdr { uint8_t StatusByte; uint32_t Nr; // packet nr }; struct C4NetIOUDP::ConnPacket : public PacketHdr { uint32_t ProtocolVer; C4NetIO::addr_t Addr; C4NetIO::addr_t MCAddr; }; struct C4NetIOUDP::ConnOKPacket : public PacketHdr { enum { MCM_NoMC, MCM_MC, MCM_MCOK } MCMode; C4NetIO::addr_t Addr; }; struct C4NetIOUDP::AddAddrPacket : public PacketHdr { C4NetIO::addr_t Addr; C4NetIO::addr_t NewAddr; }; struct C4NetIOUDP::DataPacketHdr : public PacketHdr { Packet::nr_t FNr; // start fragment of this series uint32_t Size; // packet size (all fragments) }; struct C4NetIOUDP::CheckPacketHdr : public PacketHdr { uint32_t AskCount, MCAskCount; uint32_t AckNr, MCAckNr; // numbers of the last packets received }; struct C4NetIOUDP::ClosePacket : public PacketHdr { C4NetIO::addr_t Addr; }; struct C4NetIOUDP::TestPacket : public PacketHdr { unsigned int TestNr; }; #pragma pack (pop) // construction / destruction C4NetIOUDP::C4NetIOUDP() : PeerListCSec(this), fInit(false), fMultiCast(false), iPort(~0), pPeerList(NULL), fSavePacket(false), fDelayedLoopbackTest(false), tNextCheck(C4TimeMilliseconds::PositiveInfinity), OPackets(iMaxOPacketBacklog), iOPacketCounter(0), iBroadcastRate(0) { } C4NetIOUDP::~C4NetIOUDP() { Close(); } bool C4NetIOUDP::Init(uint16_t inPort) { // already initialized? close first if (fInit) Close(); #ifdef C4NETIO_DEBUG // open log OpenDebugLog(); #endif // Initialize UDP if (!C4NetIOSimpleUDP::Init(inPort)) return false; iPort = inPort; // set callback C4NetIOSimpleUDP::SetCallback(CBProxy(this)); // set flags fInit = true; fMultiCast = false; tNextCheck = C4TimeMilliseconds::Now() + iCheckInterval; // ok, that's all for now. // call InitBroadcast for more initialization fun return true; } bool C4NetIOUDP::InitBroadcast(addr_t *pBroadcastAddr) { // no error... yet ResetError(); // security if (!pBroadcastAddr) return false; // Init() has to be called first if (!fInit) return false; // already activated? if (fMultiCast) CloseBroadcast(); // set up multicast group information C4NetIO::addr_t MCAddr = *pBroadcastAddr; // broadcast addr valid? if (MCAddr.sin_family != AF_INET || in_addr_b(MCAddr.sin_addr, 0) != 239) { // port is needed in order to search a mc address automatically if (!iPort) { SetError("broadcast address is not valid"); return false; } // set up adress MCAddr.sin_family = AF_INET; MCAddr.sin_port = htons(iPort); memset(&MCAddr.sin_zero, 0, sizeof MCAddr.sin_zero); // search for a free one for (int iRetries = 1000; iRetries; iRetries--) { // create new - random - address MCAddr.sin_addr.s_addr = 0x000000ef | ((rand() & 0xff) << 24) | ((rand() & 0xff) << 16) | ((rand() & 0xff) << 8); // init broadcast if (!C4NetIOSimpleUDP::InitBroadcast(&MCAddr)) return false; // do the loopback test if (!DoLoopbackTest()) { C4NetIOSimpleUDP::CloseBroadcast(); if (!GetError()) SetError("multicast loopback test failed"); return false; } // send a ping packet const PacketHdr PingPacket = { IPID_Ping | static_cast(0x80u), 0 }; if (!C4NetIOSimpleUDP::Broadcast(C4NetIOPacket(&PingPacket, sizeof(PingPacket)))) { C4NetIOSimpleUDP::CloseBroadcast(); return false; } bool fSuccess = false; for (;;) { fSavePacket = true; LastPacket.Clear(); // wait for something to happen if (!C4NetIOSimpleUDP::Execute(iStdTimeout)) { fSavePacket = false; C4NetIOSimpleUDP::CloseBroadcast(); return false; } fSavePacket = false; // Timeout? So expect this address to be unused if (LastPacket.isNull()) { fSuccess = true; break; } // looped back? if (C4NetIOSimpleUDP::getMCLoopback() && AddrEqual(LastPacket.getAddr(), MCLoopbackAddr)) // ignore this one continue; // otherwise: there must be someone else in this MC group C4NetIOSimpleUDP::CloseBroadcast(); break; } if (fSuccess) break; // no success? try again... } // return found address *pBroadcastAddr = MCAddr; } else { // check: must be same port if (MCAddr.sin_port != htons(iPort)) { SetError("invalid multicast address: wrong port"); return false; } // init if (!C4NetIOSimpleUDP::InitBroadcast(&MCAddr)) return false; // do loopback test (if not delayed) if (!fDelayedLoopbackTest) if (!DoLoopbackTest()) { C4NetIOSimpleUDP::CloseBroadcast(); if (!GetError()) SetError("multicast loopback test failed"); return false; } } // (try to) disable multicast loopback C4NetIOSimpleUDP::SetMCLoopback(false); // set flags fMultiCast = true; iOPacketCounter = 0; iBroadcastRate = 0; // ok return true; } bool C4NetIOUDP::Close() { // should be initialized if (!fInit) return false; // close all peers CStdShareLock PeerListLock(&PeerListCSec); for (Peer *pPeer = pPeerList; pPeer; pPeer = pPeer->Next) pPeer->Close("owner class closed"); PeerListLock.Clear(); // deactivate multicast if (fMultiCast) CloseBroadcast(); // close UDP bool fSuccess = C4NetIOSimpleUDP::Close(); #ifdef C4NETIO_DEBUG // close log CloseDebugLog(); #endif // ok fInit = false; return fSuccess; } bool C4NetIOUDP::CloseBroadcast() { ResetError(); // multicast not active? if (!fMultiCast) return true; // ok fMultiCast = false; return C4NetIOSimpleUDP::CloseBroadcast(); } bool C4NetIOUDP::Execute(int iMaxTime, pollfd *) // (mt-safe) { if (!fInit) { SetError("not yet initialized"); return false; } CStdLock ExecuteLock(&ExecuteCSec); CStdShareLock PeerListLock(&PeerListCSec); ResetError(); // adjust maximum block time C4TimeMilliseconds tNow = C4TimeMilliseconds::Now(); uint32_t iMaxBlock = Max(tNow, GetNextTick(tNow)) - tNow; if (iMaxTime == TO_INF || iMaxTime > (int) iMaxBlock) iMaxTime = iMaxBlock; // execute subclass if (!C4NetIOSimpleUDP::Execute(iMaxTime)) return false; // connection check needed? if (tNextCheck <= C4TimeMilliseconds::Now()) DoCheck(); // client timeout? for (Peer *pPeer = pPeerList; pPeer; pPeer = pPeer->Next) if (!pPeer->Closed()) pPeer->CheckTimeout(); // do a delayed loopback test once the incoming buffer is empty if (fDelayedLoopbackTest) { if (fMultiCast) fMultiCast = DoLoopbackTest(); fDelayedLoopbackTest = false; } // ok return true; } bool C4NetIOUDP::Connect(const addr_t &addr) // (mt-safe) { // connect return !! ConnectPeer(addr, true); } bool C4NetIOUDP::Close(const addr_t &addr) // (mt-safe) { CStdShareLock PeerListLock(&PeerListCSec); // find peer Peer *pPeer = GetPeer(addr); if (!pPeer) return false; // close pPeer->Close("closed"); return true; } bool C4NetIOUDP::Send(const C4NetIOPacket &rPacket) // (mt-safe) { // find Peer class for given address CStdShareLock PeerListLock(&PeerListCSec); Peer *pPeer = GetPeer(rPacket.getAddr()); // not found? if (!pPeer) return false; // send the packet return pPeer->Send(rPacket); } bool C4NetIOUDP::Broadcast(const C4NetIOPacket &rPacket) // (mt-safe) { CStdShareLock PeerListLock(&PeerListCSec); // search: any client reachable via multicast? Peer *pPeer; for (pPeer = pPeerList; pPeer; pPeer = pPeer->Next) if (pPeer->Open() && pPeer->MultiCast() && pPeer->doBroadcast()) break; bool fSuccess = true; if (pPeer) { CStdLock OutLock(&OutCSec); // send it via multicast: encapsulate packet Packet *pPkt = new Packet(rPacket.Duplicate(), iOPacketCounter); iOPacketCounter += pPkt->FragmentCnt(); // add to list OPackets.AddPacket(pPkt); // send it fSuccess &= BroadcastDirect(*pPkt); } // send to all clients connected via du, too for (pPeer = pPeerList; pPeer; pPeer = pPeer->Next) if (pPeer->Open() && !pPeer->MultiCast() && pPeer->doBroadcast()) pPeer->Send(rPacket); return true; } bool C4NetIOUDP::SetBroadcast(const addr_t &addr, bool fSet) // (mt-safe) { CStdShareLock PeerListLock(&PeerListCSec); // find peer Peer *pPeer = GetPeer(addr); if (!pPeer) return false; // set flag pPeer->SetBroadcast(fSet); return true; } C4TimeMilliseconds C4NetIOUDP::GetNextTick(C4TimeMilliseconds tNow) // (mt-safe) { // maximum time: check interval C4TimeMilliseconds tTiming = tNextCheck.IsInfinite() ? tNow : Max(tNow, tNextCheck); // client timeouts (e.g. connection timeout) CStdShareLock PeerListLock(&PeerListCSec); for (Peer *pPeer = pPeerList; pPeer; pPeer = pPeer->Next) if (!pPeer->Closed()) if (!pPeer->GetTimeout().IsInfinite()) tTiming = Min(tTiming, pPeer->GetTimeout()); // return timing value return tTiming; } bool C4NetIOUDP::GetStatistic(int *pBroadcastRate) // (mt-safe) { CStdLock StatLock(&StatCSec); if (pBroadcastRate) *pBroadcastRate = iBroadcastRate; return true; } bool C4NetIOUDP::GetConnStatistic(const addr_t &addr, int *pIRate, int *pORate, int *pLoss) // (mt-safe) { CStdShareLock PeerListLock(&PeerListCSec); // find peer Peer *pPeer = GetPeer(addr); if (!pPeer || !pPeer->Open()) return false; // return statistics if (pIRate) *pIRate = pPeer->GetIRate(); if (pORate) *pORate = pPeer->GetORate(); if (pLoss) *pLoss = 0; return true; } void C4NetIOUDP::ClearStatistic() { CStdShareLock PeerListLock(&PeerListCSec); // clear all peer statistics for (Peer *pPeer = pPeerList; pPeer; pPeer = pPeer->Next) pPeer->ClearStatistics(); // broadcast statistics CStdLock StatLock(&StatCSec); iBroadcastRate = 0; } void C4NetIOUDP::OnPacket(const C4NetIOPacket &Packet, C4NetIO *pNetIO) { assert(pNetIO == this); #ifdef C4NETIO_DEBUG // log it DebugLogPkt(false, Packet); #endif // save packet? if (fSavePacket) { LastPacket.Copy(Packet); return; } // looped back? if (fMultiCast && !fDelayedLoopbackTest) if (AddrEqual(Packet.getAddr(), MCLoopbackAddr)) return; // loopback test packet? ignore if ((Packet.getStatus() & 0x7F) == IPID_Test) return; // address add? process directly // find out who's responsible Peer *pPeer = GetPeer(Packet.getAddr()); // new connection? if (!pPeer) { // ping? answer without creating a connection if ((Packet.getStatus() & 0x7F) == IPID_Ping) { PacketHdr PingPacket = { uint8_t(IPID_Ping | (Packet.getStatus() & 0x80)), 0 }; SendDirect(C4NetIOPacket(&PingPacket, sizeof(PingPacket), false, Packet.getAddr())); return; } // conn? create connection (du only!) else if (Packet.getStatus() == IPID_Conn) { pPeer = ConnectPeer(Packet.getAddr(), false); if (!pPeer) return; } // ignore all other packets } else { // address add? if (Packet.getStatus() == IPID_AddAddr) { OnAddAddress(Packet.getAddr(), *getBufPtr(Packet)); return; } // forward to Peer object pPeer->OnRecv(Packet); } } bool C4NetIOUDP::OnConn(const addr_t &AddrPeer, const addr_t &AddrConnect, const addr_t *pOwnAddr, C4NetIO *pNetIO) { // ignore return true; } void C4NetIOUDP::OnDisconn(const addr_t &AddrPeer, C4NetIO *pNetIO, const char *szReason) { assert(pNetIO == this); // C4NetIOSimple thinks the given address is no-good and we shouldn't consider // any connection to this address valid. // So let's check wether we have some peer there Peer *pPeer = GetPeer(AddrPeer); if (!pPeer) return; // And close him (this will issue another callback) pPeer->Close(szReason); } void C4NetIOUDP::OnAddAddress(const addr_t &FromAddr, const AddAddrPacket &Packet) { // Security (this would be strange behavior indeed...) if (!AddrEqual(FromAddr, Packet.Addr) && !AddrEqual(FromAddr, Packet.NewAddr)) return; // Search peer(s) Peer *pPeer = GetPeer(Packet.Addr); Peer *pPeer2 = GetPeer(Packet.NewAddr); // Equal or not found? Nothing to do... if (!pPeer || pPeer == pPeer2) return; // Save alternate address pPeer->SetAltAddr(Packet.NewAddr); // Close superflous connection // (this will generate a close-packet, which will be ignored by the peer) pPeer2->Close("address equivalence detected"); } // * C4NetIOUDP::Packet // construction / destruction C4NetIOUDP::Packet::Packet() : iNr(~0), Data(), pFragmentGot(NULL) { } C4NetIOUDP::Packet::Packet(C4NetIOPacket RREF rnData, nr_t inNr) : iNr(inNr), Data(rnData), pFragmentGot(NULL) { } C4NetIOUDP::Packet::~Packet() { delete [] pFragmentGot; pFragmentGot = NULL; } // implementation const size_t C4NetIOUDP::Packet::MaxSize = 512; const size_t C4NetIOUDP::Packet::MaxDataSize = MaxSize - sizeof(DataPacketHdr); C4NetIOUDP::Packet::nr_t C4NetIOUDP::Packet::FragmentCnt() const { return Data.getSize() ? (Data.getSize() - 1) / MaxDataSize + 1 : 1; } C4NetIOPacket C4NetIOUDP::Packet::GetFragment(nr_t iFNr, bool fBroadcastFlag) const { assert(iFNr < FragmentCnt()); // create buffer uint16_t iFragmentSize = FragmentSize(iFNr); StdBuf Packet; Packet.New(sizeof(DataPacketHdr) + iFragmentSize); // set up header DataPacketHdr *pnHdr = getMBufPtr(Packet); pnHdr->StatusByte = IPID_Data | (fBroadcastFlag ? 0x80 : 0x00); pnHdr->Nr = iNr + iFNr; pnHdr->FNr = iNr; pnHdr->Size = Data.getSize(); // copy data Packet.Write(Data.getPart(iFNr * MaxDataSize, iFragmentSize), sizeof(DataPacketHdr)); // return return C4NetIOPacket(Packet, Data.getAddr()); } bool C4NetIOUDP::Packet::Complete() const { if (Empty()) return false; for (unsigned int i = 0; i < FragmentCnt(); i++) if (!FragmentPresent(i)) return false; return true; } bool C4NetIOUDP::Packet::FragmentPresent(uint32_t iFNr) const { return !Empty() && iFNr < FragmentCnt() && (!pFragmentGot || pFragmentGot[iFNr]); } bool C4NetIOUDP::Packet::AddFragment(const C4NetIOPacket &Packet, const C4NetIO::addr_t &addr) { // ensure the packet is big enough if (Packet.getSize() < sizeof(DataPacketHdr)) return false; size_t iPacketDataSize = Packet.getSize() - sizeof(DataPacketHdr); // get header const DataPacketHdr *pHdr = getBufPtr(Packet); // first fragment got? bool fFirstFragment = Empty(); if (fFirstFragment) { // init iNr = pHdr->FNr; Data.New(pHdr->Size); Data.SetAddr(addr); // fragmented? create fragment list if (FragmentCnt() > 1) memset(pFragmentGot = new bool [FragmentCnt()], false, FragmentCnt()); // check header if (pHdr->Nr < iNr || pHdr->Nr >= iNr + FragmentCnt()) { Data.Clear(); return false; } } else { // check header if (pHdr->FNr != iNr) return false; if (pHdr->Size != Data.getSize()) return false; if (pHdr->Nr < iNr || pHdr->Nr >= iNr + FragmentCnt()) return false; } // check packet size nr_t iFNr = pHdr->Nr - iNr; if (iPacketDataSize != FragmentSize(iFNr)) return false; // already got this fragment? (needs check for first packet as FragmentPresent always assumes true if pFragmentGot is NULL) StdBuf PacketData = Packet.getPart(sizeof(DataPacketHdr), iPacketDataSize); if (!fFirstFragment && FragmentPresent(iFNr)) { // compare if (Data.Compare(PacketData, iFNr * MaxDataSize)) return false; } else { // otherwise: copy data Data.Write(PacketData, iFNr * MaxDataSize); // set flag (if fragmented) if (pFragmentGot) pFragmentGot[iFNr] = true; // shouldn't happen else assert(Complete()); } // ok return true; } size_t C4NetIOUDP::Packet::FragmentSize(nr_t iFNr) const { assert(iFNr < FragmentCnt()); return Min(MaxDataSize, Data.getSize() - iFNr * MaxDataSize); } // * C4NetIOUDP::PacketList // construction / destruction C4NetIOUDP::PacketList::PacketList(unsigned int inMaxPacketCnt) : pFront(NULL), pBack(NULL), iPacketCnt(0), iMaxPacketCnt(inMaxPacketCnt) { } C4NetIOUDP::PacketList::~PacketList() { Clear(); } C4NetIOUDP::Packet *C4NetIOUDP::PacketList::GetPacket(unsigned int iNr) { CStdShareLock ListLock(&ListCSec); for (Packet *pPkt = pBack; pPkt; pPkt = pPkt->Prev) if (pPkt->GetNr() == iNr) return pPkt; else if (pPkt->GetNr() < iNr) return NULL; return NULL; } C4NetIOUDP::Packet *C4NetIOUDP::PacketList::GetPacketFrgm(unsigned int iNr) { CStdShareLock ListLock(&ListCSec); for (Packet *pPkt = pBack; pPkt; pPkt = pPkt->Prev) if (pPkt->GetNr() <= iNr && pPkt->GetNr() + pPkt->FragmentCnt() > iNr) return pPkt; else if (pPkt->GetNr() < iNr) return NULL; return NULL; } C4NetIOUDP::Packet *C4NetIOUDP::PacketList::GetFirstPacketComplete() { CStdShareLock ListLock(&ListCSec); return pFront && pFront->Complete() ? pFront : NULL; } bool C4NetIOUDP::PacketList::FragmentPresent(unsigned int iNr) { CStdShareLock ListLock(&ListCSec); Packet *pPkt = GetPacketFrgm(iNr); return pPkt ? pPkt->FragmentPresent(iNr - pPkt->GetNr()) : false; } bool C4NetIOUDP::PacketList::AddPacket(Packet *pPacket) { CStdLock ListLock(&ListCSec); // find insert location Packet *pInsertAfter = pBack, *pInsertBefore = NULL; for (; pInsertAfter; pInsertBefore = pInsertAfter, pInsertAfter = pInsertAfter->Prev) if (pInsertAfter->GetNr() + pInsertAfter->FragmentCnt() <= pPacket->GetNr()) break; // check: enough space? if (pInsertBefore && pInsertBefore->GetNr() < pPacket->GetNr() + pPacket->FragmentCnt()) return false; // insert (pInsertAfter ? pInsertAfter->Next : pFront) = pPacket; (pInsertBefore ? pInsertBefore->Prev : pBack) = pPacket; pPacket->Next = pInsertBefore; pPacket->Prev = pInsertAfter; // count packets, check limit ++iPacketCnt; while (iPacketCnt > iMaxPacketCnt) DeletePacket(pFront); // ok return true; } bool C4NetIOUDP::PacketList::DeletePacket(Packet *pPacket) { CStdLock ListLock(&ListCSec); #ifdef _DEBUG // check: this list? Packet *pPos = pPacket; while (pPos && pPos != pFront) pPos = pPos->Prev; assert(pPos); #endif // unlink packet (pPacket->Prev ? pPacket->Prev->Next : pFront) = pPacket->Next; (pPacket->Next ? pPacket->Next->Prev : pBack) = pPacket->Prev; // delete packet delete pPacket; // decrease count --iPacketCnt; // ok return true; } void C4NetIOUDP::PacketList::ClearPackets(unsigned int iUntil) { CStdLock ListLock(&ListCSec); while (pFront && pFront->GetNr() < iUntil) DeletePacket(pFront); } void C4NetIOUDP::PacketList::Clear() { CStdLock ListLock(&ListCSec); while (iPacketCnt) DeletePacket(pFront); } // * C4NetIOUDP::Peer // constants const unsigned int C4NetIOUDP::Peer::iConnectRetries = 5; const unsigned int C4NetIOUDP::Peer::iReCheckInterval = 1000; // (ms) // construction / destruction C4NetIOUDP::Peer::Peer(const sockaddr_in &naddr, C4NetIOUDP *pnParent) : pParent(pnParent), addr(naddr), eStatus(CS_None), fMultiCast(false), fDoBroadcast(false), OPackets(iMaxOPacketBacklog), iOPacketCounter(0), iIPacketCounter(0), iRIPacketCounter(0), iIMCPacketCounter(0), iRIMCPacketCounter(0), iMCAckPacketCounter(0), tNextReCheck(C4TimeMilliseconds::NegativeInfinity), iIRate(0), iORate(0), iLoss(0) { ZeroMem(&addr2, sizeof(addr2)); ZeroMem(&PeerAddr, sizeof(PeerAddr)); } C4NetIOUDP::Peer::~Peer() { // send close-packet Close("deleted"); } bool C4NetIOUDP::Peer::Connect(bool fFailCallback) // (mt-safe) { // initiate connection (DoConn will set status CS_Conn) fMultiCast = false; fConnFailCallback = fFailCallback; return DoConn(false); } bool C4NetIOUDP::Peer::Send(const C4NetIOPacket &rPacket) // (mt-safe) { CStdLock OutLock(&OutCSec); // encapsulate packet Packet *pnPacket = new Packet(rPacket.Duplicate(), iOPacketCounter); iOPacketCounter += pnPacket->FragmentCnt(); pnPacket->GetData().SetAddr(addr); // add it to outgoing packet stack if (!OPackets.AddPacket(pnPacket)) return false; // This should be ensured by calling function anyway. // It is not secure to send packets before the connection // is etablished completly. if (eStatus != CS_Works) return true; // send it if (!SendDirect(*pnPacket)) { Close("failed to send packet"); return false; } return true; } bool C4NetIOUDP::Peer::Check(bool fForceCheck) { // only on working connections if (eStatus != CS_Works) return true; // prevent re-check (check floods) // instead, ask for other packets that are missing until recheck is allowed bool fNoReCheck = tNextReCheck > C4TimeMilliseconds::Now(); if (!fNoReCheck) iLastPacketAsked = iLastMCPacketAsked = 0; unsigned int iStartAt = fNoReCheck ? Max(iLastPacketAsked + 1, iIPacketCounter) : iIPacketCounter; unsigned int iStartAtMC = fNoReCheck ? Max(iLastMCPacketAsked + 1, iIMCPacketCounter) : iIMCPacketCounter; // check if we have something to ask for const unsigned int iMaxAskCnt = 10; unsigned int i, iAskList[iMaxAskCnt], iAskCnt = 0, iMCAskCnt = 0; for (i = iStartAt; i < iRIPacketCounter; i++) if (!IPackets.FragmentPresent(i)) if (iAskCnt < iMaxAskCnt) iLastPacketAsked = iAskList[iAskCnt++] = i; for (i = iStartAtMC; i < iRIMCPacketCounter; i++) if (!IMCPackets.FragmentPresent(i)) if (iAskCnt + iMCAskCnt < iMaxAskCnt) iLastMCPacketAsked = iAskList[iAskCnt + iMCAskCnt++] = i; int iEAskCnt = iAskCnt + iMCAskCnt; // no re-check limit? set it if (!fNoReCheck) { if (iEAskCnt) tNextReCheck = C4TimeMilliseconds::Now() + iReCheckInterval; else tNextReCheck = C4TimeMilliseconds::NegativeInfinity; } // something to ask for? (or check forced?) if (iEAskCnt || fForceCheck) return DoCheck(iAskCnt, iMCAskCnt, iAskList); return true; } void C4NetIOUDP::Peer::OnRecv(const C4NetIOPacket &rPacket) // (mt-safe) { // statistics { CStdLock StatLock(&StatCSec); iIRate += rPacket.getSize() + iUDPHeaderSize; } // get packet header if (rPacket.getSize() < sizeof(PacketHdr)) return; const PacketHdr *pHdr = getBufPtr(rPacket); bool fBroadcasted = !!(pHdr->StatusByte & 0x80); // save packet nr (fBroadcasted ? iRIMCPacketCounter : iRIPacketCounter) = Max((fBroadcasted ? iRIMCPacketCounter : iRIPacketCounter), pHdr->Nr); #ifdef C4NETIOUDP_OPT_RECV_CHECK_IMMEDIATE // do check if (eStatus == CS_Works) Check(false); #endif // what type of packet is it? switch (pHdr->StatusByte & 0x7f) { case IPID_Conn: { // check size if (rPacket.getSize() != sizeof(ConnPacket)) break; const ConnPacket *pPkt = getBufPtr(rPacket); // right version? if (pPkt->ProtocolVer != pParent->iVersion) break; if (!fBroadcasted) { // Second connection attempt using different address? if (PeerAddr.sin_addr.s_addr && !AddrEqual(PeerAddr, pPkt->Addr)) { // Notify peer that he has two addresses to reach this connection. AddAddrPacket Pkt; Pkt.StatusByte = IPID_AddAddr; Pkt.Nr = iOPacketCounter; Pkt.Addr = PeerAddr; Pkt.NewAddr = pPkt->Addr; SendDirect(C4NetIOPacket(&Pkt, sizeof(Pkt), false, addr)); // But do nothing else - don't interfere with this connection break; } // reinit? else if (eStatus == CS_Works && iIPacketCounter != pPkt->Nr) { // close (callback!) ... OnClose("reconnect"); eStatus = CS_Closed; // ... and reconnect Connect(false); } // save back the address the peer is using PeerAddr = pPkt->Addr; } // set packet counter if (fBroadcasted) iRIMCPacketCounter = iIMCPacketCounter = pPkt->Nr; else iRIPacketCounter = iIPacketCounter = pPkt->Nr; // clear incoming packets IPackets.Clear(); IMCPackets.Clear(); tNextReCheck = C4TimeMilliseconds::NegativeInfinity; iLastPacketAsked = iLastMCPacketAsked = 0; // Activate Multicast? if (!pParent->fMultiCast) if (pPkt->MCAddr.sin_addr.s_addr) { addr_t MCAddr = pPkt->MCAddr; // Init Broadcast (with delayed loopback test) pParent->fDelayedLoopbackTest = true; if (!pParent->InitBroadcast(&MCAddr)) pParent->fDelayedLoopbackTest = false; } // build ConnOk Packet ConnOKPacket nPack; nPack.StatusByte = IPID_ConnOK; // (always du, no mc experiments here) nPack.Nr = fBroadcasted ? pParent->iOPacketCounter : iOPacketCounter; nPack.Addr = addr; if (fBroadcasted) nPack.MCMode = ConnOKPacket::MCM_MCOK; // multicast send ok else if (pParent->fMultiCast && addr.sin_port == pParent->iPort) nPack.MCMode = ConnOKPacket::MCM_MC; // du ok, try multicast next else nPack.MCMode = ConnOKPacket::MCM_NoMC; // du ok // send it SendDirect(C4NetIOPacket(&nPack, sizeof(nPack), false, addr)); } break; case IPID_ConnOK: { if (eStatus != CS_Conn) break; // check size if (rPacket.getSize() != sizeof(ConnOKPacket)) break; const ConnOKPacket *pPkt = getBufPtr(rPacket); // save port PeerAddr = pPkt->Addr; // Needs another Conn/ConnOK-sequence? switch (pPkt->MCMode) { case ConnOKPacket::MCM_MC: // multicast has to be active if (pParent->fMultiCast) { // already trying to connect via multicast? if (fMultiCast) break; // Send another Conn packet back (this time broadcasted to check if multicast works) fMultiCast = true; DoConn(true); break; } // fallthru case ConnOKPacket::MCM_NoMC: // Connection is established (no multicast support) fMultiCast = false; OnConn(); break; case ConnOKPacket::MCM_MCOK: // Connection is established (multicast support) fMultiCast = true; OnConn(); break; } } break; case IPID_Data: { // get the packet header if (rPacket.getSize() < sizeof(DataPacketHdr)) return; const DataPacketHdr *pHdr = getBufPtr(rPacket); // already complet? if (pHdr->Nr < (fBroadcasted ? iIMCPacketCounter : iIPacketCounter)) break; // find or create packet bool fAddPacket = false; PacketList *pPacketList = fBroadcasted ? &IMCPackets : &IPackets; Packet *pPkt = pPacketList->GetPacket(pHdr->FNr); if (!pPkt) { pPkt = new Packet(); fAddPacket = true; } // add the fragment if (pPkt->AddFragment(rPacket, addr)) { // add the packet to list if (fAddPacket) if (!pPacketList->AddPacket(pPkt)) { delete pPkt; break; } // check for complete packets CheckCompleteIPackets(); } else // delete the packet if (fAddPacket) delete pPkt; } break; case IPID_Check: { // get the packet header if (rPacket.getSize() < sizeof(CheckPacketHdr)) break; const CheckPacketHdr *pPkt = getBufPtr(rPacket); // check packet size if (rPacket.getSize() < sizeof(CheckPacketHdr) + (pPkt->AskCount + pPkt->MCAskCount) * sizeof(int)) break; // clear all acknowledged packets CStdLock OutLock(&OutCSec); OPackets.ClearPackets(pPkt->AckNr); if (pPkt->MCAckNr > iMCAckPacketCounter) { iMCAckPacketCounter = pPkt->MCAckNr; pParent->ClearMCPackets(); } OutLock.Clear(); // read ask list const int *pAskList = getBufPtr(rPacket, sizeof(CheckPacketHdr)); // send the packets he asks for unsigned int i; for (i = 0; i < pPkt->AskCount + pPkt->MCAskCount; i++) { // packet available? bool fMCPacket = i >= pPkt->AskCount; CStdLock OutLock(fMCPacket ? &pParent->OutCSec : &OutCSec); Packet *pPkt2Send = (fMCPacket ? pParent->OPackets : OPackets).GetPacketFrgm(pAskList[i]); if (!pPkt2Send) { Close("starvation"); break; } // send the fragment if (fMCPacket) pParent->BroadcastDirect(*pPkt2Send, pAskList[i]); else SendDirect(*pPkt2Send, pAskList[i]); } } break; case IPID_Close: { // check packet size if (rPacket.getSize() < sizeof(ClosePacket)) break; const ClosePacket *pPkt = getBufPtr(rPacket); // ignore if it's for another address if (PeerAddr.sin_addr.s_addr && !AddrEqual(PeerAddr, pPkt->Addr)) break; // close OnClose("connection closed by peer"); } break; } } void C4NetIOUDP::Peer::Close(const char *szReason) // (mt-safe) { // already closed? if (eStatus == CS_Closed) return; // send close-packet ClosePacket Pkt; Pkt.StatusByte = IPID_Close; Pkt.Nr = 0; Pkt.Addr = addr; SendDirect(C4NetIOPacket(&Pkt, sizeof(Pkt), false, addr)); // callback OnClose(szReason); } void C4NetIOUDP::Peer::CheckTimeout() { // check if (C4TimeMilliseconds::Now() > tTimeout) OnTimeout(); } void C4NetIOUDP::Peer::ClearStatistics() { CStdLock StatLock(&StatCSec); iIRate = iORate = 0; iLoss = 0; } bool C4NetIOUDP::Peer::DoConn(bool fMC) // (mt-safe) { // set status eStatus = CS_Conn; // set timeout SetTimeout(iStdTimeout, iConnectRetries); // send packet (include current outgoing packet counter and mc addr) ConnPacket Pkt; Pkt.StatusByte = uint8_t(IPID_Conn) | (fMC ? 0x80 : 0x00); Pkt.ProtocolVer = pParent->iVersion; Pkt.Nr = fMC ? pParent->iOPacketCounter : iOPacketCounter; Pkt.Addr = addr; if (pParent->fMultiCast) Pkt.MCAddr = pParent->C4NetIOSimpleUDP::getMCAddr(); else memset(&Pkt.MCAddr, 0, sizeof Pkt.MCAddr); return SendDirect(C4NetIOPacket(&Pkt, sizeof(Pkt), false, addr)); } bool C4NetIOUDP::Peer::DoCheck(int iAskCnt, int iMCAskCnt, unsigned int *pAskList) { // security if (!pAskList) iAskCnt = iMCAskCnt = 0; // statistics { CStdLock StatLock(&StatCSec); iLoss += iAskCnt + iMCAskCnt; } // alloc data int iAskListSize = (iAskCnt + iMCAskCnt) * sizeof(*pAskList); StdBuf Packet; Packet.New(sizeof(CheckPacketHdr) + iAskListSize); CheckPacketHdr *pChkPkt = getMBufPtr(Packet); // set up header pChkPkt->StatusByte = IPID_Check; // (note: always du here, see C4NetIOUDP::DoCheck) pChkPkt->Nr = iOPacketCounter; pChkPkt->AckNr = iIPacketCounter; pChkPkt->MCAckNr = iIMCPacketCounter; // copy ask list pChkPkt->AskCount = iAskCnt; pChkPkt->MCAskCount = iMCAskCnt; if (pAskList) Packet.Write(pAskList, iAskListSize, sizeof(CheckPacketHdr)); // send packet return SendDirect(C4NetIOPacket(Packet, addr)); } bool C4NetIOUDP::Peer::SendDirect(const Packet &rPacket, unsigned int iNr) { // send one fragment only? if (iNr + 1) return SendDirect(rPacket.GetFragment(iNr - rPacket.GetNr())); // otherwise: send all fragments bool fSuccess = true; for (unsigned int i = 0; i < rPacket.FragmentCnt(); i++) fSuccess &= SendDirect(rPacket.GetFragment(i)); return fSuccess; } bool C4NetIOUDP::Peer::SendDirect(C4NetIOPacket RREF rPacket) // (mt-safe) { // insert correct addr if (!(rPacket.getStatus() & 0x80)) rPacket.SetAddr(addr); // count outgoing { CStdLock StatLock(&StatCSec); iORate += rPacket.getSize() + iUDPHeaderSize; } // forward call return pParent->SendDirect(std::move(rPacket)); } void C4NetIOUDP::Peer::OnConn() { // reset timeout SetTimeout(TO_INF); // set status eStatus = CS_Works; // do callback C4NetIO::CBClass *pCB = pParent->pCB; if (pCB && !pCB->OnConn(addr, addr, &PeerAddr, pParent)) { Close("closed"); return; } // do packet callback (in case the peer sent data while the connection was in progress) CheckCompleteIPackets(); } void C4NetIOUDP::Peer::OnClose(const char *szReason) // (mt-safe) { // do callback C4NetIO::CBClass *pCB = pParent->pCB; if (eStatus == CS_Works || (eStatus == CS_Conn && fConnFailCallback)) if (pCB) pCB->OnDisconn(addr, pParent, szReason); // set status (this will schedule this peer for deletion) eStatus = CS_Closed; } void C4NetIOUDP::Peer::CheckCompleteIPackets() { // only status CS_Works if (eStatus != CS_Works) return; // (If the status is CS_Conn, we'll have to wait until the connection in the // opposite direction is etablished. There is no problem in checking for // complete packets here, but the one using the interface may get very confused // if he gets a callback for a connection that hasn't been announced to him // yet) // check for complete incoming packets Packet *pPkt; while ((pPkt = IPackets.GetFirstPacketComplete())) { // missing packet? if (pPkt->GetNr() != iIPacketCounter) break; // do callback if (pParent->pCB) pParent->pCB->OnPacket(pPkt->GetData(), pParent); // advance packet counter iIPacketCounter = pPkt->GetNr() + pPkt->FragmentCnt(); // remove packet from queue IPackets.DeletePacket(pPkt); assert(!IPackets.GetPacketFrgm(pPkt->GetNr())); } while ((pPkt = IMCPackets.GetFirstPacketComplete())) { // missing packet? if (pPkt->GetNr() != iIMCPacketCounter) break; // do callback if (pParent->pCB) pParent->pCB->OnPacket(pPkt->GetData(), pParent); // advance packet counter iIMCPacketCounter = pPkt->GetNr() + pPkt->FragmentCnt(); // remove packet from queue IMCPackets.DeletePacket(pPkt); assert(!IMCPackets.GetPacketFrgm(pPkt->GetNr())); } } void C4NetIOUDP::Peer::SetTimeout(int iLength, int iRetryCnt) // (mt-safe) { if (iLength != TO_INF) { tTimeout = C4TimeMilliseconds::Now() + iLength; } else { tTimeout = C4TimeMilliseconds::PositiveInfinity; } iRetries = iRetryCnt; } void C4NetIOUDP::Peer::OnTimeout() { // what state? if (eStatus == CS_Conn) { // retries left? if (iRetries) { int iRetryCnt = iRetries - 1; // call DoConn (will set timeout) DoConn(fMultiCast); // set retry count iRetries = iRetryCnt; return; } // connection timeout: close Close("connection timeout"); } // reset timeout SetTimeout(TO_INF); } // * C4NetIOUDP: implementation bool C4NetIOUDP::BroadcastDirect(const Packet &rPacket, unsigned int iNr) // (mt-safe) { // only one fragment? if (iNr + 1) return SendDirect(rPacket.GetFragment(iNr - rPacket.GetNr(), true)); // send all fragments bool fSuccess = true; for (unsigned int iFrgm = 0; iFrgm < rPacket.FragmentCnt(); iFrgm++) fSuccess &= SendDirect(rPacket.GetFragment(iFrgm, true)); return fSuccess; } bool C4NetIOUDP::SendDirect(C4NetIOPacket RREF rPacket) // (mt-safe) { addr_t toaddr = rPacket.getAddr(); // packet meant to be broadcasted? if (rPacket.getStatus() & 0x80) { // set addr toaddr = C4NetIOSimpleUDP::getMCAddr(); // statistics CStdLock StatLock(&StatCSec); iBroadcastRate += rPacket.getSize() + iUDPHeaderSize; } // debug #ifdef C4NETIO_DEBUG { C4NetIOPacket Pkt2 = rPacket; Pkt2.SetAddr(toaddr); DebugLogPkt(true, Pkt2); } #endif #ifdef C4NETIO_SIMULATE_PACKETLOSS if ((rPacket.getStatus() & 0x7F) != IPID_Test) if (SafeRandom(100) < C4NETIO_SIMULATE_PACKETLOSS) return true; #endif // send it return C4NetIOSimpleUDP::Send(C4NetIOPacket(rPacket.getRef(), toaddr)); } bool C4NetIOUDP::DoLoopbackTest() { // (try to) enable loopback C4NetIOSimpleUDP::SetMCLoopback(true); // ensure loopback is activate if (!C4NetIOSimpleUDP::getMCLoopback()) return false; // send test packet const PacketHdr TestPacket = { uint8_t(IPID_Test | 0x80), static_cast(rand()) }; if (!C4NetIOSimpleUDP::Broadcast(C4NetIOPacket(&TestPacket, sizeof(TestPacket)))) return false; // wait for socket to become readable (should happen immediatly, do not expect packet loss) fSavePacket = true; if (!C4NetIOSimpleUDP::Execute(iStdTimeout)) { fSavePacket = false; if (!GetError()) SetError("Multicast disabled: loopback test failed"); return false; } fSavePacket = false; // compare it to the packet that was sent if (LastPacket.getSize() != sizeof(TestPacket) || LastPacket.Compare(&TestPacket, sizeof(TestPacket))) { SetError("Multicast disabled: loopback test failed"); return false; } // save the loopback addr back MCLoopbackAddr = LastPacket.getAddr(); // disable loopback C4NetIOSimpleUDP::SetMCLoopback(false); // ok return true; } void C4NetIOUDP::ClearMCPackets() { CStdShareLock PeerListLock(&PeerListCSec); CStdLock OutLock(&OutCSec); // clear packets if no client is present if (!pPeerList) OPackets.Clear(); else { // find minimum acknowledged packet number unsigned int iAckNr = pPeerList->GetMCAckPacketCounter(); for (Peer *pPeer = pPeerList->Next; pPeer; pPeer = pPeer->Next) iAckNr = Min(iAckNr, pPeerList->GetMCAckPacketCounter()); // clear packets OPackets.ClearPackets(iAckNr); } } void C4NetIOUDP::AddPeer(Peer *pPeer) { // get locks CStdShareLock PeerListLock(&PeerListCSec); CStdLock PeerListAddLock(&PeerListAddCSec); // add pPeer->Next = pPeerList; pPeerList = pPeer; Changed(); } void C4NetIOUDP::OnShareFree(CStdCSecEx *pCSec) { if (pCSec == &PeerListCSec) { Peer *pPeer = pPeerList, *pLast = NULL; while (pPeer) { // delete? if (pPeer->Closed()) { // unlink Peer *pDelete = pPeer; (pLast ? pLast->Next : pPeerList) = pPeer = pPeer->Next; // delete delete pDelete; } else { // next peer pLast = pPeer; pPeer = pPeer->Next; } } } } C4NetIOUDP::Peer *C4NetIOUDP::GetPeer(const addr_t &addr) { CStdShareLock PeerListLock(&PeerListCSec); for (Peer *pPeer = pPeerList; pPeer; pPeer = pPeer->Next) if (!pPeer->Closed()) if (AddrEqual(pPeer->GetAddr(), addr) || AddrEqual(pPeer->GetAltAddr(), addr)) return pPeer; return NULL; } C4NetIOUDP::Peer *C4NetIOUDP::ConnectPeer(const addr_t &PeerAddr, bool fFailCallback) // (mt-safe) { CStdShareLock PeerListLock(&PeerListCSec); // lock so no new peer can be added after this point CStdLock PeerListAddLock(&PeerListAddCSec); // recheck: address already known? Peer *pnPeer = GetPeer(PeerAddr); if (pnPeer) return pnPeer; // create new Peer class pnPeer = new Peer(PeerAddr, this); if (!pnPeer) return NULL; // add peer to list AddPeer(pnPeer); PeerListAddLock.Clear(); // send connection request if (!pnPeer->Connect(fFailCallback)) { pnPeer->Close("connect failed"); return NULL; } // ok (do not wait for peer) return pnPeer; } void C4NetIOUDP::DoCheck() // (mt-safe) { CStdShareLock PeerListLock(&PeerListCSec); // mc connection check? if (fMultiCast) { // only if a peer is connected via multicast Peer *pPeer; for (pPeer = pPeerList; pPeer; pPeer = pPeer->Next) if (pPeer->Open() && pPeer->MultiCast()) break; if (pPeer) { // set up packet CheckPacketHdr Pkt; Pkt.StatusByte = uint8_t(IPID_Check | 0x80); Pkt.Nr = iOPacketCounter; Pkt.AskCount = Pkt.MCAskCount = 0; // send it SendDirect(C4NetIOPacket(&Pkt, sizeof(Pkt))); } } // peer connection checks for (Peer *pPeer = pPeerList; pPeer; pPeer = pPeer->Next) if (pPeer->Open()) pPeer->Check(); // set time for next check tNextCheck = C4TimeMilliseconds::Now() + iCheckInterval; } // debug #ifdef C4NETIO_DEBUG #ifndef _WIN32 #define _O_SEQUENTIAL 0 #define _O_TEXT 0 #endif void C4NetIOUDP::OpenDebugLog() { const char *szFileBase = "NetIOUDP%d.log"; char szFilePath[_MAX_PATH + 1]; for (int i = 0; i < 1000; i++) { sprintf(szFilePath, szFileBase, i); hDebugLog = open(szFilePath, O_CREAT | O_EXCL | O_TRUNC | _O_SEQUENTIAL | _O_TEXT | O_WRONLY, S_IREAD | S_IWRITE); if (hDebugLog != -1) break; } // initial timestamp if (hDebugLog != -1) { char O[1024+1]; time_t tTime; time(&tTime); struct tm *pLocalTime; pLocalTime=localtime(&tTime); if (pLocalTime) sprintf(O, "C4NetIOUDP debuglog starting at %d/%d/%d %d:%2d:%2d - (Daylight %d)\n", pLocalTime->tm_mon+1, pLocalTime->tm_mday, pLocalTime->tm_year+1900, pLocalTime->tm_hour, pLocalTime->tm_min, pLocalTime->tm_sec, pLocalTime->tm_isdst); else sprintf(O, "C4NetIOUDP debuglog; time not available\n"); write(hDebugLog, O, strlen(O)); } } void C4NetIOUDP::CloseDebugLog() { close(hDebugLog); } void C4NetIOUDP::DebugLogPkt(bool fOut, const C4NetIOPacket &Pkt) { StdStrBuf O; O.Format("%s %s %s:%d:", fOut ? "out" : "in ", C4TimeMilliseconds::Now().AsString().getData(), inet_ntoa(Pkt.getAddr().sin_addr), htons(Pkt.getAddr().sin_port)); // header? if (Pkt.getSize() >= sizeof(PacketHdr)) { const PacketHdr &Hdr = *getBufPtr(Pkt); switch (Hdr.StatusByte & 0x07f) { case IPID_Ping: O.Append(" PING"); break; case IPID_Test: O.Append(" TEST"); break; case IPID_Conn: O.Append(" CONN"); break; case IPID_ConnOK: O.Append(" CONO"); break; case IPID_Data: O.Append(" DATA"); break; case IPID_Check: O.Append(" CHCK"); break; case IPID_Close: O.Append(" CLSE"); break; default: O.Append(" UNKN"); break; } O.AppendFormat(" %s %04d", (Hdr.StatusByte & 0x80) ? "MC" : "DU", Hdr.Nr); #define UPACK(type) \ const type &P = *getBufPtr(Pkt); switch (Hdr.StatusByte) { case IPID_Test: { UPACK(TestPacket); O.AppendFormat(" (%d)", P.TestNr); break; } case IPID_Conn: { UPACK(ConnPacket); O.AppendFormat(" (Ver %d, MC: %s:%d)", P.ProtocolVer, inet_ntoa(P.MCAddr.sin_addr), htons(P.MCAddr.sin_port)); break; } case IPID_ConnOK: { UPACK(ConnOKPacket); switch (P.MCMode) { case ConnOKPacket::MCM_NoMC: O.Append(" (NoMC)"); break; case ConnOKPacket::MCM_MC: O.Append(" (MC)"); break; case ConnOKPacket::MCM_MCOK: O.Append(" (MCOK)"); break; default: O.Append(" (??""?)"); } break; } case IPID_Data: { UPACK(DataPacketHdr); O.AppendFormat(" (f: %d s: %d)", P.FNr, P.Size); for (int iPos = sizeof(DataPacketHdr); iPos < Min(Pkt.getSize(), sizeof(DataPacketHdr) + 16); iPos++) O.AppendFormat(" %02x", *getBufPtr(Pkt, iPos)); break; } case IPID_Check: { UPACK(CheckPacketHdr); O.AppendFormat(" (ack: %d, mcack: %d, ask: %d mcask: %d, ", P.AckNr, P.MCAckNr, P.AskCount, P.MCAskCount); if (Pkt.getSize() < sizeof(CheckPacketHdr) + sizeof(unsigned int) * (P.AskCount + P.MCAskCount)) O.AppendFormat("too small)"); else { O.Append("["); for (unsigned int i = 0; i < P.AskCount + P.MCAskCount; i++) O.AppendFormat("%s%d", i ? ", " : "", *getBufPtr(Pkt, sizeof(CheckPacketHdr) + i * sizeof(unsigned int))); O.Append("])"); } break; } } } O.AppendFormat(" (%d bytes)\n", Pkt.getSize()); write(hDebugLog, O.getData(), O.getLength()); } #endif // *** C4NetIOMan C4NetIOMan::C4NetIOMan() : StdSchedulerThread(), iNetIOCnt(0), iNetIOCapacity(0), ppNetIO(NULL) { } C4NetIOMan::~C4NetIOMan() { Clear(); } void C4NetIOMan::Clear() { delete[] ppNetIO; ppNetIO = NULL; iNetIOCnt = iNetIOCapacity = 0; StdSchedulerThread::Clear(); } void C4NetIOMan::AddIO(C4NetIO *pNetIO, bool fSetCallback) { // Set callback if (fSetCallback) pNetIO->SetCallback(this); // Add to i/o list if (iNetIOCnt + 1 > iNetIOCapacity) EnlargeIO(1); ppNetIO[iNetIOCnt++] = pNetIO; // Register with scheduler Add(pNetIO); } void C4NetIOMan::RemoveIO(C4NetIO *pNetIO) { // Search int i; for (i = 0; i < iNetIOCnt; i++) if (ppNetIO[i] == pNetIO) break; // Not found? if (i >= iNetIOCnt) return; // Remove for (i++; i < iNetIOCnt; i++) ppNetIO[i-1] = ppNetIO[i]; iNetIOCnt--; } void C4NetIOMan::OnError(StdSchedulerProc *pProc) { for (int i = 0; i < iNetIOCnt; i++) if (pProc == ppNetIO[i]) OnError(ppNetIO[i]->GetError(), ppNetIO[i]); } void C4NetIOMan::EnlargeIO(int iBy) { iNetIOCapacity += iBy; // Realloc C4NetIO **ppnNetIO = new C4NetIO *[iNetIOCapacity]; // Set data for (int i = 0; i < iNetIOCnt; i++) ppnNetIO[i] = ppNetIO[i]; delete[] ppNetIO; ppNetIO = ppnNetIO; } // *** helpers bool ResolveAddress(const char *szAddress, C4NetIO::addr_t *paddr, uint16_t iPort) { assert(szAddress && paddr); // port? StdStrBuf Buf; const char *pColon = strchr(szAddress, ':'); if (pColon) { // get port iPort = atoi(pColon + 1); // copy address Buf.CopyUntil(szAddress, ':'); szAddress = Buf.getData(); } // set up address sockaddr_in raddr; ZeroMem(&raddr, sizeof raddr); raddr.sin_family = AF_INET; raddr.sin_port = htons(iPort); // no plain IP address? if ((raddr.sin_addr.s_addr = inet_addr(szAddress)) == INADDR_NONE) { #ifdef HAVE_WINSOCK if (!AcquireWinSock()) return false; #endif // resolve hostent *pHost; if (!(pHost = gethostbyname(szAddress))) #ifdef HAVE_WINSOCK { ReleaseWinSock(); return false; } ReleaseWinSock(); #else return false; #endif // correct type? if (pHost->h_addrtype != AF_INET || pHost->h_length != sizeof(in_addr)) return false; // get address raddr.sin_addr = *reinterpret_cast(pHost->h_addr_list[0]); } // ok *paddr = raddr; return true; }