Commit 7f0b7471 authored by John Selbie's avatar John Selbie

Latest changes

parent 7db6e966
include ../common.inc
PROJECT_TARGET := libcommon.a
PROJECT_SRCS := cmdlineparser.cpp common.cpp getconsolewidth.cpp getmillisecondcounter.cpp logger.cpp prettyprint.cpp refcountobject.cpp stringhelper.cpp
PROJECT_SRCS := cmdlineparser.cpp common.cpp fasthash.cpp getconsolewidth.cpp getmillisecondcounter.cpp logger.cpp prettyprint.cpp refcountobject.cpp stringhelper.cpp
PROJECT_OBJS := $(subst .cpp,.o,$(PROJECT_SRCS))
INCLUDES := $(BOOST_INCLUDE)
PRECOMP_H_GCH := commonincludes.h.gch
......
......@@ -26,6 +26,9 @@
// Use FastHash when the maximum number of elements in the hash table is known at compile time
size_t FastHash_GetHashTableWidth(unsigned int maxConnections);
inline size_t FastHash_Hash(void* ptr)
{
return (size_t)ptr;
......@@ -459,11 +462,17 @@ public:
typedef FastHashBase<K,V> itemnode;
typedef FastHashBase<K,V>* itemnodeptr;
if ((fsize <= 0) || (tsize <= 0))
if (fsize <= 0)
{
return -1;
}
if (tsize == 0)
{
tsize = FastHash_GetHashTableWidth(fsize);
}
ResetTable();
_nodesarray = new Item[fsize];
......
.TH STUNSERVER 1 "Version 1.1" STUNSERVER "User Manuals"
.SH NAME
stunserver \- STUN protocol service (RFC 5389 and RFC 3489)
.SH SYNOPSIS
stunserver [OPTIONS]
.SH DESCRIPTION
stunserver starts a STUN listening service that responds to STUN binding
requests from remote clients. Options are described below.
.SH OPTIONS
The following options are supported.
.\" indent by 4
.in +4
--mode MODE
.br
--primaryinterface INTERFACE
.br
--altinterface INTERFACE
.br
--primaryport PORTNUMBER
.br
--altport PORTNUMBER
.br
--family IPVERSION
.br
--protocol PROTO
.br
--maxconn MAXCONN
.br
--help
.\" restore indentation
.in
Details of each option are as follows.
--mode MODE
.br
Where the MODE parameter specified is either "basic" or "full".
In basic mode, the server listens on a single port. Basic mode is sufficient for basic NAT
traversal scenarios in which a client needs to discover its external IP address
and obtain a port mapping for a local port it is listening on. The STUN
CHANGE-REQUEST attribute is not supported in basic mode.
In full mode, the STUN service listens on two different interfaces and two
different ports on each. A client binding request may specify an option
for the server to send the response back from one of the alternate
interfaces and/or ports. Full mode facilitates clients attempting to
discover NAT behavior and NAT filtering behavior of the network they are on.
Full mode requires two unique IP addresses on the host. When run over TCP,
the service is not able to support a CHANGE-REQUEST attribute from
the client.
If this parameter is not specified, basic mode is the default.
--primaryinterface INTERFACE
.br
Where INTERFACE specified is either a local IP address (e.g. "192.168.1.2")
of the host or the name of a network interface (e.g. "eth0").
The interface or address specified will be used by the service as the primary
listening address.
In basic mode, the default is to bind to all available adapters (INADDR_ANY).
In full mode, the default is to bing to the first non-localhost adapter with
a configured IP address.
--altinterface INTERFACE OR IPADDRESS
.br
Where INTERFACE specified is either a local IP address (e.g. "192.168.1.3")
of the host or the name of a network interface (e.g. "eth1").
This parameter is nearly identical as the --primaryinterface option except
that it specifies the alternate listening address for full mode.
This option is ignored in basic mode. In full mode, the default is to bind
to the second non-localhost adapter with a configured IP address.
--primaryport PORTNUM
.br
Where PORTNUM is a value between 1 to 65535.
This is the primary port the server will bind to for listening for incoming
binding requests. The service will bind both the primary address and the
alternate address to this port.
The default is 3478.
--altport PORTNUM
.br
Where PORTNUM is a value between 1 to 65535.
This is the alternate port the server will bind to for listening for incoming
binding requests. The service will bind both the primary address and the
alternate address to this port.
This option is ignored in basic mode. The default is 3479.
--family IPVERSION
.br
Where IPVERSION is either "4" or "6" to specify the usage of IPV4 or IPV6.
The default family is 4 for IPv4 usage.
--protocol PROTO
.br
Where PROTO is either IP protocol, "udp" or "tcp".
udp is the default.
--maxconn MAXCONN
.br
Where MAXCONN is a value between 1 and 100000.
For TCP mode, this parameter specifies the maximum number of simultaneous
connections that can exist at any given time.
This parameter is ignored when the protocol is UDP.
--verbosity LOGLEVEL
.br
Where LOGLEVEL is a value greater than or equal to 0.
This parameter specifies how much is printed to the console with regards to
initialization, errors, and network activity. A value of 0 specifies a
very minimal amount of output. A value of 1 shows slightly more. A value of
2 shows even more. Specifying 3 will show a lot more.
The default is 0.
--help
.br
Prints this help page
.SH EXAMPLES
stunserver
With no options, starts a basic STUN binding service on UDP port 3478.
stunserver --mode full --primaryinterface 128.34.56.78 --altinterface 128.34.56.79
Above example starts a dual-host STUN service on the the interfaces
identified by the IP address "128.34.56.78" and "128.34.56.79". There are
four UDP socket listeners:
.in +4
128.34.56.78:3478 (Primary IP, Primary Port)
.br
128.34.56.78:3479 (Primary IP, Alternate Port)
.br
128.34.56.79:3478 (Primary IP, Primary Port)
.br
128.34.56.79:3479 (Alternate IP, Alternate Port)
.in
An error occurs if the addresses specified do not exist on the local host
running the service.
.br
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.
.SH AUTHOR
john selbie (jselbie@gmail.com)
/*
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 "polling.h"
#include "fasthash.h"
#ifdef __GNUC__
#ifdef HAS_EPOLL
#pragma message "polling.cpp: EPOLL is available"
#else
#pragma message "polling.cpp: no kernel polling api available!"
#ifndef HAS_EPOLL
#pragma message "polling.cpp: WARNING - EPOLL IS NOT AVAILABLE"
#endif
#endif
......@@ -28,7 +42,7 @@ private:
uint32_t FromNativeFlags(uint32_t eventflags);
public:
virtual HRESULT Initialize();
virtual HRESULT Initialize(size_t maxSockets);
virtual HRESULT Close();
virtual HRESULT Add(int fd, uint32_t eventflags);
virtual HRESULT Remove(int fd);
......@@ -86,13 +100,13 @@ uint32_t CEpoll::FromNativeFlags(uint32_t eventflags)
}
HRESULT CEpoll::Initialize()
HRESULT CEpoll::Initialize(size_t maxSockets)
{
ASSERT(_epollfd == -1);
Close();
_epollfd = epoll_create(1000);
_epollfd = epoll_create(maxSockets); // maxSockets is likely ignored by epoll_create
if (_epollfd == -1)
{
return ERRNOHR;
......@@ -193,7 +207,7 @@ private:
uint32_t _unreadcount;
bool _fInitialized;
FastHash<int, size_t, 100, 101> _hashtable; // maps socket to position in fds
FastHashDynamic<int, size_t> _hashtable; // maps socket to position in fds
void Reindex();
......@@ -203,7 +217,7 @@ private:
bool FindNextEvent(PollEvent* pEvent);
public:
virtual HRESULT Initialize();
virtual HRESULT Initialize(size_t maxSockets);
virtual HRESULT Close();
virtual HRESULT Add(int fd, uint32_t eventflags);
virtual HRESULT Remove(int fd);
......@@ -259,16 +273,20 @@ uint32_t CPoll::FromNativeFlags(uint32_t eventflags)
}
HRESULT CPoll::Initialize()
HRESULT CPoll::Initialize(size_t maxSockets)
{
pollfd pfd = {};
pfd.fd = -1;
_fds.reserve(1000);
_fds.reserve(maxSockets);
_rotation = 0;
_unreadcount = 0;
_hashtable.InitTable(maxSockets, 0);
_fInitialized = true;
return S_OK;
}
......@@ -448,7 +466,7 @@ bool CPoll::FindNextEvent(PollEvent* pEvent)
HRESULT CreatePollingInstance(uint32_t type, IPolling** ppPolling)
HRESULT CreatePollingInstance(uint32_t type, size_t maxSockets, IPolling** ppPolling)
{
HRESULT hr = S_OK;
......@@ -472,12 +490,12 @@ HRESULT CreatePollingInstance(uint32_t type, IPolling** ppPolling)
#ifndef HAS_EPOLL
ChkA(E_FAIL);
#else
ChkA(CEpoll::CreateInstance(ppPolling));
ChkA(CEpoll::CreateInstance(maxSockets, ppPolling));
#endif
}
else if (type == IPOLLING_TYPE_POLL)
{
ChkA(CPoll::CreateInstance(ppPolling));
ChkA(CPoll::CreateInstance(maxSockets, ppPolling));
}
else
{
......
/*
* File: polling.h
* Author: jselbie
*
* Created on January 12, 2012, 5:58 PM
*/
/*
Copyright 2011 John Selbie
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef POLLING_H
#define POLLING_H
......@@ -29,7 +38,7 @@ const uint32_t IPOLLING_ERROR = 0x01 << 6;
class IPolling : public IRefCounted
{
public:
virtual HRESULT Initialize() = 0;
virtual HRESULT Initialize(size_t maxSockets) = 0;
virtual HRESULT Close() = 0;
virtual HRESULT Add(int fd, uint32_t eventflags) = 0;
virtual HRESULT Remove(int fd) = 0;
......@@ -42,7 +51,7 @@ const uint32_t IPOLLING_TYPE_BEST = 0x01 << 0;
const uint32_t IPOLLING_TYPE_EPOLL = 0x01 << 1;
const uint32_t IPOLLING_TYPE_POLL = 0x01 << 2;
HRESULT CreatePollingInstance(uint32_t type, IPolling** ppPolling);
HRESULT CreatePollingInstance(uint32_t type, size_t maxSockets, IPolling** ppPolling);
......
......@@ -28,57 +28,6 @@
#define IS_DIVISIBLE_BY(x, y) ((x % y)==0)
static bool IsPrime(unsigned int val)
{
unsigned int stop;
if (val <= 1)
{
return false;
}
if ((val == 2) || (val == 3) || (val == 5))
{
return false;
}
if (IS_DIVISIBLE_BY(val, 2))
{
return false;
}
stop = (unsigned int)((int)(ceil(sqrt(val))));
for (unsigned int i = 3; i <= stop; i+=2)
{
if (IS_DIVISIBLE_BY(val, i))
{
return false;
}
}
return true;
}
static size_t GetHashTableWidth(unsigned int maxConnections)
{
size_t width;
if (maxConnections >= 10007)
{
return 10007;
}
width = maxConnections;
while (IsPrime(width) == false)
{
width++;
}
return width;
}
// client sockets are edge triggered
const uint32_t EPOLL_CLIENT_READ_EVENT_SET = IPOLLING_EDGETRIGGER | IPOLLING_READ | IPOLLING_RDHUP;
......@@ -290,13 +239,14 @@ HRESULT CTCPStunThread::Init(const TransportAddressSet& tsaListen, const Transpo
{
HRESULT hr = S_OK;
int ret;
size_t hashTableWidth;
int countListen = 0;
int countHandler = 0;
// we shouldn't be initialized at this point
ChkIfA(_pipe[0] != -1, E_UNEXPECTED);
ChkIfA(_fThreadIsValid, E_UNEXPECTED);
_maxConnections = (maxConnections > 0) ? maxConnections : c_MaxNumberOfConnectionsDefault;
// Max sure we didn't accidently pass in anything crazy
ChkIfA(_maxConnections >= 100000, E_INVALIDARG);
......@@ -312,7 +262,6 @@ HRESULT CTCPStunThread::Init(const TransportAddressSet& tsaListen, const Transpo
_tsaListen = tsaListen;
_tsa = tsaHandler;
_spAuth.Attach(pAuth);
......@@ -320,7 +269,7 @@ HRESULT CTCPStunThread::Init(const TransportAddressSet& tsaListen, const Transpo
ChkA(CreatePipes());
ChkA(CreatePollingInstance(IPOLLING_TYPE_BEST, _spPolling.GetPointerPointer()));
ChkA(CreatePollingInstance(IPOLLING_TYPE_BEST, (size_t)_maxConnections, _spPolling.GetPointerPointer()));
// add listen socket to epoll
ASSERT(_fListenSocketsOnEpoll == false);
......@@ -330,15 +279,13 @@ HRESULT CTCPStunThread::Init(const TransportAddressSet& tsaListen, const Transpo
// add read end of pipe to epoll so we can get notified of when a signal to exit has occurred
ChkA(_spPolling->Add(_pipe[0], EPOLL_PIPE_EVENT_SET));
_maxConnections = (maxConnections > 0) ? maxConnections : c_MaxNumberOfConnectionsDefault;
// todo - get "max connections" from an init param
hashTableWidth = GetHashTableWidth(_maxConnections);
ret = _hashConnections1.InitTable(_maxConnections, hashTableWidth);
ret = _hashConnections1.InitTable(_maxConnections, 0);
ChkIfA(ret == -1, E_FAIL);
ret = _hashConnections2.InitTable(_maxConnections, hashTableWidth);
ret = _hashConnections2.InitTable(_maxConnections, 0);
ChkIfA(ret == -1, E_FAIL);
_pNewConnList = &_hashConnections1;
......@@ -404,9 +351,10 @@ void* CTCPStunThread::ThreadFunction(void* pThis)
return NULL;
}
bool CTCPStunThread::IsTimeoutNeeded()
int CTCPStunThread::GetTimeoutSeconds()
{
return ((_pNewConnList->Size() > 0) || (_pOldConnList->Size() > 0));
size_t connCount = _pNewConnList->Size() + _pOldConnList->Size();
return (connCount == 0) ? -1 : (int)c_sweepTimeoutSeconds;
}
bool CTCPStunThread::IsConnectionCountAtMax()
......@@ -430,13 +378,8 @@ void CTCPStunThread::Run()
{
PollEvent pollevent = {};
// wait for a notification
int timeout = -1; // wait forever
int timeout = GetTimeoutSeconds();
CStunSocket* pListenSocket = NULL;
if (IsTimeoutNeeded())
{
timeout = CTCPStunThread::c_sweepTimeoutMilliseconds;
}
// turn off epoll eventing from the listen sockets if we are at max connections
// otherwise, make sure it is enabled.
......@@ -842,9 +785,9 @@ void CTCPStunThread::SweepDeadConnections()
{
time_t timeCurrent = time(NULL);
StunThreadConnectionMap* pSwap = NULL;
// todo - make the timeout scale to the number of active connections
// should we try to scale the timeout based on the active number of connections?
if ((timeCurrent - _timeLastSweep) >= c_sweepTimeoutSeconds)
{
if (_pOldConnList->Size())
......
......@@ -31,7 +31,7 @@
class CTCPStunThread
{
static const int c_sweepTimeoutSeconds = 60;
static const int c_sweepTimeoutMilliseconds = c_sweepTimeoutSeconds * 1000;
int _pipe[2];
HRESULT CreatePipes();
......@@ -98,7 +98,7 @@ class CTCPStunThread
void CloseAllConnections(StunThreadConnectionMap* pConnMap);
void SweepDeadConnections();
void ThreadCleanup();
bool IsTimeoutNeeded();
int GetTimeoutSeconds();
bool IsConnectionCountAtMax();
void CloseConnection(StunConnection* pConn);
......
Usage: stunserver [OPTION]...
Start a STUN server in one of various modes of operation using any of
the [OPTION]s described below:
NAME
stunserver - STUN protocol service (RFC 5389 and RFC 3489)
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.
SYNOPSIS
stunserver [OPTIONS]
--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.
DESCRIPTION
stunserver starts a STUN listening service that responds to STUN binding requests from remote clients. Options are described below.
--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.
OPTIONS
The following options are supported.
--mode MODE
--primaryinterface INTERFACE
--altinterface INTERFACE
--primaryport PORTNUMBER
--altport PORTNUMBER
--family IPVERSION
--protocol PROTO
--maxconn MAXCONN
--help
Details of each option are as follows.
--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.
--mode MODE
Where the MODE parameter specified is either "basic" or "full".
--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 basic mode.
In basic mode, the server listens on a single port. Basic mode is sufficient for basic NAT traversal scenarios in which a client needs to discover its external IP address and obtain a port mapping for a local port it is listening on. The STUN CHANGE-REQUEST attribute is not supported in basic mode.
--family=IPVERSION
IPVERSION is either "4" or "6" to specify the usage of IPV4 or IPV6. If not specified, the default value is "4".
In full mode, the STUN service listens on two different interfaces and two different ports on each. A client binding request may specify an option for the server to send the response back from one of the alternate interfaces and/or ports. Full mode facilitates clients attempting to discover NAT behavior and NAT filtering behavior of the network they are on. Full mode requires two unique IP addresses on the host. When run over TCP, the service is not able to support a CHANGE-REQUEST attribute from the client.
--protocol=PROTO
PROTO is either "udp" or "tcp". "udp" is the default.
If this parameter is not specified, basic mode is the default.
--maxconn=MAXCONN
MAXCONN is a value between 1 and 100000. The default is 1000. This value specifies the maximum number of simultaneous TCP connections allowed (per thread). It's an ignored parameter when the PROTOCOL option is UDP.
--primaryinterface INTERFACE
Where INTERFACE specified is either a local IP address (e.g."192.168.1.2") of the host or the name of a network interface (e.g. "eth0").
--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.
The interface or address specified will be used by the service as the primary listening address.
--help
Prints this help page
In basic mode, the default is to bind to all available adapters (INADDR_ANY). In full mode, the default is to bing to the first non-localhost adapter with a configured IP address.
Examples:
stunserver
With no options, starts a basic STUN binding service on UDP port 3478.
--altinterface INTERFACE OR IPADDRESS
Where INTERFACE specified is either a local IP address (e.g. "192.168.1.3") of the host or the name of a network interface (e.g. "eth1").
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)
This parameter is nearly identical as the --primaryinterface option except that it specifies the alternate listening address for full mode.
An error occurs if the addresses specified do not exist on the local host running the service.
This option is ignored in basic mode. In full mode, the default is to bind to the second non-localhost adapter with a configured IP address.
--primaryport PORTNUM
Where PORTNUM is a value between 1 to 65535.
This is the primary port the server will bind to for listening for incoming binding requests. The service will bind both the primary address and the alternate address to this port.
The default is 3478.
--altport PORTNUM
Where PORTNUM is a value between 1 to 65535.
This is the alternate port the server will bind to for listening for incoming binding requests. The service will bind both the primary address and the alternate address to this port.
This option is ignored in basic mode. The default is 3479.
--family IPVERSION
Where IPVERSION is either "4" or "6" to specify the usage of IPV4 or IPV6.
The default family is 4 for IPv4 usage.
--protocol PROTO
Where PROTO is either IP protocol, "udp" or "tcp".
udp is the default.
--maxconn MAXCONN
Where MAXCONN is a value between 1 and 100000.
For TCP mode, this parameter specifies the maximum number of simultaneous connections that can exist at any given time.
This parameter is ignored when the protocol is UDP.
--verbosity LOGLEVEL
Where LOGLEVEL is a value greater than or equal to 0.
This parameter specifies how much is printed to the console with regards to initialization, errors, and network activity. A value of 0 specifies a very minimal amount of output. A value of 1 shows slightly more. A value of 2 shows even more. Specifying 3 will show a lot more.
The default is 0.
--help
Prints this help page
EXAMPLES
stunserver
With no options, starts a basic STUN binding service on UDP port 3478.
stunserver --mode full --primaryinterface 128.34.56.78 --altinterface 128.34.56.79
Above example starts a dual-host STUN service on the the interfaces identified by the IP address "128.34.56.78" and "128.34.56.79". There
are four UDP socket listeners:
128.34.56.78:3478 (Primary IP, Primary Port)
128.34.56.78:3479 (Primary IP, Alternate Port)
128.34.56.79:3478 (Primary IP, Primary Port)
128.34.56.79:3479 (Alternate IP, Alternate Port)
An error occurs if the addresses specified do not exist on the localhost running the service.
stunserver --mode full --primaryinterface eth0 --altinterface eth1
Same as above, except the interfaces are specified by their names as enumerated by the system. (The "ifconfig" or "ipconfig" command will enumerate available interface names.
AUTHOR
john selbie (jselbie@gmail.com)
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.
......@@ -28,8 +28,9 @@ _pMsgIn(NULL),
_pMsgOut(NULL),
_integrity(), // zero-init
_error(), // zero-init
_fRequestHasResponsePort(), // zero-init,
_transid() // zero-init
_fRequestHasResponsePort(false),
_transid(), // zero-init
_fLegacyMode(false)
{
}
......@@ -87,12 +88,13 @@ HRESULT CStunRequestHandler::ProcessRequestImpl()
// 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
// pre-prep the error message in case we wind up needing senderrorto send it
_error.msgtype = reader.GetMessageType();
_error.msgclass = StunMsgClassFailureResponse;
reader.GetTransactionId(&_transid);
_fLegacyMode = reader.IsMessageLegacyFormat();
// we always try to honor the response port
reader.GetResponsePort(&responseport);
......@@ -163,6 +165,9 @@ void CStunRequestHandler::BuildErrorResponse()
_pMsgOut->spBufferOut->SetSize(0);
builder.GetStream().Attach(_pMsgOut->spBufferOut, true);
// set RFC 3478 mode if the request appears to be that way
builder.SetLegacyMode(_fLegacyMode);
builder.AddHeader((StunMessageType)_error.msgtype, _error.msgclass);
builder.AddTransactionId(_transid);
builder.AddErrorCode(_error.errorcode, "FAILED");
......@@ -208,13 +213,13 @@ HRESULT CStunRequestHandler::ProcessBindingRequest()
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
_pMsgOut->spBufferOut->SetSize(0);
builder.GetStream().Attach(_pMsgOut->spBufferOut, true);
fLegacyFormat = reader.IsMessageLegacyFormat();
// if the client request smells like RFC 3478, then send the resposne back in the same way
builder.SetLegacyMode(_fLegacyMode);
// check for an alternate response port
// check for padding attribute (todo - figure out how to inject padding into the response)
......@@ -323,12 +328,12 @@ HRESULT CStunRequestHandler::ProcessBindingRequest()
if (fSendOriginAddress)
{
builder.AddResponseOriginAddress(addrOrigin, fLegacyFormat); // pass true to send back SOURCE_ADDRESS, otherwise, pass false to send back RESPONSE-ORIGIN
builder.AddResponseOriginAddress(addrOrigin); // pass true to send back SOURCE_ADDRESS, otherwise, pass false to send back RESPONSE-ORIGIN
}
if (fSendOtherAddress)
{
builder.AddOtherAddress(addrOther, fLegacyFormat); // pass true to send back CHANGED-ADDRESS, otherwise, pass false to send back OTHER-ADDRESS
builder.AddOtherAddress(addrOther); // pass true to send back CHANGED-ADDRESS, otherwise, pass false to send back OTHER-ADDRESS
}
// even if this is a legacy client request, we can send back XOR-MAPPED-ADDRESS since it's an optional-understanding attribute
......
......@@ -97,6 +97,7 @@ private:
bool _fRequestHasResponsePort;
StunTransactionId _transid;
bool _fLegacyMode;
bool HasAddress(SocketRole role);
bool IsIPAddressZeroOrInvalid(SocketRole role);
......
......@@ -32,11 +32,17 @@ static int g_sequence_number = 0xaaaaaaaa;
CStunMessageBuilder::CStunMessageBuilder() :
_transactionid()
_transactionid(),
_fLegacyMode(false)
{
;
}
void CStunMessageBuilder::SetLegacyMode(bool fLegacyMode)
{
_fLegacyMode = fLegacyMode;
}
HRESULT CStunMessageBuilder::AddHeader(StunMessageType msgType, StunMessageClass msgClass)
{
......@@ -151,17 +157,27 @@ Cleanup:
HRESULT CStunMessageBuilder::AddAttribute(uint16_t attribType, const void* data, uint16_t size)
{
uint8_t padBytes[4] = {0};
size_t padding;
size_t padding = 0;
HRESULT hr = S_OK;
uint16_t sizeheader = size;
if (data == NULL)
{
size = 0;
}
// attributes always start on a 4-byte boundary
padding = (size % 4) ? (4 - (size % 4)) : 0;
if (_fLegacyMode)
{
// in legacy mode (RFC 3489), the header size of the attribute includes the padding
// in RFC 5389, the attribute header is the exact size of the data, and extra padding bytes are implicitly assumed
sizeheader += padding;
}
// I suppose you can have zero length attributes as an indicator of something
Chk(AddAttributeHeader(attribType, size));
Chk(AddAttributeHeader(attribType, sizeheader));
if (size > 0)
{
......@@ -169,9 +185,8 @@ HRESULT CStunMessageBuilder::AddAttribute(uint16_t attribType, const void* data,
}
// pad with zeros to get the 4-byte alignment
if (size%4)
if (padding > 0)
{
padding = 4 - size%4;
Chk(_stream.Write(padBytes, padding));
}
......@@ -196,8 +211,10 @@ Cleanup:
HRESULT CStunMessageBuilder::AddErrorCode(uint16_t errorNumber, const char* pszReason)
{
HRESULT hr = S_OK;
uint8_t padBytes[4] = {0};
size_t strsize = (pszReason==NULL) ? 0 : strlen(pszReason);
size_t size = strsize + 4;
size_t sizeheader = size;
size_t padding = 0;
uint8_t cl = 0;
uint8_t ernum = 0;
......@@ -205,16 +222,22 @@ HRESULT CStunMessageBuilder::AddErrorCode(uint16_t errorNumber, const char* pszR
ChkIf(strsize >= 128, E_INVALIDARG);
ChkIf(errorNumber < 300, E_INVALIDARG);
ChkIf(errorNumber > 600, E_INVALIDARG);
padding = (size%4) ? (4-size%4) : 0;
// fix for RFC 3489 clients - explicitly do the 4-byte padding alignment on the string with spaces instead of
// padding the message with zeros. Adjust the length field to always be a multiple of 4.
if (size % 4)
if ((size % 4) && _fLegacyMode)
{
padding = 4 - (size % 4);
size = size + padding;
}
if (_fLegacyMode)
{
sizeheader += padding;
}
Chk(AddAttributeHeader(STUN_ATTRIBUTE_ERRORCODE, size));
Chk(AddAttributeHeader(STUN_ATTRIBUTE_ERRORCODE, sizeheader));
Chk(_stream.WriteInt16(0));
......@@ -227,16 +250,14 @@ HRESULT CStunMessageBuilder::AddErrorCode(uint16_t errorNumber, const char* pszR
if (strsize > 0)
{
_stream.Write(pszReason, strsize);
if (padding > 0)
{
const uint32_t spaces = 0x20202020; // four spaces
Chk(_stream.Write(&spaces, padding));
}
}
if (padding > 0)
{
Chk(_stream.Write(padBytes, padding));
}
Cleanup:
return hr;
}
......@@ -255,7 +276,7 @@ HRESULT CStunMessageBuilder::AddUnknownAttributes(const uint16_t* arr, size_t co
// that would make the length of the attribute not a multiple of 4, then repeat one
// attribute.
fPad = !!(count % 2);
fPad = _fLegacyMode && (!!(count % 2));
if (fPad)
{
......@@ -291,16 +312,16 @@ HRESULT CStunMessageBuilder::AddMappedAddress(const CSocketAddress& addr)
return AddMappedAddressImpl(STUN_ATTRIBUTE_MAPPEDADDRESS, addr);
}
HRESULT CStunMessageBuilder::AddResponseOriginAddress(const CSocketAddress& addr, bool fLegacy)
HRESULT CStunMessageBuilder::AddResponseOriginAddress(const CSocketAddress& addr)
{
uint16_t attribid = fLegacy ? STUN_ATTRIBUTE_SOURCEADDRESS : STUN_ATTRIBUTE_RESPONSE_ORIGIN;
uint16_t attribid = _fLegacyMode ? STUN_ATTRIBUTE_SOURCEADDRESS : STUN_ATTRIBUTE_RESPONSE_ORIGIN;
return AddMappedAddressImpl(attribid, addr);
}
HRESULT CStunMessageBuilder::AddOtherAddress(const CSocketAddress& addr, bool fLegacy)
HRESULT CStunMessageBuilder::AddOtherAddress(const CSocketAddress& addr)
{
uint16_t attribid = fLegacy ? STUN_ATTRIBUTE_CHANGEDADDRESS : STUN_ATTRIBUTE_OTHER_ADDRESS;
uint16_t attribid = _fLegacyMode ? STUN_ATTRIBUTE_CHANGEDADDRESS : STUN_ATTRIBUTE_OTHER_ADDRESS;
return AddMappedAddressImpl(attribid, addr);
}
......
......@@ -33,13 +33,17 @@ public:
private:
CDataStream _stream;
StunTransactionId _transactionid;
bool _fLegacyMode;
HRESULT AddMappedAddressImpl(uint16_t attribute, const CSocketAddress& addr);
HRESULT AddMessageIntegrityImpl(uint8_t* key, size_t keysize);
public:
CStunMessageBuilder();
void SetLegacyMode(bool fLegacyMode);
HRESULT AddHeader(StunMessageType msgType, StunMessageClass msgClass);
HRESULT AddBindingRequestHeader();
......@@ -54,8 +58,8 @@ public:
HRESULT AddXorMappedAddress(const CSocketAddress& addr);
HRESULT AddMappedAddress(const CSocketAddress& addr);
HRESULT AddResponseOriginAddress(const CSocketAddress& other, bool fLegacy);
HRESULT AddOtherAddress(const CSocketAddress& other, bool fLegacy);
HRESULT AddResponseOriginAddress(const CSocketAddress& other);
HRESULT AddOtherAddress(const CSocketAddress& other);
HRESULT AddResponsePort(uint16_t port);
HRESULT AddPaddingAttribute(uint16_t paddingSize);
......
......@@ -56,8 +56,8 @@ HRESULT CTestBuilder::Test1()
ChkA(builder.AddStringAttribute(STUN_ATTRIBUTE_SOFTWARE, "FOOBAR"));
ChkA(builder.AddMappedAddress(addr));
ChkA(builder.AddXorMappedAddress(addr));
ChkA(builder.AddOtherAddress(addrOther, false));
ChkA(builder.AddResponseOriginAddress(addrOrigin, false));
ChkA(builder.AddOtherAddress(addrOther));
ChkA(builder.AddResponseOriginAddress(addrOrigin));
ChkA(builder.AddFingerprintAttribute());
ChkA(builder.GetResult(&spBuffer));
......
......@@ -415,6 +415,7 @@ HRESULT CTestClientLogic::Test1()
config.fFilteringTest = false;
config.timeoutSeconds = 10;
config.uMaxAttempts = 2;
config.fTimeoutIsInstant = false;
ChkA(clientlogic.Initialize(config));
......
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