Commit 693fea25 authored by John Selbie's avatar John Selbie

Version 1.0!

parent 7384df4b
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commerc:qial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
.PHONY: all everything copybin debug clean
all: everything copybin
everything:
$(MAKE) $(T) --directory=common
$(MAKE) $(T) --directory=stuncore
$(MAKE) $(T) --directory=networkutils
$(MAKE) $(T) --directory=testcode
$(MAKE) $(T) --directory=client
$(MAKE) $(T) --directory=server
copybin:
rm -f ./stunserver ./stunclient ./stuntestcode
cp server/stunserver .
cp client/stunclient .
cp testcode/stuntestcode .
debug: T := debug
debug: all
clean: T := clean
clean: everything
rm -f stunserver stunclient stuntestcode
StunServer version 1.0.0
September 7, 2011
---------------------------------------------------------
The world's best stun server - coming soon! Features:
Compliant with the latest RFCs including 5389, 5769, and 5780. Also includes
backwards compatibility for RFC 3489.
IPv4 and IPv6 support
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:
UDP only. Command line options for working in TCP or TLS modes have 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++)
Parsing code has been fuzz tested with zzuf. http://caca.zoy.org/wiki/zzuf
---------------------------------------------------------
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)
/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")
pthreads header and libs (I haven't seen a distribution where this wasn't
already installed)
---------------------------------------------------------
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.
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)
TCP and TLS 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).
Cleanup Makefile and add "configure" and autotools support
---------------------------------------------------------
Contact the author
John Selbie
john@selbie.com
Interested? Follow up with jselbie@gmail.com
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
LIB_PATH := -L../common -L../stuncore -L../networkutils
LIBS := -lnetworkutils -lstuncore -lcommon -lcrypto
all: $(PROJECT_TARGET)
clean:
rm -f $(PROJECT_OBJS) $(PROJECT_TARGET) $(PROJECT_INTERMEDIATES)
$(PROJECT_TARGET): $(PROJECT_OBJS)
$(LINK.cpp) -o $@ $^ $(LIB_PATH) $(LIBS)
clientmain.cpp: usage.txtcode usagelite.txtcode
%.txtcode: %.txt
sh ../server/makecodefile.sh $< $@ $(*)_text
/*
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"
#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"
// unusual to include usage.cpp and usagelite.cpp here, but these are auto-generated resource file
#include "usage.txtcode"
#include "usagelite.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 ? usagelite_text : usage_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];
config.fBehaviorTest = false;
config.fFilteringTest = false;
config.timeoutSeconds = 5;
config.uMaxAttempts = 3;
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")
{
Logging::LogMsg(LL_ALWAYS, "Only udp is supported as a protocol option in this version");
Chk(E_INVALIDARG);
}
}
// 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 = true;
}
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.fBehaviorTestSuccess?"success":"fail");
if (results.fBehaviorTestSuccess)
{
NatFilteringToString(results.filtering, &strResult);
Logging::LogMsg(LL_ALWAYS, "Nat filtering: %s", strResult.c_str());
}
}
}
HRESULT ClientLoop(StunClientLogicConfig& config, const ClientSocketConfig& socketconfig)
{
HRESULT hr = S_OK;
CRefCountedStunSocket spStunSocket;
CRefCountedBuffer spMsg(new CBuffer(1500));
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 = CStunSocket::Create(socketconfig.addrLocal, RolePP, &spStunSocket);
if (FAILED(hr))
{
Logging::LogMsg(LL_ALWAYS, "Unable to create local socket: (error = x%x)", hr);
Chk(hr);
}
spStunSocket->EnablePktInfoOption(true);
sock = spStunSocket->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);
ClientLoop(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)
Usage: stunclient [OPTIONS] server [port]
Try 'stunclient --help' for a complete set of options and description of paramters
BOOST_INCLUDE := -I/home/jselbie/lib/boost_1_46_1
# OPENSSL_INCLUDE := /home/jselbie/lib/openssl
DEFINES := -DNDEBUG
STANDARD_FLAGS := -Wall -Wuninitialized
RELEASE_FLAGS := -O2
DEBUG_FLAGS := -g
FLAVOR_FLAGS = $(RELEASE_FLAGS)
.PHONY: all clean debug
%.h.gch: %.h
echo Building precompiled header: $@
$(COMPILE.cpp) $(INCLUDES) $(DEFINES) $(STANDARD_FLAGS) $(FLAVOR_FLAGS) $^
%.o: %.cpp
$(COMPILE.cpp) $(INCLUDES) $(DEFINES) $(STANDARD_FLAGS) $(FLAVOR_FLAGS) $^
# put "all" target first so that it is the default
all:
debug: FLAVOR_FLAGS = $(DEBUG_FLAGS)
debug: DEFINES = -DDEBUG
debug: 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_OBJS := $(subst .cpp,.o,$(PROJECT_SRCS))
INCLUDES := $(BOOST_INCLUDE)
PRECOMP_H_GCH := commonincludes.h.gch
all: $(PRECOMP_H_GCH) $(PROJECT_TARGET)
clean:
rm -f $(PROJECT_OBJS) $(PROJECT_TARGET) $(PRECOMP_H_GCH)
$(PROJECT_TARGET): $(PROJECT_OBJS)
rm -f $@
$(AR) rv $@ $^
/*
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 CHKMACROS_H
#define CHKMACROS_H
#include "hresult.h"
template <typename T>
inline HRESULT CheckCore(const T t)
{
t.What_You_Passed_To_Chk_Is_Not_HRESULT();
return E_FAIL;
}
template <typename T>
inline bool CheckIfCore(const T t)
{
t.What_You_Passed_To_ChkIf_Is_Not_bool();
return false;
}
template <>
inline HRESULT CheckCore<HRESULT>(const HRESULT t)
{
return t;
}
template <>
inline bool CheckIfCore<bool>(const bool t)
{
return t;
}
#define Chk(expr) \
{ \
((void)hr); \
hr = CheckCore((expr)); \
if (FAILED(hr)) \
{ \
goto Cleanup; \
} \
}
#define ChkIf(expr, hrerror) \
{ \
((void)hr); \
if (CheckIfCore((expr))) \
{ \
hr = (hrerror); \
goto Cleanup; \
} \
}
#define ChkA(expr) \
{ \
((void)hr); \
hr = CheckCore((expr)); \
if (FAILED(hr)) \
{ \
ASSERT(false); \
goto Cleanup; \
} \
}
#define ChkIfA(expr, hrerror) \
{ \
((void)hr); \
if (CheckIfCore((expr))) \
{ \
ASSERT(false); \
hr = (hrerror); \
goto Cleanup; \
} \
}
#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"
#include <getopt.h>
#include "cmdlineparser.h"
const option* CCmdLineParser::GenerateOptions()
{
_options.clear();
size_t len = _listOptionDetails.size();
for (size_t index = 0; index < len; index++)
{
option opt = {};
opt.has_arg = _listOptionDetails[index].has_arg;
opt.name = _listOptionDetails[index].strName.c_str();
_options.push_back(opt);
}
option zero = {0,0,0,0};
_options.push_back(zero);
return _options.data();
}
HRESULT CCmdLineParser::AddOption(const char* pszName, int has_arg, std::string* pStrResult)
{
HRESULT hr = S_OK;
OptionDetail od;
ChkIfA(pszName==NULL, E_INVALIDARG);
ChkIfA(has_arg < 0, E_INVALIDARG);
ChkIfA(has_arg > 2, E_INVALIDARG);
ChkIfA(pStrResult==NULL, E_INVALIDARG);
od.has_arg = has_arg;
od.pStrResult = pStrResult;
od.strName = pszName;
_listOptionDetails.push_back(od);
Cleanup:
return hr;
}
HRESULT CCmdLineParser::AddNonOption(std::string* pStrResult)
{
_namelessArgs.push_back(pStrResult);
return S_OK;
}
HRESULT CCmdLineParser::ParseCommandLine(int argc, char** argv, int startindex, bool* pErrorFlag)
{
int oldopterr = ::opterr;
size_t offset = 0;
::opterr = 0;
::optind = startindex;
const option* longopts = GenerateOptions();
if (pErrorFlag)
{
*pErrorFlag = false;
}
while (true)
{
int index = 0;
int ret;
ret = ::getopt_long_only(argc, argv, "", longopts, &index);
if (ret < 0)
{
break;
}
if ((ret == '?') || (ret == ':'))
{
if (pErrorFlag)
{
*pErrorFlag = true;
}
continue;
}
if ((longopts[index].has_arg != 0) && (optarg != NULL))
{
_listOptionDetails[index].pStrResult->assign(optarg);
}
else
{
_listOptionDetails[index].pStrResult->assign("1");
}
}
offset = 0;
for (int j = ::optind; j < argc; j++)
{
if (strcmp("--", argv[j]) == 0)
{
continue;
}
if (_namelessArgs.size() <= offset)
{
// should we set the error flag if we don't have a binding for this arg?
break;
}
_namelessArgs[offset]->assign(argv[j]);
offset++;
}
::opterr = oldopterr;
return S_OK;
}
/*
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 <getopt.h>
class CCmdLineParser
{
const option* GenerateOptions();
std::vector<option> _options;
std::vector<std::string*> _namelessArgs;
struct OptionDetail
{
std::string strName;
int has_arg;
std::string* pStrResult;
};
std::vector<OptionDetail> _listOptionDetails;
public:
HRESULT AddOption(const char* pszName, int has_arg, std::string* pStrResult);
HRESULT AddNonOption(std::string* pStrResult);
HRESULT ParseCommandLine(int argc, char** argv, int startindex, bool* fParseError);
};
#endif /* CMDLINEPARSER_H */
/*
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"
namespace boost
{
void assertion_failed_msg(char const * expr, char const* msg, char const * function, char const * file, long line)
{
printf("ASSERTION FAILED: %s\n"
" message: %s\n"
" function: %s\n"
" file: %s\n"
" line: %ld\n", expr?expr:"(null)", msg?msg:"(null)", function?function:"(null)", file, line);
}
void assertion_failed(char const * expr, char const * function, char const * file, long line)
{
assertion_failed_msg(expr, expr, function, file, line);
}
}
/*
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 STUNSERVER_COMMON_COMMONINCLUDES_H
#define STUNSERVER_COMMON_COMMONINCLUDES_H
// standard system includes
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>
#include <stdlib.h>
#include <memory.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <stdarg.h>
#include <boost/shared_ptr.hpp>
#include <boost/scoped_array.hpp>
#include <boost/scoped_ptr.hpp>
#include <map>
#include <vector>
#include <list>
#include <string>
#include <pthread.h>
#if !defined(DEBUG) && !defined(NDEBUG)
You_Didnt_Define_DEBUG_Or_NDEBUG g_compilererror[-1];
#endif
#if defined(DEBUG)
#ifndef BOOST_ENABLE_ASSERT_HANDLER
#define BOOST_ENABLE_ASSERT_HANDLER
#endif
#else
#ifndef BOOST_DISABLE_ASSERTS
#define BOOST_DISABLE_ASSERTS
#endif
#endif
#include <boost/assert.hpp>
#ifdef ASSERT
#undef ASSERT
#endif
#ifdef VERIFY
#undef VERIFY
#endif
#ifdef ASSERT_MSG
#undef ASSERT_MSG
#endif
#define ASSERT(expr) BOOST_ASSERT(expr)
#define VERIFY(expr) BOOST_VERIFY(expr)
#define ASSERTMSG(expr, msg) BOOST_ASSERT_MSG (expr, msg)
#define ARRAYSIZE(arr) (sizeof(arr)/sizeof(*arr))
inline void cta_noop(const char* psz)
{
;
}
#define COMPILE_TIME_ASSERT(x) {char name$$[(x)?1:-1]; cta_noop(name$$);}
#ifndef UNREFERENCED_VARIABLE
#define UNREFERENCED_VARIABLE(unrefparam) ((void)unrefparam)
#endif
// --------------------------------------------
#include "hresult.h"
#include "chkmacros.h"
// ---------------------------------------------
// Unless there's good reason, put additional header files after hresult.h and chkmacros.h
#include "refcountobject.h"
#include "objectfactory.h"
#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"
#include "oshelper.h"
#ifndef _WIN32
#include <sys/ioctl.h>
#endif
const size_t DEFAULT_CONSOLE_WIDTH = 80;
static size_t GetConsoleWidthUnix()
{
// this ioctl call on file id 0 (stdin) works on MacOSX and Linux. So it's probably universal
struct winsize ws = {};
int ret;
size_t retvalue = DEFAULT_CONSOLE_WIDTH;
ret = ioctl(0, TIOCGWINSZ, &ws);
if ((ret != -1) && (ws.ws_col > 0))
{
retvalue = ws.ws_col;
}
return retvalue;
}
size_t GetConsoleWidth()
{
#ifdef _WIN32
return DEFAULT_CONSOLE_WIDTH; // todo - call appropriate console apis when we port to windows
#else
return GetConsoleWidthUnix();
#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"
#include "oshelper.h"
static uint32_t GetMillisecondCounterUnix()
{
uint64_t milliseconds = 0;
uint32_t retvalue;
timeval tv = {};
gettimeofday(&tv, NULL);
milliseconds = (tv.tv_sec * (unsigned long long)1000) + (tv.tv_usec / 1000);
retvalue = (uint32_t)(milliseconds & (unsigned long long)0xffffffff);
return retvalue;
}
uint32_t GetMillisecondCounter()
{
#ifdef _WIN32
return GetTickCount();
#else
return GetMillisecondCounterUnix();
#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.
*/
#ifndef HRESULT_H_
#define HRESULT_H_
// HRESULT
typedef int32_t HRESULT;
#define SEVERITY_SUCCESS 0
#define SEVERITY_ERROR 1
#define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0)
#define FAILED(hr) (((HRESULT)(hr)) < 0)
#define SYSCALL_SUCCEEDED(x) (x!=-1)
#define SYSCALL_FAILED(x) (x == -1)
#define HRESULT_CODE(hr) ((hr) & 0xFFFF)
#define HRESULT_FACILITY(hr) (((hr) >> 16) & 0x1fff)
#define HRESULT_SEVERITY(hr) (((hr) >> 31) & 0x1)
#define MAKE_HRESULT(sev,fac,code) \
((HRESULT) (((unsigned long)(sev)<<31) | ((unsigned long)(fac)<<16) | ((unsigned long)(code))) )
#define FACILITY_ERRNO 0x800
#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))
#define E_NOTIMPL ((HRESULT)(0x80004001L))
#define E_OUTOFMEMORY ((HRESULT)(0x8007000EL))
#define E_INVALIDARG ((HRESULT)(0x80070057L))
#define E_NOINTERFACE ((HRESULT)(0x80004002L))
#define E_POINTER ((HRESULT)(0x80004003L))
#define E_HANDLE ((HRESULT)(0x80070006L))
#define E_ABORT ((HRESULT)(0x80004004L))
#define E_FAIL ((HRESULT)(0x80004005L))
#define E_ACCESSDENIED ((HRESULT)(0x80070005L))
#define E_PENDING ((HRESULT)(0x8000000AL))
#endif /* HRESULT_H_ */
/*
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"
#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);
}
}
/*
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 LOGGING_H
#define LOGGING_H
const uint32_t LL_ALWAYS = 0; // only help messages, output the user expects to see, and critical error messages
const uint32_t LL_DEBUG = 1; // messages helpful for debugging
const uint32_t LL_VERBOSE = 2; // every packet
const uint32_t LL_VERBOSE_EXTREME = 3; // every packet and all the details
namespace Logging
{
uint32_t GetLogLevel();
void SetLogLevel(uint32_t level);
void LogMsg(uint32_t level, const char* pszFormat, ...);
}
#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.
*/
#ifndef _OBJECTFACTORY_H
#define _OBJECTFACTORY_H
template <typename T>
class CObjectFactory
{
public:
template <typename X>
static HRESULT CreateInstanceNoInit(X** ppInstance)
{
T* pT = NULL;
if (ppInstance == NULL)
{
return E_INVALIDARG;
}
pT = new T();
if (pT == NULL)
{
return E_OUTOFMEMORY;
}
pT->AddRef();
*ppInstance = pT;
return S_OK;
}
template <typename I>
static HRESULT CreateInstance(I** ppI)
{
T* pInstance = NULL;
HRESULT hr = S_OK;
ChkIf(ppI == NULL, E_NOINTERFACE);
Chk(CreateInstanceNoInit(&pInstance));
Chk(pInstance->Initialize());
*ppI = pInstance;
pInstance = NULL;
Cleanup:
// Cleanup
if (pInstance)
{
pInstance->Release();
pInstance = NULL;
}
return hr;
}
template <typename A, typename I>
static HRESULT CreateInstance(A paramA, I** ppI)
{
T* pInstance = NULL;
HRESULT hr = S_OK;
ChkIf(ppI == NULL, E_NOINTERFACE);
Chk(CreateInstanceNoInit(&pInstance));
Chk(pInstance->Initialize(paramA));
*ppI = pInstance;
pInstance = NULL;
Cleanup:
// Cleanup
if (pInstance)
{
pInstance->Release();
pInstance = NULL;
}
return hr;
}
template <typename A, typename B, typename I>
static HRESULT CreateInstance(A paramA, B paramB, I** ppI)
{
T* pInstance = NULL;
HRESULT hr = S_OK;
ChkIf(ppI == NULL, E_NOINTERFACE);
Chk(CreateInstanceNoInit(&pInstance));
Chk(pInstance->Initialize(paramA, paramB));
*ppI = pInstance;
pInstance = NULL;
Cleanup:
// Cleanup
if (pInstance)
{
pInstance->Release();
pInstance = NULL;
}
return hr;
}
};
#endif /* _OBJECTFACTORY_H */
/*
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 OSHELPER_H
#define OSHELPER_H
uint32_t GetMillisecondCounter();
size_t GetConsoleWidth();
#endif /* OSHELPER_H */
/*
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"
static bool IsWhitespace(char ch)
{
return ((ch == ' ') || (ch == '\t') || (ch == '\v') || (ch == '\r') || (ch == '\n'));
}
static void SplitParagraphIntoWords(const char* pszLine, std::vector<std::string>& listWords)
{
std::string strWord;
if (pszLine == NULL)
{
return;
}
while (*pszLine)
{
while ((*pszLine) && IsWhitespace(*pszLine))
{
pszLine++;
}
if (*pszLine == 0)
{
return;
}
while ((*pszLine) && IsWhitespace(*pszLine)==false)
{
strWord += *pszLine;
pszLine++;
}
listWords.push_back(strWord);
strWord.clear();
}
}
static void SplitInputIntoParagraphs(const char* pszInput, std::vector<std::string>& listParagraphs)
{
// blindly scan to the next \r or \n
std::string strParagraph;
if (pszInput == NULL)
{
return;
}
while (*pszInput)
{
while ((*pszInput != '\0') && (*pszInput != '\r') && (*pszInput != '\n'))
{
strParagraph += *pszInput;
pszInput++;
}
listParagraphs.push_back(strParagraph);
strParagraph.clear();
if (*pszInput == '\r')
{
pszInput++;
if (*pszInput == '\n')
{
pszInput++;
}
}
else if (*pszInput == '\n')
{
pszInput++;
}
}
}
static void PrintParagraph(const char* psz, size_t width)
{
size_t indent = 0;
const char *pszProbe = psz;
std::vector<std::string> listWords;
bool fLineStart = true;
std::string strLine;
std::string strIndent;
size_t wordcount = 0;
size_t wordindex = 0;
if (width <= 0)
{
return;
}
if (psz==NULL)
{
return;
}
while ((*pszProbe) && IsWhitespace(*pszProbe))
{
indent++;
pszProbe++;
}
SplitParagraphIntoWords(psz, listWords);
wordcount = listWords.size();
if (indent >= width)
{
indent = width-1;
}
for (size_t x = 0; x < indent; x++)
{
strIndent += ' ';
}
while (wordindex < wordcount)
{
if (fLineStart)
{
fLineStart = false;
strLine = strIndent;
// we always consume a word and put it at the start of a line regardless of the space we have left
// one day we can consider doing hyphenation...
strLine += listWords[wordindex];
wordindex++;
}
else
{
// do we have enough room to fit including the space?
size_t newsize = strLine.size() + 1 + listWords[wordindex].size();
if (newsize <= width)
{
strLine += ' ';
strLine += listWords[wordindex];
wordindex++;
}
else
{
// not a fit, we need to start a new line
// flush whatever we have now
printf("%s\n", strLine.c_str());
// flag that we got a new line starting
fLineStart = true;
}
}
}
if ((strLine.size() > 0) || (wordcount == 0))
{
printf("%s\n", strLine.c_str());
}
}
void PrettyPrint(const char* pszInput, size_t width)
{
std::vector<std::string> listParagraphs;
size_t len;
SplitInputIntoParagraphs(pszInput, listParagraphs);
len = listParagraphs.size();
for (size_t x = 0; x < len; x++)
{
PrintParagraph(listParagraphs[x].c_str(), width);
}
}
/*
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 PRETTY_PRINT_H
#define PRETTY_PRINT_H
void PrettyPrint(const char* pszInput, size_t width);
#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"
#include "refcountobject.h"
int AtomicIncrement(int* pInt)
{
// InterlockedIncrement
return __sync_add_and_fetch(pInt, 1);
}
int AtomicDecrement(int* pInt)
{
// InterlockedDecrement
return __sync_sub_and_fetch(pInt, 1);
}
CBasicRefCount::CBasicRefCount()
{
m_nRefs = 0;
}
CBasicRefCount::~CBasicRefCount()
{
;
}
int CBasicRefCount::InternalAddRef()
{
return AtomicIncrement(&m_nRefs);
}
int CBasicRefCount::InternalRelease()
{
int refcount = AtomicDecrement(&m_nRefs);
if (refcount == 0)
{
OnFinalRelease();
}
return refcount;
}
void CBasicRefCount::OnFinalRelease()
{
delete this;
}
/*
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 _REFCOUNTOBJECT_H
#define _REFCOUNTOBJECT_H
int AtomicIncrement(int* pInt);
int AtomicDecrement(int* pInt);
class IRefCounted
{
public:
virtual int AddRef() = 0;
virtual int Release() = 0;
};
class CBasicRefCount
{
protected:
int m_nRefs;
public:
CBasicRefCount();
virtual ~CBasicRefCount();
int InternalAddRef();
int InternalRelease();
virtual void OnFinalRelease();
};
#define ADDREF_AND_RELEASE_IMPL() \
inline int AddRef() {return InternalAddRef();} \
inline int Release() {return InternalRelease();}
template <class T>
class CRefCountedPtr
{
protected:
T* m_ptr;
public:
CRefCountedPtr() : m_ptr(NULL)
{
;
}
CRefCountedPtr(T* ptr) : m_ptr(ptr)
{
if (m_ptr)
{
m_ptr->AddRef();
}
}
CRefCountedPtr(const CRefCountedPtr<T>& sp) : m_ptr(sp.m_ptr)
{
if (m_ptr)
{
m_ptr->AddRef();
}
}
~CRefCountedPtr()
{
if (m_ptr)
{
m_ptr->Release();
m_ptr = NULL;
}
}
T* GetPointer()
{
return m_ptr;
}
operator T*() const
{
return m_ptr;
}
// std::vector chokes because it references &element to infer behavior
// Use GetPointerPointer instead
//T** operator&()
//{
// return &m_ptr;
//}
T** GetPointerPointer()
{
return &m_ptr;
}
T* operator->() const
{
return m_ptr;
}
T* operator = (T* ptr)
{
if (ptr)
{
ptr->AddRef();
}
if (m_ptr)
{
m_ptr->Release();
}
m_ptr = ptr;
return m_ptr;
}
T* operator = (const CRefCountedPtr<T>& sp)
{
if (sp.m_ptr)
{
sp.m_ptr->AddRef();
}
if (m_ptr)
{
m_ptr->Release();
}
m_ptr = sp.m_ptr;
return m_ptr;
}
bool operator ! () const
{
return (m_ptr == NULL);
}
bool operator != (T* ptr) const
{
return (ptr != m_ptr);
}
bool operator == (T* ptr) const
{
return (ptr == m_ptr);
}
// manual release
void ReleaseAndClear()
{
if (m_ptr)
{
m_ptr->Release();
m_ptr = NULL;
}
}
void Attach(T* ptr)
{
if (m_ptr)
{
m_ptr->Release();
}
m_ptr = ptr;
}
T* Detach()
{
T* ptr = m_ptr;
m_ptr = NULL;
return ptr;
}
HRESULT CopyTo(T** ppT)
{
if (ppT == NULL)
{
return E_POINTER;
}
*ppT = m_ptr;
if (m_ptr)
{
m_ptr->AddRef();
}
return S_OK;
}
};
#endif /* _REFCOUNTOBJECT_H */
/*
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"
#include <stdlib.h>
#include <vector>
#include <string>
#include "stringhelper.h"
#define ISWHITESPACE(ch) (((ch >= 9)&&(ch<=0xd))||(ch== ' '))
namespace StringHelper
{
bool IsNullOrEmpty(const char* psz)
{
return ((psz == NULL) || (psz[0] == '\0'));
}
void ToLower(std::string& str)
{
const char* psz = str.c_str();
size_t length = str.length();
std::string str2;
const int diff = ('a' - 'A');
if ((psz == NULL) || (length == 0))
{
return;
}
str2.reserve(length);
for (size_t index = 0; index < length; index++)
{
char ch = str[index];
if ((ch >= 'A') && (ch <= 'Z'))
{
ch = ch + diff;
}
str2.push_back(ch);
}
str = str2;
}
void Trim(std::string& str)
{
const char* psz = str.c_str();
if (psz == NULL)
{
return;
}
int length = str.length();
int start = -1;
int end = -1;
char ch;
for (int index = 0; index < length; index++)
{
ch = psz[index];
if (ISWHITESPACE(ch))
{
continue;
}
else if (start == -1)
{
start = index;
end = index;
}
else
{
end = index;
}
}
if (start != -1)
{
str = str.substr(start, end-start+1);
}
}
int ValidateNumberString(const char* psz, int nMinValue, int nMaxValue, int* pnResult)
{
int nVal = 0;
if (IsNullOrEmpty(psz) || (pnResult==NULL))
{
return -1;
}
nVal = atoi(psz);
if(nVal < nMinValue) return -1;
if(nVal > nMaxValue) return -1;
*pnResult = nVal;
return 0;
}
}
/*
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 STRINGHELPER_H
#define STRINGHELPER_H
#include <string>
namespace StringHelper
{
bool IsNullOrEmpty(const char* psz);
void ToLower(std::string& str);
void Trim(std::string& str);
int ValidateNumberString(const char* psz, int nMinValue, int nMaxValue, int* pnResult);
}
#endif /* STRINGHELPER_H */
include ../common.inc
PROJECT_TARGET := libnetworkutils.a
PROJECT_OBJS := adapters.o recvfromex.o resolvehostname.o stunsocket.o
INCLUDES := $(BOOST_INCLUDE) -I../common -I../stuncore
.PHONY: all
.PHONY: clean
all: $(PROJECT_TARGET)
clean:
rm -f $(PROJECT_OBJS) $(PROJECT_TARGET)
$(PROJECT_TARGET): $(PROJECT_OBJS)
rm -f $@
$(AR) rv $@ $^
/*
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"
// TODO - FIX THIS SUCH THAT CSocketAddress is in it's own "base" library independent of networkutils and stuncore
#include "socketaddress.h"
void GetDefaultAdapters(int family, ifaddrs* pList, ifaddrs** ppAddrPrimary, ifaddrs** ppAddrAlternate)
{
ifaddrs* pAdapter = NULL;
*ppAddrPrimary = NULL;
*ppAddrAlternate = NULL;
pAdapter = pList;
while (pAdapter)
{
if ( (pAdapter->ifa_addr->sa_family == family) && (pAdapter->ifa_flags & IFF_UP) && !(pAdapter->ifa_flags & IFF_LOOPBACK))
{
if (*ppAddrPrimary == NULL)
{
*ppAddrPrimary = pAdapter;
}
else
{
*ppAddrAlternate = pAdapter;
break;
}
}
pAdapter = pAdapter->ifa_next;
}
}
/**
* Returns true if there are two or more host interfaces(adapters) for the specified family of IP addresses that are both "up" and not loopback adapters
* @param family either AF_INET or AF_INET6
*/
bool HasAtLeastTwoAdapters(int family)
{
HRESULT hr = S_OK;
ifaddrs* pList = NULL;
ifaddrs* pAdapter1 = NULL;
ifaddrs* pAdapter2 = NULL;
bool fRet = false;
ChkIf(getifaddrs(&pList) < 0, ERRNOHR);
GetDefaultAdapters(family, pList, &pAdapter1, &pAdapter2);
fRet = (pAdapter1 && pAdapter2);
Cleanup:
freeifaddrs(pList);
return fRet;
}
/**
* Suggests a default adapter for a given stun server socket
* @param fPrimary - true if the returned adapter is to be used for the primary socket in a stun server. Typically passing "true" means "return the first adapter enumerated", otherwise return the second adapter enumerated"
* @param family - Either AF_INET or AF_INET6
* @param pSocketAddr - OUT param. On success, contains the address to bind to
* @return S_OK on success. Error code otherwise.
*/
HRESULT GetBestAddressForSocketBind(bool fPrimary, int family, uint16_t port, CSocketAddress* pSocketAddr)
{
HRESULT hr = S_OK;
ifaddrs* pList = NULL;
ifaddrs* pAdapter = NULL;
ifaddrs* pAdapter1 = NULL;
ifaddrs* pAdapter2 = NULL;
ChkIfA(pSocketAddr == NULL, E_INVALIDARG);
ChkIf(getifaddrs(&pList) < 0, ERRNOHR);
GetDefaultAdapters(family, pList, &pAdapter1, &pAdapter2);
pAdapter = fPrimary ? pAdapter1 : pAdapter2;
ChkIf(pAdapter==NULL, E_FAIL);
*pSocketAddr = CSocketAddress(*pAdapter->ifa_addr);
pSocketAddr->SetPort(port);
Cleanup:
freeifaddrs(pList);
return hr;
}
HRESULT GetSocketAddressForAdapter(int family, const char* pszAdapterName, uint16_t port, CSocketAddress* pSocketAddr)
{
HRESULT hr = S_OK;
ifaddrs* pList = NULL;
ifaddrs* pAdapter = NULL;
ifaddrs* pAdapterFound = NULL;
ChkIfA(pszAdapterName == NULL, E_INVALIDARG);
ChkIfA(pszAdapterName[0] == '\0', E_INVALIDARG);
ChkIfA(pSocketAddr == NULL, E_INVALIDARG);
// what if the socket address is available, but not "up". Well, just let this call succeed. If the server errors out, it will get cleaned up then
ChkIf(getifaddrs(&pList) < 0, ERRNOHR);
pAdapter = pList;
while (pAdapter)
{
if (family == pAdapter->ifa_addr->sa_family)
{
if (strcmp(pAdapter->ifa_name, pszAdapterName) == 0)
{
pAdapterFound = pAdapter;
break;
}
}
pAdapter = pAdapter->ifa_next;
}
// If pszAdapterName is an IP address, convert it into a sockaddr and compare the address field with that of the adapter
// Note: an alternative approach would be to convert pAdapter->ifa_addr to a string and then do a string compare.
// But then it would be difficult to match "::1" with "0:0:0:0:0:0:0:1" and other formats of IPV6 strings
if ((pAdapterFound == NULL) && ((family == AF_INET) || (family == AF_INET6)) )
{
uint8_t addrbytes[sizeof(in6_addr)] = {};
int comparesize = (family == AF_INET) ? sizeof(in_addr) : sizeof(in6_addr);
void* pCmp = NULL;
if (inet_pton(family, pszAdapterName, addrbytes) == 1)
{
pAdapter = pList;
while (pAdapter)
{
if (family == pAdapter->ifa_addr->sa_family)
{
// offsetof(sockaddr_in, sin_addr) != offsetof(sockaddr_in6, sin6_addr)
// so you really can't do too many casting tricks like you can with sockaddr and sockaddr_in
if (family == AF_INET)
{
sockaddr_in *pAddr4 = (sockaddr_in*)(pAdapter->ifa_addr);
pCmp = &(pAddr4->sin_addr);
}
else
{
sockaddr_in6 *pAddr6 = (sockaddr_in6*)(pAdapter->ifa_addr);
pCmp = &(pAddr6->sin6_addr);
}
if (memcmp(pCmp, addrbytes, comparesize) == 0)
{
// match on ip address string found
pAdapterFound = pAdapter;
break;
}
}
pAdapter = pAdapter->ifa_next;
}
}
}
ChkIf(pAdapterFound == NULL, E_FAIL);
{
*pSocketAddr = CSocketAddress(*(pAdapterFound->ifa_addr));
pSocketAddr->SetPort(port);
}
Cleanup:
freeifaddrs(pList);
return hr;
}
/*
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 ADAPTERS_H
#define ADAPTERS_H
#include "socketaddress.h"
bool HasAtLeastTwoAdapters(int family);
HRESULT GetBestAddressForSocketBind(bool fPrimary, int family, uint16_t port, CSocketAddress* pSocketAddr);
HRESULT GetSocketAddressForAdapter(int family, const char* pszAdapterName, uint16_t port, CSocketAddress* pSocketAddr);
#endif /* ADAPTERS_H */
/*
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"
#include "socketaddress.h"
static void GetLocalPortNumberFromSocket(int sockfd, CSocketAddress* pAddr)
{
sockaddr_storage addr = {};
socklen_t len = sizeof(addr);
int ret;
ret = ::getsockname(sockfd, (sockaddr*)&addr, &len);
if (ret != -1)
{
uint16_t port=0;
if (addr.ss_family == AF_INET)
{
port = ntohs(((sockaddr_in*)&addr)->sin_port);
}
else if (addr.ss_family == AF_INET6)
{
port = ntohs(((sockaddr_in6*)&addr)->sin6_port);
}
pAddr->SetPort(port);
}
}
static void InitSocketAddress(int family, CSocketAddress* pAddr)
{
if (family == AF_INET)
{
sockaddr_in addr = {};
addr.sin_family = AF_INET;
*pAddr = CSocketAddress(addr);
}
else if (family == AF_INET6)
{
sockaddr_in6 addr = {};
addr.sin6_family = AF_INET6;
*pAddr = CSocketAddress(addr);
}
else
{
ASSERT(false);
}
}
ssize_t recvfromex(int sockfd, void* buf, size_t len, int flags, CSocketAddress* pSrcAddr, CSocketAddress* pDstAddr)
{
struct iovec vec;
ssize_t ret;
char controldata[1000];
struct msghdr hdr = {};
sockaddr_storage addrRemote = {};
vec.iov_base = buf;
vec.iov_len = len;
hdr.msg_name = &addrRemote;
hdr.msg_namelen = sizeof(addrRemote);
hdr.msg_iov = &vec;
hdr.msg_iovlen = 1;
hdr.msg_control = controldata;
hdr.msg_controllen = ARRAYSIZE(controldata);
ret = ::recvmsg(sockfd, &hdr, flags);
if (ret > 0)
{
if (pSrcAddr)
{
*pSrcAddr = CSocketAddress(*(sockaddr*)&addrRemote);
}
if (pDstAddr)
{
struct cmsghdr* pCmsg = NULL;
InitSocketAddress(addrRemote.ss_family, pDstAddr);
for (pCmsg = CMSG_FIRSTHDR(&hdr); pCmsg != NULL; pCmsg = CMSG_NXTHDR(&hdr, pCmsg))
{
// IPV6 address ----------------------------------------------------------
if ((pCmsg->cmsg_level == IPPROTO_IPV6) && (pCmsg->cmsg_type == IPV6_PKTINFO) && CMSG_DATA(pCmsg))
{
struct in6_pktinfo* pInfo = (in6_pktinfo*)CMSG_DATA(pCmsg);
sockaddr_in6 addr = {};
addr.sin6_family = AF_INET6;
addr.sin6_addr = pInfo->ipi6_addr;
*pDstAddr = CSocketAddress(addr);
GetLocalPortNumberFromSocket(sockfd, pDstAddr);
break;
}
// IPV4 address ----------------------------------------------------------
// if you change the ifdef's below, make sure you it's matched with the same logic in stunsocket.cpp
// Might be worthwhile to just use IP_RECVORIGDSTADDR and IP_ORIGDSTADDR so we can merge with the bsd code
#ifdef IP_PKTINFO
if ((pCmsg->cmsg_level == IPPROTO_IP) && (pCmsg->cmsg_type==IP_PKTINFO) && CMSG_DATA(pCmsg))
{
struct in_pktinfo* pInfo = (in_pktinfo*)CMSG_DATA(pCmsg);
sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_addr = pInfo->ipi_addr;
*pDstAddr = CSocketAddress(addr);
GetLocalPortNumberFromSocket(sockfd, pDstAddr);
break;
}
#elif defined(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))
{
sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_addr = *(in_addr*)CMSG_DATA(pCmsg);
*pDstAddr = CSocketAddress(addr);
GetLocalPortNumberFromSocket(sockfd, pDstAddr);
break;
}
#else
{
int fail[-1]; // set a compile time assert if there's no option
}
#endif
}
}
}
return ret;
}
/*
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 RECVFROMEX_H
#define RECVFROMEX_H
ssize_t recvfromex(int sockfd, void* buf, size_t len, int flags, CSocketAddress* pSrcAddr, CSocketAddress* pDstAddr);
#endif /* RECVFROMEX_H */
/*
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"
#include "socketaddress.h"
#include "stringhelper.h"
HRESULT ResolveHostName(const char* pszHostName, int family, bool fNumericOnly, CSocketAddress* pAddr)
{
int ret;
HRESULT hr = S_OK;
addrinfo* pResultList = NULL;
addrinfo hints = {};
std::string strHostName(pszHostName);
StringHelper::Trim(strHostName);
ChkIf(strHostName.length() == 0, E_INVALIDARG);
ChkIf(pAddr==NULL, E_INVALIDARG);
hints.ai_family = family;
if (fNumericOnly)
{
hints.ai_flags = AI_NUMERICHOST;
}
// without a socktype hint, getaddrinfo will return 3x the number of addresses (SOCK_STREAM, SOCK_DGRAM, and SOCK_RAW)
hints.ai_socktype = SOCK_STREAM;
ret = getaddrinfo(strHostName.c_str(), NULL, &hints, &pResultList);
ChkIf(ret != 0, ERRNO_TO_HRESULT(ret));
ChkIf(pResultList==NULL, E_FAIL)
// just pick the first one found
*pAddr = CSocketAddress(*(pResultList->ai_addr));
Cleanup:
::freeaddrinfo(pResultList);
return hr;
}
/*
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 RESOLVEHOSTNAME_H
#define RESOLVEHOSTNAME_H
#include "socketaddress.h"
/**
* Converts a string contains a numeric address or FQDN into a socket address structure
* @param pszHostName is the string of the remote host to resolve
* @param family either AF_INET or AF_INET6
* @param fNumericOnly Prevents DNS and just resolves numeric IP addresses (e.g. "192.168.1.2"
* @param pAddr - OUT param. On success is filled with a valid socket addresss information.
* @return S_OK on success. Error code otherwise
*/
HRESULT ResolveHostName(const char* pszHostName, int family, bool fNumericOnly, CSocketAddress* pAddr);
#endif /* RESOLVEHOSTNAME_H */
/*
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"
#include "stuncore.h"
#include "stunsocket.h"
CStunSocket::~CStunSocket()
{
Close();
}
void CStunSocket::Close()
{
if (_sock != -1)
{
close(_sock);
_addrlocal = CSocketAddress(0,0);
}
}
int CStunSocket::GetSocketHandle() const
{
return _sock;
}
const CSocketAddress& CStunSocket::GetLocalAddress() const
{
return _addrlocal;
}
SocketRole CStunSocket::GetRole() const
{
ASSERT(_sock != -1);
return _role;
}
HRESULT CStunSocket::EnablePktInfoOption(bool fEnable)
{
int enable = fEnable?1:0;
int ret;
int family = _addrlocal.GetFamily();
int level = (family==AF_INET) ? IPPROTO_IP : IPPROTO_IPV6;
// if you change the ifdef's below, make sure you it's matched with the same logic in recvfromex.cpp
#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
#endif
ret = ::setsockopt(_sock, level, option, &enable, sizeof(enable));
// 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);
return (ret == 0) ? S_OK : ERRNOHR;
}
//static
HRESULT CStunSocket::Create(const CSocketAddress& addrlocal, SocketRole role, boost::shared_ptr<CStunSocket>* pStunSocketShared)
{
int sock = -1;
int ret;
CStunSocket* pSocket = NULL;
sockaddr_storage addrBind = {};
socklen_t sizeaddrBind;
HRESULT hr = S_OK;
ChkIfA(pStunSocketShared == NULL, E_INVALIDARG);
sock = socket(addrlocal.GetFamily(), SOCK_DGRAM, 0);
ChkIf(sock < 0, ERRNOHR);
ret = bind(sock, addrlocal.GetSockAddr(), addrlocal.GetSockAddrLength());
ChkIf(ret < 0, ERRNOHR);
// call get sockname to find out what port we just binded to. (Useful for when addrLocal.port is 0)
sizeaddrBind = sizeof(addrBind);
ret = ::getsockname(sock, (sockaddr*)&addrBind, &sizeaddrBind);
ChkIf(ret < 0, ERRNOHR);
pSocket = new CStunSocket();
pSocket->_sock = sock;
pSocket->_addrlocal = CSocketAddress(*(sockaddr*)&addrBind);
pSocket->_role = role;
sock = -1;
{
boost::shared_ptr<CStunSocket> spTmp(pSocket);
pStunSocketShared->swap(spTmp);
}
Cleanup:
if (sock != -1)
{
close(sock);
sock = -1;
}
return hr;
}
/*
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 STUNSOCKET_H
#define STUNSOCKET_H
class CStunSocket
{
private:
int _sock;
CSocketAddress _addrlocal;
SocketRole _role;
CStunSocket() {;}
CStunSocket(const CStunSocket&) {;}
void operator=(const CStunSocket&) {;}
public:
~CStunSocket();
void Close();
int GetSocketHandle() const;
const CSocketAddress& GetLocalAddress() const;
SocketRole GetRole() const;
HRESULT EnablePktInfoOption(bool fEnable);
static HRESULT Create(const CSocketAddress& local, SocketRole role, boost::shared_ptr<CStunSocket>* pStunSocketShared);
};
typedef boost::shared_ptr<CStunSocket> CRefCountedStunSocket;
#endif /* STUNSOCKET_H */
include ../common.inc
PROJECT_TARGET := stunserver
PROJECT_OBJS := main.o server.o stunsocketthread.o
PROJECT_INTERMEDIATES := usage.txtcode usagelite.txtcode
INCLUDES := $(BOOST_INCLUDE) -I../common -I../stuncore -I../networkutils
LIB_PATH := -L../common -L../stuncore -L../networkutils
LIBS := -lnetworkutils -lstuncore -lcommon -lpthread -lcrypto
all: $(PROJECT_TARGET)
clean:
rm -f $(PROJECT_OBJS) $(PROJECT_TARGET) $(PROJECT_INTERMEDIATES)
$(PROJECT_TARGET): $(PROJECT_OBJS)
$(LINK.cpp) -o $@ $^ $(LIB_PATH) $(LIBS)
main.cpp: usage.txtcode usagelite.txtcode
%.txtcode: %.txt
sh makecodefile.sh $< $@ $(*)_text
/*
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"
#include "stuncore.h"
#include "server.h"
#include "adapters.h"
#include "cmdlineparser.h"
#include <getopt.h>
#include "prettyprint.h"
#include "oshelper.h"
#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"
void PrintUsage(bool fSummaryUsage)
{
size_t width = GetConsoleWidth();
const char* psz = fSummaryUsage ? usagelite_text : usage_text;
// save some margin space
if (width > 2)
{
width -= 2;
}
PrettyPrint(psz, width);
}
struct StartupArgs
{
std::string strMode;
std::string strPrimaryInterface;
std::string strAltInterface;
std::string strPrimaryPort;
std::string strAltPort;
std::string strFamily;
std::string strProtocol;
std::string strHelp;
std::string strVerbosity;
};
#define PRINTARG(member) Logging::LogMsg(LL_DEBUG, "%s = %s", #member, args.member.length() ? args.member.c_str() : "<empty>");
void DumpStartupArgs(StartupArgs& args)
{
Logging::LogMsg(LL_DEBUG, "\n\n--------------------------");
PRINTARG(strMode);
PRINTARG(strPrimaryInterface);
PRINTARG(strAltInterface);
PRINTARG(strPrimaryPort);
PRINTARG(strAltPort);
PRINTARG(strFamily);
PRINTARG(strProtocol);
PRINTARG(strHelp);
PRINTARG(strVerbosity);
Logging::LogMsg(LL_DEBUG, "--------------------------\n");
}
void DumpConfig(CStunServerConfig &config)
{
std::string strSocket;
if (config.fHasPP)
{
config.addrPP.ToString(&strSocket);
Logging::LogMsg(LL_DEBUG, "PP = %s", strSocket.c_str());
}
if (config.fHasPA)
{
config.addrPA.ToString(&strSocket);
Logging::LogMsg(LL_DEBUG, "PA = %s", strSocket.c_str());
}
if (config.fHasAP)
{
config.addrAP.ToString(&strSocket);
Logging::LogMsg(LL_DEBUG, "AP = %s", strSocket.c_str());
}
if (config.fHasAA)
{
config.addrAA.ToString(&strSocket);
Logging::LogMsg(LL_DEBUG, "AA = %s", strSocket.c_str());
}
}
HRESULT ResolveAdapterName(bool fPrimary, int family, std::string& strAdapterName, uint16_t port, CSocketAddress* pAddr)
{
HRESULT hr = S_OK;
if (strAdapterName.length() == 0)
{
hr = GetBestAddressForSocketBind(fPrimary, family, port, pAddr);
if (SUCCEEDED(hr))
{
pAddr->ToString(&strAdapterName);
// strip off the port suffix
size_t x = strAdapterName.find_last_of(':');
if (x != std::string::npos)
{
strAdapterName = strAdapterName.substr(0, x);
}
}
}
else
{
hr = GetSocketAddressForAdapter(family, strAdapterName.c_str(), port, pAddr);
}
return hr;
}
HRESULT BuildServerConfigurationFromArgs(StartupArgs& argsIn, CStunServerConfig* pConfigOut)
{
HRESULT hr = S_OK;
// default values;
int family = AF_INET;
// bool fIsUdp = true;
int nPrimaryPort = DEFAULT_STUN_PORT;
int nAltPort = DEFAULT_STUN_PORT + 1;
bool fHasAtLeastTwoAdapters = false;
CStunServerConfig config;
enum ServerMode
{
Basic,
Full
};
ServerMode mode=Basic;
StartupArgs args = argsIn;
ChkIfA(pConfigOut == NULL, E_INVALIDARG);
// normalize the args. The "trim" is not needed for command line args, but will be useful when we have an "init file" for intializing the server
StringHelper::ToLower(args.strMode);
StringHelper::Trim(args.strMode);
StringHelper::Trim(args.strPrimaryInterface);
StringHelper::Trim(args.strAltInterface);
StringHelper::Trim(args.strPrimaryPort);
StringHelper::Trim(args.strAltPort);
StringHelper::Trim(args.strFamily);
StringHelper::ToLower(args.strProtocol);
StringHelper::Trim(args.strProtocol);
// ---- MODE ----------------------------------------------------------
if (args.strMode.length() > 0)
{
if (args.strMode == "basic")
{
mode = Basic;
}
else if (args.strMode == "full")
{
mode = Full;
}
else
{
Logging::LogMsg(LL_ALWAYS, "Mode must be \"full\" or \"basic\".");
Chk(E_INVALIDARG);
}
}
// ---- FAMILY --------------------------------------------------------
family = AF_INET;
if (args.strFamily.length() > 0)
{
if (args.strFamily == "4")
{
family = AF_INET;
}
else if (args.strFamily == "6")
{
family = AF_INET6;
}
else
{
Logging::LogMsg(LL_ALWAYS, "Family argument must be '4' or '6'");
Chk(E_INVALIDARG);
}
}
// ---- PROTOCOL --------------------------------------------------------
if (args.strProtocol.length() > 0)
{
if (args.strProtocol != "udp")
{
Logging::LogMsg(LL_ALWAYS, "Protocol argument must be 'udp' . 'tcp' and 'tls' are not supported yet");
Chk(E_INVALIDARG);
}
}
// ---- PRIMARY PORT --------------------------------------------------------
nPrimaryPort = DEFAULT_STUN_PORT;
if (args.strPrimaryPort.length() > 0)
{
hr = StringHelper::ValidateNumberString(args.strPrimaryPort.c_str(), 0x0001, 0xffff, &nPrimaryPort);
if (FAILED(hr))
{
Logging::LogMsg(LL_ALWAYS, "Primary port value is invalid. Value must be between 1-65535");
Chk(hr);
}
}
// ---- ALT PORT --------------------------------------------------------
nAltPort = DEFAULT_STUN_PORT + 1;
if (args.strAltPort.length() > 0)
{
hr = StringHelper::ValidateNumberString(args.strAltPort.c_str(), 0x0001, 0xffff, &nAltPort);
if (FAILED(hr))
{
Logging::LogMsg(LL_ALWAYS, "Alt port value is invalid. Value must be between 1-65535");
Chk(hr);
}
}
if (nPrimaryPort == nAltPort)
{
Logging::LogMsg(LL_ALWAYS, "Primary port and altnernate port must be different values");
Chk(E_INVALIDARG);
}
// ---- Adapters and mode adjustment
fHasAtLeastTwoAdapters = ::HasAtLeastTwoAdapters(family);
if (mode == Basic)
{
uint16_t port = (uint16_t)((int16_t)nPrimaryPort);
// in basic mode, if no adapter is specified, bind to all of them
if (args.strPrimaryInterface.length() == 0)
{
if (family == AF_INET)
{
config.addrPP = CSocketAddress(0, port);
config.fHasPP = true;
}
else if (family == AF_INET6)
{
sockaddr_in6 addr6 = {}; // zero-init
addr6.sin6_family = AF_INET6;
config.addrPP = CSocketAddress(addr6);
config.addrPP.SetPort(port);
config.fHasPP = true;
}
}
else
{
CSocketAddress addr;
hr = GetSocketAddressForAdapter(family, args.strPrimaryInterface.c_str(), port, &addr);
if (FAILED(hr))
{
Logging::LogMsg(LL_ALWAYS, "No matching primary adapter found for %s", args.strPrimaryInterface.c_str());
Chk(hr);
}
config.addrPP = addr;
config.fHasPP = true;
}
}
else // Full mode
{
CSocketAddress addrPrimary;
CSocketAddress addrAlternate;
uint16_t portPrimary = (uint16_t)((int16_t)nPrimaryPort);
uint16_t portAlternate = (uint16_t)((int16_t)nAltPort);
// in full mode, we can't bind to all adapters
// so if one isn't specified, it's best guess - just avoid duplicates
if (fHasAtLeastTwoAdapters == false)
{
Logging::LogMsg(LL_ALWAYS, "There does not appear to be two or more unique IP addresses to run in full mode");
Chk(E_UNEXPECTED);
}
hr = ResolveAdapterName(true, family, args.strPrimaryInterface, 0, &addrPrimary);
if (FAILED(hr))
{
Logging::LogMsg(LL_ALWAYS, "Can't find address for primary interface");
Chk(hr);
}
hr = ResolveAdapterName(false, family, args.strAltInterface, 0, &addrAlternate);
if (FAILED(hr))
{
Logging::LogMsg(LL_ALWAYS, "Can't find address for alternate interface");
Chk(hr);
}
if (addrPrimary.IsSameIP(addrAlternate))
{
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);
config.fHasPP = true;
config.addrPA = addrPrimary;
config.addrPA.SetPort(portAlternate);
config.fHasPA = true;
config.addrAP = addrAlternate;
config.addrAP.SetPort(portPrimary);
config.fHasAP = true;
config.addrAA = addrAlternate;
config.addrAA.SetPort(portAlternate);
config.fHasAA = true;
}
*pConfigOut = config;
hr = S_OK;
Cleanup:
return hr;
}
HRESULT ParseCommandLineArgs(int argc, char** argv, int startindex, StartupArgs* pStartupArgs)
{
CCmdLineParser cmdline;
std::string strHelp;
bool fError = false;
cmdline.AddOption("mode", required_argument, &pStartupArgs->strMode);
cmdline.AddOption("primaryinterface", required_argument, &pStartupArgs->strPrimaryInterface);
cmdline.AddOption("altinterface", required_argument, &pStartupArgs->strAltInterface);
cmdline.AddOption("primaryport", required_argument, &pStartupArgs->strPrimaryPort);
cmdline.AddOption("altport", required_argument, &pStartupArgs->strAltPort);
cmdline.AddOption("family", required_argument, &pStartupArgs->strFamily);
cmdline.AddOption("protocol", required_argument, &pStartupArgs->strProtocol);
cmdline.AddOption("help", no_argument, &pStartupArgs->strHelp);
cmdline.AddOption("verbosity", required_argument, &pStartupArgs->strVerbosity);
cmdline.ParseCommandLine(argc, argv, startindex, &fError);
return fError ? E_INVALIDARG : S_OK;
}
// This routine will set SIGINT And SIGTERM to "blocked" status only so that the
// child worker threads will never have these events raised on them.
HRESULT InitAppExitListener()
{
HRESULT hr = S_OK;
sigset_t sigs;
int ret;
sigemptyset(&sigs);
sigaddset(&sigs, SIGINT);
sigaddset(&sigs, SIGTERM);
ret = pthread_sigmask(SIG_BLOCK, &sigs, NULL);
hr = (ret == 0) ? S_OK : ERRNO_TO_HRESULT(ret);
Logging::LogMsg(LL_DEBUG, "InitAppExitListener: x%x", hr);
return hr;
}
// after all the child threads have initialized with a signal mask that blocks SIGINT and SIGTERM
// then the main thread UI can just sit on WaitForAppExitSignal and wait for CTRL-C to get pressed
void WaitForAppExitSignal()
{
while (true)
{
sigset_t sigs;
sigemptyset(&sigs);
sigaddset(&sigs, SIGINT);
sigaddset(&sigs, SIGTERM);
int sig = 0;
int ret = sigwait(&sigs, &sig);
Logging::LogMsg(LL_DEBUG, "sigwait returns %d (errno==%d)", ret, (ret==-1)?0:errno);
if ((sig == SIGINT) || (sig == SIGTERM))
{
break;
}
}
}
int main(int argc, char** argv)
{
HRESULT hr = S_OK;
StartupArgs args;
CStunServerConfig config;
CRefCountedPtr<CStunServer> spServer;
#ifdef DEBUG
Logging::SetLogLevel(LL_DEBUG);
#else
Logging::SetLogLevel(LL_ALWAYS);
#endif
hr = ParseCommandLineArgs(argc, argv, 1, &args);
if (FAILED(hr))
{
PrintUsage(true);
return -1;
}
if (args.strHelp.length() > 0)
{
PrintUsage(false);
return -2;
}
if (args.strVerbosity.length() > 0)
{
int loglevel = (uint32_t)atoi(args.strVerbosity.c_str());
if (loglevel >= 0)
{
Logging::SetLogLevel((uint32_t)loglevel);
}
}
if (SUCCEEDED(hr))
{
::DumpStartupArgs(args);
hr = BuildServerConfigurationFromArgs(args, &config);
}
if (FAILED(hr))
{
Logging::LogMsg(LL_ALWAYS, "Error building configuraton from command line options");
PrintUsage(true);
return -3;
}
DumpConfig(config);
InitAppExitListener();
hr = CStunServer::CreateInstance(config, spServer.GetPointerPointer());
if (FAILED(hr))
{
Logging::LogMsg(LL_ALWAYS, "Unable to initialize server (error code = x%x)", hr);
return -4;
}
hr = spServer->Start();
if (FAILED(hr))
{
Logging::LogMsg(LL_ALWAYS, "Unable to start server (error code = x%x)", hr);
return -5;
}
Logging::LogMsg(LL_DEBUG, "Successfully started server.");
WaitForAppExitSignal();
Logging::LogMsg(LL_DEBUG, "Server is exiting");
spServer->Stop();
spServer->Release();
return 0;
}
#
# 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.
echo BUILDING $1 INTO $2
echo const char $3[] = { > $2
xxd -i < $1 >> $2
echo ",0x00};" >> $2
echo "" >> $2
/*
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"
#include <openssl/hmac.h>
#include "stuncore.h"
#include "stunsocket.h"
#include "stunsocketthread.h"
#include "server.h"
#include "sampleauthprovider.h"
static const char* c_szPrivateKey = "Change this string if you are going to use this code";
static const char* c_szRealm = "YourRealmNameHere";
static HRESULT LookupPassword(bool fWithRealm, const char* pszUserName, const char* pszRealm, char* pszPassword)
{
const char* users[] = {"bruce", "steve", "nicko", "dave", "adrian"};
const char* passwords[] = {"AcesHigh", "2MinToMid", "fearofthedark!", "#ofthebeast", "Run2TheHills" };
if (fWithRealm)
{
if ((pszRealm == NULL) || (strcmp(pszRealm, c_szRealm)))
{
return E_FAIL;
}
}
if (pszUserName == NULL)
{
return E_FAIL;
}
for (size_t index = 0; index < ARRAYSIZE(users); index++)
{
if (strcmp(pszUserName, users[index]))
{
continue;
}
strcpy(pszPassword, passwords[index]);
return S_OK;
}
return E_FAIL;
}
HRESULT CShortTermAuth::DoAuthCheck(AuthAttributes* pAuthAttributes, AuthResponse* pResponse)
{
// if you want to authenticate in "short term credential mode", then this function needs to return
// a password for the passed in user name
// short-term example
// indicate to the server we are returning a short-term credential so it knows to use
// only the pResponse->szPassword field for computing the message integrity
pResponse->authCredMech = AuthCredShortTerm;
if (pAuthAttributes->fMessageIntegrityPresent == false)
{
// RFC 5389 indicates to send back a "400" if there is no message integrity. That's
// what "Reject" will signal to the server to respond with
pResponse->responseType = Reject;
return S_OK;
}
if (SUCCEEDED(LookupPassword(false, pAuthAttributes->szUser, NULL, pResponse->szPassword)))
{
// Returning "AllowConditional" indicates that the request can be accepted if and only if the
// message integrity attribute can be validated with the value placed into pResponse->szPassword
pResponse->responseType = AllowConditional;
return S_OK;
}
// If it's not a valid user (or no password could be found), just return Unauthorized.
// This will result in a 401 getting sent back
pResponse->responseType = Unauthorized;
return S_OK;
}
HRESULT CLongTermAuth::DoAuthCheck(AuthAttributes* pAuthAttributes, AuthResponse* pResponse)
{
HRESULT hr = S_OK;
pResponse->authCredMech = AuthCredLongTerm;
// Go ahead and generate a new nonce and set the realm.
// The realm and nonce attributes will only get sent back to the client when there is an auth error
CreateNonce(pResponse->szNonce);
strcpy(pResponse->szRealm, c_szRealm);
// if we're missing any authentication attributes, then just return back a 401.
// This will trigger the server to send back the nonce and realm attributes to the client within the 401 resposne
if ((pAuthAttributes->fMessageIntegrityPresent == false) || (pAuthAttributes->szNonce[0] == 0) || (pAuthAttributes->szUser[0] == 0))
{
pResponse->responseType = Unauthorized;
return S_OK;
}
// copy the user's password into szPassword
hr = LookupPassword(true, pAuthAttributes->szUser, pAuthAttributes->szNonce, pResponse->szPassword);
if (FAILED(hr))
{
// if not a valid user, same as before. Just send back a 401
pResponse->responseType = Unauthorized;
return S_OK;
}
// validate the nonce
if (FAILED(ValidateNonce(pAuthAttributes->szNonce)))
{
pResponse->responseType = StaleNonce;
return S_OK;
}
// returning "AllowConditional" indicates that the request can be accepted if and only if the
// message integrity attribute can be validated with the value placed into pResponse->szPassword
pResponse->responseType = AllowConditional;
return S_OK;
}
void CLongTermAuth::HmacToString(uint8_t* hmacresult, char* pszResult)
{
sprintf(pszResult, "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x",
hmacresult[0], hmacresult[1], hmacresult[2], hmacresult[3], hmacresult[4],
hmacresult[5], hmacresult[6], hmacresult[7], hmacresult[8], hmacresult[9],
hmacresult[10], hmacresult[11], hmacresult[12], hmacresult[13], hmacresult[14],
hmacresult[15], hmacresult[16], hmacresult[17], hmacresult[18], hmacresult[19]);
}
HRESULT CLongTermAuth::CreateNonce(char *pszNonce)
{
// This is a sample "nonce provider". Our implementation of "nonce" is just a a string
// indicating a timestamp followed by an HMAC hash of the timestamp.
// Validation of a nonce is to just to make sure the timestamp isn't more than a couple
// of minutes old and that the hmac hash matches
// If you use this code, make sure you change the value of c_szPrivateKey!
time_t thetime = time(NULL);
uint8_t hmacresult[20] = {};
char szHMAC[20*2+1];
char szTime[sizeof(time_t)*4];
unsigned int len = ARRAYSIZE(hmacresult);
sprintf(szTime, "%u:", (unsigned int)thetime);
HMAC(::EVP_sha1(), (unsigned char*)c_szPrivateKey, strlen(c_szPrivateKey), (unsigned char*)szTime, strlen(szTime), hmacresult, &len);
HmacToString(hmacresult, szHMAC);
strcpy(pszNonce, szTime);
strcat(pszNonce, szHMAC);
return S_OK;
}
HRESULT CLongTermAuth::ValidateNonce(char* pszNonce)
{
time_t thecurrenttime = time(NULL);
time_t thetime;
uint8_t hmacresult[20] = {};
char szHMAC[20*2+1];
char szNonce[100];
char *pRightHalf = NULL;
time_t diff;
unsigned int len = ARRAYSIZE(hmacresult);
strncpy(szNonce, pszNonce, ARRAYSIZE(szNonce));
szNonce[ARRAYSIZE(szNonce)-1] = 0;
pRightHalf = strstr(szNonce, ":");
if (pRightHalf == NULL)
{
return E_FAIL;
}
*pRightHalf++ = 0;
thetime = atoi(szNonce);
diff = thecurrenttime - thetime;
if (((thecurrenttime - thetime) > 120) || (diff < 0))
{
// nonce is more than 2 minutes old - reject
return E_FAIL;
}
// nonce timestamp is valid, but was it signed by this server?
HMAC(::EVP_sha1(), (unsigned char*)c_szPrivateKey, strlen(c_szPrivateKey), (unsigned char*)szNonce, strlen(szNonce), hmacresult, &len);
HmacToString(hmacresult, szHMAC);
if (strcmp(szHMAC, pRightHalf))
{
return E_FAIL;
}
return S_OK;
}
/*
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 SAMPLE_STUN_AUTH_PROVIDER
#define SAMPLE_STUN_AUTH_PROVIDER
#if 0
sampleauthprovider.h and sampleauthprovider.cpp
The stun library code knows how to generate and validate message integrity attributes
inside stun attributes. But it relies on an implemented "auth provider" to actually
confirm a username and supply the password key.
It is out of the scope of this release to implement an authentication provider that
will work for everyone. One deployment may store usernames and passwords in a database.
Other deployments may rely on a ticketing service library.
RFC 5389 specifies two authentication mechanisms for STUN. It is recommended
that you read it before deciding how timplement
"Short term credentials" is simply the case where a stun binding request
sent from client to server contains a message integrity attribute. The message
integrity attribute is simply an attribute that contins an HMAC-SHA1 hash of
the stun msesage itself. The stun message should also contain a username attribute.
The 'key' field of the HMAC operation is a private password associated with the
username in the binding request.
"Long term credentials" is similar to short term credentials but involves a challenge
response phase to prevent replay attacks. This is the recommended mechanism for
STUN authentication.
We can also support "ticket based credentials". This is not a mechanism specified
by the STUN RFC, but has been known to work in various scenarios. The username
and/or realm fields are overloaded to represent a "ticket" signed by an external
entity. The auth provider knows how to validate the ticket.
We can also implement "legacy password" authentication. This is simply the password
(with or without a username and realm) embedded in the clear in the stun binding
request. Not recommended unless the transport type is TLS.
Implementing authentication is simply implementing a class that implements
IStunAuth. IStunAuth has only one method called "DoAuthCheck". DoAuthCheck
is called for each incoming Stun Message. It takes one input parameter pointer
to a struct instance of type AuthAttributes) and one "out" param which is a struct
for handing back authentication tokens back to the server.
HRESULT DoAuthCheck(/*in*/ AuthAttributes* pAuthAttributes, /*out*/ AuthResponse* pResponse);
The AuthAttributes struct representes various attribute values received in a STUN
binding request. They are outlined as follows:
char szUser[MAX_STUN_AUTH_STRING_SIZE]; // the user name attribute in the request (if available)
char szRealm[MAX_STUN_AUTH_STRING_SIZE]; // the realm attribute in the request (if available)
char szNonce[MAX_STUN_AUTH_STRING_SIZE]; // the nonce attribute in the request (if available)
char szLegacyPassword[MAX_STUN_AUTH_STRING_SIZE]; // this is not the password used in the message integrity, this is if the request provided a password in the clear (ala rfc 3478). Not recommended, but auth providers can use it if they want.
bool fMessageIntegrityPresent; // true if there was a message integrity field
The implementation of DoAuthCheck needs to decide how to handle the AuthAttributes
and then pass back a series of results and codes through the provided AuthResponse
paramter.
The AuthResponse parameter is for indicating to the server how to authenticate a
message integrity attribute. The implementation needs to set the following fieds
as appropriate:
AuthResponseType responseType;
responseType must be set to one of the following values
// Allow - Indicates to the server to send back a response and that no
additional validation on the message integrity field is needed
// AllowConditional - Indicates to the server that a response can be sent
as lone as the message integrity attribute in the stun request
is valid with respect to szPassword (and szUserName and szNonce if in long term cred mode)
If the message integrity attribute is deemed invalid, then
a 401 is sent back instead of a binding response.
// Reject - Indicates that the server should send back a 400 without any
additional attributes
// Unauthorized - Indicates that the server should send back a 401. In long term
cred mode, this will also send back the szNonce and szRealm fields
as attributes.
// StaleNonce - Indicates that the request was likely valid, but the nonce
attribute valid has expired
AuthCredentialMechanism authCredMech;
Is either set to AuthShortTerm or AuthLongTerm to indicate to the server
how to generate and validate message integrity fields
szPassword
Ignored if _responseType is anything other than AllowConditional.
server will not send this back as an attribute. Instead it is used
for validating and generating message integrity attributes in the
stun messages.
szRealm
Ignored if using short term credentials. Otherwise, it should be
the realm field used for generating and validating message integrity fields.
It will almost always need to be sent back in error responses to
the client.
szNonce
A new nonce for subsequent requests in the event this request can not
DoAuthCheck should return S_OK unless a fatal error occurs. If DoAuthCheck returns
a failure code, then
To have the server host an instance of an IStunAuth implementation, modify
CStunServer::Initialize to create an instance of your class and initialize
_spAuth as appropriate.
#endif
class CShortTermAuth :
public CBasicRefCount,
public CObjectFactory<CShortTermAuth>,
public IStunAuth
{
public:
virtual HRESULT DoAuthCheck(AuthAttributes* pAuthAttributes, AuthResponse* pResponse);
};
class CLongTermAuth :
public CBasicRefCount,
public CObjectFactory<CLongTermAuth>,
public IStunAuth
{
private:
void HmacToString(uint8_t* hmacvalue, char* pszResult);
HRESULT CreateNonce(char* pszNonce);
HRESULT ValidateNonce(char* pszNonce);
public:
virtual HRESULT DoAuthCheck(AuthAttributes* pAuthAttributes, AuthResponse* pResponse);
};
#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"
#include <openssl/hmac.h>
#include "stuncore.h"
#include "stunsocket.h"
#include "stunsocketthread.h"
#include "server.h"
CStunServerConfig::CStunServerConfig() :
fHasPP(false),
fHasPA(false),
fHasAP(false),
fHasAA(false),
fMultiThreadedMode(false)
{
;
}
CStunServer::CStunServer()
{
;
}
CStunServer::~CStunServer()
{
Shutdown();
}
HRESULT CStunServer::Initialize(const CStunServerConfig& config)
{
HRESULT hr = S_OK;
int socketcount = 0;
CRefCountedPtr<IStunAuth> _spAuth;
// cleanup any thing that's going on now
Shutdown();
// optional code: create an authentication provider and initialize it here (if you want authentication)
// set the _spAuth member to reference it
// Chk(CYourAuthProvider::CreateInstanceNoInit(&_spAuth));
// Create the sockets
if (config.fHasPP)
{
Chk(CStunSocket::Create(config.addrPP, RolePP, &_arrSockets[RolePP]));
_arrSockets[RolePP]->EnablePktInfoOption(true);
socketcount++;
}
if (config.fHasPA)
{
Chk(CStunSocket::Create(config.addrPA, RolePA, &_arrSockets[RolePA]));
_arrSockets[RolePA]->EnablePktInfoOption(true);
socketcount++;
}
if (config.fHasAP)
{
Chk(CStunSocket::Create(config.addrAP, RoleAP, &_arrSockets[RoleAP]));
_arrSockets[RoleAP]->EnablePktInfoOption(true);
socketcount++;
}
if (config.fHasAA)
{
Chk(CStunSocket::Create(config.addrAA, RoleAA, &_arrSockets[RoleAA]));
_arrSockets[RoleAA]->EnablePktInfoOption(true);
socketcount++;
}
ChkIf(socketcount == 0, E_INVALIDARG);
if (config.fMultiThreadedMode == false)
{
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));
}
else
{
Logging::LogMsg(LL_DEBUG, "Configuring multi-threaded mode\n");
// one thread for every socket
CStunSocketThread* pThread = NULL;
for (size_t index = 0; index < ARRAYSIZE(_arrSockets); index++)
{
if (_arrSockets[index] != NULL)
{
std::vector<CRefCountedStunSocket> listsockets;
listsockets.push_back(_arrSockets[index]);
pThread = new CStunSocketThread();
ChkIf(pThread==NULL, E_OUTOFMEMORY);
_threads.push_back(pThread);
Chk(pThread->Init(listsockets, this, _spAuth));
}
}
}
Cleanup:
if (FAILED(hr))
{
Shutdown();
}
return hr;
}
HRESULT CStunServer::Shutdown()
{
size_t len;
Stop();
// release the sockets and the thread
for (size_t index = 0; index < ARRAYSIZE(_arrSockets); index++)
{
_arrSockets[index].reset();
}
len = _threads.size();
for (size_t index = 0; index < len; index++)
{
CStunSocketThread* pThread = _threads[index];
delete pThread;
_threads[index] = NULL;
}
_threads.clear();
_spAuth.ReleaseAndClear();
return S_OK;
}
HRESULT CStunServer::Start()
{
HRESULT hr = S_OK;
size_t len = _threads.size();
ChkIfA(len == 0, E_UNEXPECTED);
for (size_t index = 0; index < len; index++)
{
CStunSocketThread* pThread = _threads[index];
if (pThread != NULL)
{
// set the "exit flag" that each thread looks at when it wakes up from waiting
ChkA(pThread->Start());
}
}
Cleanup:
if (FAILED(hr))
{
Stop();
}
return hr;
}
HRESULT CStunServer::Stop()
{
size_t len = _threads.size();
for (size_t index = 0; index < len; index++)
{
CStunSocketThread* pThread = _threads[index];
if (pThread != NULL)
{
// set the "exit flag" that each thread looks at when it wakes up from waiting
pThread->SignalForStop(false);
}
}
for (size_t index = 0; index < len; index++)
{
CStunSocketThread* pThread = _threads[index];
// Post a bunch of empty buffers to get the threads unblocked from whatever socket call they are on
// In multi-threaded mode, this may wake up a different thread. But that's ok, since all threads start and stop together
if (pThread != NULL)
{
pThread->SignalForStop(true);
}
}
for (size_t index = 0; index < len; index++)
{
CStunSocketThread* pThread = _threads[index];
if (pThread != NULL)
{
pThread->WaitForStopAndClose();
}
}
return S_OK;
}
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;
}
/*
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 STUN_SERVER_H
#define STUN_SERVER_H
#include "stunsocket.h"
#include "stunsocketthread.h"
#include "stunauth.h"
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
CSocketAddress addrPP; // address for PP
CSocketAddress addrPA; // address for PA
CSocketAddress addrAP; // address for AP
CSocketAddress addrAA; // address for AA
CStunServerConfig();
};
class CStunServer :
public CBasicRefCount,
public CObjectFactory<CStunServer>,
public IStunResponder
{
private:
CRefCountedStunSocket _arrSockets[4];
// when we support multithreaded servers, this will change to a list
std::vector<CStunSocketThread*> _threads;
CStunServer();
~CStunServer();
friend class CObjectFactory<CStunServer>;
CRefCountedPtr<IStunAuth> _spAuth;
public:
HRESULT Initialize(const CStunServerConfig& config);
HRESULT Shutdown();
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();
};
#endif /* SERVER_H */
/*
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"
#include "stuncore.h"
#include "stunsocket.h"
#include "stunsocketthread.h"
#include "recvfromex.h"
CStunSocketThread::CStunSocketThread() :
_fNeedToExit(false),
_pthread((pthread_t)-1),
_fThreadIsValid(false),
_rotation(0)
{
;
}
CStunSocketThread::~CStunSocketThread()
{
SignalForStop(true);
WaitForStopAndClose();
}
HRESULT CStunSocketThread::Init(std::vector<CRefCountedStunSocket>& listSockets, IStunResponder* pResponder, IStunAuth* pAuth)
{
HRESULT hr = S_OK;
ChkIfA(_fThreadIsValid, E_UNEXPECTED);
ChkIfA(pResponder == NULL, E_UNEXPECTED);
ChkIfA(listSockets.size() <= 0, E_INVALIDARG);
_socks = listSockets;
_handler.SetResponder(pResponder);
_handler.SetAuth(pAuth);
_fNeedToExit = false;
_rotation = 0;
Cleanup:
return hr;
}
HRESULT CStunSocketThread::Start()
{
HRESULT hr = S_OK;
int err = 0;
ChkIfA(_fThreadIsValid, E_UNEXPECTED);
ChkIfA(_socks.size() <= 0, E_FAIL);
err = ::pthread_create(&_pthread, NULL, CStunSocketThread::ThreadFunction, this);
ChkIfA(err != 0, ERRNO_TO_HRESULT(err));
_fThreadIsValid = true;
Cleanup:
return hr;
}
HRESULT CStunSocketThread::SignalForStop(bool fPostMessages)
{
size_t size = _socks.size();
HRESULT hr = S_OK;
_fNeedToExit = true;
// have the socket send a message to itself
// if another thread is sharing the same socket, this may wake that thread up to
// but all the threads should be started and shutdown together
if (fPostMessages)
{
for (size_t index = 0; index < size; index++)
{
char data = 'x';
::CSocketAddress addr(_socks[index]->GetLocalAddress());
::sendto(_socks[index]->GetSocketHandle(), &data, 1, 0, addr.GetSockAddr(), addr.GetSockAddrLength());
}
}
return hr;
}
HRESULT CStunSocketThread::WaitForStopAndClose()
{
void* pRetValFromThread = NULL;
if (_fThreadIsValid)
{
// now wait for the thread to exit
pthread_join(_pthread, &pRetValFromThread);
}
_fThreadIsValid = false;
_pthread = (pthread_t)-1;
_socks.clear();
return S_OK;
}
// static
void* CStunSocketThread::ThreadFunction(void* pThis)
{
((CStunSocketThread*)pThis)->Run();
return NULL;
}
int CStunSocketThread::WaitForSocketData()
{
fd_set set = {};
int nHighestSockValue = 0;
size_t nSocketCount = _socks.size();
int ret;
CRefCountedStunSocket spSocket;
int result = -1;
UNREFERENCED_VARIABLE(ret); // only referenced in ASSERT
// rotation gives another socket priority in the next loop
_rotation = (_rotation + 1) % nSocketCount;
ASSERT(_rotation >= 0);
FD_ZERO(&set);
for (size_t index = 0; index < nSocketCount; index++)
{
int sock = _socks[index]->GetSocketHandle();
FD_SET(sock, &set);
nHighestSockValue = (sock > nHighestSockValue) ? sock : nHighestSockValue;
}
// wait indefinitely for a socket
ret = ::select(nHighestSockValue+1, &set, NULL, NULL, NULL);
ASSERT(ret > 0); // This will be a benign assert, and should never happen. But I will want to know if it does
// now figure out which socket just got data on it
spSocket.reset();
for (size_t index = 0; index < nSocketCount; index++)
{
int indexconverted = (index + _rotation) % nSocketCount;
int sock = _socks[indexconverted]->GetSocketHandle();
if (FD_ISSET(sock, &set))
{
result = indexconverted;
break;
}
}
return result;
}
void CStunSocketThread::Run()
{
size_t nSocketCount = _socks.size();
bool fMultiSocketMode = (nSocketCount > 1);
int recvflags = fMultiSocketMode ? MSG_DONTWAIT : 0;
CRefCountedStunSocket spSocket = _socks[0];
const int RECV_BUFFER_SIZE = 1500;
CRefCountedBuffer spBuffer(new CBuffer(RECV_BUFFER_SIZE));
int ret;
int socketindex = 0;
CSocketAddress remoteAddr;
CSocketAddress localAddr;
Logging::LogMsg(LL_DEBUG, "Starting listener thread");
while (_fNeedToExit == false)
{
if (fMultiSocketMode)
{
spSocket.reset();
socketindex = WaitForSocketData();
if (_fNeedToExit)
{
break;
}
ASSERT(socketindex >= 0);
if (socketindex < 0)
{
// just go back to waiting;
continue;
}
spSocket = _socks[socketindex];
ASSERT(spSocket != NULL);
}
// now receive the data
spBuffer->SetSize(0);
ret = ::recvfromex(spSocket->GetSocketHandle(), spBuffer->GetData(), spBuffer->GetAllocatedSize(), recvflags, &remoteAddr, &localAddr);
if (Logging::GetLogLevel() >= LL_VERBOSE)
{
char szIPRemote[100];
char szIPLocal[100];
remoteAddr.ToStringBuffer(szIPRemote, 100);
localAddr.ToStringBuffer(szIPLocal, 100);
Logging::LogMsg(LL_VERBOSE, "recvfrom returns %d from %s on local interface %s", ret, szIPRemote, szIPLocal);
}
if (ret < 0)
{
// error
continue;
}
if (_fNeedToExit)
{
break;
}
spBuffer->SetSize(ret);
StunMessageEnvelope msg;
msg.remoteAddr = remoteAddr;
msg.spBuffer = spBuffer;
msg.localSocket = spSocket->GetRole();
msg.localAddr = localAddr;
_handler.ProcessRequest(msg);
}
Logging::LogMsg(LL_DEBUG, "Thread exiting");
}
/*
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 STUNSOCKETTHREAD_H
#define STUNSOCKETTHREAD_H
#include "stunsocket.h"
class CStunServer;
class CStunSocketThread
{
public:
CStunSocketThread();
~CStunSocketThread();
HRESULT Init(std::vector<CRefCountedStunSocket>& listSockets, IStunResponder* pResponder, IStunAuth* pAuth);
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:
// this is the function that runs in a thread
void Run();
static void* ThreadFunction(void* pThis);
std::vector<CRefCountedStunSocket> _socks;
bool _fNeedToExit;
CStunThreadMessageHandler _handler;
pthread_t _pthread;
bool _fThreadIsValid;
int _rotation;
};
#endif /* STUNSOCKETTHREAD_H */
Usage: stunserver [OPTION]...
Start a STUN server in one of various modes of operation using any of
the [OPTION]s described below:
Available options:
--mode=MODE
Where MODE is either "basic" or "full". In basic mode, the server only listens on one port and does not support STUN CHANGE requests. 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. Basic mode is sufficient for basic NAT traversal. Full mode facilitates clients attempting to determine NAT behavior and NAT filtering behavior. Full mode requires two unique network interfaces with different IP addresses. Full mode does not work with TCP or TLS, only UDP. If this parameter is not specified, basic mode is the default.
--primaryinterface=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 used by the service as the primary listening address in either full or basic mode. In basic mode, the default primary interface is ALL adapters (socket binds to INADDR_ANY). In full mode, the service binds to the the first non-localhost interface that in the UP state with a valid IP Address.
--altinterface=INTERFACE OR IPADDRESS
Same as the --primaryinterface option, except this option's value specifies the listening address for the alternate address in full mode. It has no meaning in basic mode nor does it have meaning in when TCP or TLS is the listening protocol. In full mode, the service will bind to the the second non-localhost interface that is in the UP state with a valid IP address.
--primaryport=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. The default is 3478 for UDP and TCP. For TLS it is 5349.
--altport=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 alternate port. The default is 3479. It has no meaning in FULL 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". (Note: tcp and tls are not yet available in this version)
--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:
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 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.
Usage: stunserver [OPTION]...
Try 'stunserver --help' for a complete set of options
include ../common.inc
PROJECT_TARGET := libstuncore.a
PROJECT_OBJS := buffer.o datastream.o messagehandler.o socketaddress.o stunbuilder.o stunclientlogic.o stunclienttests.o stunreader.o stunutils.o
INCLUDES := $(BOOST_INCLUDE) $(OPENSSL_INCLUDE) -I../common
all: $(PROJECT_TARGET)
clean:
rm -f $(PROJECT_OBJS) $(PROJECT_TARGET)
$(PROJECT_TARGET): $(PROJECT_OBJS)
rm -f $@
$(AR) rv $@ $^
/*
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"
#include "buffer.h"
CBuffer::CBuffer() :
_data(NULL),
_size(0),
_allocatedSize(0)
{
;
}
void CBuffer::Reset()
{
_spAllocation.reset();
_data = NULL;
_size = 0;
_allocatedSize = 0;
}
CBuffer::CBuffer(size_t nSize)
{
InitWithAllocation(nSize);
}
HRESULT CBuffer::InitWithAllocation(size_t size)
{
Reset();
// deliberately not checking for 0. Ok to allocate a 0 byte array
boost::scoped_array<uint8_t> spAlloc(new uint8_t[size+2]); // add two bytes for null termination (makes debugging ascii and unicode strings easier), but these two bytes are invisible to the caller (not included in _allocatedSize)
_spAllocation.swap(spAlloc);
spAlloc.reset();
_data = _spAllocation.get();
if (_data)
{
_data[size] = 0;
_data[size+1] = 0;
}
_size = (_data != NULL) ? size : 0;
_allocatedSize = _size;
return (_data != NULL) ? S_OK : E_FAIL;
}
HRESULT CBuffer::InitNoAlloc(uint8_t* pByteArray, size_t size)
{
Reset();
if (pByteArray == NULL)
{
size = 0;
}
_data = pByteArray;
_size = size;
_allocatedSize = size;
return S_OK;
}
HRESULT CBuffer::InitWithAllocAndCopy(uint8_t* pByteArray, size_t size)
{
HRESULT hr = S_OK;
Reset();
if (pByteArray == NULL)
{
size = 0;
}
hr = InitWithAllocation(size);
if (SUCCEEDED(hr))
{
memcpy(_data, pByteArray, _size);
}
return hr;
}
CBuffer::CBuffer(uint8_t* pByteArray, size_t nByteArraySize, bool fCopy)
{
if (fCopy == false)
{
InitNoAlloc(pByteArray, nByteArraySize);
}
else
{
InitWithAllocAndCopy(pByteArray, nByteArraySize);
}
}
size_t CBuffer::GetSize()
{
return _size;
}
size_t CBuffer::GetAllocatedSize()
{
return _allocatedSize;
}
HRESULT CBuffer::SetSize(size_t size)
{
HRESULT hr = S_OK;
hr = (size <= _allocatedSize);
if (SUCCEEDED(hr))
{
_size = size;
}
return hr;
}
uint8_t* CBuffer::GetData()
{
return _data;
}
bool CBuffer::IsValid()
{
return (_data != NULL);
}
/*
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 CBUFFER_H
#define CBUFFER_H
class CBuffer
{
private:
uint8_t* _data;
size_t _size;
size_t _allocatedSize;
boost::scoped_array<uint8_t> _spAllocation;
// disallow copy and assignment.
CBuffer(const CBuffer&);
void operator=(const CBuffer& other);
public:
CBuffer(); // deliberately makes the buffer null
void Reset(); // releases current pointer
CBuffer(size_t nSize);
HRESULT InitWithAllocation(size_t size);
CBuffer(uint8_t* pByteArray, size_t nByteArraySize, bool fCopy);
HRESULT InitWithAllocAndCopy(uint8_t* pByteArray, size_t nByteArraySize);
HRESULT InitNoAlloc(uint8_t* pByteArray, size_t nByteArraySize);
size_t GetSize();
size_t GetAllocatedSize();
HRESULT SetSize(size_t size);
uint8_t* GetData();
bool IsValid();
};
typedef boost::shared_ptr<CBuffer> CRefCountedBuffer;
#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"
#include "datastream.h"
CDataStream::CDataStream() :
_pos(0),
_fNoGrow(false)
{
// _spBuffer is null
}
CDataStream::CDataStream(CRefCountedBuffer& spBuffer) :
_spBuffer(spBuffer),
_pos(0),
_fNoGrow(false)
{
}
HRESULT CDataStream::SetSizeHint(size_t size)
{
return Grow(size);
}
void CDataStream::Reset()
{
_spBuffer.reset();
_pos = 0;
_fNoGrow = false;
}
void CDataStream::Attach(CRefCountedBuffer& buf, bool fForWriting)
{
Reset();
_spBuffer = buf;
if (_spBuffer && fForWriting)
{
_spBuffer->SetSize(0);
}
}
HRESULT CDataStream::Read(void* data, size_t size)
{
size_t newpos = size + _pos;
size_t currentSize = GetSize();
ASSERT(newpos <= currentSize);
if (newpos > currentSize)
{
return E_INVALIDARG;
}
memcpy(data, _spBuffer->GetData() + _pos, size);
_pos = newpos;
return S_OK;
}
HRESULT CDataStream::Grow(size_t size)
{
size_t currentAllocated = (_spBuffer ? _spBuffer->GetAllocatedSize() : 0);
size_t currentSize = GetSize();
size_t newallocationsize=0;
if (size <= currentAllocated)
{
return S_OK;
}
if (_fNoGrow)
{
return E_FAIL;
}
if (size > (currentAllocated*2))
{
newallocationsize = size;
}
else
{
newallocationsize = currentAllocated*2;
}
ASSERT(newallocationsize > 0);
CRefCountedBuffer spNewBuffer(new CBuffer(newallocationsize));
if (spNewBuffer->IsValid() == false)
{
return E_OUTOFMEMORY;
}
// Grow only increases allocated size. It doesn't influence the actual data stream size
spNewBuffer->SetSize(currentSize);
if (_spBuffer && (currentSize > 0))
{
memcpy(spNewBuffer->GetData(), _spBuffer->GetData(), currentSize);
}
_spBuffer = spNewBuffer;
return S_OK;
}
void CDataStream::SetNoGrow(bool fNoGrow)
{
_fNoGrow = fNoGrow;
}
HRESULT CDataStream::Write(const void* data, size_t size)
{
size_t newposition = size + _pos;
size_t currentSize = GetSize();
HRESULT hr = S_OK;
if ((size == 0) || (data == NULL))
{
return E_FAIL;
}
hr = Grow(newposition); // make sure we have enough buffer to write into
if (FAILED(hr))
{
return hr;
}
memcpy(_spBuffer->GetData()+_pos, data, size);
_pos = newposition;
if (newposition > currentSize)
{
hr = _spBuffer->SetSize(newposition);
ASSERT(SUCCEEDED(hr));
}
return hr;
}
bool CDataStream::IsEOF()
{
size_t currentSize = GetSize();
return (_pos >= currentSize);
}
size_t CDataStream::GetPos()
{
return _pos;
}
size_t CDataStream::GetSize()
{
return (_spBuffer ? _spBuffer->GetSize() : 0);
}
HRESULT CDataStream::SeekDirect(size_t pos)
{
HRESULT hr = S_OK;
size_t currentSize = (_spBuffer ? _spBuffer->GetSize() : 0);
// seeking is allowed anywhere between 0 and stream size
if ((pos >= 0) && (pos <= currentSize))
{
_pos = pos;
}
else
{
ASSERT(false); // likely a programmer error if we seek out of the stream
hr = E_FAIL;
}
return hr;
}
HRESULT CDataStream::SeekRelative(int offset)
{
// todo: there might be some math overflow to check here.
return SeekDirect(offset + _pos);
}
HRESULT CDataStream::GetBuffer(CRefCountedBuffer* pBuffer)
{
HRESULT hr;
if (pBuffer)
{
*pBuffer = _spBuffer;
hr = S_OK;
}
else
{
hr = E_FAIL;
}
return hr;
}
// unsafe because the pointer is not guaranteed to be valid after subsequent writes. Could also be NULL if nothing has been written
uint8_t* CDataStream::GetDataPointerUnsafe()
{
uint8_t* pRet = NULL;
if (_spBuffer)
{
pRet = _spBuffer->GetData();
}
return pRet;
}
/*
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 DATASTREAM_H
#define DATASTREAM_H
#include "buffer.h"
class CDataStream
{
CRefCountedBuffer _spBuffer;
size_t _pos;
bool _fNoGrow;
HRESULT Grow(size_t newsize);
public:
CDataStream();
CDataStream(CRefCountedBuffer& buffer);
HRESULT SetSizeHint(size_t size);
void SetNoGrow(bool fDisableGrow);
void Reset();
void Attach(CRefCountedBuffer& buffer, bool fForWriting);
HRESULT Write(const void* data, size_t size);
HRESULT Read(void* data, size_t size);
HRESULT WriteUint8(uint8_t val) {return Write(&val, sizeof(val));}
HRESULT WriteUint16(uint16_t val) {return Write(&val, sizeof(val));}
HRESULT WriteUint32(uint32_t val) {return Write(&val, sizeof(val));}
HRESULT WriteUint64(uint64_t val) {return Write(&val, sizeof(val));}
HRESULT WriteInt8(int8_t val) {return Write(&val, sizeof(val));}
HRESULT WriteInt16(int16_t val) {return Write(&val, sizeof(val));}
HRESULT WriteInt32(int32_t val) {return Write(&val, sizeof(val));}
HRESULT WriteInt64(int64_t val) {return Write(&val, sizeof(val));}
HRESULT ReadUint8(uint8_t* pVal) {return Read(pVal, sizeof(*pVal));}
HRESULT ReadUint16(uint16_t* pVal) {return Read(pVal, sizeof(*pVal));}
HRESULT ReadUint32(uint32_t* pVal) {return Read(pVal, sizeof(*pVal));}
HRESULT ReadUint64(uint64_t* pVal) {return Read(pVal, sizeof(*pVal));}
HRESULT ReadInt8(int8_t* pVal) {return Read(pVal, sizeof(*pVal));}
HRESULT ReadInt16(int16_t* pVal) {return Read(pVal, sizeof(*pVal));}
HRESULT ReadInt32(int32_t* pVal) {return Read(pVal, sizeof(*pVal));}
HRESULT ReadInt64(int64_t* pVal) {return Read(pVal, sizeof(*pVal));}
HRESULT GetBuffer(CRefCountedBuffer* pRefCountedBuffer);
uint8_t* GetDataPointerUnsafe();
HRESULT SeekDirect(size_t pos);
HRESULT SeekRelative(int nOffset);
size_t GetPos();
size_t GetSize();
bool IsEOF();
};
#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"
#include "stuncore.h"
#include "stunresponder.h"
#include "messagehandler.h"
CStunThreadMessageHandler::CStunThreadMessageHandler()
{
CRefCountedBuffer spReaderBuffer(new CBuffer(1500));
CRefCountedBuffer spResponseBuffer(new CBuffer(1500));
_spReaderBuffer.swap(spReaderBuffer);
_spResponseBuffer.swap(spResponseBuffer);
}
CStunThreadMessageHandler::~CStunThreadMessageHandler()
{
;
}
void CStunThreadMessageHandler::SetResponder(IStunResponder* pTransport)
{
_spStunResponder = pTransport;
}
void CStunThreadMessageHandler::SetAuth(IStunAuth* pAuth)
{
_spAuth = pAuth;
}
void CStunThreadMessageHandler::ProcessRequest(StunMessageEnvelope& message)
{
CStunMessageReader reader;
CStunMessageReader::ReaderParseState state;
uint16_t responsePort = 0;
HRESULT hr = S_OK;
ChkIfA(_spStunResponder == NULL, E_FAIL);
_spReaderBuffer->SetSize(0);
_spResponseBuffer->SetSize(0);
_message = message;
_addrResponse = message.remoteAddr;
_socketOutput = message.localSocket;
_fRequestHasResponsePort = false;
// zero out _error without the overhead of zero'ing out every byte in the strings
_error.errorcode = 0;
_error.szNonce[0] = 0;
_error.szRealm[0] = 0;
_error.attribUnknown = 0;
_integrity.fSendWithIntegrity = false;
_integrity.szUser[0] = '\0';
_integrity.szRealm[0] = '\0';
_integrity.szPassword[0] = '\0';
// attach the temp buffer to reader
reader.GetStream().Attach(_spReaderBuffer, true);
reader.SetAllowLegacyFormat(true);
// parse the request
state = reader.AddBytes(message.spBuffer->GetData(), message.spBuffer->GetSize());
// If we get something that can't be validated as a stun message, don't send back a response
// STUN RFC may suggest sending back a "500", but I think that's the wrong approach.
ChkIf (state != CStunMessageReader::BodyValidated, E_FAIL);
// Regardless of what we send back, let's always attempt to honor a response port request
// Fix the destination port if the client asked for us to send back to another port
if (SUCCEEDED(reader.GetResponsePort(&responsePort)))
{
_addrResponse.SetPort(responsePort);
_fRequestHasResponsePort = true;
}
reader.GetTransactionId(&_transid);
// ignore anything that is not a request (with no response)
ChkIf(reader.GetMessageClass() != StunMsgClassRequest, E_FAIL);
// pre-prep the error message in case we wind up needing to send it
_error.msgtype = reader.GetMessageType();
_error.msgclass = StunMsgClassFailureResponse;
if (reader.GetMessageType() != StunMsgTypeBinding)
{
// we're going to send back an error response
_error.errorcode = STUN_ERROR_BADREQUEST; // invalid request
}
else
{
// handle authentication - but only if an auth provider has been set
hr = ValidateAuth(reader);
// if auth succeeded, then carry on to handling the request
if (SUCCEEDED(hr) && (_error.errorcode==0))
{
// handle the binding request
hr = ProcessBindingRequest(reader);
}
// catch all for any case where an error occurred
if (FAILED(hr) && (_error.errorcode==0))
{
_error.errorcode = STUN_ERROR_BADREQUEST;
}
}
if (_error.errorcode != 0)
{
// if either ValidateAuth or ProcessBindingRequest set an errorcode, or a fatal error occurred
SendErrorResponse();
}
else
{
SendResponse();
}
Cleanup:
return;
}
void CStunThreadMessageHandler::SendResponse()
{
HRESULT hr = S_OK;
ChkIfA(_spStunResponder == NULL, E_FAIL);
ChkIfA(_spResponseBuffer->GetSize() <= 0, E_FAIL);
Chk(_spStunResponder->SendResponse(_socketOutput, _addrResponse, _spResponseBuffer));
Cleanup:
return;
}
void CStunThreadMessageHandler::SendErrorResponse()
{
HRESULT hr = S_OK;
CStunMessageBuilder builder;
CRefCountedBuffer spBuffer;
_spResponseBuffer->SetSize(0);
builder.GetStream().Attach(_spResponseBuffer, true);
builder.AddHeader((StunMessageType)_error.msgtype, _error.msgclass);
builder.AddTransactionId(_transid);
builder.AddErrorCode(_error.errorcode, "FAILED");
if ((_error.errorcode == ::STUN_ERROR_UNKNOWNATTRIB) && (_error.attribUnknown != 0))
{
builder.AddUnknownAttributes(&_error.attribUnknown, 1);
}
else if ((_error.errorcode == ::STUN_ERROR_STALENONCE) || (_error.errorcode == ::STUN_ERROR_UNAUTHORIZED))
{
if (_error.szNonce[0])
{
builder.AddStringAttribute(STUN_ATTRIBUTE_NONCE, _error.szNonce);
}
if (_error.szRealm[0])
{
builder.AddStringAttribute(STUN_ATTRIBUTE_REALM, _error.szRealm);
}
}
ChkIfA(_spStunResponder == NULL, E_FAIL);
builder.GetResult(&spBuffer);
ASSERT(spBuffer->GetSize() != 0);
ASSERT(spBuffer == _spResponseBuffer);
_spStunResponder->SendResponse(_socketOutput, _addrResponse, spBuffer);
Cleanup:
return;
}
HRESULT CStunThreadMessageHandler::ProcessBindingRequest(CStunMessageReader& reader)
{
HRESULT hrTmp;
bool fRequestHasPaddingAttribute = false;
SocketRole socketOutput = _message.localSocket;
StunChangeRequestAttribute changerequest = {};
bool fSendOtherAddress = false;
bool fSendOriginAddress = false;
SocketRole socketOther;
CSocketAddress addrOrigin;
CSocketAddress addrOther;
CStunMessageBuilder builder;
uint16_t paddingSize = 0;
bool fLegacyFormat = false; // set to true if the client appears to be rfc3489 based instead of based on rfc 5789
_spResponseBuffer->SetSize(0);
builder.GetStream().Attach(_spResponseBuffer, true);
fLegacyFormat = reader.IsMessageLegacyFormat();
// check for an alternate response port
// check for padding attribute (todo - figure out how to inject padding into the response)
// check for a change request and validate we can do it. If so, set _socketOutput. If not, fill out _error and return.
// determine if we have an "other" address to notify the caller about
// did the request come with a padding request
if (SUCCEEDED(reader.GetPaddingAttributeSize(&paddingSize)))
{
// todo - figure out how we're going to get the MTU size of the outgoing interface
fRequestHasPaddingAttribute = true;
}
// as per 5780, section 6.1, If the Request contained a PADDING attribute...
// "If the Request also contains the RESPONSE-PORT attribute the server MUST return an error response of type 400."
if (_fRequestHasResponsePort && fRequestHasPaddingAttribute)
{
_error.errorcode = STUN_ERROR_BADREQUEST;
return E_FAIL;
}
// handle change request logic and figure out what "other-address" attribute is going to be
if (SUCCEEDED(reader.GetChangeRequest(&changerequest)))
{
if (changerequest.fChangeIP)
{
socketOutput = SocketRoleSwapIP(socketOutput);
}
if(changerequest.fChangePort)
{
socketOutput = SocketRoleSwapPort(socketOutput);
}
// IsValidSocketRole just validates the enum, not whether or not we can send on it
ASSERT(IsValidSocketRole(socketOutput));
// now, make sure we have the ability to send from another socket
if (_spStunResponder->HasAddress(socketOutput) == false)
{
// send back an error. We're being asked to respond using another address that we don't have a socket for
_error.errorcode = STUN_ERROR_BADREQUEST;
return E_FAIL;
}
}
// If we're only working one socket, then that's ok, we just don't send back an "other address" unless we have all four sockets confgiured
// now here's a problem. If we binded to "INADDR_ANY", all of the sockets will have "0.0.0.0" for an address (same for IPV6)
// So we effectively can't send back "other address" if don't really know our own IP address
// Fortunately, recvfromex and the ioctls on the socket allow address discovery a bit better
fSendOtherAddress = (_spStunResponder->HasAddress(RolePP) && _spStunResponder->HasAddress(RolePA) && _spStunResponder->HasAddress(RoleAP) && _spStunResponder->HasAddress(RoleAA));
if (fSendOtherAddress)
{
socketOther = SocketRoleSwapIP(SocketRoleSwapPort(_message.localSocket));
hrTmp = _spStunResponder->GetSocketAddressForRole(socketOther, &addrOther);
ASSERT(SUCCEEDED(hrTmp));
// so if our ip address is "0.0.0.0", disable this attribute
fSendOtherAddress = (SUCCEEDED(hrTmp) && (addrOther.IsIPAddressZero()==false));
}
// What's our address origin?
VERIFY(SUCCEEDED(_spStunResponder->GetSocketAddressForRole(socketOutput, &addrOrigin)));
if (addrOrigin.IsIPAddressZero())
{
// Since we're sending back from the IP address we received on, we can just use the address the message came in on
// Otherwise, we don't actually know it
if (socketOutput == _message.localSocket)
{
addrOrigin = _message.localAddr;
}
}
fSendOriginAddress = (false == addrOrigin.IsIPAddressZero());
// Success - we're all clear to build the response
_socketOutput = socketOutput;
_spResponseBuffer->SetSize(0);
builder.GetStream().Attach(_spResponseBuffer, true);
builder.AddHeader(StunMsgTypeBinding, StunMsgClassSuccessResponse);
builder.AddTransactionId(_transid);
builder.AddMappedAddress(_message.remoteAddr);
if (fLegacyFormat == false)
{
builder.AddXorMappedAddress(_message.remoteAddr);
}
if (fSendOriginAddress)
{
builder.AddResponseOriginAddress(addrOrigin);
}
if (fSendOtherAddress)
{
builder.AddOtherAddress(addrOther, fLegacyFormat); // pass true to send back CHANGED-ADDRESS, otherwise, pass false to send back OTHER-ADDRESS
}
// finally - if we're supposed to have a message integrity attribute as a result of authorization, add it at the very end
if (_integrity.fSendWithIntegrity)
{
if (_integrity.fUseLongTerm == false)
{
builder.AddMessageIntegrityShortTerm(_integrity.szPassword);
}
else
{
builder.AddMessageIntegrityLongTerm(_integrity.szUser, _integrity.szRealm, _integrity.szPassword);
}
}
builder.FixLengthField();
return S_OK;
}
HRESULT CStunThreadMessageHandler::ValidateAuth(CStunMessageReader& reader)
{
AuthAttributes authattributes;
AuthResponse authresponse;
HRESULT hr = S_OK;
HRESULT hrRet = S_OK;
if (_spAuth == NULL)
{
return S_OK; // nothing to do if there is no auth mechanism in place
}
memset(&authattributes, '\0', sizeof(authattributes));
memset(&authresponse, '\0', sizeof(authresponse));
reader.GetStringAttributeByType(STUN_ATTRIBUTE_USERNAME, authattributes.szUser, ARRAYSIZE(authattributes.szUser));
reader.GetStringAttributeByType(STUN_ATTRIBUTE_REALM, authattributes.szRealm, ARRAYSIZE(authattributes.szRealm));
reader.GetStringAttributeByType(STUN_ATTRIBUTE_NONCE, authattributes.szNonce, ARRAYSIZE(authattributes.szNonce));
reader.GetStringAttributeByType(::STUN_ATTRIBUTE_LEGACY_PASSWORD, authattributes.szLegacyPassword, ARRAYSIZE(authattributes.szLegacyPassword));
authattributes.fMessageIntegrityPresent = reader.HasMessageIntegrityAttribute();
Chk(_spAuth->DoAuthCheck(&authattributes, &authresponse));
// enforce that everything is null terminated
authresponse.szNonce[ARRAYSIZE(authresponse.szNonce)-1] = 0;
authresponse.szRealm[ARRAYSIZE(authresponse.szRealm)-1] = 0;
authresponse.szPassword[ARRAYSIZE(authresponse.szPassword)-1] = 0;
// now decide how to handle the auth
if (authresponse.responseType == StaleNonce)
{
_error.errorcode = STUN_ERROR_STALENONCE;
}
else if (authresponse.responseType == Unauthorized)
{
_error.errorcode = STUN_ERROR_UNAUTHORIZED;
}
else if (authresponse.responseType == Reject)
{
_error.errorcode = STUN_ERROR_BADREQUEST;
}
else if (authresponse.responseType == Allow)
{
// nothing to do!
}
else if (authresponse.responseType == AllowConditional)
{
// validate the message in // if either ValidateAuth or ProcessBindingRequest set an errorcode....
if (authresponse.authCredMech == AuthCredLongTerm)
{
hrRet = reader.ValidateMessageIntegrityLong(authattributes.szUser, authattributes.szRealm, authresponse.szPassword);
}
else
{
hrRet = reader.ValidateMessageIntegrityShort(authresponse.szPassword);
}
if (SUCCEEDED(hrRet))
{
_integrity.fSendWithIntegrity = true;
_integrity.fUseLongTerm = (authresponse.authCredMech == AuthCredLongTerm);
COMPILE_TIME_ASSERT(sizeof(_integrity.szPassword)==sizeof(authresponse.szPassword));
strcpy(_integrity.szPassword, authresponse.szPassword);
strcpy(_integrity.szUser, authattributes.szUser);
strcpy(_integrity.szRealm, authattributes.szRealm);
}
else
{
// bad password - so now turn this thing into a 401
_error.errorcode = STUN_ERROR_UNAUTHORIZED;
}
}
if ((_error.errorcode == STUN_ERROR_UNAUTHORIZED) || (_error.errorcode == STUN_ERROR_STALENONCE))
{
strcpy(_error.szRealm, authresponse.szRealm);
strcpy(_error.szNonce, authresponse.szNonce);
}
Cleanup:
return hr;
}
/*
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 MESSAGEHANDLER_H_
#define MESSAGEHANDLER_H_
#include "stunresponder.h"
#include "stunauth.h"
struct StunMessageEnvelope
{
SocketRole localSocket; /// which socket id did the message arrive on
CSocketAddress localAddr; /// What local IP address the message was received on (useful if the socket binded to INADDR_ANY)
CSocketAddress remoteAddr; /// the address of the node that sent us the message
CRefCountedBuffer spBuffer; /// the data in the message
};
struct StunMessageIntegrity
{
bool fSendWithIntegrity;
bool fUseLongTerm;
char szUser[MAX_STUN_AUTH_STRING_SIZE+1]; // used for computing the message-integrity value
char szRealm[MAX_STUN_AUTH_STRING_SIZE+1]; // used for computing the message-integrity value
char szPassword[MAX_STUN_AUTH_STRING_SIZE+1]; // used for computing the message-integrity value
};
class CStunThreadMessageHandler
{
private:
CRefCountedPtr<IStunResponder> _spStunResponder;
CRefCountedPtr<IStunAuth> _spAuth;
CRefCountedBuffer _spReaderBuffer;
CRefCountedBuffer _spResponseBuffer;
StunMessageEnvelope _message; // the message, including where it came from, who it was sent to, and the socket id
CSocketAddress _addrResponse; // where do we went the response back to go back to
bool _fRequestHasResponsePort; // true if the request has a response port attribute
SocketRole _socketOutput; // which socket do we send the response on?
StunTransactionId _transid;
StunMessageIntegrity _integrity;
struct StunErrorCode
{
uint16_t errorcode;
StunMessageClass msgclass;
uint16_t msgtype;
uint16_t attribUnknown; // for now, just send back one unknown attribute at a time
char szNonce[MAX_STUN_AUTH_STRING_SIZE+1];
char szRealm[MAX_STUN_AUTH_STRING_SIZE+1];
};
StunErrorCode _error;
HRESULT ProcessBindingRequest(CStunMessageReader& reader);
void SendErrorResponse();
void SendResponse();
HRESULT ValidateAuth(CStunMessageReader& reader);
public:
CStunThreadMessageHandler();
~CStunThreadMessageHandler();
void SetResponder(IStunResponder* pTransport);
void SetAuth(IStunAuth* pAuth);
void ProcessRequest(StunMessageEnvelope& message);
};
#endif /* MESSAGEHANDLER_H_ */
/*
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"
#include "socketaddress.h"
CSocketAddress::CSocketAddress() :
_address() // zero-init
{
_address.addr4.sin_family = AF_INET;
}
CSocketAddress::CSocketAddress(const sockaddr& addr)
{
CommonConstructor(addr);
}
CSocketAddress::CSocketAddress(const sockaddr_storage& addr)
{
CommonConstructor(*(sockaddr*)&addr);
}
void CSocketAddress::CommonConstructor(const sockaddr& addr)
{
ASSERT((addr.sa_family == AF_INET) || (addr.sa_family==AF_INET6));
if (addr.sa_family == AF_INET6)
{
_address.addr6 = *(sockaddr_in6*)&addr;
}
else if (addr.sa_family == AF_INET)
{
_address.addr4 = *(sockaddr_in*)&addr;
}
else
{
_address.addr = addr;
}
}
CSocketAddress::CSocketAddress(const sockaddr_in6& addr6)
{
ASSERT(addr6.sin6_family==AF_INET6);
_address.addr6 = addr6;
}
CSocketAddress::CSocketAddress(const sockaddr_in& addr4)
{
ASSERT(addr4.sin_family == AF_INET);
_address.addr4 = addr4;
}
CSocketAddress::CSocketAddress(uint32_t ip, uint16_t port)
{
sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(ip);
_address.addr4 = addr;
}
uint16_t CSocketAddress::GetPort() const
{
return ntohs(GetPort_NBO());
}
void CSocketAddress::SetPort(uint16_t port)
{
if (_address.addr.sa_family == AF_INET)
{
_address.addr4.sin_port = htons(port);
}
else
{
_address.addr6.sin6_port = htons(port);
}
}
uint16_t CSocketAddress::GetPort_NBO() const
{
if (_address.addr.sa_family == AF_INET)
{
return _address.addr4.sin_port;
}
else
{
return _address.addr6.sin6_port;
}
}
uint16_t CSocketAddress::GetIPLength() const
{
uint16_t length;
if (_address.addr.sa_family == AF_INET)
{
length = sizeof(_address.addr4.sin_addr.s_addr);
}
else
{
length = sizeof(_address.addr6.sin6_addr.s6_addr);
}
ASSERT((length == STUN_IPV4_LENGTH) || (length == STUN_IPV6_LENGTH));
return length;
}
size_t CSocketAddress::GetIPImpl(void* pAddr, size_t length, bool fNBO) const
{
HRESULT hr = S_OK;
size_t bytescopied = 0;
ChkIfA(pAddr == NULL, E_INVALIDARG);
ChkIfA(length <= 0, E_INVALIDARG);
ChkIfA(length < GetIPLength(), E_INVALIDARG);
ASSERT((_address.addr.sa_family == AF_INET)||(_address.addr.sa_family == AF_INET6));
if (_address.addr.sa_family == AF_INET)
{
uint32_t ip = _address.addr4.sin_addr.s_addr;
if (fNBO==false)
{
ip = htonl(ip);
}
memcpy(pAddr, &ip, sizeof(ip));
bytescopied = sizeof(ip);
ASSERT(sizeof(ip) == STUN_IPV4_LENGTH);
}
else
{
// ipv6 addresses are the same in either byte ordering - just an array of 16 bytes
ASSERT(sizeof(_address.addr6.sin6_addr.s6_addr) == STUN_IPV6_LENGTH);
memcpy(pAddr, &_address.addr6.sin6_addr.s6_addr, sizeof(_address.addr6.sin6_addr.s6_addr));
bytescopied = STUN_IPV6_LENGTH;
}
Cleanup:
return bytescopied;
}
size_t CSocketAddress::GetIP(void* pAddr, size_t length) const // returns the number of bytes copied, or Zero on error
{
return GetIPImpl(pAddr, length, false);
}
size_t CSocketAddress::GetIP_NBO(void* pAddr, size_t length) const // returns the number of bytes copied, or Zero on error
{
return GetIPImpl(pAddr, length, true);
}
uint16_t CSocketAddress::GetFamily() const
{
return _address.addr.sa_family;
}
void CSocketAddress::ApplyStunXorMap(const StunTransactionId& transid)
{
// XOR Mapped address is only understood by clients written for RFC 5389 compliance
// If we're attempting to map a xor address to an RFC 3489 client, it's transaction id
// won't start with the stun cookie
ASSERT(transid.id[0] == STUN_COOKIE_B1);
ASSERT(transid.id[1] == STUN_COOKIE_B2);
ASSERT(transid.id[2] == STUN_COOKIE_B3);
ASSERT(transid.id[3] == STUN_COOKIE_B4);
if (_address.addr.sa_family == AF_INET)
{
_address.addr4.sin_port = _address.addr4.sin_port ^ htons(STUN_XOR_PORT_COOKIE);
_address.addr4.sin_addr.s_addr = _address.addr4.sin_addr.s_addr ^ htonl(STUN_COOKIE);
}
else
{
_address.addr6.sin6_port = _address.addr6.sin6_port ^ htons(STUN_XOR_PORT_COOKIE);
uint8_t* ip6 = (uint8_t*)&(_address.addr6.sin6_addr);
for (int x = 0; x < STUN_IPV6_LENGTH; x++)
{
ip6[x] = ip6[x] ^ transid.id[x];
}
}
}
const sockaddr* CSocketAddress::GetSockAddr() const
{
return &_address.addr;
}
socklen_t CSocketAddress::GetSockAddrLength() const
{
if (_address.addr.sa_family == AF_INET)
{
return sizeof(_address.addr4);
}
else
{
return sizeof(_address.addr6);
}
}
bool CSocketAddress::IsSameIP(const CSocketAddress& other) const
{
bool fRet = false;
if (_address.addr.sa_family == other._address.addr.sa_family)
{
if (_address.addr.sa_family == AF_INET)
{
fRet = !memcmp(&_address.addr4.sin_addr, &other._address.addr4.sin_addr, sizeof(_address.addr4.sin_addr));
}
else if (_address.addr.sa_family == AF_INET6)
{
fRet = !memcmp(&_address.addr6.sin6_addr, &other._address.addr6.sin6_addr, sizeof(_address.addr6.sin6_addr));
}
else
{
ASSERT(false); // comparing an address that is neither IPV4 or IPV6?
fRet = !memcmp(&_address.addr.sa_data, &other._address.addr.sa_data, sizeof(_address.addr.sa_data));
}
}
return fRet;
}
bool CSocketAddress::IsSameIP_and_Port(const CSocketAddress& other) const
{
return (IsSameIP(other) && (GetPort() == other.GetPort()) );
}
bool CSocketAddress::IsIPAddressZero() const
{
const static uint8_t ZERO_ARRAY[16] = {}; // zero init
bool fRet = false;
if (_address.addr.sa_family == AF_INET)
{
fRet = !memcmp(&_address.addr4.sin_addr, ZERO_ARRAY, sizeof(_address.addr4.sin_addr));
}
else if (_address.addr.sa_family == AF_INET6)
{
fRet = !memcmp(&_address.addr6.sin6_addr, ZERO_ARRAY, sizeof(_address.addr6.sin6_addr));
}
else
{
ASSERT(false); // comparing an address that is neither IPV4 or IPV6?
fRet = !memcmp(&_address.addr.sa_data, ZERO_ARRAY, sizeof(_address.addr.sa_data));
}
return fRet;
}
void CSocketAddress::ToString(std::string* pStr) const
{
char sz[INET6_ADDRSTRLEN + 6];
ToStringBuffer(sz, ARRAYSIZE(sz));
*pStr = sz;
}
HRESULT CSocketAddress::ToStringBuffer(char* pszAddrBytes, size_t length) const
{
HRESULT hr = S_OK;
int family = GetFamily();
const void *pAddrBytes = NULL;
const char* pszResult = NULL;
const size_t portLength = 6; // colon plus 5 digit string e.g. ":55555"
char szPort[portLength+1];
ChkIfA(pszAddrBytes == NULL, E_INVALIDARG);
ChkIf(length <= 0, E_INVALIDARG);
pszAddrBytes[0] = 0;
if (family == AF_INET)
{
pAddrBytes = &(_address.addr4.sin_addr);
ChkIf(length < (INET_ADDRSTRLEN+portLength), E_INVALIDARG);
}
else if (family == AF_INET6)
{
pAddrBytes = &(_address.addr6.sin6_addr);
ChkIf(length < (INET6_ADDRSTRLEN+portLength), E_INVALIDARG);
}
else
{
ChkA(E_FAIL);
}
pszResult = ::inet_ntop(family, pAddrBytes, pszAddrBytes, length);
ChkIf(pszResult == NULL, ERRNOHR);
sprintf(szPort, ":%d", GetPort());
#if DEBUG
ChkIfA(strlen(szPort) > portLength, E_FAIL);
#endif
strcat(pszAddrBytes, szPort);
Cleanup:
return hr;
}
/*
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 STUN_SOCK_ADDR_H
#define STUN_SOCK_ADDR_H
#include "stuntypes.h"
union simple_sockaddr
{
sockaddr addr;
sockaddr_in addr4;
sockaddr_in6 addr6;
};
class CSocketAddress
{
simple_sockaddr _address;
size_t GetIPImpl(void* pAddr, size_t length, bool fNetworkByteOrder) const; // returns the number of bytes copied, or Zero on error
public:
CSocketAddress();
CSocketAddress(const sockaddr& addr);
CSocketAddress(const sockaddr_storage& addr);
void CommonConstructor(const sockaddr& addr);
CSocketAddress(const sockaddr_in& addr);
CSocketAddress(const sockaddr_in6& addr6);
CSocketAddress(uint32_t ip, uint16_t port);
uint16_t GetPort() const;
uint16_t GetPort_NBO() const; // network byte order
void SetPort(uint16_t);
uint16_t GetIPLength() const; // either returns 4 or 16 for IPV4 and IPV6 respectively
// not sure if IPv6 has a logical "network byte order" that's different from it's normal nomenclature
size_t GetIP(void* pAddr, size_t length) const; // returns the number of bytes copied, or Zero on error
size_t GetIP_NBO(void* pAddr, size_t length) const; // returns the number of bytes copied, or Zero on error
uint16_t GetFamily() const;
void ApplyStunXorMap(const StunTransactionId& id);
const sockaddr* GetSockAddr() const;
socklen_t GetSockAddrLength() const;
bool IsIPAddressZero() const;
bool IsSameIP(const CSocketAddress& other) const;
bool IsSameIP_and_Port(const CSocketAddress& other) const;
void ToString(std::string* pStr) const;
HRESULT ToStringBuffer(char* pszAddrBytes, size_t length) const;
};
#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.
*/
#ifndef SOCKETROLE_H
#define SOCKETROLE_H
enum SocketRole
{
RolePP=0, // primary address, primary port
RolePA=1, // primary address, alternate port
RoleAP=2, // alternate address, primary port
RoleAA=3 // alternate address, alternate port
};
inline bool IsValidSocketRole(SocketRole sr)
{
return ((sr >= 0) && (sr <= 3));
}
inline SocketRole SocketRoleSwapPort(SocketRole sr)
{
return (SocketRole)(((uint16_t)sr) ^ 0x01);
}
inline SocketRole SocketRoleSwapIP(SocketRole sr)
{
return (SocketRole)(((uint16_t)sr) ^ 0x02);
}
#endif /* SOCKETROLE_H */
/*
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 STUNAUTH_H_
#define STUNAUTH_H_
const uint32_t MAX_STUN_AUTH_STRING_SIZE = 64; // max string size for username, realm, password, nonce attributes
struct AuthAttributes
{
// attributes in the request
char szUser[MAX_STUN_AUTH_STRING_SIZE+1]; // the user name attribute in the request (if available)
char szRealm[MAX_STUN_AUTH_STRING_SIZE+1]; // the realm attribute in the request (if available)
char szNonce[MAX_STUN_AUTH_STRING_SIZE+1]; // the nonce attribute in the request (if available)
char szLegacyPassword[MAX_STUN_AUTH_STRING_SIZE+1]; // this is not the password used in the message integrity, this is if the request provided a password in the clear (ala rfc 3478). Not recommended, but auth providers can use it if they want.
bool fMessageIntegrityPresent; // true if there was a message integrity field
};
enum AuthCredentialMechanism
{
AuthCredShortTerm,
AuthCredLongTerm
};
enum AuthResponseType
{
Allow, // just send back a response without any additional attributes or integrity
AllowConditional, // send back a response if the integrity matches with szPassword, otherwise respond back with a 401 and a nonce/realm
StaleNonce, // send back 438/Stale Nonce and use the new realm/nonce provided
Reject, // send back a 400/Bad Request with no additional attributes
Unauthorized // send back a 401 with realm/nonce provided
};
struct AuthResponse
{
AuthResponseType responseType; // how the server should treat the response
AuthCredentialMechanism authCredMech;
char szPassword[MAX_STUN_AUTH_STRING_SIZE+1]; // ignored if _responseType is anything other than AllowConditional
char szRealm[MAX_STUN_AUTH_STRING_SIZE+1]; // realm attribute for challenge-response. Ignored if _authCredMech is not AuthCredLongTerm
char szNonce[MAX_STUN_AUTH_STRING_SIZE+1]; // nonce attribute for challenge-response. Ignored if _authCredMech is not AuthCredLongTerm
};
class IStunAuth : public IRefCounted
{
public:
virtual HRESULT DoAuthCheck(AuthAttributes* pAuthAttributes, AuthResponse* pResponse) = 0;
};
#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"
#include "stringhelper.h"
#include "stunbuilder.h"
#include <boost/crc.hpp>
#include <openssl/md5.h>
#include <openssl/hmac.h>
#include "stunauth.h"
static int g_sequence_number = 0xaaaaaaaa;
CStunMessageBuilder::CStunMessageBuilder() :
_transactionid()
{
;
}
HRESULT CStunMessageBuilder::AddHeader(StunMessageType msgType, StunMessageClass msgClass)
{
uint16_t msgTypeField=0;
HRESULT hr = S_OK;
ChkA(_stream.SetSizeHint(200));
// merge the _msgType and _msgClass, and the leading zero bits into a 16-bit field
msgTypeField = (msgType & 0x0f80) << 2;
msgTypeField |= (msgType & 0x0070) << 1;
msgTypeField |= (msgType & 0x000f);
msgTypeField |= (msgClass & 0x02) << 7;
msgTypeField |= (msgClass & 0x01) << 4;
ChkA(_stream.WriteUint16(htons(msgTypeField))); // htons for big-endian
ChkA(_stream.WriteUint16(0)); // place holder for length
Cleanup:
return hr;
}
HRESULT CStunMessageBuilder::AddBindingRequestHeader()
{
return AddHeader(StunMsgTypeBinding, StunMsgClassRequest);
}
HRESULT CStunMessageBuilder::AddBindingResponseHeader(bool fSuccess)
{
return AddHeader(StunMsgTypeBinding, fSuccess ? StunMsgClassSuccessResponse : StunMsgClassFailureResponse);
}
HRESULT CStunMessageBuilder::AddTransactionId(const StunTransactionId& transid)
{
_transactionid = transid;
return _stream.Write(transid.id, sizeof(transid.id));
}
HRESULT CStunMessageBuilder::AddRandomTransactionId(StunTransactionId* pTransId)
{
StunTransactionId transid;
uint32_t stun_cookie_nbo = htonl(STUN_COOKIE);
uint32_t entropy=0;
// on x86, the rdtsc instruction is about as good as it gets for a random sequence number
// on linux, there's /dev/urandom
#ifdef _WIN32
// on windows, there's lots of simple stuff we can get at to give us a random number
// the rdtsc instruction is about as good as it gets
uint64_t clock = __rdtsc();
entropy ^= (uint32_t)(clock);
#else
// on linux, /dev/urandom should be sufficient
{
int randomfile = ::open("/dev/urandom", O_RDONLY);
if (randomfile >= 0)
{
int readret = read(randomfile, &entropy, sizeof(entropy));
UNREFERENCED_VARIABLE(readret);
ASSERT(readret > 0);
close(randomfile);
}
}
if (entropy == 0)
{
entropy ^= getpid();
entropy ^= reinterpret_cast<uintptr_t>(this);
entropy ^= time(NULL);
entropy ^= __sync_fetch_and_add(&g_sequence_number, 1);
}
#endif
srand(entropy);
// the first four bytes of the transaction id is always the magic cookie
// followed by 12 bytes of the real transaction id
memcpy(transid.id, &stun_cookie_nbo, sizeof(stun_cookie_nbo));
for (int x = 4; x < (STUN_TRANSACTION_ID_LENGTH-4); x++)
{
transid.id[x] = (uint8_t)(rand() % 256);
}
if (pTransId)
{
*pTransId = transid;
}
return AddTransactionId(transid);
}
HRESULT CStunMessageBuilder::AddAttributeHeader(uint16_t attribType, uint16_t size)
{
HRESULT hr = S_OK;
Chk(_stream.WriteUint16(htons(attribType)));
Chk(_stream.WriteUint16(htons(size)));
Cleanup:
return hr;
}
HRESULT CStunMessageBuilder::AddAttribute(uint16_t attribType, const void* data, uint16_t size)
{
uint8_t padBytes[4] = {0};
size_t padding;
HRESULT hr = S_OK;
if (data == NULL)
{
size = 0;
}
// I suppose you can have zero length attributes as an indicator of something
Chk(AddAttributeHeader(attribType, size));
if (size > 0)
{
Chk(_stream.Write(data, size));
}
// pad with zeros to get the 4-byte alignment
if (size%4)
{
padding = 4 - size%4;
Chk(_stream.Write(padBytes, padding));
}
Cleanup:
return hr;
}
HRESULT CStunMessageBuilder::AddStringAttribute(uint16_t attribType, const char* pstr)
{
HRESULT hr = S_OK;
// I can't think of a single string attribute that could be legitimately empty
// AddNonce, AddRealm, AddUserName below depend on this check. So if this check gets removed, add it back to everywhere else
ChkIfA(StringHelper::IsNullOrEmpty(pstr), E_INVALIDARG);
// AddAttribute allows empty attribute values, so if someone needs to add empty attribute values, do it with that call
hr = AddAttribute(attribType, pstr, pstr?strlen(pstr):0);
Cleanup:
return hr;
}
HRESULT CStunMessageBuilder::AddErrorCode(uint16_t errorNumber, const char* pszReason)
{
HRESULT hr = S_OK;
size_t strsize = (pszReason==NULL) ? 0 : strlen(pszReason);
size_t size = strsize + 4;
uint8_t cl = 0;
uint8_t ernum = 0;
ChkIf(strsize >= 128, E_INVALIDARG);
ChkIf(errorNumber < 300, E_INVALIDARG);
ChkIf(errorNumber > 600, E_INVALIDARG);
Chk(AddAttributeHeader(STUN_ATTRIBUTE_ERRORCODE, size));
Chk(_stream.WriteInt16(0));
cl = (uint8_t)(errorNumber / 100);
ernum = (uint8_t)(errorNumber % 100);
Chk(_stream.WriteUint8(cl));
Chk(_stream.WriteUint8(ernum));
if (strsize > 0)
{
_stream.Write(pszReason, strsize);
if (strsize % 4)
{
const uint32_t c_zero = 0;
uint16_t paddingSize = 4 - (strsize % 4);
_stream.Write(&c_zero, paddingSize);
}
}
Cleanup:
return hr;
}
HRESULT CStunMessageBuilder::AddUnknownAttributes(const uint16_t* arr, size_t count)
{
HRESULT hr = S_OK;
ChkIfA(arr == NULL, E_INVALIDARG);
ChkIfA(count <= 0, E_INVALIDARG)
Chk(AddAttribute(STUN_ATTRIBUTE_UNKNOWNATTRIBUTES, arr, count*sizeof(arr[0])));
Cleanup:
return hr;
}
HRESULT CStunMessageBuilder::AddXorMappedAddress(const CSocketAddress& addr)
{
CSocketAddress addrxor(addr);
addrxor.ApplyStunXorMap(_transactionid);
return AddMappedAddressImpl(STUN_ATTRIBUTE_XORMAPPEDADDRESS, addrxor);
}
HRESULT CStunMessageBuilder::AddMappedAddress(const CSocketAddress& addr)
{
return AddMappedAddressImpl(STUN_ATTRIBUTE_MAPPEDADDRESS, addr);
}
HRESULT CStunMessageBuilder::AddResponseOriginAddress(const CSocketAddress& addr)
{
return AddMappedAddressImpl(STUN_ATTRIBUTE_RESPONSE_ORIGIN, addr);
}
HRESULT CStunMessageBuilder::AddOtherAddress(const CSocketAddress& addr, bool fLegacy)
{
uint16_t attribid = fLegacy ? STUN_ATTRIBUTE_CHANGEDADDRESS : STUN_ATTRIBUTE_OTHER_ADDRESS;
return AddMappedAddressImpl(attribid, addr);
}
HRESULT CStunMessageBuilder::AddResponsePort(uint16_t port)
{
// convert to network byte order
port = htons(port);
return AddAttribute(STUN_ATTRIBUTE_RESPONSE_PORT, &port, sizeof(port));
}
HRESULT CStunMessageBuilder::AddPaddingAttribute(uint16_t paddingSize)
{
HRESULT hr = S_OK;
const uint16_t PADDING_BUFFER_SIZE = 128;
static char padding_bytes[PADDING_BUFFER_SIZE] = {};
// round up so we're a multiple of 4
if (paddingSize % 4)
{
paddingSize = paddingSize + 4 - (paddingSize % 4);
}
Chk(AddAttributeHeader(STUN_ATTRIBUTE_PADDING, paddingSize));
while (paddingSize > 0)
{
uint16_t blocksize = (paddingSize >= PADDING_BUFFER_SIZE) ? PADDING_BUFFER_SIZE : paddingSize;
Chk(_stream.Write(padding_bytes, blocksize));
paddingSize -= blocksize;
}
Cleanup:
return hr;
}
HRESULT CStunMessageBuilder::AddMappedAddressImpl(uint16_t attribute, const CSocketAddress& addr)
{
uint16_t port;
size_t length;
uint8_t ip[STUN_IPV6_LENGTH];
HRESULT hr = S_OK;
uint8_t family = (addr.GetFamily()==AF_INET) ? STUN_ATTRIBUTE_FIELD_IPV4 :STUN_ATTRIBUTE_FIELD_IPV6;
size_t attributeSize = (family == STUN_ATTRIBUTE_FIELD_IPV4) ? STUN_ATTRIBUTE_MAPPEDADDRESS_SIZE_IPV4 : STUN_ATTRIBUTE_MAPPEDADDRESS_SIZE_IPV6;
Chk(AddAttributeHeader(attribute, attributeSize));
port = addr.GetPort_NBO();
length = addr.GetIP_NBO(ip, sizeof(ip));
// if we ever had a length that was not a multiple of 4, we'd need to add padding
ASSERT((length == STUN_IPV4_LENGTH) || (length == STUN_IPV6_LENGTH));
Chk(_stream.WriteUint8(0));
Chk(_stream.WriteUint8(family));
Chk(_stream.WriteUint16(port));
Chk(_stream.Write(ip, length));
Cleanup:
return hr;
}
HRESULT CStunMessageBuilder::AddChangeRequest(const StunChangeRequestAttribute& changeAttrib)
{
uint32_t changeData = 0;
if (changeAttrib.fChangeIP)
{
changeData |= 0x04;
}
if (changeAttrib.fChangePort)
{
changeData |= 0x02;
}
changeData = htonl(changeData);
return AddAttribute(STUN_ATTRIBUTE_CHANGEREQUEST, &changeData, sizeof(changeData));
}
HRESULT CStunMessageBuilder::AddFingerprintAttribute()
{
boost::crc_32_type result;
uint32_t value;
CRefCountedBuffer spBuffer;
void* pData = NULL;
size_t length = 0;
int offset;
HRESULT hr = S_OK;
Chk(_stream.WriteUint16(htons(STUN_ATTRIBUTE_FINGERPRINT)));
Chk(_stream.WriteUint16(htons(sizeof(uint32_t)))); // field length is 4 bytes
Chk(_stream.WriteUint32(0)); // dummy value for start
Chk(FixLengthField());
// now do a CRC-32 on everything but the last 8 bytes
ChkA(_stream.GetBuffer(&spBuffer));
pData = spBuffer->GetData();
length = spBuffer->GetSize();
ASSERT(length > 8);
length = length-8;
result.process_bytes(pData, length);
value = result.checksum();
value = value ^ STUN_FINGERPRINT_XOR;
offset = -(int)(sizeof(value));
Chk(_stream.SeekRelative(offset));
Chk(_stream.WriteUint32(htonl(value)));
Cleanup:
return hr;
}
HRESULT CStunMessageBuilder::AddUserName(const char* pszUserName)
{
return AddStringAttribute(STUN_ATTRIBUTE_USERNAME, pszUserName);
}
HRESULT CStunMessageBuilder::AddNonce(const char* pszNonce)
{
return AddStringAttribute(STUN_ATTRIBUTE_NONCE, pszNonce);
}
HRESULT CStunMessageBuilder::AddRealm(const char* pszRealm)
{
return AddStringAttribute(STUN_ATTRIBUTE_REALM, pszRealm);
}
HRESULT CStunMessageBuilder::AddMessageIntegrityImpl(uint8_t* key, size_t keysize)
{
HRESULT hr = S_OK;
const size_t c_hmacsize = 20;
uint8_t hmacvaluedummy[c_hmacsize] = {}; // zero-init
unsigned int resultlength = c_hmacsize;
uint8_t* pDstBuf = NULL;
CRefCountedBuffer spBuffer;
void* pData = NULL;
size_t length = 0;
unsigned char* pHashResult = NULL;
UNREFERENCED_VARIABLE(pHashResult);
ChkIfA(key==NULL || keysize <= 0, E_INVALIDARG);
// add in a "zero-init" HMAC value. This adds 24 bytes to the length
Chk(AddAttribute(STUN_ATTRIBUTE_MESSAGEINTEGRITY, hmacvaluedummy, ARRAYSIZE(hmacvaluedummy)));
Chk(FixLengthField());
// now do a SHA1 on everything but the last 24 bytes (4 bytes of the attribute header and 20 bytes for the dummy content)
ChkA(_stream.GetBuffer(&spBuffer));
pData = spBuffer->GetData();
length = spBuffer->GetSize();
ASSERT(length > 24);
length = length-24;
// now do a little so that HMAC can write exactly to where the hash bytes will appear
pDstBuf = ((uint8_t*)pData) + length + 4;
pHashResult = HMAC(EVP_sha1(), key, keysize, (uint8_t*)pData, length, pDstBuf, &resultlength);
ASSERT(resultlength == 20);
ASSERT(pHashResult != NULL);
Cleanup:
return hr;
}
HRESULT CStunMessageBuilder::AddMessageIntegrityShortTerm(const char* pszPassword)
{
return AddMessageIntegrityImpl((uint8_t*)pszPassword, strlen(pszPassword)); // if password is null/empty, AddMessageIntegrityImpl will ChkIfA on it
}
HRESULT CStunMessageBuilder::AddMessageIntegrityLongTerm(const char* pszUserName, const char* pszRealm, const char* pszPassword)
{
HRESULT hr = S_OK;
const size_t MAX_KEY_SIZE = MAX_STUN_AUTH_STRING_SIZE*3 + 2;
uint8_t key[MAX_KEY_SIZE + 1]; // long enough for 64-char strings and two semicolons and a null char for debugging
uint8_t hash[MD5_DIGEST_LENGTH] = {};
uint8_t* pResult = NULL;
uint8_t* pDst = key;
size_t lenUserName = pszUserName ? strlen(pszUserName) : 0;
size_t lenRealm = pszRealm ? strlen(pszRealm) : 0;
size_t lenPassword = pszPassword ? strlen(pszPassword) : 0;
size_t lenTotal = lenUserName + lenRealm + lenPassword + 2; // +2 for the two colons
ChkIfA(lenTotal > MAX_KEY_SIZE, E_INVALIDARG); // if we ever hit this limit, just increase MAX_STUN_AUTH_STRING_SIZE
// too bad CDatastream really only works on refcounted buffers. Otherwise, we wouldn't have to do all this messed up pointer math
// We could create a refcounted buffer in this function, but that would mean a call to "new and delete", and we're trying to avoid memory allocations in
// critical code paths because they are a proven perf hit
// TODO - Fix CDataStream and CBuffer so that "ref counted buffers" are no longer needed
pDst = key;
memcpy(pDst, pszUserName, lenUserName);
pDst += lenUserName;
*pDst = ':';
pDst++;
memcpy(pDst, pszRealm, lenRealm);
pDst += lenRealm;
*pDst = ':';
pDst++;
memcpy(pDst, pszPassword, lenPassword);
pDst += lenPassword;
*pDst ='\0'; // null terminate for debugging (this char doesn not get hashed
ASSERT((pDst-key) == lenTotal);
pResult = MD5(key, lenTotal, hash);
ASSERT(pResult != NULL);
hr= AddMessageIntegrityImpl(hash, MD5_DIGEST_LENGTH);
Cleanup:
return hr;
}
HRESULT CStunMessageBuilder::FixLengthField()
{
size_t size = _stream.GetSize();
size_t currentPos = _stream.GetPos();
HRESULT hr = S_OK;
ChkIfA(size < STUN_HEADER_SIZE, E_UNEXPECTED);
if (size < STUN_HEADER_SIZE)
{
size = 0;
}
else
{
size = size - STUN_HEADER_SIZE;
}
ChkA(_stream.SeekDirect(2)); // 2 bytes into the stream is the length
ChkA(_stream.WriteUint16(ntohs(size)));
ChkA(_stream.SeekDirect(currentPos));
Cleanup:
return hr;
}
HRESULT CStunMessageBuilder::GetResult(CRefCountedBuffer* pspBuffer)
{
HRESULT hr;
hr = FixLengthField();
if (SUCCEEDED(hr))
{
hr = _stream.GetBuffer(pspBuffer);
}
return hr;
}
CDataStream& CStunMessageBuilder::GetStream()
{
return _stream;
}
/*
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 STUN_MESSAGE_H
#define STUN_MESSAGE_H
#include "datastream.h"
#include "socketaddress.h"
#include "stuntypes.h"
class CStunMessageBuilder
{
public:
private:
CDataStream _stream;
StunTransactionId _transactionid;
HRESULT AddMappedAddressImpl(uint16_t attribute, const CSocketAddress& addr);
HRESULT AddMessageIntegrityImpl(uint8_t* key, size_t keysize);
public:
CStunMessageBuilder();
HRESULT AddHeader(StunMessageType msgType, StunMessageClass msgClass);
HRESULT AddBindingRequestHeader();
HRESULT AddBindingResponseHeader(bool fSuccess);
HRESULT AddTransactionId(const StunTransactionId& transid);
HRESULT AddRandomTransactionId(StunTransactionId* pTransId);
HRESULT AddAttributeHeader(uint16_t attribType, uint16_t size);
HRESULT AddAttribute(uint16_t attribType, const void* data, uint16_t size);
HRESULT AddStringAttribute(uint16_t attribType, const char* pstr);
HRESULT AddXorMappedAddress(const CSocketAddress& addr);
HRESULT AddMappedAddress(const CSocketAddress& addr);
HRESULT AddResponseOriginAddress(const CSocketAddress& other);
HRESULT AddOtherAddress(const CSocketAddress& other, bool fLegacy);
HRESULT AddResponsePort(uint16_t port);
HRESULT AddPaddingAttribute(uint16_t paddingSize);
HRESULT AddChangeRequest(const StunChangeRequestAttribute& changeAttrib);
HRESULT AddErrorCode(uint16_t errorNumber, const char* pszReason);
HRESULT AddUnknownAttributes(const uint16_t* arrAttributeIds, size_t count);
HRESULT AddFingerprintAttribute();
HRESULT AddUserName(const char* pszUserName);
HRESULT AddRealm(const char* pszRealm);
HRESULT AddNonce(const char* pszNonce);
HRESULT AddMessageIntegrityShortTerm(const char* pszPassword);
HRESULT AddMessageIntegrityLongTerm(const char* pszUserName, const char* pszRealm, const char* pszPassword);
HRESULT FixLengthField();
HRESULT GetResult(CRefCountedBuffer* pspBuffer);
CDataStream& GetStream();
};
#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"
#include "stuncore.h"
#include "stunclientlogic.h"
#include "stunclienttests.h"
#include "stunclientlogic.h"
StunClientResults::StunClientResults()
{
Init();
}
void StunClientResults::Init()
{
CSocketAddress addrZero;
fBindingTestSuccess = false;
fIsDirect = false;
fHasOtherAddress = false; // set to true if the basic binding request got an "other adddress" back from the server
fBehaviorTestSuccess = false;
behavior = UnknownBehavior;
fFilteringTestSuccess = false;
filtering = UnknownFiltering;
fGotTest2Response = false;
fGotTest3Response = false;
addrLocal = addrZero;
addrMapped = addrZero;
addrPA = addrZero;
addrAP = addrZero;
addrAA = addrZero;
addrMappingAP = addrZero;
addrMappingAA = addrZero;
errorBitmask = 0;
}
CStunClientLogic::CStunClientLogic() :
_fInitialized(false),
_timeLastMessageSent(0),
_sendCount(0),
_nTestIndex(0)
{
}
HRESULT CStunClientLogic::Initialize(StunClientLogicConfig& config)
{
HRESULT hr = S_OK;
// Don't ever try to "re-use" a CStunClientLogic instance after it's already been used. Create a new instance after
// putting a test cycle into motion.
// Too much code in the tests expects everything in a clean state
ChkIfA(_fInitialized, E_UNEXPECTED);
ChkIfA(config.addrServer.IsIPAddressZero(), E_INVALIDARG);
ChkIfA(config.addrServer.GetPort() == 0, E_INVALIDARG);
_config = config;
_fInitialized = true;
if (config.timeoutSeconds == 0)
{
config.timeoutSeconds = 5;
}
if (config.uMaxAttempts <= 0)
{
config.uMaxAttempts = 2;
}
_nTestIndex = 0;
_testlist.clear();
// always add the binding test to start
_test1.Init(&_config, &_results);
_testlist.push_back(&_test1);
if (_config.fBehaviorTest)
{
_testBehavior2.Init(&_config, &_results);
_testlist.push_back(&_testBehavior2);
_testBehavior3.Init(&_config, &_results);
_testBehavior3.RunAsTest3(true);
_testlist.push_back(&_testBehavior3);
}
if (_config.fFilteringTest)
{
_testFiltering2.Init(&_config, &_results);
_testlist.push_back(&_testFiltering2);
_testFiltering3.Init(&_config, &_results);
_testFiltering3.RunAsTest3(true);
_testlist.push_back(&_testFiltering3);
}
_fPreCheckRunOnTest = false;
_timeLastMessageSent = 0;
Cleanup:
return hr;
}
HRESULT CStunClientLogic::GetNextMessage(CRefCountedBuffer& spMsg, CSocketAddress* pAddrDest, uint32_t timeCurrentMilliseconds)
{
HRESULT hr = S_OK;
uint32_t diff = 0;
IStunClientTest* pCurrentTest = NULL;
bool fReadyToReturn = false;
ChkIfA(_fInitialized == false, E_FAIL);
ChkIfA(spMsg->GetAllocatedSize() == 0, E_INVALIDARG);
ChkIfA(pAddrDest == NULL, E_INVALIDARG);
// clients should pass in at least 1000 bytes
ChkIfA(spMsg->GetAllocatedSize() < 1000, E_INVALIDARG);
while (fReadyToReturn==false)
{
if (_nTestIndex >= _testlist.size())
{
hr = E_STUNCLIENT_RESULTS_READY; // no more tests to run
break;
}
pCurrentTest = _testlist[_nTestIndex];
if (_fPreCheckRunOnTest==false)
{
// give the test an early chance to complete before sending a message (based on results of previous test)
pCurrentTest->PreRunCheck();
_fPreCheckRunOnTest = true;
}
// has this test completed or is it in a state in which it can't run?
if (pCurrentTest->IsCompleted() || pCurrentTest->IsReadyToRun()==false)
{
// increment to the next test
_nTestIndex++;
_sendCount = 0;
_fPreCheckRunOnTest = false;
continue;
}
// Have we waited long enough for a response
diff = (timeCurrentMilliseconds - _timeLastMessageSent) / 1000; // convert from milliseconds to seconds
if ((diff < _config.timeoutSeconds) && (_sendCount != 0))
{
hr = E_STUNCLIENT_STILL_WAITING;
break;
}
// have we exceed the retry count
if (_sendCount >= _config.uMaxAttempts)
{
// notify the test that it has timed out
// this should put it in the completed state (and we increment to next test on next loop)
pCurrentTest->NotifyTimeout();
ASSERT(pCurrentTest->IsCompleted());
continue;
}
// ok - we are ready to go fetch a message
hr = pCurrentTest->GetMessage(spMsg, pAddrDest);
ASSERT(SUCCEEDED(hr));
if (FAILED(hr))
{
break;
}
// success
_sendCount++;
_timeLastMessageSent = timeCurrentMilliseconds;
fReadyToReturn = true;
hr = S_OK;
}
Cleanup:
return hr;
}
HRESULT CStunClientLogic::ProcessResponse(CRefCountedBuffer& spMsg, CSocketAddress& addrRemote, CSocketAddress& addrLocal)
{
HRESULT hr = S_OK;
IStunClientTest* pCurrentTest = NULL;
ChkIfA(_fInitialized == false, E_FAIL);
ChkIfA(spMsg->GetSize() == 0, E_INVALIDARG);
ChkIf (_nTestIndex >= _testlist.size(), E_UNEXPECTED);
pCurrentTest = _testlist[_nTestIndex];
// passing a response to a test that is already completed ??
ChkIfA(pCurrentTest->IsCompleted(), E_UNEXPECTED);
hr = pCurrentTest->ProcessResponse(spMsg, addrRemote, addrLocal);
// this likely puts the test into the completed state
// A subsequent call to GetNextMessage will invoke the next tset
Cleanup:
return hr;
}
HRESULT CStunClientLogic::GetResults(StunClientResults* pResults)
{
HRESULT hr=S_OK;
ChkIfA(pResults == NULL, E_INVALIDARG);
*pResults = _results;
Cleanup:
return hr;
}
/*
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 STUNCLIENTLOGIC_H
#define STUNCLIENTLOGIC_H
#include "stunclienttests.h"
struct StunClientLogicConfig
{
CSocketAddress addrServer;
uint32_t timeoutSeconds;
uint32_t uMaxAttempts;
bool fBehaviorTest;
bool fFilteringTest;
};
enum NatBehavior
{
UnknownBehavior,
DirectMapping, // IP address and port are the same between client and server view (NO NAT)
EndpointIndependentMapping, // same mapping regardless of IP:port original packet sent to (the kind of NAT we like)
AddressDependentMapping, // mapping changes for local socket based on remote IP address only, but remote port can change (partially symmetric, not great)
AddressAndPortDependentMapping // different port mapping if the ip address or port change (symmetric NAT, difficult to predict port mappings)
};
enum NatFiltering
{
UnknownFiltering,
DirectConnectionFiltering,
EndpointIndependentFiltering, // shouldn't be common unless connection is already direct (can receive on mapped address from anywhere regardless of where the original send went)
AddressDependentFiltering, // IP-restricted NAT
AddressAndPortDependentFiltering // port-restricted NAT
};
struct StunClientResults
{
// basic binding test will set these results
bool fBindingTestSuccess;
bool fIsDirect; // true if addrLocal == addrMapped
CSocketAddress addrLocal; // local address
CSocketAddress addrMapped; // mapped address from PP for local address
bool fHasOtherAddress; // set to true if the basic binding request got an "other adddress" back from the server
CSocketAddress addrPA; // the other address (with primary IP) as identified by the basic binding request
CSocketAddress addrAP; // the other address (with primary port) as identified by the basic binding request
CSocketAddress addrAA; // the other address as identified by the basic binding request
// -----------------------------------------
// behavior state test --------------------------
bool fBehaviorTestSuccess;
NatBehavior behavior;
CSocketAddress addrMappingAP; // result of binding request for AP (behavior test 2)
CSocketAddress addrMappingAA; // result of binding request for AA (behavior test 3)
// -----------------------------------------
// filtering state test --------------------------
bool fFilteringTestSuccess;
NatFiltering filtering;
bool fGotTest2Response;
bool fGotTest3Response;
// -----------------------------------------
uint32_t errorBitmask;
static const uint32_t SCR_TIMEOUT = 0x0001; // a timeout occurred waiting for an expected response
static const uint32_t SCR_NO_OTHER = 0x0002; // the server doesn't offer an alternate address/port
StunClientResults();
void Init();
};
#define FACILITY_STUN_CLIENT_LOGIC_ERR 0x101
#define E_STUNCLIENT_STILL_WAITING ((HRESULT)0x81010001)
#define E_STUNCLIENT_RESULTS_READY ((HRESULT)0x81010002)
#define E_STUNCLIENT_TIMEOUT ((HRESULT)0x81010003)
#define E_STUNCLIENT_BUFFER_TOO_SMALL ((HRESULT)0x81010004)
class CStunClientLogic
{
private:
StunClientLogicConfig _config;
StunClientResults _results;
bool _fInitialized;
uint32_t _timeLastMessageSent;
uint32_t _sendCount;
bool _fPreCheckRunOnTest;
CBasicBindingTest _test1;
CBehaviorTest _testBehavior2;
CBehaviorTest _testBehavior3;
CFilteringTest _testFiltering2;
CFilteringTest _testFiltering3;
std::vector<IStunClientTest*> _testlist;
size_t _nTestIndex;
public:
CStunClientLogic();
HRESULT Initialize(StunClientLogicConfig& config);
HRESULT GetNextMessage(CRefCountedBuffer& spMsg, CSocketAddress* pAddrDest, uint32_t timeCurrentMilliseconds);
HRESULT ProcessResponse(CRefCountedBuffer& spMsg, CSocketAddress& addrRemote, CSocketAddress& addrLocal);
HRESULT GetResults(StunClientResults* pResults);
};
#endif /* STUNCLIENTLOGIC_H */
/*
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"
#include "stuncore.h"
#include "stunclientlogic.h"
#include "stunclienttests.h"
CStunClientTestBase::CStunClientTestBase() :
_fInit(false),
_pConfig(NULL),
_pResults(NULL),
_fCompleted(false),
_transid() // zero-init
{
;
}
HRESULT CStunClientTestBase::Init(StunClientLogicConfig* pConfig, StunClientResults* pResults)
{
HRESULT hr = S_OK;
ChkIfA(pConfig == NULL, E_INVALIDARG);
ChkIfA(pResults == NULL, E_INVALIDARG);
_fInit = true;
_pConfig = pConfig;
_pResults = pResults;
_fCompleted = false;
memset(&_transid, 0, sizeof(_transid));
Cleanup:
return hr;
}
HRESULT CStunClientTestBase::StartBindingRequest(CStunMessageBuilder& builder)
{
builder.AddBindingRequestHeader();
if (IsTransactionIdValid(_transid))
{
builder.AddTransactionId(_transid);
}
else
{
builder.AddRandomTransactionId(&_transid);
}
return S_OK;
}
HRESULT CStunClientTestBase::BasicReaderValidation(CRefCountedBuffer& spMsg, CStunMessageReader& reader)
{
HRESULT hr = S_OK;
CStunMessageReader::ReaderParseState readerstate;
StunTransactionId transid;
int cmp = 0;
readerstate = reader.AddBytes(spMsg->GetData(), spMsg->GetSize());
ChkIf(readerstate != CStunMessageReader::BodyValidated, E_FAIL);
reader.GetTransactionId(&transid);
cmp = memcmp(transid.id, _transid.id, sizeof(_transid));
ChkIf(cmp!=0, E_FAIL);
Cleanup:
return hr;
}
bool CStunClientTestBase::IsCompleted()
{
return _fCompleted;
}
void CStunClientTestBase::PreRunCheck()
{
return;
}
// ----------------------------------------------------------------------------------
bool CBasicBindingTest::IsReadyToRun()
{
// the binding test can always be run if it hasn't already been run
return (_fCompleted == false);
}
HRESULT CBasicBindingTest::GetMessage(CRefCountedBuffer& spMsg, CSocketAddress* pAddrDest)
{
HRESULT hr = S_OK;
ASSERT(spMsg->GetAllocatedSize() > 0);
ASSERT(pAddrDest);
ASSERT(_fInit);
CStunMessageBuilder builder;
builder.GetStream().Attach(spMsg, true);
Chk(StartBindingRequest(builder));
builder.FixLengthField();
*pAddrDest = _pConfig->addrServer;
Cleanup:
return hr;
}
HRESULT CBasicBindingTest::ProcessResponse(CRefCountedBuffer& spMsg, CSocketAddress& addrRemote, CSocketAddress& addrLocal)
{
HRESULT hr = S_OK;
CStunMessageReader reader;
CSocketAddress addrMapped;
CSocketAddress addrOther;
bool fHasOtherAddress = false;
// todo - figure out a way to make buffering TCP fragments work
Chk(BasicReaderValidation(spMsg, reader));
hr = reader.GetXorMappedAddress(&addrMapped);
if (FAILED(hr))
{
hr = reader.GetMappedAddress(&addrMapped);
}
Chk(hr); // again drop the message if we can't parse the binding response
fHasOtherAddress = SUCCEEDED(reader.GetOtherAddress(&addrOther));
// ok, we got a response. So we are done
_fCompleted = true;
_pResults->fBindingTestSuccess = true;
_pResults->fIsDirect = addrLocal.IsSameIP_and_Port(addrMapped);
_pResults->addrLocal = addrLocal;
_pResults->addrMapped = addrMapped;
_pResults->fHasOtherAddress = fHasOtherAddress;
if (fHasOtherAddress)
{
_pResults->addrAA = addrOther;
_pResults->addrPA = _pConfig->addrServer;
_pResults->addrPA.SetPort(addrOther.GetPort());
_pResults->addrAP = addrOther;
_pResults->addrAP.SetPort(_pConfig->addrServer.GetPort());
if (Logging::GetLogLevel() >= LL_DEBUG)
{
char sz[100];
addrOther.ToStringBuffer(sz, 100);
Logging::LogMsg(LL_DEBUG, "Other address is %s\n",sz);
}
}
Cleanup:
return hr;
}
void CBasicBindingTest::NotifyTimeout()
{
// we timed out - mark the request as failed and get out of here
_fCompleted = true;
_pResults->fBindingTestSuccess = false; // should already be false
}
// ----------------------------------------------------------------------------------
CBehaviorTest::CBehaviorTest()
{
this->_fIsTest3 = false;
}
void CBehaviorTest::PreRunCheck()
{
if (_fIsTest3 == false)
{
// we don't need to run BehaviorTest2 or BehaviorTest3 if we know we are direct
if (_pResults->fBindingTestSuccess && _pResults->fIsDirect)
{
_fCompleted = true;
_pResults->behavior = ::DirectMapping;
_pResults->fBehaviorTestSuccess = true;
}
}
}
bool CBehaviorTest::IsReadyToRun()
{
// we can run if the CBasicBindingTest succeeded and we have an "other" address
bool fRet = ((_fCompleted==false) && _pResults->fBindingTestSuccess && _pResults->fHasOtherAddress && (_pResults->fBehaviorTestSuccess==false));
if (_fIsTest3)
{
// check to see that test2 succeeded before allowing test3 to run
fRet = (fRet && (_pResults->addrMappingAP.IsIPAddressZero() == false));
}
return fRet;
}
HRESULT CBehaviorTest::GetMessage(CRefCountedBuffer& spMsg, CSocketAddress* pAddrDest)
{
HRESULT hr = S_OK;
ASSERT(spMsg->GetAllocatedSize() > 0);
ASSERT(pAddrDest);
CStunMessageBuilder builder;
builder.GetStream().Attach(spMsg, true);
StartBindingRequest(builder);
builder.FixLengthField();
if (_fIsTest3 == false)
{
*pAddrDest = _pResults->addrAP;
}
else
{
*pAddrDest = _pResults->addrAA;
}
return hr;
}
HRESULT CBehaviorTest::ProcessResponse(CRefCountedBuffer& spMsg, CSocketAddress& addrRemote, CSocketAddress& addrLocal)
{
HRESULT hr = S_OK;
CStunMessageReader reader;
CSocketAddress addrMapped;
Chk(BasicReaderValidation(spMsg, reader));
hr = reader.GetXorMappedAddress(&addrMapped);
if (FAILED(hr))
{
hr = reader.GetMappedAddress(&addrMapped);
}
Chk(hr); // again drop the message if we can't parse the binding response
_fCompleted = true;
if (_fIsTest3)
{
_pResults->addrMappingAA = addrMapped;
_pResults->fBehaviorTestSuccess = true;
if (addrMapped.IsSameIP_and_Port(_pResults->addrMappingAP))
{
_pResults->behavior = ::AddressDependentMapping;
}
else
{
_pResults->behavior = ::AddressAndPortDependentMapping;
}
}
else
{
_pResults->addrMappingAP = addrMapped;
if (addrMapped.IsSameIP_and_Port(_pResults->addrMapped))
{
_pResults->fBehaviorTestSuccess = true;
_pResults->behavior = ::EndpointIndependentMapping;
}
}
Cleanup:
return hr;
}
void CBehaviorTest::NotifyTimeout()
{
// the behavior test fails if it never got a response
_fCompleted = true;
_pResults->fBehaviorTestSuccess = false;
}
void CBehaviorTest::RunAsTest3(bool fSetAsTest3)
{
_fIsTest3 = fSetAsTest3;
}
// ----------------------------------------------------------------------------------
CFilteringTest::CFilteringTest()
{
_fIsTest3 = false;
}
void CFilteringTest::PreRunCheck()
{
// if the binding test detected "direct", there's nothing for us to do except declare this as "endpoint indedepent"
if (_fIsTest3 == false)
{
if (_pResults->fBindingTestSuccess && _pResults->fIsDirect)
{
_fCompleted = true;
_pResults->filtering = ::EndpointIndependentFiltering;
_pResults->fFilteringTestSuccess = true;
}
}
}
bool CFilteringTest::IsReadyToRun()
{
// we can run if the CBasicBindingTest succeeded and we have an "other" address
bool fRet = ((_fCompleted==false) && _pResults->fBindingTestSuccess && _pResults->fHasOtherAddress && (_pResults->fFilteringTestSuccess==false) && (_pResults->fGotTest2Response==false));
return fRet;
}
HRESULT CFilteringTest::GetMessage(CRefCountedBuffer& spMsg, CSocketAddress* pAddrDest)
{
CStunMessageBuilder builder;
StunChangeRequestAttribute change;
builder.GetStream().Attach(spMsg, true);
StartBindingRequest(builder);
*pAddrDest = _pConfig->addrServer;
if (_fIsTest3 == false)
{
change.fChangeIP = true;
change.fChangePort = true;
builder.AddChangeRequest(change);
}
else
{
change.fChangeIP = false;
change.fChangePort = true;
builder.AddChangeRequest(change);
}
builder.FixLengthField();
return S_OK;
}
HRESULT CFilteringTest::ProcessResponse(CRefCountedBuffer& spMsg, CSocketAddress& addrRemote, CSocketAddress& addrLocal)
{
HRESULT hr = S_OK;
CStunMessageReader reader;
Chk(BasicReaderValidation(spMsg, reader));
// BasicReaderValidation will check the transaction ID!
// we don't really care what's in the response other than if the transactionID is correct
_fCompleted = true;
if (_fIsTest3)
{
_pResults->fGotTest3Response = true;
_pResults->fFilteringTestSuccess = true;
_pResults->filtering = ::AddressDependentFiltering;
}
else
{
// if we got a response back from the other IP and Port, then we have independent filtering
_pResults->fGotTest2Response = true;
_pResults->fFilteringTestSuccess = true;
_pResults->filtering = ::EndpointIndependentFiltering;
}
Cleanup:
return hr;
}
void CFilteringTest::NotifyTimeout()
{
// in the filtering test, it's expected to not get a response for test2 or test3
_fCompleted = true;
// if we didn't get a response in test3, that implies we never got a response in test2 (because we don't run test3 if test2 got a response)
if (_fIsTest3)
{
_pResults->fFilteringTestSuccess = true;
_pResults->filtering = ::AddressAndPortDependentFiltering;
}
}
void CFilteringTest::RunAsTest3(bool fSetAsTest3)
{
_fIsTest3 = fSetAsTest3;
}
/*
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 STUNCLIENTTESTS_H
#define STUNCLIENTTESTS_H
#include "stuntypes.h"
#include "buffer.h"
#include "stunbuilder.h"
struct StunClientLogicConfig;
struct StunClientResults;
class IStunClientTest
{
public:
virtual HRESULT Init(StunClientLogicConfig* pConfig, StunClientResults* pResults) = 0;
virtual void PreRunCheck() = 0; // gives a test a chance to complete right away based on the results of a previous test
virtual bool IsReadyToRun() = 0;
virtual HRESULT GetMessage(CRefCountedBuffer& spMsg, CSocketAddress* pAddrDest) = 0;
virtual HRESULT ProcessResponse(CRefCountedBuffer& spMsg, CSocketAddress& addrRemote, CSocketAddress& addrLocal) = 0;
virtual void NotifyTimeout() = 0;
virtual bool IsCompleted() = 0;
};
class CStunClientTestBase : public IStunClientTest
{
protected:
bool _fInit;
StunClientLogicConfig* _pConfig;
StunClientResults* _pResults;
bool _fCompleted;
StunTransactionId _transid;
HRESULT StartBindingRequest(CStunMessageBuilder& builder);
HRESULT BasicReaderValidation(CRefCountedBuffer& spMsg, CStunMessageReader& reader);
public:
CStunClientTestBase();
virtual HRESULT Init(StunClientLogicConfig* pConfig, StunClientResults* pResults);
virtual void PreRunCheck();
virtual bool IsCompleted();
};
class CBasicBindingTest : public CStunClientTestBase
{
public:
bool IsReadyToRun();
HRESULT GetMessage(CRefCountedBuffer& spMsg, CSocketAddress* pAddrDest) ;
HRESULT ProcessResponse(CRefCountedBuffer& spMsg, CSocketAddress& addrRemote, CSocketAddress& addrLocal) ;
void NotifyTimeout();
};
class CBehaviorTest : public CStunClientTestBase
{
protected:
bool _fIsTest3;
public:
CBehaviorTest();
void PreRunCheck();
bool IsReadyToRun();
HRESULT GetMessage(CRefCountedBuffer& spMsg, CSocketAddress* pAddrDest) ;
HRESULT ProcessResponse(CRefCountedBuffer& spMsg, CSocketAddress& addrRemote, CSocketAddress& addrLocal) ;
void NotifyTimeout();
void RunAsTest3(bool fSetAsTest3);
};
class CFilteringTest : public CStunClientTestBase
{
protected:
bool _fIsTest3;
public:
CFilteringTest();
void PreRunCheck();
bool IsReadyToRun();
HRESULT GetMessage(CRefCountedBuffer& spMsg, CSocketAddress* pAddrDest) ;
HRESULT ProcessResponse(CRefCountedBuffer& spMsg, CSocketAddress& addrRemote, CSocketAddress& addrLocal) ;
void NotifyTimeout();
void RunAsTest3(bool fSetAsTest3);
};
#endif /* STUNCLIENTTESTS_H */
/*
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 STUNCORE_H_
#define STUNCORE_H_
#include "buffer.h"
#include "datastream.h"
#include "socketaddress.h"
#include "stunbuilder.h"
#include "stunreader.h"
#include "stuntypes.h"
#include "stunutils.h"
#include "messagehandler.h"
#include "stunresponder.h"
#include "stunauth.h"
#include "stunclienttests.h"
#include "stunclientlogic.h"
#endif /* STUNCORE_H_ */
/*
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"
#include "stunreader.h"
#include "stunutils.h"
#include "socketaddress.h"
#include <boost/crc.hpp>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/md5.h>
#include "stunauth.h"
CStunMessageReader::CStunMessageReader() :
_fAllowLegacyFormat(false),
_fMessageIsLegacyFormat(false),
_state(HeaderNotRead),
_nAttributeCount(0),
_transactionid(),
_msgTypeNormalized(0xffff),
_msgClass(StunMsgClassInvalidMessageClass),
_msgLength(0),
_indexFingerprint(-1),
_indexResponsePort(-1),
_indexChangeRequest(-1),
_indexPaddingAttribute(-1),
_indexErrorCode(-1),
_indexMessageIntegrity(-1)
{
;
}
void CStunMessageReader::SetAllowLegacyFormat(bool fAllowLegacyFormat)
{
_fAllowLegacyFormat = fAllowLegacyFormat;
}
bool CStunMessageReader::IsMessageLegacyFormat()
{
return _fMessageIsLegacyFormat;
}
uint16_t CStunMessageReader::HowManyBytesNeeded()
{
size_t currentSize = _stream.GetSize();
switch (_state)
{
case HeaderNotRead:
BOOST_ASSERT(STUN_HEADER_SIZE > currentSize);
return STUN_HEADER_SIZE - currentSize;
case HeaderValidated:
BOOST_ASSERT(_msgLength > currentSize);
return _msgLength - currentSize;
default:
return 0;
}
return 0;
}
bool CStunMessageReader::HasFingerprintAttribute()
{
return (_indexFingerprint >= 0);
}
bool CStunMessageReader::IsFingerprintAttributeValid()
{
HRESULT hr = S_OK;
StunAttribute attrib={};
CRefCountedBuffer spBuffer;
size_t size=0;
boost::crc_32_type crc;
uint32_t computedValue=1;
uint32_t readValue=0;
uint8_t* ptr = NULL;
// the fingerprint attribute MUST be the last attribute in the stream.
// If it's not, then the code below will return false
GetAttributeByIndex(_indexFingerprint, &attrib);
ChkIfA(attrib.attributeType != STUN_ATTRIBUTE_FINGERPRINT, E_FAIL);
ChkIf(attrib.size != 4, E_FAIL);
ChkIf(_state != BodyValidated, E_FAIL);
Chk(_stream.GetBuffer(&spBuffer));
size = _stream.GetSize();
ChkIf(size < STUN_HEADER_SIZE, E_FAIL);
ptr = spBuffer->GetData();
ChkIfA(ptr==NULL, E_FAIL);
crc.process_bytes(ptr, size-8); // -8 because we're assuming the fingerprint attribute is 8 bytes and is the last attribute in the stream
computedValue = crc.checksum();
computedValue = computedValue ^ STUN_FINGERPRINT_XOR;
readValue = *(uint32_t*)(ptr+attrib.offset);
readValue = ntohl(readValue);
hr = (readValue==computedValue) ? S_OK : E_FAIL;
Cleanup:
return (SUCCEEDED(hr));
}
bool CStunMessageReader::HasMessageIntegrityAttribute()
{
return (_indexMessageIntegrity >= 0);
}
HRESULT CStunMessageReader::ValidateMessageIntegrity(uint8_t* key, size_t keylength)
{
HRESULT hr = S_OK;
int lastAttributeIndex = _nAttributeCount - 1;
bool fFingerprintAdjustment = false;
bool fNoOtherAttributesAfterIntegrity = false;
const size_t c_hmacsize = 20;
uint8_t hmaccomputed[c_hmacsize] = {}; // zero-init
unsigned int hmaclength = c_hmacsize;
HMAC_CTX ctx = {};
uint32_t chunk32;
uint16_t chunk16;
size_t len, nChunks;
CDataStream stream;
CRefCountedBuffer spBuffer;
StunAttribute attribIntegrity;
int cmp = 0;
ChkIf(_state != BodyValidated, E_FAIL);
ChkIf(_indexMessageIntegrity < 0, E_FAIL);
// can a key be empty?
ChkIfA(key==NULL, E_INVALIDARG);
ChkIfA(keylength==0, E_INVALIDARG);
Chk(this->GetAttributeByIndex(_indexMessageIntegrity, &attribIntegrity));
ChkIf(attribIntegrity.size != c_hmacsize, E_FAIL);
ChkIfA(lastAttributeIndex < 0, E_FAIL);
// first, check to make sure that no other attributes (other than fingerprint) follow the message integrity
fNoOtherAttributesAfterIntegrity = (_indexMessageIntegrity == lastAttributeIndex) || ((_indexMessageIntegrity == (lastAttributeIndex-1)) && (_indexFingerprint == lastAttributeIndex));
ChkIf(fNoOtherAttributesAfterIntegrity==false, E_FAIL);
fFingerprintAdjustment = (_indexMessageIntegrity == (lastAttributeIndex-1));
Chk(GetBuffer(&spBuffer));
stream.Attach(spBuffer, false);
// Here comes the fun part. If there is a fingerprint attribute, we have to adjust the length header in computing the hash
HMAC_CTX_init(&ctx);
HMAC_Init(&ctx, key, keylength, EVP_sha1());
// message type
Chk(stream.ReadUint16(&chunk16));
HMAC_Update(&ctx, (unsigned char*)&chunk16, sizeof(chunk16));
// message length
Chk(stream.ReadUint16(&chunk16));
if (fFingerprintAdjustment)
{
// subtract the length of the fingerprint off the length header
// fingerprint attribute is 8 bytes long including it's own header
// and to do this, we have to fix the network byte ordering issue
uint16_t lengthHeader = ntohs(chunk16);
lengthHeader -= 8;
chunk16 = htons(lengthHeader);
}
HMAC_Update(&ctx, (unsigned char*)&chunk16, sizeof(chunk16));
// now include everything up to the hash attribute itself.
len = _attributes[_indexMessageIntegrity].offset;
len -= 4; // subtract the size of the attribute header
len -= 4; // subtrack the size of the message header (not including the transaction id)
// len should be the number of bytes from the start of the transaction ID up through to the start of the integrity attribute header
// the stun message has to be a multiple of 4 bytes, so we can read in 32 bit chunks
nChunks = len / 4;
ASSERT((len % 4) == 0);
for (size_t count = 0; count < nChunks; count++)
{
Chk(stream.ReadUint32(&chunk32));
HMAC_Update(&ctx, (unsigned char*)&chunk32, sizeof(chunk32));
}
HMAC_Final(&ctx, hmaccomputed, &hmaclength);
// now compare the bytes
cmp = memcmp(hmaccomputed, spBuffer->GetData() + attribIntegrity.offset, c_hmacsize);
hr = (cmp == 0 ? S_OK : E_FAIL);
Cleanup:
return hr;
}
HRESULT CStunMessageReader::ValidateMessageIntegrityShort(const char* pszPassword)
{
return ValidateMessageIntegrity((uint8_t*)pszPassword, strlen(pszPassword));
}
HRESULT CStunMessageReader::ValidateMessageIntegrityLong(const char* pszUser, const char* pszRealm, const char* pszPassword)
{
HRESULT hr = S_OK;
const size_t MAX_KEY_SIZE = MAX_STUN_AUTH_STRING_SIZE*3 + 2;
uint8_t key[MAX_KEY_SIZE + 1]; // long enough for 64-char strings and two semicolons and a null char
uint8_t* pData = NULL;
uint8_t* pDst = key;
size_t totallength = 0;
size_t passwordlength = pszPassword ? strlen(pszPassword) : 0;
size_t userLength = pszUser ? strlen(pszUser) : 0;
size_t realmLength = pszRealm ? strlen(pszRealm) : 0;
uint8_t hash[MD5_DIGEST_LENGTH] = {};
ChkIf(_state != BodyValidated, E_FAIL);
totallength = userLength + realmLength + passwordlength + 2; // +2 for two semi-colons
pData = GetStream().GetDataPointerUnsafe();
ChkIfA(pData==NULL, E_FAIL);
if (userLength > 0)
{
memcpy(pDst, pszUser, userLength);
pDst += userLength;
}
*pDst = ':';
pDst++;
if (realmLength > 0)
{
memcpy(pDst, pszRealm, realmLength);
pDst += realmLength;
}
*pDst = ':';
pDst++;
if (passwordlength > 0)
{
memcpy(pDst, pszPassword, passwordlength);
pDst += passwordlength;
}
*pDst = '0'; // null terminate for debugging (does not get hashed)
ASSERT((pDst-key) == totallength);
ChkIfA(NULL == MD5(key, totallength, hash), E_FAIL);
Chk(ValidateMessageIntegrity(hash, ARRAYSIZE(hash)));
Cleanup:
return hr;
}
HRESULT CStunMessageReader::GetAttributeByType(uint16_t attributeType, StunAttribute* pAttribute)
{
HRESULT hr = E_FAIL;
for (size_t index = 0; index < _nAttributeCount; index++)
{
if (attributeType == _attributes[index].attributeType)
{
hr = S_OK;
// pAttribute can be NULL - useful if the caller just wants to detect the presence of an attribute
if (pAttribute)
{
*pAttribute = _attributes[index];
}
}
}
return hr;
}
HRESULT CStunMessageReader::GetAttributeByIndex(int index, StunAttribute* pAttribute)
{
HRESULT hr = E_FAIL;
if ((index >= 0) && (index < (int)_nAttributeCount))
{
hr = S_OK;
if (pAttribute)
{
*pAttribute = _attributes[index];
}
}
return hr;
}
HRESULT CStunMessageReader::GetResponsePort(uint16_t* pPort)
{
StunAttribute attrib;
HRESULT hr = S_OK;
uint16_t portNBO;
uint8_t *pData = NULL;
ChkIfA(pPort == NULL, E_INVALIDARG);
Chk(GetAttributeByIndex(_indexResponsePort, &attrib));
ChkIf(attrib.size != STUN_ATTRIBUTE_RESPONSE_PORT_SIZE, E_UNEXPECTED);
pData = _stream.GetDataPointerUnsafe();
ChkIf(pData==NULL, E_UNEXPECTED);
memcpy(&portNBO, pData + attrib.offset, STUN_ATTRIBUTE_RESPONSE_PORT_SIZE);
*pPort = ntohs(portNBO);
Cleanup:
return hr;
}
HRESULT CStunMessageReader::GetChangeRequest(StunChangeRequestAttribute* pChangeRequest)
{
HRESULT hr = S_OK;
uint8_t *pData = NULL;
StunAttribute attrib;
uint32_t value = 0;
ChkIfA(pChangeRequest == NULL, E_INVALIDARG);
Chk(GetAttributeByIndex(_indexChangeRequest, &attrib));
ChkIf(attrib.size != STUN_ATTRIBUTE_CHANGEREQUEST_SIZE, E_UNEXPECTED);
pData = _stream.GetDataPointerUnsafe();
ChkIf(pData==NULL, E_UNEXPECTED);
memcpy(&value, pData + attrib.offset, STUN_ATTRIBUTE_CHANGEREQUEST_SIZE);
value = ntohl(value);
pChangeRequest->fChangeIP = !!(value & 0x0004);
pChangeRequest->fChangePort = !!(value & 0x0002);
Cleanup:
if (FAILED(hr) && pChangeRequest)
{
pChangeRequest->fChangeIP = false;
pChangeRequest->fChangePort = false;
}
return hr;
}
HRESULT CStunMessageReader::GetPaddingAttributeSize(uint16_t* pSizePadding)
{
HRESULT hr = S_OK;
StunAttribute attrib;
ChkIfA(pSizePadding == NULL, E_INVALIDARG);
*pSizePadding = 0;
Chk(GetAttributeByIndex(_indexPaddingAttribute, &attrib));
*pSizePadding = attrib.size;
Cleanup:
return hr;
}
HRESULT CStunMessageReader::GetErrorCode(uint16_t* pErrorNumber)
{
HRESULT hr = S_OK;
uint8_t* ptr = NULL;
uint8_t cl = 0;
uint8_t num = 0;
StunAttribute attrib;
ChkIf(pErrorNumber==NULL, E_INVALIDARG);
Chk(GetAttributeByIndex(_indexErrorCode, &attrib));
// first 21 bits of error-code attribute must be zero.
// followed by 3 bits of "class" and 8 bits for the error number modulo 100
ptr = _stream.GetDataPointerUnsafe() + attrib.offset + 2;
cl = *ptr++;
cl = cl & 0x07;
num = *ptr;
*pErrorNumber = cl * 100 + num;
Cleanup:
return hr;
}
HRESULT CStunMessageReader::GetAddressHelper(uint16_t attribType, CSocketAddress* pAddr)
{
HRESULT hr = S_OK;
StunAttribute attrib={};
uint8_t *pAddrStart = NULL;
Chk(GetAttributeByType(attribType, &attrib));
pAddrStart = _stream.GetDataPointerUnsafe() + attrib.offset;
Chk(::GetMappedAddress(pAddrStart, attrib.offset, pAddr));
Cleanup:
return hr;
}
HRESULT CStunMessageReader::GetMappedAddress(CSocketAddress* pAddr)
{
HRESULT hr = S_OK;
Chk(GetAddressHelper(STUN_ATTRIBUTE_MAPPEDADDRESS, pAddr));
Cleanup:
return hr;
}
HRESULT CStunMessageReader::GetOtherAddress(CSocketAddress* pAddr)
{
HRESULT hr = S_OK;
hr = GetAddressHelper(STUN_ATTRIBUTE_OTHER_ADDRESS, pAddr);
if (FAILED(hr))
{
// look for the legacy changed address attribute that a legacy (RFC 3489) server would send
hr = GetAddressHelper(STUN_ATTRIBUTE_CHANGEDADDRESS, pAddr);
}
return hr;
}
HRESULT CStunMessageReader::GetXorMappedAddress(CSocketAddress* pAddr)
{
HRESULT hr = S_OK;
Chk(GetAddressHelper(STUN_ATTRIBUTE_XORMAPPEDADDRESS, pAddr));
pAddr->ApplyStunXorMap(_transactionid);
Cleanup:
return hr;
}
HRESULT CStunMessageReader::GetStringAttributeByType(uint16_t attributeType, char* pszValue, /*in-out*/ size_t size)
{
HRESULT hr = S_OK;
StunAttribute attrib = {};
ChkIfA(pszValue == NULL, E_INVALIDARG);
Chk(GetAttributeByType(attributeType, &attrib));
// size needs to be 1 greater than attrib.size so we can properly copy over a null char at the end
ChkIf(attrib.size >= size, E_INVALIDARG);
memcpy(pszValue, _stream.GetDataPointerUnsafe() + attrib.offset, attrib.size);
pszValue[attrib.size] = '\0';
Cleanup:
return hr;
}
HRESULT CStunMessageReader::ReadHeader()
{
HRESULT hr = S_OK;
bool fHeaderValid = false;
uint16_t msgType;
uint16_t msgLength;
uint32_t cookie;
StunTransactionId transID;
Chk(_stream.SeekDirect(0));
Chk(_stream.ReadUint16(&msgType));
Chk(_stream.ReadUint16(&msgLength));
Chk(_stream.Read(&transID.id, sizeof(transID.id)));
// convert from big endian to native type
msgType = ntohs(msgType);
msgLength = ntohs(msgLength);
memcpy(&cookie, transID.id, 4);
cookie = ntohl(cookie);
_fMessageIsLegacyFormat = !(cookie == STUN_COOKIE);
fHeaderValid = ( (0==(msgType & 0xc000)) && ((msgLength%4)==0) );
// if we aren't in legacy format (the default), then the cookie field of the transaction id must be the STUN_COOKIE
fHeaderValid = (fHeaderValid && (_fAllowLegacyFormat || !_fMessageIsLegacyFormat));
ChkIf(fHeaderValid == false, E_FAIL);
_msgTypeNormalized = ( (msgType & 0x000f) | ((msgType & 0x00e0)>>1) | ((msgType & 0x3E00)>>2) );
_msgLength = msgLength;
_transactionid = transID;
ChkIf(_msgLength > MAX_STUN_MESSAGE_SIZE, E_UNEXPECTED);
if (STUN_IS_REQUEST(msgType))
{
_msgClass = StunMsgClassRequest;
}
else if (STUN_IS_INDICATION(msgType))
{
_msgClass = StunMsgClassIndication;
}
else if (STUN_IS_SUCCESS_RESP(msgType))
{
_msgClass = StunMsgClassSuccessResponse;
}
else if (STUN_IS_ERR_RESP(msgType))
{
_msgClass = StunMsgClassFailureResponse;
}
else
{
// couldn't possibly happen, because msgClass is only two bits
_msgClass = StunMsgClassInvalidMessageClass;
ChkA(E_FAIL);
}
Cleanup:
return hr;
}
HRESULT CStunMessageReader::ReadBody()
{
size_t currentSize = _stream.GetSize();
size_t bytesConsumed = STUN_HEADER_SIZE;
HRESULT hr = S_OK;
Chk(_stream.SeekDirect(STUN_HEADER_SIZE));
while (SUCCEEDED(hr) && (bytesConsumed < currentSize))
{
uint16_t attributeType;
uint16_t attributeLength;
uint16_t attributeOffset;
int paddingLength=0;
hr = _stream.ReadUint16(&attributeType);
if (SUCCEEDED(hr))
{
hr = _stream.ReadUint16(&attributeLength);
}
if (SUCCEEDED(hr))
{
attributeOffset = _stream.GetPos();
attributeType = ntohs(attributeType);
attributeLength = ntohs(attributeLength);
// todo - if an attribute has no size, it's length is not padded by 4 bytes, right?
if (attributeLength % 4)
{
paddingLength = 4 - attributeLength % 4;
}
hr = (attributeLength <= MAX_STUN_ATTRIBUTE_SIZE) ? S_OK : E_FAIL;
}
if (SUCCEEDED(hr))
{
hr = (_nAttributeCount < MAX_NUM_ATTRIBUTES) ? S_OK : E_FAIL;
}
if (SUCCEEDED(hr))
{
StunAttribute attrib;
int attributeIndex = _nAttributeCount;
attrib.attributeType = attributeType;
attrib.size = attributeLength;
attrib.offset = attributeOffset;
_attributes[_nAttributeCount++] = attrib;
// now if this attribute is one we want to cache the index for, let's do it here
// todo - think about a "fast map" for this operation
switch (attributeType)
{
case STUN_ATTRIBUTE_FINGERPRINT:
_indexFingerprint = attributeIndex; break;
case STUN_ATTRIBUTE_RESPONSE_PORT:
_indexResponsePort = attributeIndex; break;
case STUN_ATTRIBUTE_CHANGEREQUEST:
_indexChangeRequest = attributeIndex; break;
case STUN_ATTRIBUTE_PADDING:
_indexPaddingAttribute = attributeIndex; break;
case STUN_ATTRIBUTE_ERRORCODE:
_indexErrorCode = attributeIndex; break;
case STUN_ATTRIBUTE_MESSAGEINTEGRITY:
_indexMessageIntegrity = attributeIndex; break;
default: break;
}
hr = _stream.SeekRelative(attributeLength);
}
// consume the padding
if (SUCCEEDED(hr))
{
if (paddingLength > 0)
{
hr = _stream.SeekRelative(paddingLength);
}
}
if (SUCCEEDED(hr))
{
bytesConsumed += sizeof(attributeType) + sizeof(attributeLength) + attributeLength + paddingLength;
}
}
// I don't think we could consume more bytes than stream size, but it's a worthy check to still keep here
hr = (bytesConsumed == currentSize) ? S_OK : E_FAIL;
Cleanup:
return hr;
}
CStunMessageReader::ReaderParseState CStunMessageReader::AddBytes(const uint8_t* pData, uint32_t size)
{
HRESULT hr = S_OK;
size_t currentSize;
if (_state == ParseError)
{
return ParseError;
}
if (size == 0)
{
return _state;
}
if (FAILED(_stream.Write(pData, size)))
{
return ParseError;
}
currentSize = _stream.GetSize();
if (_state == HeaderNotRead)
{
if (currentSize >= STUN_HEADER_SIZE)
{
hr = ReadHeader();
_state = SUCCEEDED(hr) ? HeaderValidated : ParseError;
if (SUCCEEDED(hr) && (_msgLength==0))
{
_state = BodyValidated;
}
}
}
if (_state == HeaderValidated)
{
if (currentSize >= (_msgLength+STUN_HEADER_SIZE))
{
if (currentSize == (_msgLength+STUN_HEADER_SIZE))
{
hr = ReadBody();
_state = SUCCEEDED(hr) ? BodyValidated : ParseError;
}
else
{
// TOO MANY BYTES FED IN
_state = ParseError;
}
}
}
if (_state == BodyValidated)
{
// What? After validating the body, the caller still passes in way too many bytes?
if (currentSize > (_msgLength+STUN_HEADER_SIZE))
{
_state = ParseError;
}
}
return _state;
}
void CStunMessageReader::GetTransactionId(StunTransactionId* pTrans)
{
if (pTrans)
{
*pTrans = _transactionid;
}
}
StunMessageClass CStunMessageReader::GetMessageClass()
{
return _msgClass;
}
uint16_t CStunMessageReader::GetMessageType()
{
return _msgTypeNormalized;
}
HRESULT CStunMessageReader::GetBuffer(CRefCountedBuffer* pRefBuffer)
{
HRESULT hr = S_OK;
ChkIf(pRefBuffer == NULL, E_INVALIDARG);
Chk(_stream.GetBuffer(pRefBuffer));
Cleanup:
return hr;
}
CDataStream& CStunMessageReader::GetStream()
{
return _stream;
}
/*
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 STUN_MESSAGE_READER_H
#define STUN_MESSAGE_READER_H
#include "stuntypes.h"
#include "datastream.h"
#include "socketaddress.h"
class CStunMessageReader
{
public:
enum ReaderParseState
{
HeaderNotRead,
HeaderValidated,
BodyValidated,
ParseError
};
private:
CDataStream _stream;
bool _fAllowLegacyFormat; // if true, allows for messages of type RFC 3489 (no magic cookie) to be accepted
bool _fMessageIsLegacyFormat; // set by readheader - true if the stun_magic_cookie is missing, but the message appears to be intact otherwise
ReaderParseState _state;
static const size_t MAX_NUM_ATTRIBUTES = 30;
StunAttribute _attributes[MAX_NUM_ATTRIBUTES];
size_t _nAttributeCount;
StunTransactionId _transactionid;
uint16_t _msgTypeNormalized;
StunMessageClass _msgClass;
uint16_t _msgLength;
HRESULT ReadHeader();
HRESULT ReadBody();
// cached indexes for common properties
int _indexFingerprint;
int _indexResponsePort;
int _indexChangeRequest;
int _indexPaddingAttribute;
int _indexErrorCode;
int _indexMessageIntegrity;
HRESULT GetAddressHelper(uint16_t attribType, CSocketAddress* pAddr);
HRESULT ValidateMessageIntegrity(uint8_t* key, size_t keylength);
public:
CStunMessageReader();
void SetAllowLegacyFormat(bool fAllowLegacyFormat);
ReaderParseState AddBytes(const uint8_t* pData, uint32_t size);
uint16_t HowManyBytesNeeded();
bool IsMessageLegacyFormat();
bool HasFingerprintAttribute();
bool IsFingerprintAttributeValid();
bool HasMessageIntegrityAttribute();
HRESULT ValidateMessageIntegrityShort(const char* pszPassword);
HRESULT ValidateMessageIntegrityLong(const char* pszUser, const char* pszRealm, const char* pszPassword);
HRESULT GetAttributeByType(uint16_t attributeType, StunAttribute* pAttribute);
HRESULT GetAttributeByIndex(int index, StunAttribute* pAttribute);
int GetAttributeCount();
void GetTransactionId(StunTransactionId* pTransId );
StunMessageClass GetMessageClass();
uint16_t GetMessageType();
HRESULT GetResponsePort(uint16_t *pPort);
HRESULT GetChangeRequest(StunChangeRequestAttribute* pChangeRequest);
HRESULT GetPaddingAttributeSize(uint16_t* pSizePadding);
HRESULT GetErrorCode(uint16_t* pErrorNumber);
HRESULT GetBuffer(CRefCountedBuffer* pRefBuffer);
HRESULT GetXorMappedAddress(CSocketAddress* pAddress);
HRESULT GetMappedAddress(CSocketAddress* pAddress);
HRESULT GetOtherAddress(CSocketAddress* pAddress);
HRESULT GetStringAttributeByType(uint16_t attributeType, char* pszValue, /*in-out*/ size_t size);
CDataStream& GetStream();
};
#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.
*/
#ifndef STUNRESPONDER_H_
#define STUNRESPONDER_H_
#include "socketrole.h"
class IStunResponder : public IRefCounted
{
public:
virtual HRESULT SendResponse(SocketRole roleOutput, const CSocketAddress& addr, CRefCountedBuffer& spResponse) = 0;
virtual bool HasAddress(SocketRole role)=0;
virtual HRESULT GetSocketAddressForRole(SocketRole role, /*out*/ CSocketAddress* pAddr)=0;
};
#endif /* STUNRESPONDER_H_ */
/*
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 STUNTYPES_H
#define STUNTYPES_H
// RFC 5389: STUN RFC
// RFC 3489: OLD STUN RFC. Obsolete, but newer specs reference it
// RFC 5769 - test vectors for STUN
// RFC 5780 - Nat behavior discovery using STUN
const uint16_t DEFAULT_STUN_PORT = 3478;
const uint16_t DEFAULT_STUN_TLS_PORT = 5349;
const uint16_t STUN_ATTRIBUTE_MAPPEDADDRESS = 0x0001;
const uint16_t STUN_ATTRIBUTE_RESPONSEADDRESS = 0x0002;
const uint16_t STUN_ATTRIBUTE_CHANGEREQUEST = 0x0003;
const uint16_t STUN_ATTRIBUTE_SOURCEADDRESS = 0x0004;
const uint16_t STUN_ATTRIBUTE_CHANGEDADDRESS = 0x0005; // this is the legacy "other address" from rfc 3489, superceded by STUN_ATTRIBUTE_OTHERADDRESS below
const uint16_t STUN_ATTRIBUTE_USERNAME = 0x0006;
const uint16_t STUN_ATTRIBUTE_LEGACY_PASSWORD = 0x0007; // old rfc
const uint16_t STUN_ATTRIBUTE_MESSAGEINTEGRITY = 0x0008;
const uint16_t STUN_ATTRIBUTE_ERRORCODE = 0x0009;
const uint16_t STUN_ATTRIBUTE_UNKNOWNATTRIBUTES = 0x000A;
const uint16_t STUN_ATTRIBUTE_REFLECTEDFROM = 0x000B; // old rfc
const uint16_t STUN_ATTRIBUTE_REALM = 0x0014;
const uint16_t STUN_ATTRIBUTE_NONCE = 0x0015;
const uint16_t STUN_ATTRIBUTE_XORMAPPEDADDRESS = 0x0020;
const uint16_t STUN_ATTRIBUTE_SOFTWARE = 0x8022;
const uint16_t STUN_ATTRIBUTE_ALTERNATESERVER = 0x8023;
const uint16_t STUN_ATTRIBUTE_PADDING = 0x8026;
const uint16_t STUN_ATTRIBUTE_RESPONSE_PORT = 0x8027;
const uint16_t STUN_ATTRIBUTE_FINGERPRINT = 0x8028;
const uint16_t STUN_ATTRIBUTE_RESPONSE_ORIGIN = 0x802b;
const uint16_t STUN_ATTRIBUTE_OTHER_ADDRESS = 0x802c;
const uint16_t STUN_TRANSACTION_ID_LENGTH = 16;
const uint8_t STUN_ATTRIBUTE_FIELD_IPV4 = 1;
const uint8_t STUN_ATTRIBUTE_FIELD_IPV6 = 2;
const uint16_t STUN_IPV4_LENGTH = 4;
const uint16_t STUN_IPV6_LENGTH = 16;
const uint16_t STUN_ERROR_TRYALTERNATE = 300;
const uint16_t STUN_ERROR_BADREQUEST = 400;
const uint16_t STUN_ERROR_UNAUTHORIZED = 401;
const uint16_t STUN_ERROR_UNKNOWNATTRIB = 420;
const uint16_t STUN_ERROR_STALENONCE = 438;
const uint16_t STUN_ERROR_SERVERERROR = 500;
enum StunMessageClass
{
StunMsgClassRequest=0x00,
StunMsgClassIndication=0x01,
StunMsgClassSuccessResponse=0x02,
StunMsgClassFailureResponse=0x03,
StunMsgClassInvalidMessageClass = 0xff
};
enum StunMessageType
{
StunMsgTypeBinding = 0x0001,
StunMsgTypeInvalid = 0xffff
};
struct StunAttribute
{
uint16_t attributeType;
uint16_t size;
uint16_t offset;
};
struct StunMappedAddressAttribute_IPV4
{
uint8_t zero;
uint8_t family;
uint16_t xport;
uint32_t xip;
};
struct StunMappedAddressAttribute_IPV6
{
uint8_t zero;
uint8_t family;
uint16_t xport;
uint8_t xip[16];
};
struct StunChangeRequestAttribute
{
bool fChangeIP;
bool fChangePort;
};
const uint16_t STUN_ATTRIBUTE_MAPPEDADDRESS_SIZE_IPV4 = 8;
const uint16_t STUN_ATTRIBUTE_MAPPEDADDRESS_SIZE_IPV6 = 20;
const uint16_t STUN_ATTRIBUTE_RESPONSE_PORT_SIZE = 2;
const uint16_t STUN_ATTRIBUTE_CHANGEREQUEST_SIZE = 4;
#define STUN_IS_REQUEST(msg_type) (((msg_type) & 0x0110) == 0x0000)
#define STUN_IS_INDICATION(msg_type) (((msg_type) & 0x0110) == 0x0010)
#define STUN_IS_SUCCESS_RESP(msg_type) (((msg_type) & 0x0110) == 0x0100)
#define STUN_IS_ERR_RESP(msg_type) (((msg_type) & 0x0110) == 0x0110)
struct StunTransactionId
{
uint8_t id[STUN_TRANSACTION_ID_LENGTH]; // first four bytes is usually the magic cookie field
};
inline bool operator==(const StunTransactionId &id1, const StunTransactionId &id2)
{
return (0 == memcmp(id1.id, id2.id, STUN_TRANSACTION_ID_LENGTH));
}
// stun header
// todo - unit test to validate packing isn't broken between windows and linux
const uint32_t STUN_COOKIE = 0x2112A442;
const uint8_t STUN_COOKIE_B1 = 0x21;
const uint8_t STUN_COOKIE_B2 = 0x12;
const uint8_t STUN_COOKIE_B3 = 0xA4;
const uint8_t STUN_COOKIE_B4 = 0x42;
const uint32_t STUN_FINGERPRINT_XOR = 0x5354554e;
const uint16_t STUN_XOR_PORT_COOKIE = 0x2112;
const uint32_t STUN_HEADER_SIZE = 20;
const uint32_t MAX_STUN_MESSAGE_SIZE = 2000; // some reasonable length
const uint32_t MAX_STUN_ATTRIBUTE_SIZE = 1980; // more than reasonable
#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"
#include "stuntypes.h"
#include "socketaddress.h"
#include "buffer.h"
#include "datastream.h"
bool IsTransactionIdValid(StunTransactionId& transid)
{
uint8_t zerobytes[sizeof(transid.id)] = {}; // zero-init
return (0 != memcmp(&transid.id, zerobytes, sizeof(zerobytes)));
}
HRESULT GetMappedAddress(uint8_t* pData, size_t size, CSocketAddress* pAddr)
{
uint16_t port;
HRESULT hr = S_OK;
uint8_t attributeid;
uint8_t ip6[STUN_IPV6_LENGTH];
uint32_t ip4;
CRefCountedBuffer spBuffer(new CBuffer(pData, size, false));
CDataStream stream(spBuffer);
ChkIfA(pAddr==NULL, E_INVALIDARG);
Chk(stream.SeekDirect(1)); // skip over the zero byte
Chk(stream.ReadUint8(&attributeid));
Chk(stream.ReadUint16(&port));
port = ntohs(port);
if (attributeid == STUN_ATTRIBUTE_FIELD_IPV4)
{
Chk(stream.ReadUint32(&ip4));
ip4 = ntohl(ip4);
*pAddr = CSocketAddress(ip4, port);
}
else
{
sockaddr_in6 addr6={};
Chk(stream.Read(ip6, STUN_IPV6_LENGTH));
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(port);
memcpy(&addr6.sin6_addr, ip6, STUN_IPV6_LENGTH);
*pAddr = CSocketAddress(addr6);
}
Cleanup:
return hr;
}
HRESULT GetXorMappedAddress(uint8_t* pData, size_t size, StunTransactionId &transid, CSocketAddress* pAddr)
{
HRESULT hr = S_OK;
Chk(GetMappedAddress(pData, size, pAddr));
pAddr->ApplyStunXorMap(transid);
Cleanup:
return hr;
}
/*
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 STUN_UTILS_H
#define STUN_UTILS_H
bool IsTransactionIdValid(StunTransactionId& transid);
HRESULT GetXorMappedAddress(uint8_t* pData, size_t size, StunTransactionId &transid, CSocketAddress* pAddr);
HRESULT GetMappedAddress(uint8_t* pData, size_t size, CSocketAddress* pAddr);
#endif
include ../common.inc
PROJECT_TARGET := stuntestcode
PROJECT_OBJS := testbuilder.o testclientlogic.o testcmdline.o testcode.o testdatastream.o testintegrity.o testmessagehandler.o testreader.o testrecvfromex.o
INCLUDES := $(BOOST_INCLUDE) $(OPENSSL_INCLUDE) -I../common -I../stuncore -I../networkutils
LIB_PATH := -L../networkutils -L../stuncore -L../common
LIBS := -lnetworkutils -lstuncore -lcommon -lpthread -lcrypto
all: $(PROJECT_TARGET)
clean:
rm -f $(PROJECT_OBJS) $(PROJECT_TARGET)
$(PROJECT_TARGET): $(PROJECT_OBJS)
$(LINK.cpp) -o $@ $^ $(LIB_PATH) $(LIBS)
/*
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"
#include "stuncore.h"
#include "testbuilder.h"
HRESULT CTestBuilder::Run()
{
HRESULT hr = S_OK;
Chk(Test1())
Chk(Test2());
Cleanup:
return hr;
}
// The goal of this test is to just validate that we can create a message from CStunMessageBuilder and have it's output parsed correctly by CStunMessageReader
// Also helps validate CSocketAddress
HRESULT CTestBuilder::Test1()
{
HRESULT hr = S_OK;
CStunMessageBuilder builder;
CStunMessageReader reader;
StunAttribute attrib;
CRefCountedBuffer spBuffer;
CRefCountedBuffer spBufferReader;
CSocketAddress addrValidate(0,0);
StunTransactionId transid = {};
uint32_t ipvalidate = 0;
CSocketAddress addr(0x7f000001, 9999);
CSocketAddress addrOrigin(0xAABBCCDD, 8888);
CSocketAddress addrOther(0x11223344, 7777);
ChkA(builder.AddBindingRequestHeader());
ChkA(builder.AddRandomTransactionId(&transid));
ChkA(builder.AddStringAttribute(STUN_ATTRIBUTE_SOFTWARE, "FOOBAR"));
ChkA(builder.AddMappedAddress(addr));
ChkA(builder.AddXorMappedAddress(addr));
ChkA(builder.AddOtherAddress(addrOther, false));
ChkA(builder.AddResponseOriginAddress(addrOrigin));
ChkA(builder.AddFingerprintAttribute());
ChkA(builder.GetResult(&spBuffer));
ChkIfA(CStunMessageReader::BodyValidated != reader.AddBytes(spBuffer->GetData(), spBuffer->GetSize()), E_FAIL);
ChkIfA(reader.HasFingerprintAttribute() == false, E_FAIL);
ChkIfA(reader.IsFingerprintAttributeValid() == false, E_FAIL);
ChkIfA(reader.GetMessageClass() != StunMsgClassRequest, E_FAIL);
ChkIfA(reader.GetMessageType() != StunMsgTypeBinding, E_FAIL);
ChkA(reader.GetBuffer(&spBufferReader));
ChkA(reader.GetAttributeByType(STUN_ATTRIBUTE_SOFTWARE, &attrib));
ChkIfA(attrib.attributeType != STUN_ATTRIBUTE_SOFTWARE, E_FAIL);
ChkIfA(0 != ::strncmp("FOOBAR", (const char*)(spBufferReader->GetData() + attrib.offset), attrib.size), E_FAIL);
ChkA(reader.GetXorMappedAddress(&addrValidate));
ChkIf(addrValidate.IsSameIP_and_Port(addr) == false, E_FAIL);
ChkIfA(addrValidate.GetIPLength() != 4, E_FAIL);
addrValidate = CSocketAddress(0,0);
ChkA(reader.GetMappedAddress(&addrValidate));
ChkIfA(addrValidate.GetPort() != 9999, E_FAIL);
ChkIfA(addrValidate.GetIPLength() != 4, E_FAIL);
ChkIfA(4 != addrValidate.GetIP(&ipvalidate, 4), E_FAIL);
ChkIfA(ipvalidate != 0x7f000001, E_FAIL);
addrValidate = CSocketAddress(0,0);
ipvalidate = 0;
reader.GetOtherAddress(&addrValidate);
ChkIfA(addrValidate.GetPort() != 7777, E_FAIL);
ChkIfA(addrValidate.GetIPLength() != 4, E_FAIL);
ChkIfA(4 != addrValidate.GetIP(&ipvalidate, 4), E_FAIL);
ChkIf(ipvalidate != 0x11223344, E_FAIL);
Cleanup:
return hr;
}
// this test validates that IPV6 addresses work fine with CStunMessageBuilder and CStunMessageReader
HRESULT CTestBuilder::Test2()
{
HRESULT hr = S_OK;
CSocketAddress addr(0,0);
CSocketAddress addrValidate(0,0);
const char* ip6addr = "ABCDEFGHIJKLMNOP";
sockaddr_in6 addr6 = {};
CStunMessageReader reader;
StunTransactionId transid;
CStunMessageBuilder builder;
CRefCountedBuffer spBuffer;
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(9999);
memcpy(addr6.sin6_addr.s6_addr, ip6addr, 16);
addr = CSocketAddress(addr6);
ChkA(builder.AddHeader(StunMsgTypeBinding, StunMsgClassRequest));
ChkA(builder.AddRandomTransactionId(&transid));
ChkA(builder.AddMappedAddress(addr));
ChkA(builder.AddXorMappedAddress(addr));
ChkA(builder.GetResult(&spBuffer));
ChkIfA(CStunMessageReader::BodyValidated != reader.AddBytes(spBuffer->GetData(), spBuffer->GetSize()), E_FAIL);
ChkA(reader.GetXorMappedAddress(&addrValidate));
ChkIf(addrValidate.IsSameIP_and_Port(addr) == false, E_FAIL);
Cleanup:
return hr;
}
/*
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 TESTBUILDER_H_
#define TESTBUILDER_H_
#include "unittest.h"
class CTestBuilder : public IUnitTest
{
public:
HRESULT Test1();
HRESULT Test2();
virtual HRESULT Run();
UT_DECLARE_TEST_NAME("CTestBuilder");
};
#endif /* TESTBUILDER_H_ */
/*
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"
#include "stuncore.h"
#include "unittest.h"
#include "testmessagehandler.h"
#include "testclientlogic.h"
HRESULT CTestClientLogic::Run()
{
HRESULT hr = S_OK;
ChkA(Test1());
printf("Testing detection for DirectMapping\n");
ChkA(TestBehaviorAndFiltering(true, DirectMapping, false, EndpointIndependentFiltering));
printf("Testing detection for EndpointIndependent mapping\n");
ChkA(TestBehaviorAndFiltering(true, EndpointIndependentMapping, false, EndpointIndependentFiltering));
printf("Testing detection for AddressDependentMapping\n");
ChkA(TestBehaviorAndFiltering(true, AddressDependentMapping, false, EndpointIndependentFiltering));
printf("Testing detection for AddressAndPortDependentMapping\n");
ChkA(TestBehaviorAndFiltering(true, AddressAndPortDependentMapping, false, EndpointIndependentFiltering));
printf("Testing detection for EndpointIndependentFiltering\n");
ChkA(TestBehaviorAndFiltering(true, EndpointIndependentMapping, true, EndpointIndependentFiltering));
printf("Testing detection for AddressDependentFiltering\n");
ChkA(TestBehaviorAndFiltering(true, EndpointIndependentMapping, true, AddressDependentFiltering));
printf("Testing detection for AddressAndPortDependentFiltering\n");
ChkA(TestBehaviorAndFiltering(true, EndpointIndependentMapping, true, AddressAndPortDependentFiltering));
Cleanup:
return hr;
}
HRESULT CTestClientLogic::ValidateBindingRequest(CRefCountedBuffer& spMsg, StunTransactionId* pTransId)
{
HRESULT hr = S_OK;
CStunMessageReader reader;
CStunMessageReader::ReaderParseState state;
state = reader.AddBytes(spMsg->GetData(), spMsg->GetSize());
ChkIfA(state != CStunMessageReader::BodyValidated, E_UNEXPECTED);
ChkIfA(reader.GetMessageType() != StunMsgTypeBinding, E_UNEXPECTED);
reader.GetTransactionId(pTransId);
ChkIfA(false == IsTransactionIdValid(*pTransId), E_FAIL);
Cleanup:
return hr;
}
HRESULT CTestClientLogic::GenerateBindingResponseMessage(const CSocketAddress& addrMapped, const StunTransactionId& transid, CRefCountedBuffer& spMsg)
{
HRESULT hr = S_OK;
CStunMessageBuilder builder;
builder.GetStream().Attach(spMsg, true);
ChkA(builder.AddBindingResponseHeader(true));
ChkA(builder.AddTransactionId(transid));
ChkA(builder.AddXorMappedAddress(addrMapped));
ChkA(builder.FixLengthField());
Cleanup:
return hr;
}
HRESULT CTestClientLogic::GetMappedAddressForDestinationAddress(const CSocketAddress& addrDest, CSocketAddress* pAddrMapped)
{
HRESULT hr = S_OK;
if (addrDest.IsSameIP_and_Port(_addrServerPP))
{
*pAddrMapped = _addrMappedPP;
}
else if (addrDest.IsSameIP_and_Port(_addrServerPA))
{
*pAddrMapped = _addrMappedPA;
}
else if (addrDest.IsSameIP_and_Port(_addrServerAP))
{
*pAddrMapped = _addrMappedAP;
}
else if (addrDest.IsSameIP_and_Port(_addrServerAA))
{
*pAddrMapped = _addrMappedAA;
}
else
{
ASSERT(false);
hr = E_FAIL;
}
return hr;
}
SocketRole CTestClientLogic::GetSocketRoleForDestinationAddress(const CSocketAddress& addrDest)
{
if (addrDest.IsSameIP_and_Port(_addrServerPP))
{
return RolePP;
}
else if (addrDest.IsSameIP_and_Port(_addrServerPA))
{
return RolePA;
}
else if (addrDest.IsSameIP_and_Port(_addrServerAP))
{
return RoleAP;
}
else if (addrDest.IsSameIP_and_Port(_addrServerAA))
{
return RoleAA;
}
ASSERT(false);
return RolePP;
}
HRESULT CTestClientLogic::CommonInit(NatBehavior behavior, NatFiltering filtering)
{
HRESULT hr = S_OK;
CSocketAddress addrMapped(0x22222222, 6000);
_addrServerPP = CSocketAddress(0xaaaaaaaa, 1001);
_addrServerPA = CSocketAddress(0xaaaaaaaa, 1002);
_addrServerAP = CSocketAddress(0xbbbbbbbb, 1001);
_addrServerAA = CSocketAddress(0xbbbbbbbb, 1002);
_addrLocal = CSocketAddress(0x33333333, 7000);
_addrMappedPP = addrMapped;
_addrMappedPA = addrMapped;
_addrMappedAP = addrMapped;
_addrMappedAA = addrMapped;
_spClientLogic = boost::shared_ptr<CStunClientLogic>(new CStunClientLogic());
Chk(CMockTransport::CreateInstanceNoInit(_spTransport.GetPointerPointer()));
_spHandler = boost::shared_ptr<CStunThreadMessageHandler>(new CStunThreadMessageHandler);
_spHandler->SetResponder(_spTransport);
_spTransport->AddPP(_addrServerPP);
_spTransport->AddPA(_addrServerPA);
_spTransport->AddAP(_addrServerAP);
_spTransport->AddAA(_addrServerAA);
switch (behavior)
{
case DirectMapping:
{
_addrMappedPP = _addrLocal;
_addrMappedPA = _addrLocal;
_addrMappedAP = _addrLocal;
_addrMappedAA = _addrLocal;
break;
}
case EndpointIndependentMapping:
{
break;
}
case AddressDependentMapping:
{
_addrMappedAP.SetPort(6002);
_addrMappedAA.SetPort(6002);
break;
}
case AddressAndPortDependentMapping:
{
_addrMappedPA.SetPort(6001);
_addrMappedAP.SetPort(6002);
_addrMappedAA.SetPort(6003);
break;
}
default:
{
ChkA(E_FAIL);
}
}
switch (filtering)
{
case EndpointIndependentFiltering:
case DirectConnectionFiltering:
{
_fAllowChangeRequestPA = true;
_fAllowChangeRequestAA = true;
break;
}
case AddressDependentFiltering:
{
_fAllowChangeRequestPA = true;
_fAllowChangeRequestAA = false;
break;
}
case AddressAndPortDependentFiltering:
{
_fAllowChangeRequestPA = false;
_fAllowChangeRequestAA = false;
break;
}
default:
{
ChkA(E_FAIL);
}
}
Cleanup:
return hr;
}
HRESULT CTestClientLogic::TestBehaviorAndFiltering(bool fBehaviorTest, NatBehavior behavior, bool fFilteringTest, NatFiltering filtering)
{
HRESULT hr = S_OK;
StunClientLogicConfig config;
HRESULT hrRet;
uint32_t time = 0;
CRefCountedBuffer spMsgOut(new CBuffer(1500));
CRefCountedBuffer spMsgIn;
CSocketAddress addrDest;
CSocketAddress addrMapped;
CSocketAddress addrServerResponse; // what address the fake server responded back on
StunClientResults results;
StunTransactionId transid={};
//std::string strAddr;
ChkA(CommonInit(behavior, filtering));
config.addrServer = _addrServerPP;
config.fBehaviorTest = fBehaviorTest;
config.fFilteringTest = fFilteringTest;
config.timeoutSeconds = 5;
config.uMaxAttempts = 10;
ChkA(_spClientLogic->Initialize(config));
while (true)
{
bool fDropMessage = false;
StunMessageEnvelope envelope;
time += 1000;
hrRet = _spClientLogic->GetNextMessage(spMsgOut, &addrDest, time);
if (hrRet == E_STUNCLIENT_STILL_WAITING)
{
//printf("GetNextMessage returned 'still waiting'\n");
continue;
}
if (hrRet == E_STUNCLIENT_RESULTS_READY)
{
//printf("GetNextMessage returned 'results ready'\n");
break;
}
//addrDest.ToString(&strAddr);
//printf("Client is sending stun packet to %s\n", strAddr.c_str());
ChkA(GetMappedAddressForDestinationAddress(addrDest, &addrMapped));
//addrMapped.ToString(&strAddr);
//printf("Server is receiving stun packet from %s\n", strAddr.c_str());
ChkA(ValidateBindingRequest(spMsgOut, &transid));
envelope.localAddr = addrDest;
envelope.remoteAddr = addrMapped;
envelope.spBuffer = spMsgOut;
envelope.localSocket = GetSocketRoleForDestinationAddress(addrDest);
_spTransport->ClearStream();
_spHandler->ProcessRequest(envelope);
// simulate the message coming back
// make sure we got something!
ChkIfA(_spTransport->m_outputRole == ((SocketRole)-1), E_FAIL);
if (spMsgIn != NULL)
{
spMsgIn->SetSize(0);
}
_spTransport->m_outputstream.GetBuffer(&spMsgIn);
ChkIf(spMsgIn->GetSize() == 0, E_FAIL);
ChkA(_spTransport->GetSocketAddressForRole(_spTransport->m_outputRole, &addrServerResponse));
//addrServerResponse.ToString(&strAddr);
//printf("Server is sending back from %s\n", strAddr.c_str());
// if the request went to PP, but came back from AA or AP, then it's likely a filtering test
// decide if we need to drop the response
fDropMessage = ( addrDest.IsSameIP_and_Port(_addrServerPP) &&
( ((_spTransport->m_outputRole == RoleAA) && (_fAllowChangeRequestAA==false)) ||
((_spTransport->m_outputRole == RolePA) && (_fAllowChangeRequestPA==false))
)
);
if (fDropMessage == false)
{
ChkA(_spClientLogic->ProcessResponse(spMsgIn, addrServerResponse, _addrLocal));
}
}
// now validate the results
results.Init(); // zero it out
_spClientLogic->GetResults(&results);
ChkIfA(results.behavior != behavior, E_UNEXPECTED);
Cleanup:
return hr;
}
HRESULT CTestClientLogic::Test1()
{
HRESULT hr = S_OK;
HRESULT hrTmp = 0;
CStunClientLogic clientlogic;
::StunClientLogicConfig config;
CRefCountedBuffer spMsgOut(new CBuffer(1500));
CRefCountedBuffer spMsgIn(new CBuffer(1500));
StunClientResults results;
StunTransactionId transid;
CSocketAddress addrDest;
CSocketAddress addrServerPP = CSocketAddress(0xaaaaaaaa, 1001);
CSocketAddress addrLocal = CSocketAddress(0xdddddddd, 4444);
CSocketAddress addrMapped = CSocketAddress(0xeeeeeeee, 5555);
config.addrServer = addrServerPP;
config.fBehaviorTest = false;
config.fFilteringTest = false;
config.timeoutSeconds = 10;
config.uMaxAttempts = 2;
ChkA(clientlogic.Initialize(config));
ChkA(clientlogic.GetNextMessage(spMsgOut, &addrDest, 0));
// we expect to get back a message for the serverPP
ChkIfA(addrDest.IsSameIP_and_Port(addrServerPP)==false, E_UNEXPECTED);
// check to make sure out timeout logic appears to work
hrTmp = clientlogic.GetNextMessage(spMsgOut, &addrDest, 1);
ChkIfA(hrTmp != E_STUNCLIENT_STILL_WAITING, E_UNEXPECTED);
// now we should get a dupe of what we had before
ChkA(clientlogic.GetNextMessage(spMsgOut, &addrDest, 11000));
// the message should be a binding request
ChkA(ValidateBindingRequest(spMsgOut, &transid));
// now let's generate a response
ChkA(GenerateBindingResponseMessage(addrMapped, transid, spMsgIn));
ChkA(clientlogic.ProcessResponse(spMsgIn, addrServerPP, addrLocal));
// results should be ready
hrTmp = clientlogic.GetNextMessage(spMsgOut, &addrDest, 12000);
ChkIfA(hrTmp != E_STUNCLIENT_RESULTS_READY, E_UNEXPECTED);
ChkA(clientlogic.GetResults(&results));
// results should have a successful binding result
ChkIfA(results.fBindingTestSuccess==false, E_UNEXPECTED);
ChkIfA(results.fIsDirect, E_UNEXPECTED);
ChkIfA(results.addrMapped.IsSameIP_and_Port(addrMapped)==false, E_UNEXPECTED);
ChkIfA(results.addrLocal.IsSameIP_and_Port(addrLocal)==false, E_UNEXPECTED);
Cleanup:
return hr;
}
/*
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 TESTCLIENTLOGIC_H
#define TESTCLIENTLOGIC_H
#include "unittest.h"
#include "testmessagehandler.h"
class CTestClientLogic : public IUnitTest
{
private:
CSocketAddress _addrServerPP;
CSocketAddress _addrServerPA;
CSocketAddress _addrServerAP;
CSocketAddress _addrServerAA;
CSocketAddress _addrLocal;
CSocketAddress _addrMappedPP; // what PP returns
CSocketAddress _addrMappedPA; // what PA returns
CSocketAddress _addrMappedAP; // what AP returns
CSocketAddress _addrMappedAA; // what AA returns
bool _fAllowChangeRequestAA;
bool _fAllowChangeRequestPA;
NatBehavior _behavior;
NatFiltering _filtering;
boost::shared_ptr<CStunClientLogic> _spClientLogic;
boost::shared_ptr<CStunThreadMessageHandler> _spHandler;
CRefCountedPtr<CMockTransport> _spTransport;
HRESULT ValidateBindingRequest(CRefCountedBuffer& spMsg, StunTransactionId* pTransId);
HRESULT GenerateBindingResponseMessage(const CSocketAddress& addrMapped , const StunTransactionId& transid, CRefCountedBuffer& spMsg);
HRESULT Test1();
HRESULT TestBehaviorAndFiltering(bool fBehaviorTest, NatBehavior behavior, bool fFilteringTest, NatFiltering filtering);
HRESULT CommonInit(NatBehavior behavior, NatFiltering filtering);
HRESULT GetMappedAddressForDestinationAddress(const CSocketAddress& addrDest, CSocketAddress* pAddrMapped);
SocketRole GetSocketRoleForDestinationAddress(const CSocketAddress& addrDest);
public:
CTestClientLogic() {;}
~CTestClientLogic() {;}
HRESULT Run();
UT_DECLARE_TEST_NAME("CTestClientLogic");
};
#endif /* TESTCLIENTLOGIC_H */
/*
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"
#include "cmdlineparser.h"
#include "unittest.h"
#include "testcmdline.h"
// This test validates that the CCmdLineParser works correctly
HRESULT CTestCmdLineParser::Run()
{
HRESULT hr = S_OK;
typedef const char* PCSTR;
//char* argv[] = {(char*)"app", (char*)"--zebra", (char*)"--yak=123", (char*)"--walrus=456", (char*)"--vulture"};
const char* argv[5] = {};
argv[0] = "app";
argv[1] = "--zebra";
argv[2] = "--yak=123";
argv[3] = "--walrus=456";
argv[4] = "--vulture";
int argc = ARRAYSIZE(argv);
bool fErrorFlag = false;
std::string strZebra;
std::string strYak;
std::string strWalrus;
std::string strVulture;
std::string strAardvark;
CCmdLineParser parser;
parser.AddOption("aardvark", 1, &strAardvark);
parser.AddOption("vulture", 0, &strVulture);
parser.AddOption("walrus", 1, &strWalrus);
parser.AddOption("yak", 2, &strYak);
parser.AddOption("zebra", 2, &strZebra);
parser.ParseCommandLine(argc, (char**)argv, 1, &fErrorFlag);
ChkIf(fErrorFlag, E_FAIL);
ChkIf(strAardvark.length() > 0, E_FAIL);
ChkIf(strVulture.length() == 0, E_FAIL);
ChkIf(strWalrus != "456", E_FAIL);
ChkIf(strYak != "123", E_FAIL);
ChkIf(strZebra != "1", E_FAIL);
Cleanup:
return hr;
}
/*
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 TESTCMDLINE_H
#define TESTCMDLINE_H
class CTestCmdLineParser : public IUnitTest
{
public:
HRESULT Run();
UT_DECLARE_TEST_NAME("CTestCmdLineParser");
};
#endif /* TESTCMDLINE_H */
/*
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"
#include "stuncore.h"
#include "testdatastream.h"
#include "testreader.h"
#include "testbuilder.h"
#include "testmessagehandler.h"
#include "testcmdline.h"
#include "testintegrity.h"
#include "testclientlogic.h"
#include "testrecvfromex.h"
#include "cmdlineparser.h"
#include "oshelper.h"
#include "prettyprint.h"
void ReaderFuzzTest()
{
uint8_t msgbytes[1500];
ssize_t messagesize;
CStunMessageReader reader;
int ret;
messagesize=0;
while (messagesize < 1500)
{
ret = getchar();
if (ret < 0)
{
break;
}
unsigned char ch = (unsigned char)ret;
msgbytes[messagesize] = ch;
messagesize++;
}
printf("Processing %d bytes\n", (int)messagesize);
reader.AddBytes(msgbytes, messagesize);
printf("Test Run (%u)\n", GetMillisecondCounter());
exit(0);
}
void RunUnitTests()
{
std::vector<IUnitTest*> vecTests;
vecTests.push_back(new CTestDataStream());
vecTests.push_back(new CTestReader());
vecTests.push_back(new CTestBuilder());
vecTests.push_back(new CTestIntegrity());
vecTests.push_back(new CTestMessageHandler());
vecTests.push_back(new CTestCmdLineParser());
vecTests.push_back(new CTestClientLogic());
vecTests.push_back(new CTestRecvFromEx());
for (size_t index = 0; index < vecTests.size(); index++)
{
HRESULT hr = vecTests[index]->Run();
printf("Result of %s: %s\n", vecTests[index]->GetName(), SUCCEEDED(hr)?"PASS": "FAIL");
}
}
void PrettyPrintTest()
{
const size_t MAX_TEXT_SIZE = 100000;
char* buffer = new char[MAX_TEXT_SIZE];
size_t messagesize = 0;
while (messagesize < MAX_TEXT_SIZE)
{
int ret = getchar();
if (ret < 0)
{
break;
}
buffer[messagesize] = (signed char)(ret);
messagesize++;
}
::PrettyPrint(buffer, 78);
delete [] buffer;
}
int main(int argc, char** argv)
{
CCmdLineParser cmdline;
std::string strFuzz;
std::string strPP;
bool fParseError = false;
cmdline.AddOption("fuzz", no_argument, &strFuzz);
cmdline.AddOption("pp", no_argument, &strPP);
cmdline.ParseCommandLine(argc, argv, 1, &fParseError);
if (strFuzz.size() > 0)
{
ReaderFuzzTest();
}
else if (strPP.size() > 0)
{
PrettyPrintTest();
}
else
{
RunUnitTests();
}
return 0;
}
/*
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"
#include "stuncore.h"
#include "testdatastream.h"
// This test validates the code in stuncore\datastream.cpp
HRESULT CTestDataStream::Run()
{
CDataStream stream;
HRESULT hr;
const char* str1 = "ABCDEFGHIJ";
const char* str2 = "KLMNOPQRSTUVW";
const char* str3 = "mnop";
const char* str4 = "XYZ";
char ch1= ' ', ch2=' ', ch3= ' ';
Chk(stream.Write(str1, strlen(str1)));
Chk(stream.Write(str2, strlen(str2)));
Chk(stream.SeekDirect(12)); // seek to "M"
Chk(stream.Write(str3, strlen(str3)));
Chk(stream.SeekDirect(stream.GetSize()));
Chk(stream.Write(str4, strlen(str4)));
Chk(stream.WriteUint8(0));
stream.SeekDirect(9);
stream.ReadInt8((int8_t*)&ch1);
ChkIf(ch1 != 'J', E_FAIL);
Chk(stream.ReadInt8((int8_t*)&ch2));
ChkIf(ch2 != 'K', E_FAIL);
stream.SeekRelative(1);
stream.ReadInt8((int8_t*)&ch3);
ChkIf(ch3 != 'm', E_FAIL);
Cleanup:
return hr;
}
/*
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 TESTDATASTREAM_H_
#define TESTDATASTREAM_H_
#include "unittest.h"
class CTestDataStream : public IUnitTest
{
virtual HRESULT Run();
UT_DECLARE_TEST_NAME("CTestDataStream");
};
#endif /* TESTDATASTREAM_H_ */
#include "commonincludes.h"
#include "stuncore.h"
#include "testintegrity.h"
// This test validates that the construction and parsing of the message integrity attribute in a stun message works as expected
// The test also validates both short term and long term credential modes with or without the presence of a fingerprint attribute
HRESULT CTestIntegrity::TestMessageIntegrity(bool fWithFingerprint, bool fLongCredentials)
{
HRESULT hr = S_OK;
const char* pszUserName = "username";
const char* pszRealm = "stunrealm";
const char* pszPassword = "ThePassword";
CStunMessageBuilder builder;
CStunMessageReader reader;
uint8_t *pMsg = NULL;
size_t sizeMsg = 0;
CStunMessageReader::ReaderParseState state;
CRefCountedBuffer spBuffer;
builder.AddBindingRequestHeader();
builder.AddRandomTransactionId(NULL);
builder.AddUserName(pszUserName);
builder.AddRealm(pszRealm);
if (fLongCredentials == false)
{
Chk(builder.AddMessageIntegrityShortTerm(pszPassword));
}
else
{
Chk(builder.AddMessageIntegrityLongTerm(pszUserName, pszRealm, pszPassword));
}
if (fWithFingerprint)
{
builder.AddFingerprintAttribute();
}
Chk(builder.GetResult(&spBuffer));
pMsg = spBuffer->GetData();
sizeMsg = spBuffer->GetSize();
state = reader.AddBytes(pMsg, sizeMsg);
ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL);
ChkIfA(reader.HasMessageIntegrityAttribute()==false, E_FAIL);
if (fLongCredentials == false)
{
ChkA(reader.ValidateMessageIntegrityShort(pszPassword));
}
else
{
ChkA(reader.ValidateMessageIntegrityLong(pszUserName, pszRealm, pszPassword));
}
Cleanup:
return hr;
}
HRESULT CTestIntegrity::Run()
{
HRESULT hr = S_OK;
Chk(TestMessageIntegrity(false, false));
ChkA(TestMessageIntegrity(true, false));
Chk(TestMessageIntegrity(false, true));
ChkA(TestMessageIntegrity(true, true));
Cleanup:
return hr;
}
/*
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 TESTINTEGRITY_H
#define TESTINTEGRITY_H
#include "unittest.h"
class CTestIntegrity : public IUnitTest
{
private:
HRESULT TestMessageIntegrity(bool fWithFingerprint, bool fLongCredentials);
public:
virtual HRESULT Run();
UT_DECLARE_TEST_NAME("CTestIntegrity");
};
#endif /* TESTINTEGRITY_H */
/*
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"
#include "stuncore.h"
#include "unittest.h"
#include "stunauth.h"
#include "testmessagehandler.h"
CMockTransport::CMockTransport()
{
;
}
CMockTransport::~CMockTransport()
{
;
}
HRESULT CMockTransport::Reset()
{
for (unsigned int index = 0; index < ARRAYSIZE(m_addrs); index++)
{
m_addrs[index] = CSocketAddress();
}
ClearStream();
return S_OK;
}
HRESULT CMockTransport::ClearStream()
{
m_outputstream.Reset();
m_outputRole = (SocketRole)-1;
m_addrDestination = CSocketAddress();
return S_OK;
}
HRESULT CMockTransport::AddPP(const CSocketAddress& addr)
{
int index = RolePP;
m_addrs[index] = addr;
return S_OK;
}
HRESULT CMockTransport::AddPA(const CSocketAddress& addr)
{
int index = RolePA;
m_addrs[index] = addr;
return S_OK;
}
HRESULT CMockTransport::AddAP(const CSocketAddress& addr)
{
int index = RoleAP;
m_addrs[index] = addr;
return S_OK;
}
HRESULT CMockTransport::AddAA(const CSocketAddress& addr)
{
int index = RoleAA;
m_addrs[index] = addr;
return S_OK;
}
HRESULT CMockTransport::SendResponse(SocketRole roleOutput, const CSocketAddress& addr, CRefCountedBuffer& spResponse)
{
m_outputRole = roleOutput;
m_addrDestination = addr;
m_outputstream.Write(spResponse->GetData(), spResponse->GetSize());
return S_OK;
}
bool CMockTransport::HasAddress(SocketRole role)
{
int index = (int)role;
ASSERT(index >= 0);
ASSERT(index <= 3);
return (m_addrs[index].GetPort() != 0);
}
HRESULT CMockTransport::GetSocketAddressForRole(SocketRole role, CSocketAddress* pAddr)
{
HRESULT hr=S_OK;
if (HasAddress(role))
{
*pAddr = m_addrs[(int)role];
}
else
{
hr = E_FAIL;
}
return hr;
}
HRESULT CMockAuthShort::DoAuthCheck(AuthAttributes* pAuthAttributes, AuthResponse *pResponse)
{
const char* c_pszAuthorizedUser = "AuthorizedUser";
pResponse->authCredMech = AuthCredShortTerm;
if (0 == strcmp(pAuthAttributes->szUser, c_pszAuthorizedUser))
{
strcpy(pResponse->szPassword, "password");
pResponse->responseType = AllowConditional;
}
else if (pAuthAttributes->szUser[0] == '\0')
{
pResponse->responseType = Reject;
}
else
{
pResponse->responseType = Unauthorized;
}
return S_OK;
}
HRESULT CMockAuthLong::DoAuthCheck(AuthAttributes* pAuthAttributes, AuthResponse* pResponse)
{
// go ahead and return a realm/nonce. It won't get used unless it's an error response
bool fIsKnownUser = false;
strcpy(pResponse->szRealm, "MyRealm");
strcpy(pResponse->szNonce, "NewNonce");
pResponse->authCredMech = AuthCredLongTerm;
fIsKnownUser = !strcmp(pAuthAttributes->szUser, "AuthorizedUser");
if ((pAuthAttributes->fMessageIntegrityPresent == false) || (fIsKnownUser == false))
{
pResponse->responseType = Unauthorized;
}
else
{
pResponse->responseType = AllowConditional;
strcpy(pResponse->szPassword, "password");
}
return S_OK;
}
// ---------------------------------------------------------------------------------------------
CTestMessageHandler::CTestMessageHandler()
{
CMockTransport::CreateInstanceNoInit(_spTransport.GetPointerPointer());
CMockAuthShort::CreateInstanceNoInit(_spAuthShort.GetPointerPointer());
CMockAuthLong::CreateInstanceNoInit(_spAuthLong.GetPointerPointer());
}
HRESULT CTestMessageHandler::InitBindingRequest(CStunMessageBuilder& builder)
{
StunTransactionId transid;
builder.AddHeader(StunMsgTypeBinding, StunMsgClassRequest);
builder.AddRandomTransactionId(&transid);
builder.FixLengthField();
return S_OK;
}
HRESULT CTestMessageHandler::ValidateMappedAddress(CStunMessageReader& reader, const CSocketAddress& addrClient)
{
HRESULT hr = S_OK;
StunTransactionId transid;
CSocketAddress mappedaddr;
CRefCountedBuffer spBuffer;
Chk(reader.GetStream().GetBuffer(&spBuffer));
reader.GetTransactionId(&transid);
//ChkA(reader.GetAttributeByType(STUN_ATTRIBUTE_XORMAPPEDADDRESS, &attrib));
//ChkA(GetXorMappedAddress(spBuffer->GetData()+attrib.offset, attrib.size, transid, &mappedaddr));
reader.GetXorMappedAddress(&mappedaddr);
ChkIfA(false == addrClient.IsSameIP_and_Port(mappedaddr), E_FAIL);
//ChkA(reader.GetAttributeByType(STUN_ATTRIBUTE_MAPPEDADDRESS, &attrib));
//ChkA(GetMappedAddress(spBuffer->GetData()+attrib.offset, attrib.size, &mappedaddr));
reader.GetMappedAddress(&mappedaddr);
ChkIfA(false == addrClient.IsSameIP_and_Port(mappedaddr), E_FAIL);
Cleanup:
return hr;
}
HRESULT CTestMessageHandler::ValidateOriginAddress(CStunMessageReader& reader, SocketRole socketExpected)
{
HRESULT hr = S_OK;
StunAttribute attrib;
CSocketAddress addrExpected, mappedaddr;
CRefCountedBuffer spBuffer;
Chk(reader.GetStream().GetBuffer(&spBuffer));
Chk(_spTransport->GetSocketAddressForRole(socketExpected, &addrExpected));
ChkA(reader.GetAttributeByType(STUN_ATTRIBUTE_RESPONSE_ORIGIN, &attrib));
ChkA(GetMappedAddress(spBuffer->GetData()+attrib.offset, attrib.size, &mappedaddr));
ChkIfA(false == addrExpected.IsSameIP_and_Port(mappedaddr), E_FAIL);
ChkIfA(socketExpected != _spTransport->m_outputRole, E_FAIL);
Cleanup:
return hr;
}
HRESULT CTestMessageHandler::ValidateResponseAddress(const CSocketAddress& addr)
{
HRESULT hr = S_OK;
if (false == _spTransport->m_addrDestination.IsSameIP_and_Port(addr))
{
hr = E_FAIL;
}
return hr;
}
// Test1 - just do a basic binding request
HRESULT CTestMessageHandler::Test1()
{
HRESULT hr=S_OK;
CStunMessageBuilder builder;
CSocketAddress clientaddr(0x12345678, 9876);
CRefCountedBuffer spBuffer;
CStunThreadMessageHandler handler;
CStunMessageReader reader;
CStunMessageReader::ReaderParseState state;
StunMessageEnvelope message;
_spTransport->Reset();
_spTransport->AddPP(CSocketAddress(0xaaaaaaaa, 1234));
InitBindingRequest(builder);
builder.GetStream().GetBuffer(&spBuffer);
handler.SetResponder(_spTransport);
message.localSocket = RolePP;
message.remoteAddr = clientaddr;
message.spBuffer = spBuffer;
_spTransport->GetSocketAddressForRole(message.localSocket, &(message.localAddr));
handler.ProcessRequest(message);
spBuffer.reset();
_spTransport->GetOutputStream().GetBuffer(&spBuffer);
state = reader.AddBytes(spBuffer->GetData(), spBuffer->GetSize());
ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL);
// validate that the binding response matches our expectations
ChkA(ValidateMappedAddress(reader, clientaddr));
// validate that it came from the server port we expected
ChkA(ValidateOriginAddress(reader, RolePP));
// did we get back the binding request we expected
ChkA(ValidateResponseAddress(clientaddr));
Cleanup:
return hr;
}
// send a binding request to a duplex server instructing it to send back on it's alternate port and alternate IP to an alternate client port
HRESULT CTestMessageHandler::Test2()
{
HRESULT hr=S_OK;
CStunMessageBuilder builder;
CSocketAddress clientaddr(0x12345678, 9876);
CSocketAddress recvaddr;
uint16_t responsePort = 2222;
CRefCountedBuffer spBuffer;
CStunThreadMessageHandler handler;
CStunMessageReader reader;
CStunMessageReader::ReaderParseState state;
::StunChangeRequestAttribute changereq;
StunMessageEnvelope message;
_spTransport->Reset();
_spTransport->AddPP(CSocketAddress(0xaaaaaaaa, 1234));
_spTransport->AddPA(CSocketAddress(0xaaaaaaaa, 1235));
_spTransport->AddAP(CSocketAddress(0xbbbbbbbb, 1234));
_spTransport->AddAA(CSocketAddress(0xbbbbbbbb, 1235));
InitBindingRequest(builder);
builder.AddResponsePort(responsePort);
changereq.fChangeIP = true;
changereq.fChangePort = true;
builder.AddChangeRequest(changereq);
builder.AddResponsePort(responsePort);
builder.GetResult(&spBuffer);
message.localSocket = RolePP;
message.remoteAddr = clientaddr;
message.spBuffer = spBuffer;
_spTransport->GetSocketAddressForRole(RolePP, &(message.localAddr));
handler.SetResponder(_spTransport);
handler.ProcessRequest(message);
spBuffer->Reset();
_spTransport->GetOutputStream().GetBuffer(&spBuffer);
// parse the response
state = reader.AddBytes(spBuffer->GetData(), spBuffer->GetSize());
ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL);
// validate that the binding response matches our expectations
ChkA(ValidateMappedAddress(reader, clientaddr));
ChkA(ValidateOriginAddress(reader, RoleAA));
// did it get sent back to where we thought it was
recvaddr = clientaddr;
recvaddr.SetPort(responsePort);
ChkA(ValidateResponseAddress(recvaddr));
Cleanup:
return hr;
}
// test simple authentication
HRESULT CTestMessageHandler::Test3()
{
HRESULT hr=S_OK;
CStunMessageBuilder builder1, builder2, builder3;
CStunMessageReader reader1, reader2, reader3;
CSocketAddress clientaddr(0x12345678, 9876);
CRefCountedBuffer spBuffer;
CStunThreadMessageHandler handler;
uint16_t errorcode = 0;
CStunMessageReader::ReaderParseState state;
StunMessageEnvelope message;
_spTransport->Reset();
_spTransport->AddPP(CSocketAddress(0xaaaaaaaa, 1234));
handler.SetAuth(_spAuthShort);
handler.SetResponder(_spTransport);
// -----------------------------------------------------------------------
// simulate an authorized user making a request with a valid password
InitBindingRequest(builder1);
builder1.AddStringAttribute(STUN_ATTRIBUTE_USERNAME, "AuthorizedUser");
builder1.AddMessageIntegrityShortTerm("password");
builder1.GetResult(&spBuffer);
message.localSocket = RolePP;
message.remoteAddr = clientaddr;
message.spBuffer = spBuffer;
_spTransport->GetSocketAddressForRole(message.localSocket, &(message.localAddr));
handler.ProcessRequest(message);
// we expect back a response with a valid message integrity field
spBuffer.reset();
_spTransport->m_outputstream.GetBuffer(&spBuffer);
state = reader1.AddBytes(spBuffer->GetData(), spBuffer->GetSize());
ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL);
ChkA(reader1.ValidateMessageIntegrityShort("password"));
// -----------------------------------------------------------------------
// simulate a user with a bad password
spBuffer.reset();
InitBindingRequest(builder2);
builder2.AddStringAttribute(STUN_ATTRIBUTE_USERNAME, "WrongUser");
builder2.AddMessageIntegrityShortTerm("wrongpassword");
builder2.GetResult(&spBuffer);
message.localSocket = RolePP;
message.remoteAddr = clientaddr;
message.spBuffer = spBuffer;
_spTransport->GetSocketAddressForRole(message.localSocket, &(message.localAddr));
_spTransport->ClearStream();
handler.ProcessRequest(message);
spBuffer.reset();
_spTransport->m_outputstream.GetBuffer(&spBuffer);
state = reader2.AddBytes(spBuffer->GetData(), spBuffer->GetSize());
ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL);
errorcode = 0;
ChkA(reader2.GetErrorCode(&errorcode));
ChkIfA(errorcode != ::STUN_ERROR_UNAUTHORIZED, E_FAIL);
// -----------------------------------------------------------------------
// simulate a client sending no credentials - we expect it to fire back with a 400/bad-request
spBuffer.reset();
InitBindingRequest(builder3);
builder3.GetResult(&spBuffer);
message.localSocket = RolePP;
message.remoteAddr = clientaddr;
message.spBuffer = spBuffer;
_spTransport->GetSocketAddressForRole(message.localSocket, &(message.localAddr));
_spTransport->ClearStream();
handler.ProcessRequest(message);
spBuffer.reset();
_spTransport->m_outputstream.GetBuffer(&spBuffer);
state = reader3.AddBytes(spBuffer->GetData(), spBuffer->GetSize());
ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL);
errorcode = 0;
ChkA(reader3.GetErrorCode(&errorcode));
ChkIfA(errorcode != ::STUN_ERROR_BADREQUEST, E_FAIL);
Cleanup:
return hr;
}
// test long-credential authentication
HRESULT CTestMessageHandler::Test4()
{
HRESULT hr=S_OK;
CStunMessageBuilder builder1, builder2;
CStunMessageReader reader1, reader2;
CSocketAddress clientaddr(0x12345678, 9876);
CSocketAddress addrMapped;
CRefCountedBuffer spBuffer;
CStunThreadMessageHandler handler;
uint16_t errorcode = 0;
char szNonce[MAX_STUN_AUTH_STRING_SIZE+1];
char szRealm[MAX_STUN_AUTH_STRING_SIZE+1];
CStunMessageReader::ReaderParseState state;
StunMessageEnvelope message;
_spTransport->Reset();
_spTransport->AddPP(CSocketAddress(0xaaaaaaaa, 1234));
handler.SetAuth(_spAuthLong);
handler.SetResponder(_spTransport);
// -----------------------------------------------------------------------
// simulate a user making a request with no message integrity attribute (or username, or realm)
InitBindingRequest(builder1);
builder1.GetResult(&spBuffer);
message.localSocket = RolePP;
message.remoteAddr = clientaddr;
message.spBuffer = spBuffer;
_spTransport->GetSocketAddressForRole(message.localSocket, &(message.localAddr));
handler.ProcessRequest(message);
spBuffer.reset();
_spTransport->m_outputstream.GetBuffer(&spBuffer);
state = reader1.AddBytes(spBuffer->GetData(), spBuffer->GetSize());
ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL);
// we expect the response back will be a 401 with a provided nonce and realm
Chk(reader1.GetErrorCode(&errorcode));
ChkIfA(reader1.GetMessageClass() != ::StunMsgClassFailureResponse, E_UNEXPECTED);
ChkIf(errorcode != ::STUN_ERROR_UNAUTHORIZED, E_UNEXPECTED);
reader1.GetStringAttributeByType(STUN_ATTRIBUTE_REALM, szRealm, ARRAYSIZE(szRealm));
reader1.GetStringAttributeByType(STUN_ATTRIBUTE_NONCE, szNonce, ARRAYSIZE(szNonce));
// --------------------------------------------------------------------------------
// now simulate the follow-up request
_spTransport->ClearStream();
spBuffer.reset();
InitBindingRequest(builder2);
builder2.AddNonce(szNonce);
builder2.AddRealm(szRealm);
builder2.AddUserName("AuthorizedUser");
builder2.AddMessageIntegrityLongTerm("AuthorizedUser", szRealm, "password");
builder2.GetResult(&spBuffer);
message.localSocket = RolePP;
message.remoteAddr = clientaddr;
message.spBuffer = spBuffer;
_spTransport->GetSocketAddressForRole(message.localSocket, &(message.localAddr));
handler.ProcessRequest(message);
spBuffer.reset();
_spTransport->m_outputstream.GetBuffer(&spBuffer);
state = reader2.AddBytes(spBuffer->GetData(), spBuffer->GetSize());
ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL);
ChkIfA(reader2.GetMessageClass() != ::StunMsgClassSuccessResponse, E_UNEXPECTED);
// should have a mapped address
ChkA(reader2.GetMappedAddress(&addrMapped));
// and the message integrity field should be valid
ChkA(reader2.ValidateMessageIntegrityLong("AuthorizedUser", szRealm, "password"));
Cleanup:
return hr;
}
HRESULT CTestMessageHandler::Run()
{
HRESULT hr = S_OK;
Chk(Test1());
Chk(Test2());
Chk(Test3());
Chk(Test4());
Cleanup:
return hr;
}
/*
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 _TEST_MESSAGE_HANDLER_H
#define _TEST_MESSAGE_HANDLER_H
class CMockTransport :
public CBasicRefCount,
public CObjectFactory<CMockTransport>,
public IStunResponder
{
private:
CSocketAddress m_addrs[4];
public:
virtual HRESULT SendResponse(SocketRole roleOutput, const CSocketAddress& addr, CRefCountedBuffer& spResponse);
virtual bool HasAddress(SocketRole role);
virtual HRESULT GetSocketAddressForRole(SocketRole role, /*out*/ CSocketAddress* pAddr);
CMockTransport();
~CMockTransport();
HRESULT Reset();
HRESULT ClearStream();
CDataStream& GetOutputStream() {return m_outputstream;}
HRESULT AddPP(const CSocketAddress& addr);
HRESULT AddPA(const CSocketAddress& addr);
HRESULT AddAP(const CSocketAddress& addr);
HRESULT AddAA(const CSocketAddress& addr);
CDataStream m_outputstream;
SocketRole m_outputRole;
CSocketAddress m_addrDestination;
ADDREF_AND_RELEASE_IMPL();
};
class CMockAuthShort :
public CBasicRefCount,
public CObjectFactory<CMockAuthShort>,
public IStunAuth
{
public:
virtual HRESULT DoAuthCheck(AuthAttributes* pAuthAttributes, AuthResponse* pResponse);
ADDREF_AND_RELEASE_IMPL();
};
class CMockAuthLong :
public CBasicRefCount,
public CObjectFactory<CMockAuthLong>,
public IStunAuth
{
public:
virtual HRESULT DoAuthCheck(AuthAttributes* pAuthAttributes, AuthResponse* pResponse);
ADDREF_AND_RELEASE_IMPL();
};
class CTestMessageHandler : public IUnitTest
{
private:
CRefCountedPtr<CMockTransport> _spTransport;
CRefCountedPtr<CMockAuthShort> _spAuthShort;
CRefCountedPtr<CMockAuthLong> _spAuthLong;
HRESULT InitBindingRequest(CStunMessageBuilder& builder);
HRESULT ValidateMappedAddress(CStunMessageReader& reader, const CSocketAddress& addr);
HRESULT ValidateOriginAddress(CStunMessageReader& reader, SocketRole socketExpected);
HRESULT ValidateResponseAddress(const CSocketAddress& addr);
public:
CTestMessageHandler();
HRESULT Test1();
HRESULT Test2();
HRESULT Test3();
HRESULT Test4();
HRESULT Run();
UT_DECLARE_TEST_NAME("CTestMessageHandler");
};
#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"
#include "stuncore.h"
#include "testreader.h"
// static
const unsigned char c_requestbytes[] =
"\x00\x01\x00\x58"
"\x21\x12\xa4\x42"
"\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae"
"\x80\x22\x00\x10"
"STUN test client"
"\x00\x24\x00\x04"
"\x6e\x00\x01\xff"
"\x80\x29\x00\x08"
"\x93\x2f\xf9\xb1\x51\x26\x3b\x36"
"\x00\x06\x00\x09"
"\x65\x76\x74\x6a\x3a\x68\x36\x76\x59\x20\x20\x20"
"\x00\x08\x00\x14"
"\x9a\xea\xa7\x0c\xbf\xd8\xcb\x56\x78\x1e\xf2\xb5"
"\xb2\xd3\xf2\x49\xc1\xb5\x71\xa2"
"\x80\x28\x00\x04"
"\xe5\x7a\x3b\xcf";
HRESULT CTestReader::Run()
{
return Test1();
}
HRESULT CTestReader::Test1()
{
HRESULT hr = S_OK;
StunAttribute attrib;
const char* pszExpectedSoftwareAttribute = "STUN test client";
const char* pszExpectedUserName = "evtj:h6vY";
CRefCountedBuffer spBuffer;
char szStringValue[100];
const unsigned char *req = c_requestbytes;
size_t requestsize = sizeof(c_requestbytes)-1; // -1 to get rid of the trailing null
CStunMessageReader reader;
CStunMessageReader::ReaderParseState state;
// reader is expecting at least enough bytes to fill the header
ChkIfA(reader.AddBytes(NULL, 0) != CStunMessageReader::HeaderNotRead, E_FAIL);
ChkIfA(reader.HowManyBytesNeeded() != STUN_HEADER_SIZE, E_FAIL);
state = reader.AddBytes(req, requestsize);
ChkIfA(state != CStunMessageReader::BodyValidated, E_FAIL);
ChkIfA(reader.HowManyBytesNeeded() != 0, E_FAIL);
ChkA(reader.GetBuffer(&spBuffer));
ChkIfA(reader.GetMessageClass() != StunMsgClassRequest, E_FAIL);
ChkIfA(reader.GetMessageType() != StunMsgTypeBinding, E_FAIL);
Chk(reader.GetAttributeByType(STUN_ATTRIBUTE_SOFTWARE, &attrib));
ChkIf(attrib.attributeType != STUN_ATTRIBUTE_SOFTWARE, E_FAIL);
ChkIf(0 != ::strncmp(pszExpectedSoftwareAttribute, (const char*)(spBuffer->GetData() + attrib.offset), attrib.size), E_FAIL);
Chk(reader.GetAttributeByType(STUN_ATTRIBUTE_USERNAME, &attrib));
ChkIf(attrib.attributeType != STUN_ATTRIBUTE_USERNAME, E_FAIL);
ChkIf(0 != ::strncmp(pszExpectedUserName, (const char*)(spBuffer->GetData() + attrib.offset), attrib.size), E_FAIL);
Chk(reader.GetStringAttributeByType(STUN_ATTRIBUTE_SOFTWARE, szStringValue, ARRAYSIZE(szStringValue)));
ChkIf(0 != ::strcmp(pszExpectedSoftwareAttribute, szStringValue), E_FAIL);
ChkIf(reader.HasFingerprintAttribute() == false, E_FAIL);
ChkIf(reader.IsFingerprintAttributeValid() == false, E_FAIL);
Cleanup:
return hr;
}
/*
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 STUN_TEST_READER
#define STUN_TEST_READER
#include "unittest.h"
class CTestReader : public IUnitTest
{
public:
HRESULT Test1();
HRESULT Run();
UT_DECLARE_TEST_NAME("CTestReader");
};
#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"
#include "stuncore.h"
#include "recvfromex.h"
#include "testrecvfromex.h"
#include "stunsocket.h"
HRESULT CTestRecvFromEx::Run()
{
HRESULT hr = S_OK;
Chk(DoTest(false)); // ipv4
Chk(DoTest(true)); // ipv6
Cleanup:
return hr;
}
// This test validates that the EnablePktInfoOption set on a socket allows us to get at the destination IP address for incoming packets
// This is needed so that we can correctly insert an origin address into responses from the server
// Otherwise, the server doesn't have a way of knowing which interface a packet arrived on when it's listening on INADDR_ANY (all available addresses)
HRESULT CTestRecvFromEx::DoTest(bool fIPV6)
{
// create a couple of sockets for send/recv
HRESULT hr = S_OK;
CSocketAddress addrAny(0,0); // INADDR_ANY, random port
sockaddr_in6 addrAnyIPV6 = {};
uint16_t portRecv = 0;
CRefCountedStunSocket spSocketSend, spSocketRecv;
fd_set set = {};
CSocketAddress addrDestForSend;
CSocketAddress addrDestOnRecv;
CSocketAddress addrSrcOnRecv;
CSocketAddress addrSrc;
CSocketAddress addrDst;
char ch = 'x';
sockaddr_storage addrDummy;
socklen_t addrlength;
int ret;
timeval tv = {};
if (fIPV6)
{
addrAnyIPV6.sin6_family = AF_INET6;
addrAny = CSocketAddress(addrAnyIPV6);
}
// create two sockets listening on INADDR_ANY. One for sending and one for receiving
ChkA(CStunSocket::Create(addrAny, RolePP, &spSocketSend));
ChkA(CStunSocket::Create(addrAny, RolePP, &spSocketRecv));
spSocketRecv->EnablePktInfoOption(true);
portRecv = spSocketRecv->GetLocalAddress().GetPort();
// now send to localhost
if (fIPV6)
{
sockaddr_in6 addr6 = {};
addr6.sin6_family = AF_INET6;
::inet_pton(AF_INET6, "::1", &(addr6.sin6_addr));
addrDestForSend = CSocketAddress(addr6);
}
else
{
sockaddr_in addr4 = {};
addr4.sin_family = AF_INET;
::inet_pton(AF_INET, "127.0.0.1", &(addr4.sin_addr));
addrDestForSend = CSocketAddress(addr4);
}
addrDestForSend.SetPort(portRecv);
// flush out any residual packets that might be buffered up on recv socket
ret = -1;
do
{
addrlength = sizeof(addrDummy);
ret = ::recvfrom(spSocketRecv->GetSocketHandle(), &ch, sizeof(ch), MSG_DONTWAIT, (sockaddr*)&addrDummy, &addrlength);
} while (ret >= 0);
// now send some data to ourselves
ret = sendto(spSocketSend->GetSocketHandle(), &ch, sizeof(ch), 0, addrDestForSend.GetSockAddr(), addrDestForSend.GetSockAddrLength());
ChkIfA(ret <= 0, E_UNEXPECTED);
// now wait for the data to arrive
FD_ZERO(&set);
FD_SET(spSocketRecv->GetSocketHandle(), &set);
tv.tv_sec = 3;
ret = select(spSocketRecv->GetSocketHandle()+1, &set, NULL, NULL, &tv);
ChkIfA(ret <= 0, E_UNEXPECTED);
ret = ::recvfromex(spSocketRecv->GetSocketHandle(), &ch, 1, MSG_DONTWAIT, &addrSrcOnRecv, &addrDestOnRecv);
ChkIfA(ret <= 0, E_UNEXPECTED);
ChkIfA(addrSrcOnRecv.IsIPAddressZero(), E_UNEXPECTED);
ChkIfA(addrDestOnRecv.IsIPAddressZero(), E_UNEXPECTED);
Cleanup:
return hr;
}
/*
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 _TESTRECVFROMEX_H
#define _TESTRECVFROMEX_H
#include "unittest.h"
class CTestRecvFromEx : public IUnitTest
{
public:
HRESULT DoTest(bool fUseIPV6);
HRESULT Run();
UT_DECLARE_TEST_NAME("CTestRecvFromEx");
};
#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.
*/
#ifndef UNITTEST_DECLARE_H
#define UNITTEST_DECLARE_H
#include "commonincludes.h"
class IUnitTest
{
public:
virtual ~IUnitTest() {};
virtual HRESULT Run() = 0;
virtual const char* GetName() = 0;
};
#define UT_DECLARE_TEST_NAME(testname) virtual const char* GetName() {return testname;}
#endif
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