//static HRESULT CStunSocket::Create(const CSocketAddress& addrlocal, SocketRole role, boost::shared_ptr<CStunSocket>* pStunSocketShared) { int sock = -1; int ret; CStunSocket* pSocket = NULL; sockaddr_storage addrBind = {}; socklen_t sizeaddrBind; HRESULT hr = S_OK; ChkIfA(pStunSocketShared == NULL, E_INVALIDARG); sock = socket(addrlocal.GetFamily(), SOCK_DGRAM, 0); ChkIf(sock < 0, ERRNOHR); ret = bind(sock, addrlocal.GetSockAddr(), addrlocal.GetSockAddrLength()); ChkIf(ret < 0, ERRNOHR); // call get sockname to find out what port we just binded to. (Useful for when addrLocal.port is 0) sizeaddrBind = sizeof(addrBind); ret = ::getsockname(sock, (sockaddr*)&addrBind, &sizeaddrBind); ChkIf(ret < 0, ERRNOHR); pSocket = new CStunSocket(); pSocket->_sock = sock; pSocket->_addrlocal = CSocketAddress(*(sockaddr*)&addrBind); pSocket->_role = role; sock = -1; { boost::shared_ptr<CStunSocket> spTmp(pSocket); pStunSocketShared->swap(spTmp); } Cleanup: if (sock != -1) { close(sock); sock = -1; } return hr; }
// This test validates that the EnablePktInfoOption set on a socket allows us to get at the destination IP address for incoming packets // This is needed so that we can correctly insert an origin address into responses from the server // Otherwise, the server doesn't have a way of knowing which interface a packet arrived on when it's listening on INADDR_ANY (all available addresses) HRESULT CTestRecvFromEx::DoTest(bool fIPV6) { // create a couple of sockets for send/recv HRESULT hr = S_OK; CSocketAddress addrAny(0,0); // INADDR_ANY, random port sockaddr_in6 addrAnyIPV6 = {}; uint16_t portRecv = 0; CRefCountedStunSocket spSocketSend, spSocketRecv; fd_set set = {}; CSocketAddress addrDestForSend; CSocketAddress addrDestOnRecv; CSocketAddress addrSrcOnRecv; CSocketAddress addrSrc; CSocketAddress addrDst; char ch = 'x'; sockaddr_storage addrDummy; socklen_t addrlength; int ret; timeval tv = {}; if (fIPV6) { addrAnyIPV6.sin6_family = AF_INET6; addrAny = CSocketAddress(addrAnyIPV6); } // create two sockets listening on INADDR_ANY. One for sending and one for receiving ChkA(CStunSocket::Create(addrAny, RolePP, &spSocketSend)); ChkA(CStunSocket::Create(addrAny, RolePP, &spSocketRecv)); spSocketRecv->EnablePktInfoOption(true); portRecv = spSocketRecv->GetLocalAddress().GetPort(); // now send to localhost if (fIPV6) { sockaddr_in6 addr6 = {}; addr6.sin6_family = AF_INET6; ::inet_pton(AF_INET6, "::1", &(addr6.sin6_addr)); addrDestForSend = CSocketAddress(addr6); } else { sockaddr_in addr4 = {}; addr4.sin_family = AF_INET; ::inet_pton(AF_INET, "127.0.0.1", &(addr4.sin_addr)); addrDestForSend = CSocketAddress(addr4); } addrDestForSend.SetPort(portRecv); // flush out any residual packets that might be buffered up on recv socket ret = -1; do { addrlength = sizeof(addrDummy); ret = ::recvfrom(spSocketRecv->GetSocketHandle(), &ch, sizeof(ch), MSG_DONTWAIT, (sockaddr*)&addrDummy, &addrlength); } while (ret >= 0); // now send some data to ourselves ret = sendto(spSocketSend->GetSocketHandle(), &ch, sizeof(ch), 0, addrDestForSend.GetSockAddr(), addrDestForSend.GetSockAddrLength()); ChkIfA(ret <= 0, E_UNEXPECTED); // now wait for the data to arrive FD_ZERO(&set); FD_SET(spSocketRecv->GetSocketHandle(), &set); tv.tv_sec = 3; ret = select(spSocketRecv->GetSocketHandle()+1, &set, NULL, NULL, &tv); ChkIfA(ret <= 0, E_UNEXPECTED); ret = ::recvfromex(spSocketRecv->GetSocketHandle(), &ch, 1, MSG_DONTWAIT, &addrSrcOnRecv, &addrDestOnRecv); ChkIfA(ret <= 0, E_UNEXPECTED); ChkIfA(addrSrcOnRecv.IsIPAddressZero(), E_UNEXPECTED); ChkIfA(addrDestOnRecv.IsIPAddressZero(), E_UNEXPECTED); Cleanup: return hr; }
HRESULT UdpClientLoop(StunClientLogicConfig& config, const ClientSocketConfig& socketconfig) { HRESULT hr = S_OK; CRefCountedStunSocket spStunSocket; CStunSocket stunSocket;; CRefCountedBuffer spMsg(new CBuffer(MAX_STUN_MESSAGE_SIZE)); int sock = -1; CSocketAddress addrDest; // who we send to CSocketAddress addrRemote; // who we CSocketAddress addrLocal; int ret; fd_set set; timeval tv = {}; std::string strAddr; std::string strAddrLocal; StunClientResults results; CStunClientLogic clientlogic; hr = clientlogic.Initialize(config); if (FAILED(hr)) { Logging::LogMsg(LL_ALWAYS, "Unable to initialize client: (error = x%x)", hr); Chk(hr); } hr = stunSocket.UDPInit(socketconfig.addrLocal, RolePP); if (FAILED(hr)) { Logging::LogMsg(LL_ALWAYS, "Unable to create local socket: (error = x%x)", hr); Chk(hr); } stunSocket.EnablePktInfoOption(true); sock = stunSocket.GetSocketHandle(); // let's get a loop going! while (true) { HRESULT hrRet; spMsg->SetSize(0); hrRet = clientlogic.GetNextMessage(spMsg, &addrDest, GetMillisecondCounter()); if (SUCCEEDED(hrRet)) { addrDest.ToString(&strAddr); ASSERT(spMsg->GetSize() > 0); if (Logging::GetLogLevel() >= LL_DEBUG) { std::string strAddr; addrDest.ToString(&strAddr); Logging::LogMsg(LL_DEBUG, "Sending message to %s", strAddr.c_str()); } ret = ::sendto(sock, spMsg->GetData(), spMsg->GetSize(), 0, addrDest.GetSockAddr(), addrDest.GetSockAddrLength()); if (ret <= 0) { Logging::LogMsg(LL_DEBUG, "ERROR. sendto failed (errno = %d)", errno); } // there's not much we can do if "sendto" fails except time out and try again } else if (hrRet == E_STUNCLIENT_STILL_WAITING) { Logging::LogMsg(LL_DEBUG, "Continuing to wait for response..."); } else if (hrRet == E_STUNCLIENT_RESULTS_READY) { break; } else { Logging::LogMsg(LL_DEBUG, "Fatal error (hr == %x)", hrRet); Chk(hrRet); } // now wait for a response spMsg->SetSize(0); FD_ZERO(&set); FD_SET(sock, &set); tv.tv_usec = 500000; // half-second tv.tv_sec = config.timeoutSeconds; ret = select(sock+1, &set, NULL, NULL, &tv); if (ret > 0) { ret = ::recvfromex(sock, spMsg->GetData(), spMsg->GetAllocatedSize(), MSG_DONTWAIT, &addrRemote, &addrLocal); if (ret > 0) { addrRemote.ToString(&strAddr); addrLocal.ToString(&strAddrLocal); Logging::LogMsg(LL_DEBUG, "Got response (%d bytes) from %s on interface %s", ret, strAddr.c_str(), strAddrLocal.c_str()); spMsg->SetSize(ret); clientlogic.ProcessResponse(spMsg, addrRemote, addrLocal); } } } results.Init(); clientlogic.GetResults(&results); DumpResults(config, results); Cleanup: return hr; }