stunserver/client/0000755000175000017500000000000012346765410014400 5ustar jselbiejselbiestunserver/client/Makefile0000644000175000017500000000070212346765410016037 0ustar jselbiejselbieinclude ../common.inc PROJECT_TARGET := stunclient PROJECT_OBJS := clientmain.o INCLUDES := $(BOOST_INCLUDE) -I../common -I../stuncore -I../networkutils -I../resources LIB_PATH := -L../common -L../stuncore -L../networkutils LIBS := -lnetworkutils -lstuncore -lcommon -lcrypto all: $(PROJECT_TARGET) clean: rm -f $(PROJECT_OBJS) $(PROJECT_TARGET) $(PROJECT_TARGET): $(PROJECT_OBJS) $(LINK.cpp) -o $@ $^ $(LIB_PATH) $(LIBS) $(SOCKET_LIBS) stunserver/client/clientmain.cpp0000644000175000017500000004553412346765410017242 0ustar jselbiejselbie/* Copyright 2011 John Selbie Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include "commonincludes.hpp" #include "stuncore.h" #include "socketrole.h" // so we can re-use the "SocketRole" definitions again #include "stunsocket.h" #include "cmdlineparser.h" #include "recvfromex.h" #include "resolvehostname.h" #include "stringhelper.h" #include "adapters.h" #include "oshelper.h" #include "prettyprint.h" // These files are in ../resources #include "stunclient.txtcode" #include "stunclient_lite.txtcode" struct ClientCmdLineArgs { std::string strRemoteServer; std::string strRemotePort; std::string strLocalAddr; std::string strLocalPort; std::string strMode; std::string strFamily; std::string strProtocol; std::string strVerbosity; std::string strHelp; }; struct ClientSocketConfig { int family; int socktype; CSocketAddress addrLocal; }; void DumpConfig(StunClientLogicConfig& config, ClientSocketConfig& socketConfig) { std::string strAddr; Logging::LogMsg(LL_DEBUG, "config.fBehaviorTest = %s", config.fBehaviorTest?"true":"false"); Logging::LogMsg(LL_DEBUG, "config.fFilteringTest = %s", config.fFilteringTest?"true":"false"); Logging::LogMsg(LL_DEBUG, "config.timeoutSeconds = %d", config.timeoutSeconds); Logging::LogMsg(LL_DEBUG, "config.uMaxAttempts = %d", config.uMaxAttempts); config.addrServer.ToString(&strAddr); Logging::LogMsg(LL_DEBUG, "config.addrServer = %s", strAddr.c_str()); socketConfig.addrLocal.ToString(&strAddr); Logging::LogMsg(LL_DEBUG, "socketconfig.addrLocal = %s", strAddr.c_str()); } void PrintUsage(bool fSummaryUsage) { size_t width = GetConsoleWidth(); const char* psz = fSummaryUsage ? stunclient_lite_text : stunclient_text; // save some margin space if (width > 2) { width -= 2; } PrettyPrint(psz, width); } HRESULT CreateConfigFromCommandLine(ClientCmdLineArgs& args, StunClientLogicConfig* pConfig, ClientSocketConfig* pSocketConfig) { HRESULT hr = S_OK; StunClientLogicConfig& config = *pConfig; ClientSocketConfig& socketconfig = *pSocketConfig; int ret; uint16_t localport = 0; uint16_t remoteport = 0; int nPort = 0; char szIP[100]; bool fTCP = false; config.fBehaviorTest = false; config.fFilteringTest = false; config.fTimeoutIsInstant = false; config.timeoutSeconds = 0; // use default config.uMaxAttempts = 0; socketconfig.family = AF_INET; socketconfig.socktype = SOCK_DGRAM; socketconfig.addrLocal = CSocketAddress(0, 0); ChkIfA(pConfig == NULL, E_INVALIDARG); ChkIfA(pSocketConfig==NULL, E_INVALIDARG); // family (protocol type) ------------------------------------ if (StringHelper::IsNullOrEmpty(args.strFamily.c_str())==false) { int optvalue = atoi(args.strFamily.c_str()); switch (optvalue) { case 4: socketconfig.family = AF_INET; break; case 6: socketconfig.family = AF_INET6; break; default: { Logging::LogMsg(LL_ALWAYS, "Family option must be either 4 or 6"); Chk(E_INVALIDARG); } } } // protocol -------------------------------------------- StringHelper::ToLower(args.strProtocol); if (StringHelper::IsNullOrEmpty(args.strProtocol.c_str()) == false) { if ((args.strProtocol != "udp") && (args.strProtocol != "tcp")) { Logging::LogMsg(LL_ALWAYS, "Only udp and tcp are supported protocol versions"); Chk(E_INVALIDARG); } if (args.strProtocol == "tcp") { fTCP = true; socketconfig.socktype = SOCK_STREAM; config.uMaxAttempts = 1; } } // remote port --------------------------------------------- if (StringHelper::IsNullOrEmpty(args.strRemotePort.c_str()) == false) { ret = StringHelper::ValidateNumberString(args.strRemotePort.c_str(), 1, 0xffff, &nPort); if (ret < 0) { Logging::LogMsg(LL_ALWAYS, "Remote port must be between 1 - 65535"); Chk(E_INVALIDARG); } remoteport = (uint16_t)(unsigned int)nPort; } else { remoteport = DEFAULT_STUN_PORT; } // remote server ----------------------------------------- if (StringHelper::IsNullOrEmpty(args.strRemoteServer.c_str())) { Logging::LogMsg(LL_ALWAYS, "No server address specified"); Chk(E_INVALIDARG); } hr = ::ResolveHostName(args.strRemoteServer.c_str(), socketconfig.family, false, &config.addrServer); if (FAILED(hr)) { Logging::LogMsg(LL_ALWAYS, "Unable to resolve hostname for %s", args.strRemoteServer.c_str()); Chk(hr); } config.addrServer.ToStringBuffer(szIP, ARRAYSIZE(szIP)); Logging::LogMsg(LL_DEBUG, "Resolved %s to %s", args.strRemoteServer.c_str(), szIP); config.addrServer.SetPort(remoteport); // local port -------------------------------------------- if (StringHelper::IsNullOrEmpty(args.strLocalPort.c_str()) == false) { ret = StringHelper::ValidateNumberString(args.strLocalPort.c_str(), 1, 0xffff, &nPort); if (ret < 0) { Logging::LogMsg(LL_ALWAYS, "Local port must be between 1 - 65535"); Chk(E_INVALIDARG); } localport = (uint16_t)(unsigned int)nPort; } // local address ------------------------------------------ if (StringHelper::IsNullOrEmpty(args.strLocalAddr.c_str()) == false) { hr = GetSocketAddressForAdapter(socketconfig.family, args.strLocalAddr.c_str(), localport, &socketconfig.addrLocal); if (FAILED(hr)) { Logging::LogMsg(LL_ALWAYS, "Unable to find matching adapter or interface for local address option"); Chk(hr); } } else { if (socketconfig.family == AF_INET6) { sockaddr_in6 addr6 = {}; addr6.sin6_family = AF_INET6; socketconfig.addrLocal = CSocketAddress(addr6); socketconfig.addrLocal.SetPort(localport); } else { socketconfig.addrLocal = CSocketAddress(0,localport); } } // mode --------------------------------------------- StringHelper::ToLower(args.strMode); if (StringHelper::IsNullOrEmpty(args.strMode.c_str()) == false) { if (args.strMode == "basic") { ; } else if (args.strMode == "full") { config.fBehaviorTest = true; config.fFilteringTest = (fTCP == false); // impossible to to a filtering test in TCP } else { Logging::LogMsg(LL_ALWAYS, "Mode option must be 'full' or 'basic'"); } } Cleanup: return hr; } void NatBehaviorToString(NatBehavior behavior, std::string* pStr) { std::string& str = *pStr; switch (behavior) { case UnknownBehavior: str="Unknown Behavior"; break; case DirectMapping: str="Direct Mapping"; break; case EndpointIndependentMapping: str = "Endpoint Independent Mapping"; break; case AddressDependentMapping: str = "Address Dependent Mapping"; break; case AddressAndPortDependentMapping: str = "Address and Port Dependent Mapping"; break; default: ASSERT(false); str = ""; break; } } void NatFilteringToString(NatFiltering filtering, std::string* pStr) { std::string& str = *pStr; switch (filtering) { case UnknownFiltering: str="Unknown Behavior"; break; case DirectConnectionFiltering: str="Direct Mapping"; break; case EndpointIndependentFiltering: str = "Endpoint Independent Filtering"; break; case AddressDependentFiltering: str = "Address Dependent Filtering"; break; case AddressAndPortDependentFiltering: str = "Address and Port Dependent Filtering"; break; default: ASSERT(false); str = ""; break; } } void DumpResults(StunClientLogicConfig& config, StunClientResults& results) { char szBuffer[100]; const int buffersize = 100; std::string strResult; Logging::LogMsg(LL_ALWAYS, "Binding test: %s", results.fBindingTestSuccess?"success":"fail"); if (results.fBindingTestSuccess) { results.addrLocal.ToStringBuffer(szBuffer, buffersize); Logging::LogMsg(LL_ALWAYS, "Local address: %s", szBuffer); results.addrMapped.ToStringBuffer(szBuffer, buffersize); Logging::LogMsg(LL_ALWAYS, "Mapped address: %s", szBuffer); } if (config.fBehaviorTest) { Logging::LogMsg(LL_ALWAYS, "Behavior test: %s", results.fBehaviorTestSuccess?"success":"fail"); if (results.fBehaviorTestSuccess) { NatBehaviorToString(results.behavior, &strResult); Logging::LogMsg(LL_ALWAYS, "Nat behavior: %s", strResult.c_str()); } } if (config.fFilteringTest) { Logging::LogMsg(LL_ALWAYS, "Filtering test: %s", results.fFilteringTestSuccess?"success":"fail"); if (results.fFilteringTestSuccess) { NatFilteringToString(results.filtering, &strResult); Logging::LogMsg(LL_ALWAYS, "Nat filtering: %s", strResult.c_str()); } } } void TcpClientLoop(StunClientLogicConfig& config, ClientSocketConfig& socketconfig) { HRESULT hr = S_OK; CStunSocket stunsocket; CStunClientLogic clientlogic; int sock; CRefCountedBuffer spMsg(new CBuffer(1500)); CRefCountedBuffer spMsgReader(new CBuffer(1500)); CSocketAddress addrDest, addrLocal; HRESULT hrRet, hrResult; int ret; size_t bytes_sent, bytes_recv; size_t bytes_to_send, max_bytes_recv, remaining; uint8_t* pData=NULL; size_t readsize; CStunMessageReader reader; StunClientResults results; hr= clientlogic.Initialize(config); if (FAILED(hr)) { Logging::LogMsg(LL_ALWAYS, "clientlogic.Initialize failed (hr == %x)", hr); Chk(hr); } while (true) { stunsocket.Close(); hr = stunsocket.TCPInit(socketconfig.addrLocal, RolePP, true); if (FAILED(hr)) { Logging::LogMsg(LL_ALWAYS, "Unable to create local socket for TCP connection (hr == %x)", hr); Chk(hr); } hrRet = clientlogic.GetNextMessage(spMsg, &addrDest, ::GetMillisecondCounter()); if (hrRet == E_STUNCLIENT_RESULTS_READY) { // clean exit break; } // we should never get a "still waiting" return with TCP, because config.timeout is 0 ASSERT(hrRet != E_STUNCLIENT_STILL_WAITING); if (FAILED(hrRet)) { Chk(hrRet); } // connect to server sock = stunsocket.GetSocketHandle(); ret = ::connect(sock, addrDest.GetSockAddr(), addrDest.GetSockAddrLength()); if (ret == -1) { hrResult = ERRNOHR; Logging::LogMsg(LL_ALWAYS, "Can't connect to server (hr == %x)", hrResult); Chk(hrResult); } Logging::LogMsg(LL_DEBUG, "Connected to server"); bytes_to_send = (int)(spMsg->GetSize()); bytes_sent = 0; pData = spMsg->GetData(); while (bytes_sent < bytes_to_send) { ret = ::send(sock, pData+bytes_sent, bytes_to_send-bytes_sent, 0); if (ret < 0) { hrResult = ERRNOHR; Logging::LogMsg(LL_ALWAYS, "Send failed (hr == %x)", hrResult); Chk(hrResult); } bytes_sent += ret; } Logging::LogMsg(LL_DEBUG, "Request sent - waiting for response"); // consume the response reader.Reset(); reader.GetStream().Attach(spMsgReader, true); pData = spMsg->GetData(); bytes_recv = 0; max_bytes_recv = spMsg->GetAllocatedSize(); remaining = max_bytes_recv; while (remaining > 0) { readsize = reader.HowManyBytesNeeded(); if (readsize == 0) { break; } if (readsize > remaining) { // technically an error, but the client logic will figure it out ASSERT(false); break; } ret = ::recv(sock, pData+bytes_recv, readsize, 0); if (ret == 0) { // server cut us off before we got all the bytes we thought we were supposed to get? ASSERT(false); break; } if (ret < 0) { hrResult = ERRNOHR; Logging::LogMsg(LL_ALWAYS, "Recv failed (hr == %x)", hrResult); Chk(hrResult); } reader.AddBytes(pData+bytes_recv, ret); bytes_recv += ret; remaining = max_bytes_recv - bytes_recv; spMsg->SetSize(bytes_recv); } // now feed the response into the client logic stunsocket.UpdateAddresses(); addrLocal = stunsocket.GetLocalAddress(); clientlogic.ProcessResponse(spMsg, addrDest, addrLocal); } stunsocket.Close(); results.Init(); clientlogic.GetResults(&results); ::DumpResults(config, results); Cleanup: return; } 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; } int main(int argc, char** argv) { CCmdLineParser cmdline; ClientCmdLineArgs args; StunClientLogicConfig config; ClientSocketConfig socketconfig; bool fError = false; uint32_t loglevel = LL_ALWAYS; #ifdef DEBUG loglevel = LL_DEBUG; #endif Logging::SetLogLevel(loglevel); cmdline.AddNonOption(&args.strRemoteServer); cmdline.AddNonOption(&args.strRemotePort); cmdline.AddOption("localaddr", required_argument, &args.strLocalAddr); cmdline.AddOption("localport", required_argument, &args.strLocalPort); cmdline.AddOption("mode", required_argument, &args.strMode); cmdline.AddOption("family", required_argument, &args.strFamily); cmdline.AddOption("protocol", required_argument, &args.strProtocol); cmdline.AddOption("verbosity", required_argument, &args.strVerbosity); cmdline.AddOption("help", no_argument, &args.strHelp); if (argc <= 1) { PrintUsage(true); return -1; } cmdline.ParseCommandLine(argc, argv, 1, &fError); if (args.strHelp.length() > 0) { PrintUsage(false); return -2; } if (fError) { PrintUsage(true); return -3; } if (args.strVerbosity.length() > 0) { int level = atoi(args.strVerbosity.c_str()); if (level >= 0) { Logging::SetLogLevel(level); } } if (FAILED(CreateConfigFromCommandLine(args, &config, &socketconfig))) { Logging::LogMsg(LL_ALWAYS, "Can't start client"); PrintUsage(true); return -4; } DumpConfig(config, socketconfig); if (socketconfig.socktype == SOCK_STREAM) { TcpClientLoop(config, socketconfig); } else { UdpClientLoop(config, socketconfig); } return 0; } stunserver/common/0000755000175000017500000000000012346765410014412 5ustar jselbiejselbiestunserver/common/logger.cpp0000644000175000017500000000236112346765410016377 0ustar jselbiejselbie/* Copyright 2011 John Selbie Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include "commonincludes.hpp" #include "logger.h" namespace Logging { static uint32_t s_loglevel = LL_ALWAYS; // error and usage messages only void VPrintMsg(const char* pszFormat, va_list& args) { ::vprintf(pszFormat, args); ::printf("\n"); } uint32_t GetLogLevel() { return s_loglevel; } void SetLogLevel(uint32_t level) { s_loglevel = level; } void LogMsg(uint32_t level, const char* pszFormat, ...) { va_list args; va_start(args, pszFormat); if (level <= s_loglevel) { VPrintMsg(pszFormat, args); } va_end(args); } } stunserver/common/cmdlineparser.h0000644000175000017500000000233312346765410017414 0ustar jselbiejselbie/* Copyright 2011 John Selbie Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #ifndef CMDLINEPARSER_H #define CMDLINEPARSER_H #include class CCmdLineParser { const option* GenerateOptions(); std::vector