Commit 46df7262 authored by John Selbie's avatar John Selbie

Remove unused #define

parents b29c2035 66772e42
......@@ -3,7 +3,7 @@ nbproject/
*.o
*.gch
*.a
*.txtcode
callgrind.out.*
stunclient
stunserver
......
......@@ -20,6 +20,8 @@ copybin: everything
debug: T := debug
debug: all
profile: T := profile
profile: all
clean: T := clean
clean: everything
......
StunServer version 1.0.0
September 7, 2011
STUNK - An open source STUN server
Version 1.1.0
February 1, 2012
---------------------------------------------------------
Features:
Compliant with the latest RFCs including 5389, 5769, and 5780. Also includes
backwards compatibility for RFC 3489.
IPv4 and IPv6 support
Supports both UDP and TCP on both IPv4 and IPv6.
Client test app provided
Client test app provided.
Stun server can operate in "full" mode as well as "basic" mode. Basic mode
configures the server to listen on one port and respond to STUN binding
requests. Full mode configures the service to listen on two different IP
address interfaces (if available) and provide NAT behavior and filtering
detection support for clients
detection support for clients.
Open source Apache license. See LICENSE file fore more details.
---------------------------------------------------------
......@@ -23,8 +25,7 @@ Features:
Known issues:
UDP only. Command line options for working in TCP or TLS modes have yet to
be implemented.
TLS mode has yet to be implemented.
Server does not honor the stun padding attribute. If someone really wants
this support, let me know and I will consider adding it.
......@@ -38,7 +39,7 @@ Known issues:
hooks are provided for implementors to write their own code to validate a
username, fetch a password, and allow/deny a request. Details of writing
your own authentication provider code are described in the file
"server/sampleauthprovider.h"
"server/sampleauthprovider.h".
Dependency checking is not implemented in the Makefile. So if you need to
recompile, I recommend "make clean" from the root to preceed any subsequent
......@@ -60,6 +61,8 @@ Testing:
Amazon AWS with gcc/g++ 4.4
MacOS Snow Leopard (will not compile on earlier versions without updating to
a newer version of gcc/g++)
FreeBSD 9.0 with gcc/g++ 4.2.1
Solaris 11 with gcc/g++ 4.5.2
Parsing code has been fuzz tested with zzuf. http://caca.zoy.org/wiki/zzuf
---------------------------------------------------------
......@@ -67,26 +70,34 @@ Testing:
Prerequisites before compiling and running.
Boost header files. (Actual boost runtime not required) www.boost.org (sudo
yum install boost-devel)
OpenSSL development files and runtime. www.boost.org (sudo yum install
openssl-devel)
Boost header files. www.boost.org (sudo yum install boost-devel). The
compiled Boost runtime is not necessary. Just unpacking the Boost source
code distribution will suffice. If you do not have the adminstrative
privaleges to install the Boost distribution into a standard system include
path, you may uncomment and edit the top line of the common.inc file for the
BOOST_INCLUDE variable. The common.inc file is in the same folder as this
README file.
/usr/bin/xxd (this is a tool for converting the help text into resources. It
is usually universally installed. If not, then "sudo yum install
vim-common")
OpenSSL development files and runtime. www.openssl.org (sudo yum install
openssl-devel). On most systems with development tools already installed,
OpenSSL include files are already installed in the standard include path. If
this is not the case, you can uncomment and edit the common.inc file to have
the OPENSSL_INCLUDE variable defined.
pthreads header and libs (I haven't seen a distribution where this wasn't
already installed)
perl. Only needed if you rebuild the content in the resources directory.
Otherwise, not needed.
---------------------------------------------------------
Compiling and running
Got Boost and OpenSSL taken care of as described above? Good. Just type
"make". There will be three resulting binaries in the root of the source
code package produced.
"make" (or "gmake" on some systems). There will be three resulting binaries
in the root of the source code package produced.
stuntestcode - This is the unit test code. I highly recommend you run this
program first. When run, you'll see a series of lines being printed in
......@@ -113,17 +124,18 @@ Firewall
Feature roadmap (the features I want to implement in a subsequent release)
TCP and TLS support
Host a full server across two separate machines (such that two ip addresses
on a single machine will not be required for full mode).
Cleanup Makefile and add "configure" and autotools support
Finish Windows port and able to run as a Windows service
Scale across more than one CPU (for multi-core and multi-proc machines). The
threading code has already been written, just needs some finish work.
Host a full server across two separate machines (such that two ip addresses
on a single machine will not be required for full mode).
TLS support
Cleanup Makefile and add "configure" and autotools support
---------------------------------------------------------
......
......@@ -3,9 +3,8 @@ include ../common.inc
PROJECT_TARGET := stunclient
PROJECT_OBJS := clientmain.o
PROJECT_INTERMEDIATES := usage.txtcode usagelite.txtcode
INCLUDES := $(BOOST_INCLUDE) -I../common -I../stuncore -I../networkutils
INCLUDES := $(BOOST_INCLUDE) -I../common -I../stuncore -I../networkutils -I../resources
LIB_PATH := -L../common -L../stuncore -L../networkutils
LIBS := -lnetworkutils -lstuncore -lcommon -lcrypto
......@@ -13,14 +12,9 @@ LIBS := -lnetworkutils -lstuncore -lcommon -lcrypto
all: $(PROJECT_TARGET)
clean:
rm -f $(PROJECT_OBJS) $(PROJECT_TARGET) $(PROJECT_INTERMEDIATES)
rm -f $(PROJECT_OBJS) $(PROJECT_TARGET)
$(PROJECT_TARGET): $(PROJECT_OBJS)
$(LINK.cpp) -o $@ $^ $(LIB_PATH) $(LIBS)
$(LINK.cpp) -o $@ $^ $(LIB_PATH) $(LIBS) $(SOCKET_LIBS)
clientmain.cpp: usage.txtcode usagelite.txtcode
%.txtcode: %.txt
sh ../server/makecodefile.sh $< $@ $(*)_text
......@@ -28,9 +28,9 @@
#include "oshelper.h"
#include "prettyprint.h"
// unusual to include usage.cpp and usagelite.cpp here, but these are auto-generated resource file
#include "usage.txtcode"
#include "usagelite.txtcode"
// These files are in ../resources
#include "stunclient.txtcode"
#include "stunclient_lite.txtcode"
struct ClientCmdLineArgs
......@@ -74,7 +74,7 @@ void DumpConfig(StunClientLogicConfig& config, ClientSocketConfig& socketConfig)
void PrintUsage(bool fSummaryUsage)
{
size_t width = GetConsoleWidth();
const char* psz = fSummaryUsage ? usagelite_text : usage_text;
const char* psz = fSummaryUsage ? stunclient_lite_text : stunclient_text;
// save some margin space
if (width > 2)
......@@ -97,12 +97,14 @@ HRESULT CreateConfigFromCommandLine(ClientCmdLineArgs& args, StunClientLogicConf
uint16_t remoteport = 0;
int nPort = 0;
char szIP[100];
bool fTCP = false;
config.fBehaviorTest = false;
config.fFilteringTest = false;
config.timeoutSeconds = 5;
config.uMaxAttempts = 3;
config.fTimeoutIsInstant = false;
config.timeoutSeconds = 0; // use default
config.uMaxAttempts = 0;
socketconfig.family = AF_INET;
socketconfig.socktype = SOCK_DGRAM;
......@@ -132,11 +134,18 @@ HRESULT CreateConfigFromCommandLine(ClientCmdLineArgs& args, StunClientLogicConf
StringHelper::ToLower(args.strProtocol);
if (StringHelper::IsNullOrEmpty(args.strProtocol.c_str()) == false)
{
if (args.strProtocol != "udp")
if ((args.strProtocol != "udp") && (args.strProtocol != "tcp"))
{
Logging::LogMsg(LL_ALWAYS, "Only udp is supported as a protocol option in this version");
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 ---------------------------------------------
......@@ -224,7 +233,7 @@ HRESULT CreateConfigFromCommandLine(ClientCmdLineArgs& args, StunClientLogicConf
else if (args.strMode == "full")
{
config.fBehaviorTest = true;
config.fFilteringTest = true;
config.fFilteringTest = (fTCP == false); // impossible to to a filtering test in TCP
}
else
{
......@@ -241,7 +250,6 @@ Cleanup:
void NatBehaviorToString(NatBehavior behavior, std::string* pStr)
{
std::string& str = *pStr;
......@@ -308,17 +316,166 @@ void DumpResults(StunClientLogicConfig& config, StunClientResults& results)
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 ClientLoop(StunClientLogicConfig& config, const ClientSocketConfig& socketconfig)
HRESULT UdpClientLoop(StunClientLogicConfig& config, const ClientSocketConfig& socketconfig)
{
HRESULT hr = S_OK;
CRefCountedStunSocket spStunSocket;
CRefCountedBuffer spMsg(new CBuffer(1500));
CStunSocket stunSocket;;
CRefCountedBuffer spMsg(new CBuffer(MAX_STUN_MESSAGE_SIZE));
int sock = -1;
CSocketAddress addrDest; // who we send to
CSocketAddress addrRemote; // who we
......@@ -341,16 +498,17 @@ HRESULT ClientLoop(StunClientLogicConfig& config, const ClientSocketConfig& sock
Chk(hr);
}
hr = CStunSocket::Create(socketconfig.addrLocal, RolePP, &spStunSocket);
hr = stunSocket.UDPInit(socketconfig.addrLocal, RolePP);
if (FAILED(hr))
{
Logging::LogMsg(LL_ALWAYS, "Unable to create local socket: (error = x%x)", hr);
Chk(hr);
}
spStunSocket->EnablePktInfoOption(true);
stunSocket.EnablePktInfoOption(true);
sock = spStunSocket->GetSocketHandle();
sock = stunSocket.GetSocketHandle();
// let's get a loop going!
......@@ -429,6 +587,11 @@ Cleanup:
}
int main(int argc, char** argv)
{
CCmdLineParser cmdline;
......@@ -493,7 +656,15 @@ int main(int argc, char** argv)
}
DumpConfig(config, socketconfig);
ClientLoop(config, socketconfig);
if (socketconfig.socktype == SOCK_STREAM)
{
TcpClientLoop(config, socketconfig);
}
else
{
UdpClientLoop(config, socketconfig);
}
return 0;
}
Usage: stunclient [OPTIONS] server [port]
Perform a binding test with a remote STUN server and optionally perform a full behavior test
Parameters:
"server" is the IP address or FQDN of the remote server to befrom the binding tests with. It is the only required paramter
"port" is an optional paramter that can follow the server paramter. The default is 3478 for UDP and TCP. And 5349 for TLS.
Available options:
--localaddr = INTERFACE OR IPADDRESS
The value for this option may the name of an interface (such as "eth0" or "lo"). Or it may be one of the available IP addresses assigned to a network interface present on the host (such as "128.23.45.67"). The interface chosen will be the preferred address for sending and receiving responses with the remote server. The default is to let the system decide which address to send on and to listen on all addresses (INADDR_ANY).
--localport=PORTNUM
PORTNUM is a value between 1 to 65535. This is the UDP or TCP port that the primary and alternate interfaces listen on as the primary port for binding requests. If not specified, a randomly avaialbe port chosed by the system is used.
--mode=MODE
Where MODE is either "basic" or "full". "basic" mode is the default and indicates that the client should perform a STUN binding test only. "full" mode indicates that the client should attempt to diagnose NAT behavior and filtering methodologies if the server supports this mode.
--family=IPVERSION
IPVERSION is either "4" or "6" to specify the usage of IPV4 or IPV6. If not specified, the default value is "4".
--protocol=PROTO
PROTO is either "udp", "tcp", or "tls". Where "udp" is the default. "tcp" and "tls" modes are only available when the --mode option is "basic".
--verbosity=LOGLEVEL
Sets the verbosity of the logging level. 0 is the default (minimal output and logging). 1 shows slightly more. 2 and higher shows even more.
--help
Prints this help page
Examples:
stunclient stunserver.org 3478
Performs a simple binding test request with the server listening at "stunserver.org"
stunclient --mode full --localport 9999 12.34.56.78
Performs a full set of UDP NAT behavior tests from local port 9999 to the server listening at IP Address 12.34.56.78 (port 3478)
# BOOST_INCLUDE := -I/home/jselbie/lib/boost_1_46_1
# BOOST_INCLUDE := -I/home/jselbie/boost_1_48_0
# OPENSSL_INCLUDE := -I/home/jselbie/lib/openssl
DEFINES := -DNDEBUG
......@@ -7,8 +7,18 @@ STANDARD_FLAGS := -Wall -Wuninitialized
RELEASE_FLAGS := -O2
DEBUG_FLAGS := -g
PROFILE_FLAGS := -O2 -g
FLAVOR_FLAGS = $(RELEASE_FLAGS)
#SOLARIS HACK
UNAME := $(shell uname -s)
ifeq ($(UNAME),SunOS)
SOCKET_LIBS := -lsocket -lnsl
endif
.PHONY: all clean debug
%.h.gch: %.h
......@@ -26,3 +36,7 @@ debug: DEFINES = -DDEBUG
debug: all
profile: FLAVOR_FLAGS = $(PROFILE_FLAGS)
profile: all
include ../common.inc
PROJECT_TARGET := libcommon.a
PROJECT_SRCS := cmdlineparser.cpp common.cpp getconsolewidth.cpp getmillisecondcounter.cpp logger.cpp prettyprint.cpp refcountobject.cpp stringhelper.cpp
PROJECT_SRCS := cmdlineparser.cpp common.cpp fasthash.cpp getconsolewidth.cpp getmillisecondcounter.cpp logger.cpp prettyprint.cpp refcountobject.cpp stringhelper.cpp
PROJECT_OBJS := $(subst .cpp,.o,$(PROJECT_SRCS))
INCLUDES := $(BOOST_INCLUDE)
PRECOMP_H_GCH := commonincludes.h.gch
......
......@@ -30,7 +30,11 @@ const option* CCmdLineParser::GenerateOptions()
{
option opt = {};
opt.has_arg = _listOptionDetails[index].has_arg;
opt.name = _listOptionDetails[index].strName.c_str();
// Solaris 11 (released in 2011), only sees fit to include header files from 2004 where "option::name" is just a char* and not const char*
opt.name = (char*)(_listOptionDetails[index].strName.c_str());
_options.push_back(opt);
}
......
......@@ -21,6 +21,11 @@
// Fix for Lion (http://www.opensource.apple.com/source/xnu/xnu-1699.24.8/bsd/netinet6/in6.h)
#define __APPLE_USE_RFC_3542
#if __linux || __linux__ || __gnu_linux__ || linux
#define IS_LINUX
#endif
// standard system includes
#include <sys/types.h>
#include <sys/socket.h>
......@@ -31,6 +36,7 @@
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>
......@@ -39,6 +45,8 @@
#include <ifaddrs.h>
#include <net/if.h>
#include <stdarg.h>
#include <math.h>
#include <sys/termios.h>
#include <boost/shared_ptr.hpp>
#include <boost/scoped_array.hpp>
......@@ -49,6 +57,15 @@
#include <list>
#include <string>
#ifdef IS_LINUX
#define HAS_EPOLL
#include <sys/epoll.h>
#endif
#include <poll.h>
#include <pthread.h>
......@@ -123,4 +140,6 @@ inline void cta_noop(const char* psz)
#include "logger.h"
#endif
/*
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.h"
#define IS_DIVISIBLE_BY(x, y) ((x % y)==0)
static unsigned int FindPrime(unsigned int val)
{
unsigned int result = val;
bool fPrime = false;
if (val <= 2)
{
return 2;
}
if (val == 3)
{
return 3;
}
if ((result % 2)==0)
{
result++;
}
while (fPrime == false)
{
unsigned int stop = (unsigned int)(sqrt(result)+1);
fPrime = true;
// test to see if result is prime, if it is, return it
for (unsigned int x = 3; x <= stop; x++)
{
if (IS_DIVISIBLE_BY(result, x))
{
fPrime = false;
break;
}
}
if (fPrime==false)
{
result += 2;
}
}
return result;
}
size_t FastHash_GetHashTableWidth(unsigned int maxConnections)
{
// find highest prime, greater than or equal to maxConnections
return FindPrime(maxConnections);
}
This diff is collapsed.
......@@ -41,6 +41,8 @@ typedef int32_t HRESULT;
#define ERRNO_TO_HRESULT(err) MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ERRNO, err)
#define ERRNOHR ERRNO_TO_HRESULT(ERRNO_TO_HRESULT(errno))
#define S_OK ((HRESULT)0)
#define S_FALSE ((HRESULT)1L)
#define E_UNEXPECTED ((HRESULT)(0x8000FFFFL))
......
include ../common.inc
PROJECT_TARGET := libnetworkutils.a
PROJECT_OBJS := adapters.o recvfromex.o resolvehostname.o stunsocket.o
PROJECT_OBJS := adapters.o polling.o recvfromex.o resolvehostname.o stunsocket.o
INCLUDES := $(BOOST_INCLUDE) -I../common -I../stuncore
......
This diff is collapsed.
/*
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 POLLING_H
#define POLLING_H
struct PollEvent
{
int fd;
uint32_t eventflags;
};
// event flags
const uint32_t IPOLLING_READ = 0x01 << 0;
const uint32_t IPOLLING_WRITE = 0x01 << 1;
const uint32_t IPOLLING_EDGETRIGGER = 0x01 << 2;
const uint32_t IPOLLING_RDHUP = 0x01 << 3;
const uint32_t IPOLLING_HUP = 0x01 << 4;
const uint32_t IPOLLING_PRI = 0x01 << 5;
const uint32_t IPOLLING_ERROR = 0x01 << 6;
class IPolling : public IRefCounted
{
public:
virtual HRESULT Initialize(size_t maxSockets) = 0;
virtual HRESULT Close() = 0;
virtual HRESULT Add(int fd, uint32_t eventflags) = 0;
virtual HRESULT Remove(int fd) = 0;
virtual HRESULT ChangeEventSet(int fd, uint32_t eventflags) = 0;
virtual HRESULT WaitForNextEvent(PollEvent* pPollEvent, int timeoutMilliseconds) = 0;
};
const uint32_t IPOLLING_TYPE_BEST = 0x01 << 0;
const uint32_t IPOLLING_TYPE_EPOLL = 0x01 << 1;
const uint32_t IPOLLING_TYPE_POLL = 0x01 << 2;
HRESULT CreatePollingInstance(uint32_t type, size_t maxSockets, IPolling** ppPolling);
#endif /* POLLING_H */
......@@ -132,8 +132,9 @@ ssize_t recvfromex(int sockfd, void* buf, size_t len, int flags, CSocketAddress*
GetLocalPortNumberFromSocket(sockfd, pDstAddr);
break;
}
#endif
#elif defined(IP_RECVDSTADDR)
#ifdef IP_RECVDSTADDR
// This code path for MacOSX and likely BSD as well
if ((pCmsg->cmsg_level == IPPROTO_IP) && (pCmsg->cmsg_type==IP_RECVDSTADDR) && CMSG_DATA(pCmsg))
{
......@@ -144,10 +145,6 @@ ssize_t recvfromex(int sockfd, void* buf, size_t len, int flags, CSocketAddress*
GetLocalPortNumberFromSocket(sockfd, pDstAddr);
break;
}
#else
{
int fail[-1]; // set a compile time assert if there's no option
}
#endif
}
}
......
......@@ -18,18 +18,65 @@
#include "stuncore.h"
#include "stunsocket.h"
CStunSocket::CStunSocket() :
_sock(-1),
_role(RolePP)
{
}
CStunSocket::~CStunSocket()
{
Close();
}
void CStunSocket::Reset()
{
_sock = -1;
_addrlocal = CSocketAddress(0,0);
_addrremote = CSocketAddress(0,0);
_role = RolePP;
}
void CStunSocket::Close()
{
if (_sock != -1)
{
close(_sock);
_addrlocal = CSocketAddress(0,0);
_sock = -1;
}
Reset();
}
bool CStunSocket::IsValid()
{
return (_sock != -1);
}
HRESULT CStunSocket::Attach(int sock)
{
if (sock == -1)
{
ASSERT(false);
return E_INVALIDARG;
}
if (sock != _sock)
{
// close any existing socket
Close(); // this will also call "Reset"
_sock = sock;
}
UpdateAddresses();
return S_OK;
}
int CStunSocket::Detach()
{
int sock = _sock;
Reset();
return sock;
}
int CStunSocket::GetSocketHandle() const
......@@ -42,85 +89,207 @@ const CSocketAddress& CStunSocket::GetLocalAddress() const
return _addrlocal;
}
const CSocketAddress& CStunSocket::GetRemoteAddress() const
{
return _addrremote;
}
SocketRole CStunSocket::GetRole() const
{
ASSERT(_sock != -1);
return _role;
}
HRESULT CStunSocket::EnablePktInfoOption(bool fEnable)
void CStunSocket::SetRole(SocketRole role)
{
_role = role;
}
// About the "packet info option"
// What we are trying to do is enable the socket to be able to provide the "destination address"
// for packets we receive. However, Linux, BSD, and MacOS all differ in what the
// socket option is. And it differs even differently between IPV4 and IPV6 across these operating systems.
// So we have the "try one or the other" implementation based on what's DEFINED
// On some operating systems, there's only one option defined. Other's have both, but only one works!
// So we have to try them both
HRESULT CStunSocket::EnablePktInfoImpl(int level, int option1, int option2, bool fEnable)
{
HRESULT hr = S_OK;
int enable = fEnable?1:0;
int ret;
int ret = -1;
int family = _addrlocal.GetFamily();
int level = (family==AF_INET) ? IPPROTO_IP : IPPROTO_IPV6;
ChkIfA((option1 == -1) && (option2 == -1), E_FAIL);
if (option1 != -1)
{
ret = setsockopt(_sock, level, option1, &enable, sizeof(enable));
}
if ((ret < 0) && (option2 != -1))
{
enable = fEnable?1:0;
ret = setsockopt(_sock, level, option2, &enable, sizeof(enable));
}
ChkIfA(ret < 0, ERRNOHR);
Cleanup:
return hr;
}
// if you change the ifdef's below, make sure you it's matched with the same logic in recvfromex.cpp
HRESULT CStunSocket::EnablePktInfo_IPV4(bool fEnable)
{
int level = IPPROTO_IP;
int option1 = -1;
int option2 = -1;
#ifdef IP_PKTINFO
int option = (family==AF_INET) ? IP_PKTINFO : IPV6_RECVPKTINFO;
#elif defined(IP_RECVDSTADDR)
int option = (family==AF_INET) ? IP_RECVDSTADDR : IPV6_PKTINFO;
#else
int fail[-1]; // set a compile time assert
option1 = IP_PKTINFO;
#endif
#ifdef IP_RECVDSTADDR
option2 = IP_RECVDSTADDR;
#endif
return EnablePktInfoImpl(level, option1, option2, fEnable);
}
HRESULT CStunSocket::EnablePktInfo_IPV6(bool fEnable)
{
int level = IPPROTO_IPV6;
int option1 = -1;
int option2 = -1;
#ifdef IPV6_RECVPKTINFO
option1 = IPV6_RECVPKTINFO;
#endif
#ifdef IPV6_PKTINFO
option2 = IPV6_PKTINFO;
#endif
return EnablePktInfoImpl(level, option1, option2, fEnable);
}
HRESULT CStunSocket::EnablePktInfoOption(bool fEnable)
{
int family = _addrlocal.GetFamily();
HRESULT hr;
if (family == AF_INET)
{
hr = EnablePktInfo_IPV4(fEnable);
}
else
{
hr = EnablePktInfo_IPV6(fEnable);
}
return hr;
}
ret = ::setsockopt(_sock, level, option, &enable, sizeof(enable));
HRESULT CStunSocket::SetNonBlocking(bool fEnable)
{
HRESULT hr = S_OK;
int result;
int flags;
flags = ::fcntl(_sock, F_GETFL, 0);
ChkIf(flags == -1, ERRNOHR);
flags |= O_NONBLOCK;
result = fcntl(_sock , F_SETFL , flags);
// Linux documentation (man ipv6) says you are supposed to set IPV6_PKTINFO as the option
// Yet, it's really IPV6_RECVPKTINFO. Other operating systems might expect IPV6_PKTINFO.
// We'll cross that bridge, when we get to it.
// todo - we should write a unit test that tests the packet info behavior
ASSERT(ret == 0);
ChkIf(result == -1, ERRNOHR);
return (ret == 0) ? S_OK : ERRNOHR;
Cleanup:
return hr;
}
//static
HRESULT CStunSocket::Create(const CSocketAddress& addrlocal, SocketRole role, boost::shared_ptr<CStunSocket>* pStunSocketShared)
void CStunSocket::UpdateAddresses()
{
sockaddr_storage addrLocal = {};
sockaddr_storage addrRemote = {};
socklen_t len;
int ret;
ASSERT(_sock != -1);
if (_sock == -1)
{
return;
}
len = sizeof(addrLocal);
ret = ::getsockname(_sock, (sockaddr*)&addrLocal, &len);
if (ret != -1)
{
_addrlocal = addrLocal;
}
len = sizeof(addrRemote);
ret = ::getpeername(_sock, (sockaddr*)&addrRemote, &len);
if (ret != -1)
{
_addrremote = addrRemote;
}
}
HRESULT CStunSocket::InitCommon(int socktype, const CSocketAddress& addrlocal, SocketRole role, bool fSetReuseFlag)
{
int sock = -1;
int ret;
CStunSocket* pSocket = NULL;
sockaddr_storage addrBind = {};
socklen_t sizeaddrBind;
HRESULT hr = S_OK;
ChkIfA(pStunSocketShared == NULL, E_INVALIDARG);
ASSERT((socktype == SOCK_DGRAM)||(socktype==SOCK_STREAM));
sock = socket(addrlocal.GetFamily(), SOCK_DGRAM, 0);
sock = socket(addrlocal.GetFamily(), socktype, 0);
ChkIf(sock < 0, ERRNOHR);
ret = bind(sock, addrlocal.GetSockAddr(), addrlocal.GetSockAddrLength());
ChkIf(ret < 0, ERRNOHR);
if (fSetReuseFlag)
{
int fAllow = 1;
ret = ::setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &fAllow, sizeof(fAllow));
ChkIf(ret == -1, 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);
ret = bind(sock, addrlocal.GetSockAddr(), addrlocal.GetSockAddrLength());
ChkIf(ret == -1, ERRNOHR);
pSocket = new CStunSocket();
pSocket->_sock = sock;
pSocket->_addrlocal = CSocketAddress(*(sockaddr*)&addrBind);
pSocket->_role = role;
Attach(sock);
sock = -1;
{
boost::shared_ptr<CStunSocket> spTmp(pSocket);
pStunSocketShared->swap(spTmp);
}
SetRole(role);
Cleanup:
if (sock != -1)
{
close(sock);
sock = -1;
}
return hr;
}
HRESULT CStunSocket::UDPInit(const CSocketAddress& local, SocketRole role)
{
return InitCommon(SOCK_DGRAM, local, role, false);
}
HRESULT CStunSocket::TCPInit(const CSocketAddress& local, SocketRole role, bool fSetReuseFlag)
{
return InitCommon(SOCK_STREAM, local, role, fSetReuseFlag);
}
......@@ -18,28 +18,52 @@
#define STUNSOCKET_H
class CStunSocket
{
private:
int _sock;
CSocketAddress _addrlocal;
CSocketAddress _addrremote;
SocketRole _role;
CStunSocket() {;}
CStunSocket(const CStunSocket&) {;}
void operator=(const CStunSocket&) {;}
HRESULT InitCommon(int socktype, const CSocketAddress& addrlocal, SocketRole role, bool fSetReuseFlag);
void Reset();
HRESULT EnablePktInfoImpl(int level, int option1, int option2, bool fEnable);
HRESULT EnablePktInfo_IPV4(bool fEnable);
HRESULT EnablePktInfo_IPV6(bool fEnable);
public:
CStunSocket();
~CStunSocket();
void Close();
bool IsValid();
HRESULT Attach(int sock);
int Detach();
int GetSocketHandle() const;
const CSocketAddress& GetLocalAddress() const;
const CSocketAddress& GetRemoteAddress() const;
SocketRole GetRole() const;
void SetRole(SocketRole role);
HRESULT EnablePktInfoOption(bool fEnable);
HRESULT SetNonBlocking(bool fEnable);
void UpdateAddresses();
static HRESULT Create(const CSocketAddress& local, SocketRole role, boost::shared_ptr<CStunSocket>* pStunSocketShared);
HRESULT UDPInit(const CSocketAddress& local, SocketRole role);
HRESULT TCPInit(const CSocketAddress& local, SocketRole role, bool fSetReuseFlag);
};
typedef boost::shared_ptr<CStunSocket> CRefCountedStunSocket;
......
include ../common.inc
%.txtcode: %.txt
sh makecodefile.sh $^ $@ $(*)_text
%.txt: %.md
pandoc --no-wrap -s -w plain -o $@ $^
%.1: %.md
pandoc -s -w man -o $@ $^
all:
@echo "specify one of the following make options: textres manpages README or clean"
textres: stunserver.txtcode stunserver_lite.txtcode stunclient.txtcode stunclient_lite.txtcode
manpages: stunserver.1 stunclient.1
README: readme.src
../stuntestcode --pp < readme.src > README
clean:
rm -f *.txtcode README *.1
STUNK - An open source STUN server
Version 1.1.0
February 1, 2012
---------------------------------------------------------
Features:
Compliant with the latest RFCs including 5389, 5769, and 5780. Also includes
backwards compatibility for RFC 3489.
Supports both UDP and TCP on both IPv4 and IPv6.
Client test app provided.
Stun server can operate in "full" mode as well as "basic" mode. Basic mode
configures the server to listen on one port and respond to STUN binding
requests. Full mode configures the service to listen on two different IP
address interfaces (if available) and provide NAT behavior and filtering
detection support for clients.
Open source Apache license. See LICENSE file fore more details.
---------------------------------------------------------
Known issues:
TLS mode has yet to be implemented.
Server does not honor the stun padding attribute. If someone really wants
this support, let me know and I will consider adding it.
By default, the stun server operates in an open mode without performing
authentication. All the code for authentication, challenge-response, message
hashing, and message integrity attributes are fully coded. HMAC/SHA1/MD5
hashing code for generating and validating the message integrity attribute
has been implemented and tested. However, the code for validating a username
or looking up a password is outside the scope of this release. Instead,
hooks are provided for implementors to write their own code to validate a
username, fetch a password, and allow/deny a request. Details of writing
your own authentication provider code are described in the file
"server/sampleauthprovider.h".
Dependency checking is not implemented in the Makefile. So if you need to
recompile, I recommend "make clean" from the root to preceed any subsequent
"make" call.
If you run an instance of stunserver locally, you may observe that
"stunclient localhost" may not successfully work. This is because the server
is not listening on the loopback adapter when running in full mode. The
workaround is to specify the actual IP address that the server is listening
on. Type "ifconfig" to discover your IP address (e.g. 10.11.12.13) followed
by "stunclient 10.11.12.13"
---------------------------------------------------------
Testing:
Fedora 15 with gcc/g++ 4.6.0
Ubuntu 11 with gcc/g++ 4.5.2
Amazon AWS with gcc/g++ 4.4
MacOS Snow Leopard (will not compile on earlier versions without updating to
a newer version of gcc/g++)
FreeBSD 9.0 with gcc/g++ 4.2.1
Solaris 11 with gcc/g++ 4.5.2
Parsing code has been fuzz tested with zzuf. http://caca.zoy.org/wiki/zzuf
---------------------------------------------------------
Prerequisites before compiling and running.
Boost header files. www.boost.org (sudo yum install boost-devel). The
compiled Boost runtime is not necessary. Just unpacking the Boost source
code distribution will suffice. If you do not have the adminstrative
privaleges to install the Boost distribution into a standard system include
path, you may uncomment and edit the top line of the common.inc file for the
BOOST_INCLUDE variable. The common.inc file is in the same folder as this
README file.
OpenSSL development files and runtime. www.openssl.org (sudo yum install
openssl-devel). On most systems with development tools already installed,
OpenSSL include files are already installed in the standard include path. If
this is not the case, you can uncomment and edit the common.inc file to have
the OPENSSL_INCLUDE variable defined.
pthreads header and libs (I haven't seen a distribution where this wasn't
already installed)
perl. Only needed if you rebuild the content in the resources directory.
Otherwise, not needed.
---------------------------------------------------------
Compiling and running
Got Boost and OpenSSL taken care of as described above? Good. Just type
"make" (or "gmake" on some systems). There will be three resulting binaries
in the root of the source code package produced.
stuntestcode - This is the unit test code. I highly recommend you run this
program first. When run, you'll see a series of lines being printed in
regards to different code paths being tested. If you see any line that ends
in "FAIL", we likely have a bug. Please contact me immediately if you see
this.
stunserver - this is the server binary. Run "./stunserver --help" for
details on running this program. Running this program without any command
line arguments defaults to listening on port 3478 on all adapters.
stunclient - this is the client test binary. Run "./stunclient --help" for
details on running this program. Example: "./stunclient stun.selbie.com"
---------------------------------------------------------
Firewall
Don't forget to configure your firewall to allow traffic for the local ports
the stunserver will be listening on!
---------------------------------------------------------
Feature roadmap (the features I want to implement in a subsequent release)
Host a full server across two separate machines (such that two ip addresses
on a single machine will not be required for full mode).
Cleanup Makefile and add "configure" and autotools support
Finish Windows port and able to run as a Windows service
Scale across more than one CPU (for multi-core and multi-proc machines). The
threading code has already been written, just needs some finish work.
TLS support
---------------------------------------------------------
Contact the author
John Selbie
john@selbie.com
......@@ -13,9 +13,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
plfile=`dirname $0`/xxdperl.pl
echo BUILDING $1 INTO $2
echo const char $3[] = { > $2
xxd -i < $1 >> $2
#xxd -i < $1 >> $2
echo "perl $plfile <" $1 ">>" $2
perl $plfile < $1 >> $2
echo ",0x00};" >> $2
echo "" >> $2
......
STUNK - An open source STUN server
Version 1.1.0
February 1, 2012
---------------------------------------------------------
Features:
Compliant with the latest RFCs including 5389, 5769, and 5780. Also includes backwards compatibility for RFC 3489.
Supports both UDP and TCP on both IPv4 and IPv6.
Client test app provided.
Stun server can operate in "full" mode as well as "basic" mode. Basic mode configures the server to listen on one port and respond to STUN binding requests. Full mode configures the service to listen on two different IP address interfaces (if available) and provide NAT behavior and filtering detection support for clients.
Open source Apache license. See LICENSE file fore more details.
---------------------------------------------------------
Known issues:
TLS mode has yet to be implemented.
Server does not honor the stun padding attribute. If someone really wants this support, let me know and I will consider adding it.
By default, the stun server operates in an open mode without performing authentication. All the code for authentication, challenge-response, message hashing, and message integrity attributes are fully coded. HMAC/SHA1/MD5 hashing code for generating and validating the message integrity attribute has been implemented and tested. However, the code for validating a username or looking up a password is outside the scope of this release. Instead, hooks are provided for implementors to write their own code to validate a username, fetch a password, and allow/deny a request. Details of writing your own authentication provider code are described in the file "server/sampleauthprovider.h".
Dependency checking is not implemented in the Makefile. So if you need to recompile, I recommend "make clean" from the root to preceed any subsequent "make" call.
If you run an instance of stunserver locally, you may observe that "stunclient localhost" may not successfully work. This is because the server is not listening on the loopback adapter when running in full mode. The workaround is to specify the actual IP address that the server is listening on. Type "ifconfig" to discover your IP address (e.g. 10.11.12.13) followed by "stunclient 10.11.12.13"
---------------------------------------------------------
Testing:
Fedora 15 with gcc/g++ 4.6.0
Ubuntu 11 with gcc/g++ 4.5.2
Amazon AWS with gcc/g++ 4.4
MacOS Snow Leopard (will not compile on earlier versions without updating to a newer version of gcc/g++)
FreeBSD 9.0 with gcc/g++ 4.2.1
Solaris 11 with gcc/g++ 4.5.2
Parsing code has been fuzz tested with zzuf. http://caca.zoy.org/wiki/zzuf
---------------------------------------------------------
Prerequisites before compiling and running.
Boost header files. www.boost.org (sudo yum install boost-devel). The compiled Boost runtime is not necessary. Just unpacking the Boost source code distribution will suffice. If you do not have the adminstrative privaleges to install the Boost distribution into a standard system include path, you may uncomment and edit the top line of the common.inc file for the BOOST_INCLUDE variable. The common.inc file is in the same folder as this README file.
OpenSSL development files and runtime. www.openssl.org (sudo yum install openssl-devel). On most systems with development tools already installed, OpenSSL include files are already installed in the standard include path. If this is not the case, you can uncomment and edit the common.inc file to have the OPENSSL_INCLUDE variable defined.
pthreads header and libs (I haven't seen a distribution where this wasn't already installed)
perl. Only needed if you rebuild the content in the resources directory. Otherwise, not needed.
---------------------------------------------------------
Compiling and running
Got Boost and OpenSSL taken care of as described above? Good. Just type "make" (or "gmake" on some systems). There will be three resulting binaries in the root of the source code package produced.
stuntestcode - This is the unit test code. I highly recommend you run this program first. When run, you'll see a series of lines being printed in regards to different code paths being tested. If you see any line that ends in "FAIL", we likely have a bug. Please contact me immediately if you see this.
stunserver - this is the server binary. Run "./stunserver --help" for details on running this program. Running this program without any command line arguments defaults to listening on port 3478 on all adapters.
stunclient - this is the client test binary. Run "./stunclient --help" for details on running this program. Example: "./stunclient stun.selbie.com"
---------------------------------------------------------
Firewall
Don't forget to configure your firewall to allow traffic for the local ports the stunserver will be listening on!
---------------------------------------------------------
Feature roadmap (the features I want to implement in a subsequent release)
Host a full server across two separate machines (such that two ip addresses on a single machine will not be required for full mode).
Cleanup Makefile and add "configure" and autotools support
Finish Windows port and able to run as a Windows service
Scale across more than one CPU (for multi-core and multi-proc machines). The threading code has already been written, just needs some finish work.
TLS support
---------------------------------------------------------
Contact the author
John Selbie
john@selbie.com
.TH STUNCLIENT 1 "" "January 22, 2012" "User Manual"
.SH NAME
.PP
stunclient - command line app for the STUN protocol
.SH SYNOPSIS
.PP
\f[B]stunclient\f[] [OPTIONS] server [port]
.SH DESCRIPTION
.PP
stunclient attempts to discover the local host's own external IP
address, obtain a port mapping, and optionally discover properties
of the Network Address Translator (NAT) between the host and the
the server.
.SH OPTIONS
.PP
The following options are supported.
.PP
\f[CR]
--mode\ MODE
--localaddr\ INTERFACE
--localport\ PORTNUMBER
--family\ IPVERSION
--protocol\ PROTO
--verbosity\ LOGLEVEL
--help
\f[]
.PP
Details of each option and paramters are as follows.
.PP
\f[B]server\f[]
.PP
The \f[B]server\f[] parameter is the IP address or FQDN of the
remote server to befrom the binding tests with.
It is the only required parameter.
.PP
* * * * *
.PP
\f[B]port\f[]
.PP
The \f[B]port\f[] parameter is an optional parameter that can
follow the server parameter.
The default is 3478 for UDP and TCP.
.PP
* * * * *
.PP
\f[B]\[em]mode\f[] MODE
.PP
Where MODE is either \[lq]basic\[rq] or \[lq]full\[rq].
\[lq]basic\[rq] mode is the default and indicates that the client
should perform a STUN binding test only.
\[lq]full\[rq] mode indicates that the client should attempt to
diagnose NAT behavior and filtering methodologies if the server
supports this mode.
The NAT filtering test is only supported for UDP.
.PP
* * * * *
.PP
\f[B]\[em]localaddr\f[] INTERFACE or IPADDRESS
.PP
The value for this option may the name of an interface (such as
\[lq]eth0\[rq] or \[lq]lo\[rq]).
Or it may be one of the available IP addresses assigned to a
network interface present on the host (such as
\[lq]128.23.45.67\[rq]).
The interface chosen will be the preferred address for sending and
receiving responses with the remote server.
The default is to let the system decide which address to send on
and to listen for responses on all addresses (INADDR_ANY).
.PP
* * * * *
.PP
\f[B]\[em]localport\f[] PORTNUM
.PP
PORTNUM is a value between 1 to 65535.
This is the UDP or TCP port that the primary and alternate
interfaces listen on as the primary port for binding requests.
If not specified, a randomly available port chosen by the system is
used.
.PP
* * * * *
.PP
\f[B]\[em]family\f[] IPVERSION
.PP
IPVERSION is either \[lq]4\[rq] or \[lq]6\[rq] to specify the usage
of IPV4 or IPV6.
If not specified, the default value is \[lq]4\[rq].
.PP
* * * * *
.PP
\f[B]\[em]protocol\f[] PROTO
.PP
PROTO is either \[lq]udp\[rq] or \[lq]tcp\[rq].
\[lq]udp\[rq] is the default if this parameter is not specified
.PP
* * * * *
.PP
\f[B]\[em]verbosity\f[] LOGLEVEL
.PP
Sets the verbosity of the logging level.
0 is the default (minimal output and logging).
1 shows slightly more.
2 and higher shows even more.
.PP
* * * * *
.PP
\f[B]\[em]help\f[] Prints this help page
.SH EXAMPLES
.TP
.B stunclient stunserver.org 3478
Performs a simple binding test request with the server listening at
\[lq]stunserver.org\[rq]
.RS
.RE
.TP
.B stunclient \[em]mode full \[em]localport 9999 12.34.56.78
Performs a full set of UDP NAT behavior tests from local port 9999
to the server listening at IP Address 12.34.56.78 (port 3478)
.RS
.RE
.TP
.B stunclient \[em]protocol tcp stun.selbie.com
Performs a simple binding test using TCP to server listening on the
default port of 3478 at stun.selbie.com
.RS
.RE
.SH AUTHOR
.PP
john selbie (jselbie\@gmail.com)
% STUNCLIENT(1) January 22, 2012 | User Manual
# NAME
stunclient \- command line app for the STUN protocol
# SYNOPSIS
**stunclient** [OPTIONS] server [port]
#DESCRIPTION
stunclient attempts to discover the local host's own external IP address, obtain a
port mapping, and optionally discover properties of the Network Address Translator (NAT)
between the host and the the server.
# OPTIONS
The following options are supported.
--mode MODE
--localaddr INTERFACE
--localport PORTNUMBER
--family IPVERSION
--protocol PROTO
--verbosity LOGLEVEL
--help
Details of each option and paramters are as follows.
**server**
The **server** parameter is the IP address or FQDN of the remote server to befrom the binding tests with. It is the only required parameter.
_____
**port**
The **port** parameter is an optional parameter that can follow the server parameter. The default is 3478 for UDP and TCP.
_____
**--mode** MODE
Where MODE is either "basic" or "full". "basic" mode is the default and indicates that the
client should perform a STUN binding test only. "full" mode indicates that the client should
attempt to diagnose NAT behavior and filtering methodologies if the server supports this mode.
The NAT filtering test is only supported for UDP.
____
**--localaddr** INTERFACE or IPADDRESS
The value for this option may the name of an interface (such as "eth0" or "lo"). Or it may be
one of the available IP addresses assigned to a network interface present on the host (such as
"128.23.45.67"). The interface chosen will be the preferred address for sending and receiving
responses with the remote server. The default is to let the system decide which address to send
on and to listen for responses on all addresses (INADDR_ANY).
____
**--localport** PORTNUM
PORTNUM is a value between 1 to 65535. This is the UDP or TCP port that the primary and
alternate interfaces listen on as the primary port for binding requests. If not specified, a
randomly available port chosen by the system is used.
____
**--family** IPVERSION
IPVERSION is either "4" or "6" to specify the usage of IPV4 or IPV6. If not specified, the default value is "4".
____
**--protocol** PROTO
PROTO is either "udp" or "tcp". "udp" is the default if this parameter is not specified
____
**--verbosity** LOGLEVEL
Sets the verbosity of the logging level. 0 is the default (minimal output and logging). 1 shows slightly more. 2 and higher shows even more.
____
**--help**
Prints this help page
# EXAMPLES
stunclient stunserver.org 3478
: Performs a simple binding test request with the server listening at "stunserver.org"
stunclient --mode full --localport 9999 12.34.56.78
: Performs a full set of UDP NAT behavior tests from local port 9999 to the server listening at IP
Address 12.34.56.78 (port 3478)
stunclient --protocol tcp stun.selbie.com
: Performs a simple binding test using TCP to server listening on the default port of 3478 at
stun.selbie.com
# AUTHOR
john selbie (jselbie@gmail.com)
This diff is collapsed.
const char stunclient_lite_text[] = {
0x55, 0x73, 0x61, 0x67, 0x65, 0x3a, 0x20, 0x73, 0x74, 0x75, 0x6e, 0x63,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x5b, 0x4f, 0x50, 0x54, 0x49, 0x4f,
0x4e, 0x53, 0x5d, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x5b,
0x70, 0x6f, 0x72, 0x74, 0x5d, 0x0a, 0x54, 0x72, 0x79, 0x20, 0x27, 0x73,
0x74, 0x75, 0x6e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x2d, 0x2d,
0x68, 0x65, 0x6c, 0x70, 0x27, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x20,
0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x73, 0x65, 0x74,
0x20, 0x6f, 0x66, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20,
0x61, 0x6e, 0x64, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6d,
0x74, 0x65, 0x72, 0x73, 0x0a, 0x0a,0x00};
.TH STUNSERVER 1 "" "January 22, 2012" "User Manual"
.SH NAME
.PP
stunserver - STUN protocol service (RFCs: 3489, 5389, 5789, 5780)
.SH SYNOPSIS
.PP
\f[B]stunserver\f[] [OPTIONS]
.SH DESCRIPTION
.PP
stunserver starts a STUN listening service that responds to STUN
binding requests from remote clients.
Options are described below.
.SH OPTIONS
.PP
The following options are supported.
.PP
\f[CR]
--mode\ MODE
--primaryinterface\ INTERFACE
--altinterface\ INTERFACE
--primaryport\ PORTNUMBER
--altport\ PORTNUMBER
--family\ IPVERSION
--protocol\ PROTO
--maxconn\ MAXCONN
--verbosity\ LOGLEVEL
--help
\f[]
.PP
Details of each option are as follows.
.PP
\f[B]\[em]mode\f[] MODE
.PP
Where the MODE parameter specified is either \[lq]basic\[rq] or
\[lq]full\[rq].
In basic mode, the server listens on a single port.
Basic mode is sufficient for basic NAT traversal scenarios in which
a client needs to discover its external IP address and obtain a
port mapping for a local port it is listening on.
The STUN CHANGE-REQUEST attribute is not supported in basic mode.
.PP
In full mode, the STUN service listens on two different interfaces
and two different ports on each.
A client binding request may specify an option for the server to
send the response back from one of the alternate interfaces and/or
ports.
Full mode facilitates clients attempting to discover NAT behavior
and NAT filtering behavior of the network they are on.
Full mode requires two unique IP addresses on the host.
When run over TCP, the service is not able to support a
CHANGE-REQUEST attribute from the client.
.PP
If this parameter is not specified, basic mode is the default.
.PP
* * * * *
.PP
\f[B]\[em]primaryinterface\f[] INTERFACE
.PP
Where INTERFACE specified is either a local IP address (e.g.
\[lq]192.168.1.2\[rq]) of the host or the name of a network
interface (e.g.
\[lq]eth0\[rq]).
.PP
The interface or address specified will be used by the service as
the primary listening address.
.PP
In basic mode, the default is to bind to all available adapters
(INADDR_ANY).
In full mode, the default is to bing to the first non-localhost
adapter with a configured IP address.
.PP
* * * * *
.PP
\f[B]\[em]altinterface\f[] INTERFACE OR IPADDRESS
.PP
Where INTERFACE specified is either a local IP address (e.g.
\[lq]192.168.1.3\[rq]) of the host or the name of a network
interface (e.g.
\[lq]eth1\[rq]).
.PP
This parameter is nearly identical as the \[em]primaryinterface
option except that it specifies the alternate listening address for
full mode.
.PP
This option is ignored in basic mode.
In full mode, the default is to bind to the second non-localhost
adapter with a configured IP address.
.PP
* * * * *
.PP
\f[B]\[em]primaryport\f[] PORTNUM
.PP
Where PORTNUM is a value between 1 to 65535.
.PP
This is the primary port the server will bind to for listening for
incoming binding requests.
The service will bind both the primary address and the alternate
address to this port.
.PP
The default is 3478.
.PP
* * * * *
.PP
\f[B]\[em]altport\f[] PORTNUM
.PP
Where PORTNUM is a value between 1 to 65535.
.PP
This is the alternate port the server will bind to for listening
for incoming binding requests.
The service will bind both the primary address and the alternate
address to this port.
.PP
This option is ignored in basic mode.
The default is 3479.
.PP
* * * * *
.PP
\f[B]\[em]family\f[] IPVERSION
.PP
Where IPVERSION is either \[lq]4\[rq] or \[lq]6\[rq] to specify the
usage of IPV4 or IPV6.
.PP
The default family is 4 for IPv4 usage.
.PP
* * * * *
.PP
\f[B]\[em]protocol\f[] PROTO
.PP
Where PROTO is either IP protocol, \[lq]udp\[rq] or \[lq]tcp\[rq].
.PP
udp is the default.
.PP
* * * * *
.PP
\f[B]\[em]maxconn\f[] MAXCONN
.PP
Where MAXCONN is a value between 1 and 100000.
.PP
For TCP mode, this parameter specifies the maximum number of
simultaneous connections that can exist at any given time.
.PP
This parameter is ignored when the protocol is UDP.
The default value is 1000
.PP
* * * * *
.PP
\f[B]\[em]verbosity\f[] LOGLEVEL
.PP
Where LOGLEVEL is a value greater than or equal to 0.
.PP
This parameter specifies how much is printed to the console with
regards to initialization, errors, and network activity.
A value of 0 specifies a very minimal amount of output.
A value of 1 shows slightly more.
A value of 2 shows even more.
Specifying 3 will show a lot more.
.PP
The default is 0.
.PP
* * * * *
.PP
\f[B]\[em]help\f[]
.PP
Prints this help page
.SH EXAMPLES
.TP
.B stunserver
With no options, starts a basic STUN binding service on UDP port
3478.
.RS
.RE
.TP
.B stunserver \[em]mode full \[em]primaryinterface 128.34.56.78 \[em]altinterface 128.34.56.79
Above example starts a dual-host STUN service on the the interfaces
identified by the IP address \[lq]128.34.56.78\[rq] and
\[lq]128.34.56.79\[rq].
There are four UDP socket listeners
.RS
.PP
128.34.56.78:3478 (Primary IP, Primary Port) 128.34.56.78:3479
(Primary IP, Alternate Port) 128.34.56.79:3478 (Primary IP, Primary
Port) 128.34.56.79:3479 (Alternate IP, Alternate Port)
.RE
.PP
An error occurs if the addresses specified do not exist on the
local host running the service.
.TP
.B stunserver \[em]mode full \[em]primaryinterface eth0 \[em]altinterface eth1
Same as above, except the interfaces are specified by their names
as enumerated by the system.
(The \[lq]ifconfig\[rq] or \[lq]ipconfig\[rq] command will
enumerate available interface names.
.RS
.RE
.SH AUTHOR
.PP
john selbie (john\@selbie.com)
% STUNSERVER(1) January 22, 2012 | User Manual
# NAME
stunserver \- STUN protocol service (RFCs: 3489, 5389, 5789, 5780)
# SYNOPSIS
**stunserver** [OPTIONS]
#DESCRIPTION
stunserver starts a STUN listening service that responds to STUN binding requests from remote
clients. Options are described below.
# OPTIONS
The following options are supported.
--mode MODE
--primaryinterface INTERFACE
--altinterface INTERFACE
--primaryport PORTNUMBER
--altport PORTNUMBER
--family IPVERSION
--protocol PROTO
--maxconn MAXCONN
--verbosity LOGLEVEL
--help
Details of each option are as follows.
**--mode** MODE
Where the MODE parameter specified is either "basic" or "full".
In basic mode, the server listens on a single port. Basic mode is sufficient for basic NAT
traversal scenarios in which a client needs to discover its external IP address
and obtain a port mapping for a local port it is listening on. The STUN
CHANGE-REQUEST attribute is not supported in basic mode.
In full mode, the STUN service listens on two different interfaces and two
different ports on each. A client binding request may specify an option
for the server to send the response back from one of the alternate
interfaces and/or ports. Full mode facilitates clients attempting to
discover NAT behavior and NAT filtering behavior of the network they are on.
Full mode requires two unique IP addresses on the host. When run over TCP,
the service is not able to support a CHANGE-REQUEST attribute from
the client.
If this parameter is not specified, basic mode is the default.
____
**--primaryinterface** INTERFACE
Where INTERFACE specified is either a local IP address (e.g. "192.168.1.2")
of the host or the name of a network interface (e.g. "eth0").
The interface or address specified will be used by the service as the primary
listening address.
In basic mode, the default is to bind to all available adapters (INADDR_ANY).
In full mode, the default is to bing to the first non-localhost adapter with
a configured IP address.
____
**--altinterface** INTERFACE OR IPADDRESS
Where INTERFACE specified is either a local IP address (e.g. "192.168.1.3")
of the host or the name of a network interface (e.g. "eth1").
This parameter is nearly identical as the --primaryinterface option except
that it specifies the alternate listening address for full mode.
This option is ignored in basic mode. In full mode, the default is to bind
to the second non-localhost adapter with a configured IP address.
____
**--primaryport** PORTNUM
Where PORTNUM is a value between 1 to 65535.
This is the primary port the server will bind to for listening for incoming
binding requests. The service will bind both the primary address and the
alternate address to this port.
The default is 3478.
____
**--altport** PORTNUM
Where PORTNUM is a value between 1 to 65535.
This is the alternate port the server will bind to for listening for incoming
binding requests. The service will bind both the primary address and the
alternate address to this port.
This option is ignored in basic mode. The default is 3479.
____
**--family** IPVERSION
Where IPVERSION is either "4" or "6" to specify the usage of IPV4 or IPV6.
The default family is 4 for IPv4 usage.
____
**--protocol** PROTO
Where PROTO is either IP protocol, "udp" or "tcp".
udp is the default.
____
**--maxconn** MAXCONN
Where MAXCONN is a value between 1 and 100000.
For TCP mode, this parameter specifies the maximum number of simultaneous
connections that can exist at any given time.
This parameter is ignored when the protocol is UDP. The default value is 1000
____
**--verbosity** LOGLEVEL
Where LOGLEVEL is a value greater than or equal to 0.
This parameter specifies how much is printed to the console with regards to
initialization, errors, and network activity. A value of 0 specifies a
very minimal amount of output. A value of 1 shows slightly more. A value of
2 shows even more. Specifying 3 will show a lot more.
The default is 0.
____
**--help**
Prints this help page
# EXAMPLES
stunserver
: With no options, starts a basic STUN binding service on UDP port 3478.
stunserver --mode full --primaryinterface 128.34.56.78 --altinterface 128.34.56.79
: Above example starts a dual-host STUN service on the the interfaces
identified by the IP address "128.34.56.78" and "128.34.56.79". There are
four UDP socket listeners
128.34.56.78:3478 (Primary IP, Primary Port)
128.34.56.78:3479 (Primary IP, Alternate Port)
128.34.56.79:3478 (Primary IP, Primary Port)
128.34.56.79:3479 (Alternate IP, Alternate Port)
An error occurs if the addresses specified do not exist on the local host
running the service.
stunserver --mode full --primaryinterface eth0 --altinterface eth1
: Same as above, except the interfaces are specified by their names as
enumerated by the system. (The "ifconfig" or "ipconfig" command will
enumerate available interface names.
# AUTHOR
john selbie (john@selbie.com)
This diff is collapsed.
const char stunserver_lite_text[] = {
0x55, 0x73, 0x61, 0x67, 0x65, 0x3a, 0x20, 0x73, 0x74, 0x75, 0x6e, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x5b, 0x4f, 0x50, 0x54, 0x49, 0x4f,
0x4e, 0x5d, 0x2e, 0x2e, 0x2e, 0x0a, 0x54, 0x72, 0x79, 0x20, 0x27, 0x73,
0x74, 0x75, 0x6e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x2d, 0x2d,
0x68, 0x65, 0x6c, 0x70, 0x27, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x20,
0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x73, 0x65, 0x74,
0x20, 0x6f, 0x66, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a,0x00};
#!/usr/bin/perl
# xxdperl.pl
# emulates "xxd -i" in perl (stdin and stdout only)
$count = 0;
while ($input = <STDIN>) {
@characters = split(//, $input);
foreach (@characters) {
if ($count != 0) {
print ',';
if (($count % 12)==0) {
print "\n ";
}
}
else {
print ' ';
}
print sprintf(" 0x%.2x", ord($_));
$count++;
}
}
include ../common.inc
PROJECT_TARGET := stunserver
PROJECT_OBJS := main.o server.o stunsocketthread.o
PROJECT_OBJS := main.o server.o stunconnection.o stunsocketthread.o tcpserver.o
PROJECT_INTERMEDIATES := usage.txtcode usagelite.txtcode
INCLUDES := $(BOOST_INCLUDE) -I../common -I../stuncore -I../networkutils
INCLUDES := $(BOOST_INCLUDE) -I../common -I../stuncore -I../networkutils -I../resources
LIB_PATH := -L../common -L../stuncore -L../networkutils
LIBS := -lnetworkutils -lstuncore -lcommon -pthread -lcrypto
......@@ -14,16 +11,9 @@ LIBS := -lnetworkutils -lstuncore -lcommon -pthread -lcrypto
all: $(PROJECT_TARGET)
clean:
rm -f $(PROJECT_OBJS) $(PROJECT_TARGET) $(PROJECT_INTERMEDIATES)
rm -f $(PROJECT_OBJS) $(PROJECT_TARGET)
$(PROJECT_TARGET): $(PROJECT_OBJS)
$(LINK.cpp) -o $@ $^ $(LIB_PATH) $(LIBS)
main.cpp: usage.txtcode usagelite.txtcode
%.txtcode: %.txt
sh makecodefile.sh $< $@ $(*)_text
$(LINK.cpp) -o $@ $^ $(LIB_PATH) $(LIBS) $(SOCKET_LIBS)
......@@ -17,6 +17,7 @@
#include "commonincludes.h"
#include "stuncore.h"
#include "server.h"
#include "tcpserver.h"
#include "adapters.h"
#include "cmdlineparser.h"
......@@ -27,15 +28,15 @@
#include "stringhelper.h"
// unusual to include usage.cpp and usagelite.cpp here, but these are auto-generated resource file
#include "usage.txtcode"
#include "usagelite.txtcode"
// these are auto-generated files made from markdown sources. See ../resources
#include "stunserver.txtcode"
#include "stunserver_lite.txtcode"
void PrintUsage(bool fSummaryUsage)
{
size_t width = GetConsoleWidth();
const char* psz = fSummaryUsage ? usagelite_text : usage_text;
const char* psz = fSummaryUsage ? stunserver_lite_text : stunserver_text;
// save some margin space
if (width > 2)
......@@ -46,6 +47,54 @@ void PrintUsage(bool fSummaryUsage)
PrettyPrint(psz, width);
}
void LogHR(uint16_t level, HRESULT hr)
{
uint32_t facility = HRESULT_FACILITY(hr);
char msg[400];
const char* pMsg = NULL;
bool fGotMsg = false;
if (facility == FACILITY_ERRNO)
{
msg[0] = '\0';
int err = (int)(HRESULT_CODE(hr));
#ifdef _GNU_SOURCE
pMsg = strerror_r(err, msg, ARRAYSIZE(msg));
#else
{
int result = strerror_r(err, msg, ARRAYSIZE(msg));
if (result == -1)
{
sprintf(msg, "%d", err);
}
pMsg = msg;
}
#endif
if (pMsg)
{
Logging::LogMsg(level, "Error: %s", pMsg);
fGotMsg = true;
}
if (err == EADDRINUSE)
{
Logging::LogMsg(level,
"This error likely means another application is listening on one\n"
"or more of the same ports you are attempting to configure this\n"
"server to listen on. Run \"netstat -a -p -t -u\" to see a list\n"
"of all ports in use and associated process id for each");
}
}
if (fGotMsg == false)
{
Logging::LogMsg(level, "Error: %x", hr);
}
}
struct StartupArgs
......@@ -59,6 +108,7 @@ struct StartupArgs
std::string strProtocol;
std::string strHelp;
std::string strVerbosity;
std::string strMaxConnections;
};
#define PRINTARG(member) Logging::LogMsg(LL_DEBUG, "%s = %s", #member, args.member.length() ? args.member.c_str() : "<empty>");
......@@ -75,6 +125,7 @@ void DumpStartupArgs(StartupArgs& args)
PRINTARG(strProtocol);
PRINTARG(strHelp);
PRINTARG(strVerbosity);
PRINTARG(strMaxConnections);
Logging::LogMsg(LL_DEBUG, "--------------------------\n");
}
......@@ -106,6 +157,11 @@ void DumpConfig(CStunServerConfig &config)
Logging::LogMsg(LL_DEBUG, "AA = %s", strSocket.c_str());
}
Logging::LogMsg(LL_DEBUG, "Protocol = %s", config.fTCP ? "TCP" : "UDP");
if (config.fTCP && (config.nMaxConnections>0))
{
Logging::LogMsg(LL_DEBUG, "Max TCP Connections per thread: %d", config.nMaxConnections);
}
}
......@@ -149,6 +205,7 @@ HRESULT BuildServerConfigurationFromArgs(StartupArgs& argsIn, CStunServerConfig*
int nAltPort = DEFAULT_STUN_PORT + 1;
bool fHasAtLeastTwoAdapters = false;
CStunServerConfig config;
int nMaxConnections = 0;
enum ServerMode
{
......@@ -224,13 +281,37 @@ HRESULT BuildServerConfigurationFromArgs(StartupArgs& argsIn, CStunServerConfig*
// ---- PROTOCOL --------------------------------------------------------
if (args.strProtocol.length() > 0)
{
if (args.strProtocol != "udp")
if ((args.strProtocol != "udp") && (args.strProtocol != "tcp"))
{
Logging::LogMsg(LL_ALWAYS, "Protocol argument must be 'udp' . 'tcp' and 'tls' are not supported yet");
Logging::LogMsg(LL_ALWAYS, "Protocol argument must be 'udp' or 'tcp'. 'tls' is not supported yet");
Chk(E_INVALIDARG);
}
config.fTCP = (args.strProtocol == "tcp");
}
// ---- MAX Connections -----------------------------------------------------
nMaxConnections = 0;
if (args.strMaxConnections.length() > 0)
{
if (config.fTCP == false)
{
Logging::LogMsg(LL_ALWAYS, "Max connections parameter has no meaning in UDP mode. Did you mean to specify \"--protocol=tcp ?\"");
}
else
{
hr = StringHelper::ValidateNumberString(args.strMaxConnections.c_str(), 1, 100000, &nMaxConnections);
if (FAILED(hr))
{
Logging::LogMsg(LL_ALWAYS, "Max connections must be between 1-100000");
Chk(hr);
}
}
config.nMaxConnections = nMaxConnections;
}
// ---- PRIMARY PORT --------------------------------------------------------
nPrimaryPort = DEFAULT_STUN_PORT;
if (args.strPrimaryPort.length() > 0)
......@@ -334,6 +415,7 @@ HRESULT BuildServerConfigurationFromArgs(StartupArgs& argsIn, CStunServerConfig*
Logging::LogMsg(LL_ALWAYS, "Error - Primary interface and Alternate Interface appear to have the same IP address. Full mode requires two IP addresses that are unique");
Chk(E_INVALIDARG);
}
config.addrPP = addrPrimary;
config.addrPP.SetPort(portPrimary);
......@@ -350,6 +432,7 @@ HRESULT BuildServerConfigurationFromArgs(StartupArgs& argsIn, CStunServerConfig*
config.addrAA = addrAlternate;
config.addrAA.SetPort(portAlternate);
config.fHasAA = true;
}
*pConfigOut = config;
......@@ -373,6 +456,7 @@ HRESULT ParseCommandLineArgs(int argc, char** argv, int startindex, StartupArgs*
cmdline.AddOption("altport", required_argument, &pStartupArgs->strAltPort);
cmdline.AddOption("family", required_argument, &pStartupArgs->strFamily);
cmdline.AddOption("protocol", required_argument, &pStartupArgs->strProtocol);
cmdline.AddOption("maxconn", required_argument, &pStartupArgs->strMaxConnections);
cmdline.AddOption("help", no_argument, &pStartupArgs->strHelp);
cmdline.AddOption("verbosity", required_argument, &pStartupArgs->strVerbosity);
......@@ -426,6 +510,55 @@ void WaitForAppExitSignal()
HRESULT StartUDP(CRefCountedPtr<CStunServer>& spServer, CStunServerConfig& config)
{
HRESULT hr;
hr = CStunServer::CreateInstance(config, spServer.GetPointerPointer());
if (FAILED(hr))
{
Logging::LogMsg(LL_ALWAYS, "Unable to initialize server (error code = x%x)", hr);
LogHR(LL_ALWAYS, hr);
return hr;
}
hr = spServer->Start();
if (FAILED(hr))
{
Logging::LogMsg(LL_ALWAYS, "Unable to start server (error code = x%x)", hr);
LogHR(LL_ALWAYS, hr);
return hr;
}
return S_OK;
}
HRESULT StartTCP(CRefCountedPtr<CTCPServer>& spTCPServer, CStunServerConfig& config)
{
HRESULT hr;
hr = CTCPServer::CreateInstance(config, spTCPServer.GetPointerPointer());
if (FAILED(hr))
{
Logging::LogMsg(LL_ALWAYS, "Unable to initialize TCP server (error code = x%x)", hr);
LogHR(LL_ALWAYS, hr);
return hr;
}
hr = spTCPServer->Start();
if (FAILED(hr))
{
Logging::LogMsg(LL_ALWAYS, "Unable to start TCP server (error code = x%x)", hr);
LogHR(LL_ALWAYS, hr);
return hr;
}
return S_OK;
}
int main(int argc, char** argv)
{
......@@ -433,6 +566,8 @@ int main(int argc, char** argv)
StartupArgs args;
CStunServerConfig config;
CRefCountedPtr<CStunServer> spServer;
CRefCountedPtr<CTCPServer> spTCPServer;
#ifdef DEBUG
Logging::SetLogLevel(LL_DEBUG);
......@@ -482,19 +617,22 @@ int main(int argc, char** argv)
DumpConfig(config);
InitAppExitListener();
hr = CStunServer::CreateInstance(config, spServer.GetPointerPointer());
if (FAILED(hr))
if (config.fTCP == false)
{
Logging::LogMsg(LL_ALWAYS, "Unable to initialize server (error code = x%x)", hr);
return -4;
hr = StartUDP(spServer, config);
if (FAILED(hr))
{
return -4;
}
}
hr = spServer->Start();
if (FAILED(hr))
else
{
Logging::LogMsg(LL_ALWAYS, "Unable to start server (error code = x%x)", hr);
return -5;
hr = StartTCP(spTCPServer, config);
if (FAILED(hr))
{
return -5;
}
}
Logging::LogMsg(LL_DEBUG, "Successfully started server.");
......@@ -503,8 +641,18 @@ int main(int argc, char** argv)
Logging::LogMsg(LL_DEBUG, "Server is exiting");
spServer->Stop();
spServer->Release();
if (spServer != NULL)
{
spServer->Stop();
spServer.ReleaseAndClear();
}
if (spTCPServer != NULL)
{
spTCPServer->Stop();
spTCPServer.ReleaseAndClear();
}
return 0;
}
......
......@@ -124,7 +124,7 @@ as appropriate:
To have the server host an instance of an IStunAuth implementation, modify
CStunServer::Initialize to create an instance of your class and initialize
CStunServer::Initialize and CTCPServer::Initialize to create an instance of your class and initialize
_spAuth as appropriate.
#endif
......@@ -138,6 +138,7 @@ class CShortTermAuth :
{
public:
virtual HRESULT DoAuthCheck(AuthAttributes* pAuthAttributes, AuthResponse* pResponse);
ADDREF_AND_RELEASE_IMPL();
};
......@@ -154,6 +155,7 @@ private:
public:
virtual HRESULT DoAuthCheck(AuthAttributes* pAuthAttributes, AuthResponse* pResponse);
ADDREF_AND_RELEASE_IMPL();
};
......
......@@ -29,14 +29,17 @@ fHasPP(false),
fHasPA(false),
fHasAP(false),
fHasAA(false),
fMultiThreadedMode(false)
fMultiThreadedMode(false),
fTCP(false),
nMaxConnections(0) // zero means default
{
;
}
CStunServer::CStunServer()
CStunServer::CStunServer() :
_arrSockets() // zero-init
{
;
}
......@@ -62,29 +65,29 @@ HRESULT CStunServer::Initialize(const CStunServerConfig& config)
// Create the sockets
if (config.fHasPP)
{
Chk(CStunSocket::Create(config.addrPP, RolePP, &_arrSockets[RolePP]));
_arrSockets[RolePP]->EnablePktInfoOption(true);
Chk(_arrSockets[RolePP].UDPInit(config.addrPP, RolePP));
ChkA(_arrSockets[RolePP].EnablePktInfoOption(true));
socketcount++;
}
if (config.fHasPA)
{
Chk(CStunSocket::Create(config.addrPA, RolePA, &_arrSockets[RolePA]));
_arrSockets[RolePA]->EnablePktInfoOption(true);
Chk(_arrSockets[RolePA].UDPInit(config.addrPA, RolePA));
ChkA(_arrSockets[RolePA].EnablePktInfoOption(true));
socketcount++;
}
if (config.fHasAP)
{
Chk(CStunSocket::Create(config.addrAP, RoleAP, &_arrSockets[RoleAP]));
_arrSockets[RoleAP]->EnablePktInfoOption(true);
Chk(_arrSockets[RoleAP].UDPInit(config.addrAP, RoleAP));
ChkA(_arrSockets[RoleAP].EnablePktInfoOption(true));
socketcount++;
}
if (config.fHasAA)
{
Chk(CStunSocket::Create(config.addrAA, RoleAA, &_arrSockets[RoleAA]));
_arrSockets[RoleAA]->EnablePktInfoOption(true);
Chk(_arrSockets[RoleAA].UDPInit(config.addrAA, RoleAA));
ChkA(_arrSockets[RoleAA].EnablePktInfoOption(true));
socketcount++;
}
......@@ -95,22 +98,13 @@ HRESULT CStunServer::Initialize(const CStunServerConfig& config)
{
Logging::LogMsg(LL_DEBUG, "Configuring single threaded mode\n");
std::vector<CRefCountedStunSocket> listsockets;
for (size_t index = 0; index < ARRAYSIZE(_arrSockets); index++)
{
if (_arrSockets[index] != NULL)
{
listsockets.push_back(_arrSockets[index]);
}
}
// create one thread for all the sockets
CStunSocketThread* pThread = new CStunSocketThread();
ChkIf(pThread==NULL, E_OUTOFMEMORY);
_threads.push_back(pThread);
Chk(pThread->Init(listsockets, this, _spAuth));
Chk(pThread->Init(_arrSockets, _spAuth, (SocketRole)-1));
}
else
{
......@@ -120,14 +114,14 @@ HRESULT CStunServer::Initialize(const CStunServerConfig& config)
CStunSocketThread* pThread = NULL;
for (size_t index = 0; index < ARRAYSIZE(_arrSockets); index++)
{
if (_arrSockets[index] != NULL)
if (_arrSockets[index].IsValid())
{
std::vector<CRefCountedStunSocket> listsockets;
listsockets.push_back(_arrSockets[index]);
SocketRole rolePrimaryRecv = _arrSockets[index].GetRole();
ASSERT(rolePrimaryRecv == (SocketRole)index);
pThread = new CStunSocketThread();
ChkIf(pThread==NULL, E_OUTOFMEMORY);
_threads.push_back(pThread);
Chk(pThread->Init(listsockets, this, _spAuth));
Chk(pThread->Init(_arrSockets, _spAuth, rolePrimaryRecv));
}
}
}
......@@ -154,7 +148,7 @@ HRESULT CStunServer::Shutdown()
for (size_t index = 0; index < ARRAYSIZE(_arrSockets); index++)
{
_arrSockets[index].reset();
_arrSockets[index].Close();
}
len = _threads.size();
......@@ -244,40 +238,5 @@ HRESULT CStunServer::Stop()
}
bool CStunServer::HasAddress(SocketRole role)
{
return (::IsValidSocketRole(role) && (_arrSockets[role].get() != NULL));
}
HRESULT CStunServer::GetSocketAddressForRole(SocketRole role, CSocketAddress* pAddr)
{
HRESULT hr = S_OK;
ChkIf(pAddr == NULL, E_INVALIDARG);
ChkIf(false == HasAddress(role), E_FAIL);
*pAddr = _arrSockets[role]->GetLocalAddress();
Cleanup:
return S_OK;
}
HRESULT CStunServer::SendResponse(SocketRole roleOutput, const CSocketAddress& addr, CRefCountedBuffer& spResponse)
{
HRESULT hr = S_OK;
int sockhandle = -1;
int ret;
ChkIf(false == HasAddress(roleOutput), E_FAIL);
sockhandle = _arrSockets[roleOutput]->GetSocketHandle();
ret = ::sendto(sockhandle, spResponse->GetData(), spResponse->GetSize(), 0, addr.GetSockAddr(), addr.GetSockAddrLength());
ChkIf(ret < 0, ERRNOHR);
Cleanup:
return hr;
}
......@@ -28,12 +28,16 @@
class CStunServerConfig
{
public:
bool fHasPP; // PP: Primary ip, Primary port
bool fHasPA; // PA: Primary ip, Alternate port
bool fHasAP; // AP: Alternate ip, Primary port
bool fHasAA; // AA: Alternate ip, Alternate port
bool fMultiThreadedMode; // if true, one thread for each socket
bool fTCP; // if true, then use TCP instead of UDP
uint32_t nMaxConnections; // only valid for TCP (on a per-thread basis)
CSocketAddress addrPP; // address for PP
CSocketAddress addrPA; // address for PA
......@@ -51,12 +55,10 @@ public:
class CStunServer :
public CBasicRefCount,
public CObjectFactory<CStunServer>,
public IStunResponder
public IRefCounted
{
private:
CRefCountedStunSocket _arrSockets[4];
// when we support multithreaded servers, this will change to a list
CStunSocket _arrSockets[4];
std::vector<CStunSocketThread*> _threads;
......@@ -65,10 +67,8 @@ private:
friend class CObjectFactory<CStunServer>;
CRefCountedPtr<IStunAuth> _spAuth;
public:
HRESULT Initialize(const CStunServerConfig& config);
......@@ -77,14 +77,6 @@ public:
HRESULT Start();
HRESULT Stop();
// IStunResponder
virtual HRESULT SendResponse(SocketRole roleOutput, const CSocketAddress& addr, CRefCountedBuffer& spResponse);
virtual bool HasAddress(SocketRole role);
virtual HRESULT GetSocketAddressForRole(SocketRole role, /*out*/ CSocketAddress* pAddr);
ADDREF_AND_RELEASE_IMPL();
};
......
#include "commonincludes.h"
#include "stuncore.h"
#include "stunconnection.h"
CConnectionPool::CConnectionPool() :
_freelist(NULL)
{
}
CConnectionPool::~CConnectionPool()
{
Reset();
}
void CConnectionPool::Reset()
{
StunConnection* pConn;
while (_freelist)
{
pConn = _freelist;
_freelist = _freelist->pNext;
delete pConn;
}
}
HRESULT CConnectionPool::Grow()
{
const size_t c_growrate = 50;
for (size_t i = 0; i < c_growrate; i++)
{
StunConnection* pConn = NULL;
pConn = new StunConnection();
if (pConn == NULL)
{
return E_OUTOFMEMORY;
}
pConn->_spOutputBuffer = CRefCountedBuffer(new CBuffer(MAX_STUN_MESSAGE_SIZE));
pConn->_spReaderBuffer = CRefCountedBuffer(new CBuffer(MAX_STUN_MESSAGE_SIZE));
if ((pConn->_spOutputBuffer == NULL) || (pConn->_spReaderBuffer == NULL))
{
delete pConn;
return E_OUTOFMEMORY;
}
pConn->pNext = _freelist;
_freelist = pConn;
}
return S_OK;
}
StunConnection* CConnectionPool::GetConnection(int sock, SocketRole role)
{
StunConnection* pConn = NULL;
if (_freelist == NULL)
{
Grow();
if (_freelist == NULL)
{
return NULL; // out of memory ?
}
}
pConn = _freelist;
_freelist = pConn->pNext;
pConn->pNext = NULL;
// prep this connection for usage
pConn->_reader.Reset();
pConn->_reader.GetStream().Attach(pConn->_spReaderBuffer, true);
pConn->_state = ConnectionState_Receiving;
pConn->_stunsocket.Attach(sock);
pConn->_stunsocket.SetRole(role);
pConn->_txCount = 0;
pConn->_timeStart = time(NULL);
pConn->_idHashTable = -1;
return pConn;
}
void CConnectionPool::ReleaseConnection(StunConnection* pConn)
{
ASSERT(pConn->_stunsocket.IsValid() == false); // not the pool's job to close a socket!
pConn->pNext = _freelist;
_freelist = pConn;
}
#ifndef STUN_CONNECTION_H
#define STUN_CONNECTION_H
#include "stuncore.h"
#include "stunsocket.h"
enum StunConnectionState
{
ConnectionState_Idle,
ConnectionState_Receiving,
ConnectionState_Transmitting,
ConnectionState_Closing, // shutdown has been called, waiting for close notification on other end
};
struct StunConnection
{
time_t _timeStart;
StunConnectionState _state;
CStunSocket _stunsocket;
CStunMessageReader _reader;
CRefCountedBuffer _spReaderBuffer;
CRefCountedBuffer _spOutputBuffer; // contains the response
size_t _txCount; // number of bytes of response transmitted thus far
int _idHashTable; // hints at which hash table the connection got inserted into
StunConnection* pNext; // next item in pool - meaningless outside of the pool
};
class CConnectionPool
{
private:
StunConnection* _freelist;
HRESULT Grow();
public:
CConnectionPool();
~CConnectionPool();
StunConnection* GetConnection(int sock, SocketRole role);
void ReleaseConnection(StunConnection* pConn);
void Reset();
};
#endif
\ No newline at end of file
This diff is collapsed.
......@@ -32,14 +32,13 @@ public:
CStunSocketThread();
~CStunSocketThread();
HRESULT Init(std::vector<CRefCountedStunSocket>& listSockets, IStunResponder* pResponder, IStunAuth* pAuth);
HRESULT Init(CStunSocket* arrayOfFourSockets, IStunAuth* pAuth, SocketRole rolePrimaryRecv);
HRESULT Start();
HRESULT SignalForStop(bool fPostMessages);
HRESULT WaitForStopAndClose();
/// returns back the index of the socket _socks that is ready for data, otherwise, -1
int WaitForSocketData();
private:
......@@ -48,13 +47,40 @@ private:
static void* ThreadFunction(void* pThis);
std::vector<CRefCountedStunSocket> _socks;
CStunSocket* WaitForSocketData();
CStunSocket* _arrSendSockets; // matches CStunServer::_arrSockets
std::vector<CStunSocket*> _socks; // sockets for receiving on
bool _fNeedToExit;
CStunThreadMessageHandler _handler;
pthread_t _pthread;
bool _fThreadIsValid;
int _rotation;
TransportAddressSet _tsa;
CRefCountedPtr<IStunAuth> _spAuth;
// pre-allocated objects for the thread
CStunMessageReader _reader;
CRefCountedBuffer _spBufferReader; // buffer internal to the reader
CRefCountedBuffer _spBufferIn; // buffer we receive requests on
CRefCountedBuffer _spBufferOut; // buffer we send response on
StunMessageIn _msgIn;
StunMessageOut _msgOut;
HRESULT InitThreadBuffers();
void UninitThreadBuffers();
HRESULT ProcessRequestAndSendResponse();
void ClearSocketArray();
};
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -122,15 +122,6 @@ CBuffer::CBuffer(uint8_t* pByteArray, size_t nByteArraySize, bool fCopy)
size_t CBuffer::GetSize()
{
return _size;
}
size_t CBuffer::GetAllocatedSize()
{
return _allocatedSize;
}
HRESULT CBuffer::SetSize(size_t size)
{
......@@ -147,10 +138,7 @@ HRESULT CBuffer::SetSize(size_t size)
}
uint8_t* CBuffer::GetData()
{
return _data;
}
bool CBuffer::IsValid()
{
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment