1242 lines
35 KiB
C++
1242 lines
35 KiB
C++
/**
|
|
* ystunchan.cpp
|
|
* This file is part of the YATE Project http://YATE.null.ro
|
|
*
|
|
* STUN support module
|
|
*
|
|
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
|
* Copyright (C) 2004-2006 Null Team
|
|
* Author: Marian Podgoreanu
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <yatephone.h>
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
|
|
using namespace TelEngine;
|
|
|
|
namespace { // anonymous
|
|
|
|
/*
|
|
chan.stun parameters
|
|
|
|
uselocalusername Add USERNAME attribute when sending requests
|
|
Defaults to true
|
|
localusername The USERNAME attribute for outgoing requests
|
|
useremoteusername Check USERNAME attribute when receiving requests
|
|
Defaults to true
|
|
remoteusername The USERNAME attribute for incoming requests
|
|
remoteip The initial remote address
|
|
remoteport The initial remote port
|
|
userid The id of the user that requested the filter
|
|
Defaults to 'UNKNOWN'
|
|
|
|
The message's userdata must be a RefObject with the socket to filter
|
|
|
|
*/
|
|
|
|
class YStunError; // STUN errors
|
|
class YStunAttribute; // Message attributes
|
|
class YStunAttributeError; // ERROR-CODE
|
|
class YStunAttributeChangeReq; // CHANGE-REQUEST
|
|
class YStunAttributeAuth; // USERNAME, PASSWORD
|
|
class YStunAttributeAddr; // MAPPED-ADDRESS, RESPONSE-ADDRESS,
|
|
// SOURCE-ADDRESS, CHANGED-ADDRESS, REFLECTED-FROM
|
|
class YStunAttributeUnknown; // The others
|
|
class YStunMessage; // STUN message
|
|
class YStunUtils; // General usefull functions
|
|
class YStunMessageOut; // Outgoing STUN message (message + retransmission info)
|
|
class YStunSocketFilter; // Socket filter for STUN
|
|
class StunHandler; // chan.stun handler
|
|
class YStunPlugin; // The plugin
|
|
|
|
/**
|
|
* Defines
|
|
*/
|
|
// *** Message
|
|
#define STUN_MSG_IDLENGTH 16 // Size in bytes of message id
|
|
#define STUN_MSG_HEADERLENGTH 20 // Size in bytes of message header (type+length+id)
|
|
|
|
// *** Attributes
|
|
#define STUN_ATTR_OPTIONAL 0x7fff // Start value for optional attributes
|
|
#define STUN_ATTR_HEADERLENGTH 4 // Size in bytes of attribute header (type+length)
|
|
#define STUN_ATTR_ADDR_IPV4 0x01 // IPv4 type address for address attributes
|
|
#define STUN_ATTR_CHGREQ_PORT 2 // CHANGE-REQUEST: Change port flag
|
|
#define STUN_ATTR_CHGREQ_ADDR 4 // CHANGE-REQUEST: Change address flag
|
|
|
|
// *** Filters
|
|
#define FILTER_SECURITYLENGTH 8 // The length of the string used in id generation
|
|
// to validate binding responses
|
|
// *** Plugin
|
|
#define STUN_SERVER_DEFAULTPORT 3478 // Server port
|
|
|
|
// Bind request
|
|
#define STUN_BINDINTERVAL_MIN 5000 // Bind request interval margins
|
|
#define STUN_BINDINTERVAL_MAX 60000
|
|
#define STUN_BINDINTERVAL 15000 // Bind request interval in miliseconds
|
|
|
|
// Message retransmission
|
|
#define STUN_RETRANS_COUNT 5 // Retransmission counter
|
|
#define STUN_RETRANS_INTERVAL 500 // Starting value (in miliseconds) for retransmission interval
|
|
|
|
// Set message header values (type+length)
|
|
inline void setHeader(u_int8_t* buffer, u_int16_t type, u_int16_t len)
|
|
{
|
|
buffer[0] = type >> 8;
|
|
buffer[1] = type;
|
|
buffer[2] = len >> 8;
|
|
buffer[3] = len;
|
|
}
|
|
|
|
// Get message header values (type+length)
|
|
inline void getHeader(const u_int8_t* buffer, u_int16_t& type, u_int16_t& len)
|
|
{
|
|
type = buffer[0] << 8 | buffer[1];
|
|
len = buffer[2] << 8 | buffer[3];
|
|
}
|
|
|
|
/**
|
|
* YStunError
|
|
*/
|
|
class YStunError
|
|
{
|
|
public:
|
|
enum Type {
|
|
BadReq = 144, // BAD REQUEST
|
|
Auth = 174, // STALE CREDENDIALS
|
|
};
|
|
static TokenDict s_tokens[]; // Error strings
|
|
};
|
|
|
|
/**
|
|
* Attributes
|
|
*/
|
|
class YStunAttribute : public RefObject
|
|
{
|
|
public:
|
|
enum Type {
|
|
MappedAddress = 0x0001, // MAPPED-ADDRESS
|
|
ResponseAddress = 0x0002, // RESPONSE-ADDRESS
|
|
ChangeRequest = 0x0003, // CHANGE-REQUEST
|
|
SourceAddress = 0x0004, // SOURCE-ADDRESS
|
|
ChangedAddress = 0x0005, // CHANGED-ADDRESS
|
|
Username = 0x0006, // USERNAME
|
|
Password = 0x0007, // PASSWORD
|
|
MessageIntegrity = 0x0008, // MESSAGE-INTEGRITY
|
|
ErrorCode = 0x0009, // ERROR-CODE
|
|
UnknownAttributes = 0x000a, // UNKNOWN-ATTRIBUTES
|
|
ReflectedFrom = 0x000b, // REFLECTED-FROM
|
|
|
|
Unknown, // None of the above
|
|
};
|
|
|
|
inline YStunAttribute(u_int16_t type) : m_type(type) {}
|
|
virtual ~YStunAttribute() {}
|
|
inline u_int16_t type() const
|
|
{ return m_type; }
|
|
const char* text() const
|
|
{ return lookup(m_type,s_tokens); }
|
|
/**
|
|
* Add the attribute to a string.
|
|
* @param dest The destination.
|
|
*/
|
|
virtual void toString(String& dest) = 0;
|
|
/**
|
|
* Create from received buffer.
|
|
* @param buffer Data to process. Doesn't include the attribute header (type+length).
|
|
* @param len Data length (for this attribute).
|
|
* @return False on invalid data.
|
|
*/
|
|
virtual bool fromBuffer(u_int8_t* buffer, u_int16_t len) = 0;
|
|
/**
|
|
* Append this attribute to a buffer to be sent.
|
|
* @param buffer The destination buffer.
|
|
*/
|
|
virtual void toBuffer(DataBlock& buffer) = 0;
|
|
|
|
static TokenDict s_tokens[];
|
|
|
|
private:
|
|
u_int16_t m_type; // Attribute type
|
|
};
|
|
|
|
class YStunAttributeError : public YStunAttribute
|
|
{
|
|
public:
|
|
inline YStunAttributeError(u_int16_t code = 0, const char* text = 0)
|
|
: YStunAttribute(ErrorCode), m_code(code), m_text(text)
|
|
{}
|
|
virtual ~YStunAttributeError() {}
|
|
virtual void toString(String& dest);
|
|
virtual bool fromBuffer(u_int8_t* buffer, u_int16_t len);
|
|
virtual void toBuffer(DataBlock& buffer);
|
|
private:
|
|
u_int16_t m_code; // Error code
|
|
String m_text; // Error string
|
|
};
|
|
|
|
// Change request
|
|
// 4 bytes. Bits 1 and 2 are used
|
|
class YStunAttributeChangeReq : public YStunAttribute
|
|
{
|
|
public:
|
|
inline YStunAttributeChangeReq(bool chg_port = false, bool chg_addr = false)
|
|
: YStunAttribute(ChangeRequest), m_flags(0)
|
|
{
|
|
if (chg_port)
|
|
m_flags |= STUN_ATTR_CHGREQ_PORT;
|
|
if (chg_addr)
|
|
m_flags |= STUN_ATTR_CHGREQ_ADDR;
|
|
}
|
|
virtual ~YStunAttributeChangeReq() {}
|
|
virtual void toString(String& dest);
|
|
virtual bool fromBuffer(u_int8_t* buffer, u_int16_t len);
|
|
virtual void toBuffer(DataBlock& buffer);
|
|
private:
|
|
u_int32_t m_flags; // Change request flags
|
|
};
|
|
|
|
// Username or Password
|
|
// The length MUST be a multiple of 4
|
|
class YStunAttributeAuth : public YStunAttribute
|
|
{
|
|
public:
|
|
inline YStunAttributeAuth(u_int16_t type)
|
|
: YStunAttribute(type)
|
|
{}
|
|
inline YStunAttributeAuth(const char* value, bool username = true)
|
|
: YStunAttribute(username ? Username : Password), m_auth(value)
|
|
{}
|
|
virtual ~YStunAttributeAuth() {}
|
|
virtual void toString(String& dest);
|
|
virtual bool fromBuffer(u_int8_t* buffer, u_int16_t len);
|
|
virtual void toBuffer(DataBlock& buffer);
|
|
private:
|
|
String m_auth; // Username/Password value
|
|
};
|
|
|
|
// IP Address + port
|
|
class YStunAttributeAddr : public YStunAttribute
|
|
{
|
|
public:
|
|
inline YStunAttributeAddr(u_int16_t type)
|
|
: YStunAttribute(type), m_port(0)
|
|
{}
|
|
inline YStunAttributeAddr(u_int16_t type, const String& addr,
|
|
u_int16_t port)
|
|
: YStunAttribute(type), m_addr(addr), m_port(port)
|
|
{}
|
|
virtual ~YStunAttributeAddr() {}
|
|
virtual void toString(String& dest);
|
|
virtual bool fromBuffer(u_int8_t* buffer, u_int16_t len);
|
|
virtual void toBuffer(DataBlock& buffer);
|
|
private:
|
|
String m_addr; // Address
|
|
u_int16_t m_port; // Port
|
|
};
|
|
|
|
// Unknown
|
|
class YStunAttributeUnknown : public YStunAttribute
|
|
{
|
|
public:
|
|
inline YStunAttributeUnknown(u_int16_t type)
|
|
: YStunAttribute(Unknown), m_unknownType(type)
|
|
{}
|
|
virtual ~YStunAttributeUnknown() {}
|
|
virtual void toString(String& dest);
|
|
virtual bool fromBuffer(u_int8_t* buffer, u_int16_t len);
|
|
virtual void toBuffer(DataBlock& buffer);
|
|
private:
|
|
u_int16_t m_unknownType; // The unknown type
|
|
DataBlock m_data; // Data
|
|
};
|
|
|
|
/**
|
|
* YStunMessage
|
|
*/
|
|
class YStunMessage : public RefObject
|
|
{
|
|
public:
|
|
enum Type {
|
|
BindReq = 0x0001, // Binding Request
|
|
BindRsp = 0x0101, // Binding Response
|
|
BindErr = 0x0111, // Binding Error Response
|
|
SecretReq = 0x0002, // Shared Secret Request
|
|
SecretRsp = 0x0102, // Shared Secret Response
|
|
SecretErr = 0x0112, // Shared Secret Error Response
|
|
};
|
|
YStunMessage(Type type, const char* id = 0);
|
|
virtual ~YStunMessage() {}
|
|
inline Type type() const
|
|
{ return m_type; }
|
|
inline const String& id() const
|
|
{ return m_id; }
|
|
const char* text() const
|
|
{ return lookup(m_type,s_tokens); }
|
|
inline void addAttribute(YStunAttribute* attr)
|
|
{ m_attributes.append(attr); }
|
|
YStunAttribute* getAttribute(u_int16_t attrType, bool remove = false);
|
|
void toMessage(Message& msg) const;
|
|
bool toBuffer(DataBlock& buffer) const;
|
|
void print();
|
|
static TokenDict s_tokens[];
|
|
private:
|
|
Type m_type; // Message type
|
|
String m_id; // Message id
|
|
ObjList m_attributes; // Message attributes
|
|
};
|
|
|
|
/**
|
|
* YStunUtils
|
|
*/
|
|
class YStunUtils
|
|
{
|
|
public:
|
|
YStunUtils();
|
|
static bool isStun(const void* data, u_int32_t len, YStunMessage::Type& type);
|
|
static YStunMessage* decode(const void* data, u_int32_t len, bool& isStun);
|
|
// Create an id used to send a binding request
|
|
static void createId(String& id);
|
|
// Send a message through the given socket
|
|
static bool sendMessage(Socket* socket, const YStunMessage* msg,
|
|
const SocketAddr& addr, void* sender = 0);
|
|
// Get the error attribute of a message.
|
|
// Return false if the message doesn't have one
|
|
static bool getAttrError(YStunMessage* msg, String& errStr);
|
|
// Get an address attribute of a message.
|
|
// Return false if the message doesn't have this attribute
|
|
static bool getAttrAddr(YStunMessage* msg, YStunAttribute::Type type,
|
|
String& addr);
|
|
// Get an auth attribute of a message.
|
|
// Return false if the message doesn't have this attribute
|
|
static bool getAttrAuth(YStunMessage* msg, YStunAttribute::Type type,
|
|
String& auth);
|
|
protected:
|
|
static Mutex m_idMutex; // Lock id changes
|
|
static unsigned int m_id; // Used to generate unique id for requests
|
|
};
|
|
|
|
/**
|
|
* YStunMessageOut
|
|
*/
|
|
class YStunMessageOut : public RefObject
|
|
{
|
|
friend class YStunSocketFilter;
|
|
public:
|
|
YStunMessageOut(YStunMessage* msg, const SocketAddr addr, void* sender = 0);
|
|
virtual ~YStunMessageOut();
|
|
inline bool isId(const YStunMessage* msg) const {
|
|
if (!(m_msg && msg))
|
|
return false;
|
|
return msg->id().endsWith(m_msg->id().c_str() + FILTER_SECURITYLENGTH);
|
|
}
|
|
inline bool timeToSend(u_int64_t time)
|
|
{ return time >= m_next; }
|
|
inline bool timeout()
|
|
{ return !m_count; }
|
|
inline bool send(Socket* socket, u_int64_t time) {
|
|
update(time);
|
|
return YStunUtils::sendMessage(socket,m_msg,m_addr,m_sender);
|
|
}
|
|
// Reset retransmission info: m_count, m_interval, m_next
|
|
// Set the address to the new one
|
|
void reset(const SocketAddr& addr);
|
|
|
|
protected:
|
|
inline void update(u_int64_t time) {
|
|
m_count--;
|
|
m_interval *= 2;
|
|
m_next = time + m_interval;
|
|
}
|
|
private:
|
|
YStunMessage* m_msg; // The message
|
|
SocketAddr m_addr; // Remote peer's address
|
|
void* m_sender; // The sender
|
|
u_int16_t m_count; // Retransmission counter
|
|
u_int64_t m_interval; // Retransmission interval
|
|
u_int64_t m_next; // Time for next retransmission
|
|
};
|
|
|
|
/**
|
|
* YStunSocketFilter
|
|
*/
|
|
class YStunSocketFilter : public SocketFilter
|
|
{
|
|
friend class YStunPlugin;
|
|
public:
|
|
YStunSocketFilter();
|
|
virtual ~YStunSocketFilter();
|
|
// Received: call by the socket
|
|
virtual bool received(void* buffer, int length, int flags,
|
|
const struct sockaddr* addr, socklen_t addrlen);
|
|
// Timer handler: Handle retransmission for binding request
|
|
virtual void timerTick(const Time& when);
|
|
// Install the filter. Return false if it fails
|
|
bool install(Socket* sock, const Message* msg);
|
|
protected:
|
|
// Process a received message. Destroy it after processing
|
|
bool processMessage(YStunMessage* msg);
|
|
// Process a received binding request
|
|
// Respond to it
|
|
void processBindReq(YStunMessage* msg);
|
|
// Process a received binding response (error or response)
|
|
void processBindResult(YStunMessage* msg);
|
|
private:
|
|
SocketAddr m_remoteAddr; // Last received packet's address
|
|
bool m_useLocalUsername; // True to use local username when sending bind request
|
|
bool m_useRemoteUsername; // True to use remote username to check bind requests
|
|
String m_localUsername; // Local username
|
|
String m_remoteUsername; // Remote username
|
|
// Filter's user
|
|
String m_userId; // User's id
|
|
// Bind request
|
|
YStunMessageOut* m_bindReq; // The message
|
|
Mutex m_bindReqMutex; // Lock message operations
|
|
u_int64_t m_bindReqNext; // Time for the next request
|
|
// Address authenticated ?
|
|
bool m_notFound; // Flag set when we found the right address for the remote peer
|
|
String m_security; // Random string used to build id for a binding request
|
|
};
|
|
|
|
/**
|
|
* chan.stun message handler
|
|
*/
|
|
class StunHandler : public MessageHandler
|
|
{
|
|
public:
|
|
StunHandler() : MessageHandler("chan.stun") {}
|
|
// Process message. Create and install filter.
|
|
virtual bool received(Message &msg);
|
|
};
|
|
|
|
/**
|
|
* YStunPlugin
|
|
*/
|
|
class YStunPlugin : public Module
|
|
{
|
|
public:
|
|
YStunPlugin();
|
|
virtual ~YStunPlugin();
|
|
virtual void initialize();
|
|
|
|
inline u_int64_t bindInterval()
|
|
{ return m_bindInterval; }
|
|
private:
|
|
u_int32_t m_bindInterval; // Bind request interval
|
|
};
|
|
|
|
/**
|
|
* Local data
|
|
*/
|
|
static YStunPlugin iplugin;
|
|
static Configuration s_cfg;
|
|
|
|
/**
|
|
* YStunError
|
|
*/
|
|
TokenDict YStunError::s_tokens[] = {
|
|
{"BAD REQUEST", BadReq},
|
|
{"STALE CREDENDIALS", Auth},
|
|
{0,0}
|
|
};
|
|
|
|
/**
|
|
* Attributes
|
|
*/
|
|
TokenDict YStunAttribute::s_tokens[] = {
|
|
{"MAPPED-ADDRESS", MappedAddress},
|
|
{"RESPONSE-ADDRESS", ResponseAddress},
|
|
{"CHANGE-REQUEST", ChangeRequest},
|
|
{"SOURCE-ADDRESS", SourceAddress},
|
|
{"CHANGED-ADDRESS", ChangedAddress},
|
|
{"USERNAME", Username},
|
|
{"PASSWORD", Password},
|
|
{"MESSAGE-INTEGRITY", MessageIntegrity},
|
|
{"ERROR-CODE", ErrorCode},
|
|
{"UNKNOWN-ATTRIBUTES", UnknownAttributes},
|
|
{"REFLECTED-FROM", ReflectedFrom},
|
|
{"UNKNOWN", Unknown},
|
|
{0,0}
|
|
};
|
|
|
|
// YStunAttributeError
|
|
void YStunAttributeError::toString(String& dest)
|
|
{
|
|
dest = "";
|
|
dest << m_code << ":" << m_text;
|
|
}
|
|
|
|
bool YStunAttributeError::fromBuffer(u_int8_t* buffer, u_int16_t len)
|
|
{
|
|
// 'len' must be at least 4 and a multiple of 4
|
|
// buffer[2]: Error class (3 bits)
|
|
// buffer[3]: Error code modulo 100 (Values: 0..99)
|
|
if (!(buffer && len >= 4 && (len % 4) == 0))
|
|
return false;
|
|
m_code = (buffer[2] & 0x07) * 100 + (buffer[3] < 100 ? buffer[3] : 0);
|
|
if (len > 4)
|
|
m_text.assign((const char*)buffer + 4,len - 4);
|
|
return true;
|
|
}
|
|
|
|
void YStunAttributeError::toBuffer(DataBlock& buffer)
|
|
{
|
|
u_int8_t header[8] = {0,0,0,0,
|
|
0,0,m_code / 100,m_code % 100};
|
|
setHeader(header,type(),4 + m_text.length());
|
|
DataBlock tmp(header,sizeof(header));
|
|
buffer += tmp;
|
|
buffer.append(m_text);
|
|
}
|
|
|
|
// YStunAttributeChangeReq
|
|
void YStunAttributeChangeReq::toString(String& dest)
|
|
{
|
|
dest = (unsigned int)m_flags;
|
|
}
|
|
|
|
bool YStunAttributeChangeReq::fromBuffer(u_int8_t* buffer, u_int16_t len)
|
|
{
|
|
if (!(buffer && len == 4))
|
|
return false;
|
|
m_flags = buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3];
|
|
return true;
|
|
}
|
|
|
|
void YStunAttributeChangeReq::toBuffer(DataBlock& buffer)
|
|
{
|
|
u_int8_t header[8] = {0,0,0,0,
|
|
m_flags >> 24,m_flags >> 16,m_flags >> 8,m_flags};
|
|
setHeader(header,type(),4);
|
|
DataBlock tmp(header,sizeof(header));
|
|
buffer += tmp;
|
|
}
|
|
|
|
// YStunAttributeAuth
|
|
void YStunAttributeAuth::toString(String& dest)
|
|
{
|
|
dest = m_auth;
|
|
}
|
|
|
|
bool YStunAttributeAuth::fromBuffer(u_int8_t* buffer, u_int16_t len)
|
|
{
|
|
if (!(buffer && (len % 4) == 0))
|
|
return false;
|
|
m_auth.assign((const char*)buffer,len);
|
|
return true;
|
|
}
|
|
|
|
void YStunAttributeAuth::toBuffer(DataBlock& buffer)
|
|
{
|
|
u_int8_t header[4];
|
|
setHeader(header,type(),m_auth.length());
|
|
DataBlock tmp(header,sizeof(header));
|
|
buffer += tmp;
|
|
buffer.append(m_auth);
|
|
}
|
|
|
|
// YStunAttributeAddr
|
|
void YStunAttributeAddr::toString(String& dest)
|
|
{
|
|
dest = "";
|
|
dest << m_addr << ":" << m_port;
|
|
}
|
|
|
|
bool YStunAttributeAddr::fromBuffer(u_int8_t* buffer, u_int16_t len)
|
|
{
|
|
if (!(buffer && len == 8 && buffer[1] == STUN_ATTR_ADDR_IPV4))
|
|
return false;
|
|
m_port = buffer[2] << 8 | buffer[3];
|
|
m_addr = "";
|
|
m_addr << buffer[4] << "." << buffer[5] << "." << buffer[6] << "."
|
|
<< buffer[7];
|
|
return true;
|
|
}
|
|
|
|
void YStunAttributeAddr::toBuffer(DataBlock& buffer)
|
|
{
|
|
u_int8_t header[12] = {0,0,0,0,
|
|
0,STUN_ATTR_ADDR_IPV4,m_port >> 8,m_port,
|
|
0,0,0,0};
|
|
setHeader(header,type(),8);
|
|
for (int start = 0, i = 8; i < 12; i++) {
|
|
int end = m_addr.find('.',start);
|
|
if (end == -1)
|
|
end = m_addr.length();
|
|
if (end != start)
|
|
header[i] = m_addr.substr(start,end - start).toInteger();
|
|
if (end == (int)m_addr.length())
|
|
break;
|
|
start = end + 1;
|
|
}
|
|
DataBlock tmp(header,sizeof(header));
|
|
buffer += tmp;
|
|
}
|
|
|
|
// YStunAttributeUnknown
|
|
void YStunAttributeUnknown::toString(String& dest)
|
|
{
|
|
dest = "";
|
|
dest << "Data length: " << m_data.length();
|
|
}
|
|
|
|
bool YStunAttributeUnknown::fromBuffer(u_int8_t* buffer, u_int16_t len)
|
|
{
|
|
if (!(buffer && (len % 4) == 0))
|
|
return false;
|
|
DataBlock tmp(buffer,len);
|
|
m_data = tmp;
|
|
return true;
|
|
}
|
|
|
|
void YStunAttributeUnknown::toBuffer(DataBlock& buffer)
|
|
{
|
|
u_int8_t header[4];
|
|
setHeader(header,m_unknownType,m_data.length());
|
|
DataBlock tmp(header,sizeof(header));
|
|
buffer += tmp;
|
|
buffer += m_data;
|
|
}
|
|
|
|
/**
|
|
* YStunMessage
|
|
*/
|
|
TokenDict YStunMessage::s_tokens[] = {
|
|
{"BindReq", BindReq},
|
|
{"BindRsp", BindRsp},
|
|
{"BindErr", BindErr},
|
|
{"SecretReq", SecretReq},
|
|
{"SecretRsp", SecretRsp},
|
|
{"SecretErr", SecretErr},
|
|
{0,0}
|
|
};
|
|
|
|
YStunMessage::YStunMessage(Type type, const char* id)
|
|
: m_type(type),
|
|
m_id(id)
|
|
{
|
|
if (!id)
|
|
YStunUtils::createId(m_id);
|
|
}
|
|
|
|
YStunAttribute* YStunMessage::getAttribute(u_int16_t attrType, bool remove)
|
|
{
|
|
ObjList* obj = m_attributes.skipNull();
|
|
for (; obj; obj = obj->skipNext()) {
|
|
YStunAttribute* attr = static_cast<YStunAttribute*>(obj->get());
|
|
if (attr->type() == attrType) {
|
|
if (remove)
|
|
m_attributes.remove(attr,false);
|
|
return attr;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void YStunMessage::toMessage(Message& msg) const
|
|
{
|
|
msg.addParam("message_type",text());
|
|
msg.addParam("message_id",m_id);
|
|
// Add attributes
|
|
ObjList* obj = m_attributes.skipNull();
|
|
for (; obj; obj = obj->skipNext()) {
|
|
YStunAttribute* attr = static_cast<YStunAttribute*>(obj->get());
|
|
String tmp;
|
|
attr->toString(tmp);
|
|
msg.addParam(attr->text(),tmp);
|
|
}
|
|
}
|
|
|
|
bool YStunMessage::toBuffer(DataBlock& buffer) const
|
|
{
|
|
// Create attributes
|
|
DataBlock attr_buffer;
|
|
ObjList* obj = m_attributes.skipNull();
|
|
for(; obj; obj = obj->skipNext()) {
|
|
YStunAttribute* attr = static_cast<YStunAttribute*>(obj->get());
|
|
attr->toBuffer(attr_buffer);
|
|
}
|
|
// Set message buffer
|
|
u_int8_t header[4];
|
|
setHeader(header,m_type,attr_buffer.length());
|
|
buffer.assign(header,sizeof(header));
|
|
buffer.append(m_id);
|
|
buffer.append(attr_buffer);
|
|
return true;
|
|
}
|
|
|
|
void YStunMessage::print()
|
|
{
|
|
Debug(&iplugin,DebugAll,"YStunMessage [%p]. Type: '%s'. ID: '%s'.",
|
|
this,text(),m_id.c_str());
|
|
// Print attributes
|
|
ObjList* obj = m_attributes.skipNull();
|
|
for (; obj; obj = obj->skipNext()) {
|
|
YStunAttribute* attr = static_cast<YStunAttribute*>(obj->get());
|
|
String tmp;
|
|
attr->toString(tmp);
|
|
Debug(&iplugin,DebugAll,"YStunMessage [%p]. Attribute: %s=%s",
|
|
this,attr->text(),tmp.c_str());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* YStunUtils
|
|
*/
|
|
unsigned int YStunUtils::m_id = 1;
|
|
Mutex YStunUtils::m_idMutex(true);
|
|
|
|
YStunUtils::YStunUtils()
|
|
{
|
|
}
|
|
|
|
bool YStunUtils::isStun(const void* data, u_int32_t len,
|
|
YStunMessage::Type& type)
|
|
{
|
|
// Check if received buffer is a STUN message:
|
|
// - Length: Greater then or equal to STUN_MSG_HEADERLENGTH
|
|
// Multiple of 4
|
|
// Match the length field of the header
|
|
// - Type: YStunMessage::Type
|
|
const u_int8_t* buffer = (const u_int8_t*)data;
|
|
// Check length
|
|
if (!(data && len >= STUN_MSG_HEADERLENGTH && !(len % 4)))
|
|
return false;
|
|
u_int16_t msg_type, msg_len;
|
|
getHeader(buffer,msg_type,msg_len);
|
|
//TODO: Check if the message contains a message integrity attribute: Size is different
|
|
if (msg_len != len - STUN_MSG_HEADERLENGTH)
|
|
return false;
|
|
// Check type
|
|
switch (msg_type) {
|
|
case YStunMessage::BindReq:
|
|
case YStunMessage::BindRsp:
|
|
case YStunMessage::BindErr:
|
|
case YStunMessage::SecretReq:
|
|
case YStunMessage::SecretRsp:
|
|
case YStunMessage::SecretErr:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
type = (YStunMessage::Type)msg_type;
|
|
// OK: go on!
|
|
return true;
|
|
}
|
|
|
|
YStunMessage* YStunUtils::decode(const void* data, u_int32_t len, bool& isStun)
|
|
{
|
|
u_int8_t* buffer = (u_int8_t*)data;
|
|
YStunMessage::Type type;
|
|
isStun = YStunUtils::isStun(data,len,type);
|
|
if (!isStun)
|
|
return 0;
|
|
// Get ID
|
|
String id((const char*)buffer + 4,STUN_MSG_IDLENGTH);
|
|
YStunMessage* msg = new YStunMessage(type,id);
|
|
// Get attributes
|
|
u_int32_t i = STUN_MSG_HEADERLENGTH;
|
|
for (; i < len;) {
|
|
// Check if we have an attribute header
|
|
if (i + 4 > len)
|
|
break;
|
|
// Get type & length
|
|
u_int16_t attr_type, attr_len;
|
|
getHeader(buffer+i,attr_type,attr_len);
|
|
i += 4;
|
|
// Check if length matches
|
|
if (i + attr_len > len)
|
|
break;
|
|
// Create object
|
|
YStunAttribute* attr = 0;
|
|
switch (attr_type) {
|
|
// Addresses
|
|
case YStunAttribute::MappedAddress:
|
|
case YStunAttribute::ResponseAddress:
|
|
case YStunAttribute::SourceAddress:
|
|
case YStunAttribute::ChangedAddress:
|
|
case YStunAttribute::ReflectedFrom:
|
|
attr = new YStunAttributeAddr(attr_type);
|
|
break;
|
|
// Error
|
|
case YStunAttribute::ErrorCode:
|
|
attr = new YStunAttributeError(attr_type);
|
|
break;
|
|
// Flags
|
|
case YStunAttribute::ChangeRequest:
|
|
attr = new YStunAttributeChangeReq();
|
|
break;
|
|
// Auth
|
|
case YStunAttribute::Username:
|
|
case YStunAttribute::Password:
|
|
attr = new YStunAttributeAuth(attr_type);
|
|
break;
|
|
//
|
|
case YStunAttribute::MessageIntegrity:
|
|
case YStunAttribute::UnknownAttributes:
|
|
attr = new YStunAttributeUnknown(attr_type);
|
|
break;
|
|
default:
|
|
attr = new YStunAttributeUnknown(attr_type);
|
|
}
|
|
// Parse attribute. Add on success
|
|
if (!attr->fromBuffer(buffer + i,attr_len)) {
|
|
attr->deref();
|
|
break;
|
|
}
|
|
msg->addAttribute(attr);
|
|
// Skip processed data
|
|
i += attr_len;
|
|
}
|
|
if (i < len) {
|
|
msg->deref();
|
|
return 0;
|
|
}
|
|
return msg;
|
|
}
|
|
|
|
void YStunUtils::createId(String& id)
|
|
{
|
|
id = "";
|
|
m_idMutex.lock();
|
|
id << m_id++ << "_";
|
|
m_idMutex.unlock();
|
|
for (; id.length() < STUN_MSG_IDLENGTH;)
|
|
id << (int)random();
|
|
id = id.substr(0,STUN_MSG_IDLENGTH);
|
|
}
|
|
|
|
bool YStunUtils::sendMessage(Socket* socket, const YStunMessage* msg,
|
|
const SocketAddr& addr, void* sender)
|
|
{
|
|
if (!(socket && msg))
|
|
return false;
|
|
XDebug(&iplugin,DebugAll,"Send message ('%s') to '%s:%d'. [%p]",
|
|
msg->text(),addr.host().c_str(),addr.port(),sender);
|
|
DataBlock buffer;
|
|
msg->toBuffer(buffer);
|
|
int result = socket->sendTo(buffer.data(),buffer.length(),addr);
|
|
if (result != Socket::socketError())
|
|
return true;
|
|
if (!socket->canRetry())
|
|
Debug(&iplugin,DebugWarn,"Socket write error: '%s' (%d). [%p]",
|
|
::strerror(socket->error()),socket->error(),sender);
|
|
else
|
|
DDebug(&iplugin,DebugMild,"Socket temporary unavailable: '%s' (%d). [%p]",
|
|
::strerror(socket->error()),socket->error(),sender);
|
|
return false;
|
|
}
|
|
|
|
bool YStunUtils::getAttrError(YStunMessage* msg, String& errStr)
|
|
{
|
|
if (!msg)
|
|
return false;
|
|
YStunAttributeError* errAttr = static_cast<YStunAttributeError*>
|
|
(msg->getAttribute(YStunAttribute::ErrorCode));
|
|
if (!errAttr)
|
|
return false;
|
|
errAttr->toString(errStr);
|
|
return true;
|
|
}
|
|
|
|
bool YStunUtils::getAttrAddr(YStunMessage* msg, YStunAttribute::Type type,
|
|
String& addr)
|
|
{
|
|
if (!msg)
|
|
return false;
|
|
YStunAttributeAddr* attr = 0;
|
|
switch (type) {
|
|
case YStunAttribute::MappedAddress:
|
|
case YStunAttribute::ResponseAddress:
|
|
case YStunAttribute::SourceAddress:
|
|
case YStunAttribute::ChangedAddress:
|
|
case YStunAttribute::ReflectedFrom:
|
|
attr = static_cast<YStunAttributeAddr*>(msg->getAttribute(type));
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
if (!attr)
|
|
return false;
|
|
attr->toString(addr);
|
|
return true;
|
|
}
|
|
|
|
bool YStunUtils::getAttrAuth(YStunMessage* msg, YStunAttribute::Type type,
|
|
String& auth)
|
|
{
|
|
if (!msg)
|
|
return false;
|
|
YStunAttributeAuth* attr = 0;
|
|
switch (type) {
|
|
case YStunAttribute::Username:
|
|
case YStunAttribute::Password:
|
|
attr = static_cast<YStunAttributeAuth*>(msg->getAttribute(type));
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
if (!attr)
|
|
return false;
|
|
attr->toString(auth);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* YStunMessageOut
|
|
*/
|
|
YStunMessageOut::YStunMessageOut(YStunMessage* msg, const SocketAddr addr,
|
|
void* sender)
|
|
: m_msg(msg),
|
|
m_addr(addr),
|
|
m_sender(sender),
|
|
m_count(STUN_RETRANS_COUNT),
|
|
m_interval(STUN_RETRANS_INTERVAL),
|
|
m_next(0)
|
|
{
|
|
}
|
|
|
|
YStunMessageOut::~YStunMessageOut()
|
|
{
|
|
if (m_msg)
|
|
m_msg->deref();
|
|
}
|
|
|
|
void YStunMessageOut::reset(const SocketAddr& addr)
|
|
{
|
|
m_addr = addr;
|
|
m_count = STUN_RETRANS_COUNT;
|
|
m_interval = STUN_RETRANS_INTERVAL;
|
|
m_next = 0;
|
|
}
|
|
|
|
/**
|
|
* YStunSocketFilter
|
|
*/
|
|
YStunSocketFilter::YStunSocketFilter()
|
|
: SocketFilter(),
|
|
m_remoteAddr(AF_INET),
|
|
m_useLocalUsername(false),
|
|
m_useRemoteUsername(false),
|
|
m_bindReq(0),
|
|
m_bindReqMutex(true),
|
|
m_bindReqNext(0),
|
|
m_notFound(true)
|
|
{
|
|
DDebug(&iplugin,DebugAll,"YStunSocketFilter. [%p]",this);
|
|
for (; m_security.length() < FILTER_SECURITYLENGTH; )
|
|
m_security << (int)random();
|
|
m_security = m_security.substr(0,FILTER_SECURITYLENGTH);
|
|
}
|
|
|
|
YStunSocketFilter::~YStunSocketFilter()
|
|
{
|
|
DDebug(&iplugin,DebugAll,"~YStunSocketFilter. [%p]",this);
|
|
}
|
|
|
|
bool YStunSocketFilter::received(void* buffer, int length, int flags,
|
|
const struct sockaddr* addr, socklen_t addrlen)
|
|
{
|
|
bool isStun = false;
|
|
YStunMessage* msg = YStunUtils::decode(buffer,length,isStun);
|
|
if (!isStun)
|
|
return false;
|
|
if (msg) {
|
|
SocketAddr tmp(addr,addrlen);
|
|
if (m_remoteAddr != tmp) {
|
|
if (m_notFound) {
|
|
Debug(&iplugin,DebugNote,
|
|
"Filter remote address changed from '%s:%d' to '%s:%d'. [%p]",
|
|
m_remoteAddr.host().c_str(),m_remoteAddr.port(),
|
|
tmp.host().c_str(),tmp.port(),this);
|
|
m_remoteAddr = tmp;
|
|
// Remote address changed: reset bind request
|
|
m_bindReqMutex.lock();
|
|
if (m_bindReq)
|
|
m_bindReq->reset(m_remoteAddr);
|
|
else
|
|
timerTick(Time());
|
|
m_bindReqMutex.unlock();
|
|
}
|
|
else {
|
|
Debug(&iplugin,DebugInfo,
|
|
"Filter ignoring message from invalid address '%s:%d'. [%p]",
|
|
tmp.host().c_str(),tmp.port(),this);
|
|
msg->deref();
|
|
return true;
|
|
}
|
|
}
|
|
processMessage(msg);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool YStunSocketFilter::install(Socket* sock, const Message* msg)
|
|
{
|
|
if (socket() || !(sock && msg))
|
|
return false;
|
|
// Set data
|
|
m_useLocalUsername = msg->getBoolValue("uselocalusername",true);
|
|
m_localUsername = msg->getValue("localusername");
|
|
m_useRemoteUsername = msg->getBoolValue("useremoteusername",true);
|
|
m_remoteUsername = msg->getValue("remoteusername");
|
|
m_remoteAddr.host(msg->getValue("remoteip"));
|
|
m_remoteAddr.port(msg->getIntValue("remoteport"));
|
|
m_userId = msg->getValue("userid");
|
|
// Install
|
|
if (!sock->installFilter(this)) {
|
|
Debug(&iplugin,DebugGoOn,
|
|
"Error installing filter for '%s'. [%p]",
|
|
m_userId.c_str(),this);
|
|
return false;
|
|
}
|
|
DDebug(&iplugin,DebugAll,
|
|
"Filter installed for '%s'. [%p]",
|
|
m_userId.c_str(),this);
|
|
// Send first bind request
|
|
Time when;
|
|
timerTick(when);
|
|
return true;
|
|
}
|
|
|
|
void YStunSocketFilter::timerTick(const Time& when)
|
|
{
|
|
u_int64_t time = when.msec();
|
|
Lock lock(m_bindReqMutex);
|
|
// Send another request ?
|
|
if (!m_bindReq) {
|
|
// It's time to send ?
|
|
if (time < m_bindReqNext)
|
|
return;
|
|
String id;
|
|
YStunUtils::createId(id);
|
|
id = id.substr(0,FILTER_SECURITYLENGTH) + m_security;
|
|
YStunMessage* req = new YStunMessage(YStunMessage::BindReq,id);
|
|
if (m_useLocalUsername)
|
|
req->addAttribute(new YStunAttributeAuth(m_localUsername));
|
|
m_bindReq = new YStunMessageOut(req,m_remoteAddr,this);
|
|
m_bindReq->send(socket(),time);
|
|
m_bindReqNext = time + iplugin.bindInterval();
|
|
return;
|
|
}
|
|
// We have a pending request
|
|
// Time to send ?
|
|
if (!m_bindReq->timeToSend(time))
|
|
return;
|
|
// Timeout or resend
|
|
if (m_bindReq->timeout()) {
|
|
processBindResult(0);
|
|
m_bindReq->deref();
|
|
m_bindReq = 0;
|
|
}
|
|
else
|
|
m_bindReq->send(socket(),time);
|
|
m_bindReqNext = time + iplugin.bindInterval();
|
|
}
|
|
|
|
bool YStunSocketFilter::processMessage(YStunMessage* msg)
|
|
{
|
|
if (!msg)
|
|
return false;
|
|
DDebug(&iplugin,DebugAll,
|
|
"Filter received %s (%p) from '%s:%d'. Id: '%s'. [%p]",
|
|
msg->text(),msg,m_remoteAddr.host().c_str(),
|
|
m_remoteAddr.port(),msg->id().c_str(),this);
|
|
switch(msg->type()) {
|
|
case YStunMessage::BindReq:
|
|
processBindReq(msg);
|
|
break;
|
|
case YStunMessage::BindRsp:
|
|
case YStunMessage::BindErr:
|
|
m_bindReqMutex.lock();
|
|
if (m_bindReq && m_bindReq->isId(msg)) {
|
|
processBindResult(msg);
|
|
m_bindReqMutex.unlock();
|
|
break;
|
|
}
|
|
m_bindReqMutex.unlock();
|
|
DDebug(&iplugin,DebugNote,
|
|
"Filter: (%p) is a response to a non existing request. [%p]",
|
|
msg,this);
|
|
break;
|
|
default:
|
|
Debug(&iplugin,DebugNote,
|
|
"Filter got unexpected message (%p). [%p]",msg,this);
|
|
}
|
|
msg->deref();
|
|
return true;
|
|
}
|
|
|
|
void YStunSocketFilter::processBindReq(YStunMessage* msg)
|
|
{
|
|
YStunMessage::Type response = YStunMessage::BindRsp;
|
|
String username;
|
|
// Check username
|
|
if (m_useRemoteUsername &&
|
|
(!YStunUtils::getAttrAuth(msg,YStunAttribute::Username,username) ||
|
|
username != m_remoteUsername))
|
|
response = YStunMessage::BindErr;
|
|
// Create response
|
|
YStunMessage* rspMsg = new YStunMessage(response,msg->id());
|
|
rspMsg->addAttribute(new YStunAttributeAuth(username));
|
|
if (response == YStunMessage::BindErr) {
|
|
Debug(&iplugin,DebugInfo,
|
|
"Filter: Bind request (%p) has invalid username. [%p]",msg,this);
|
|
// Add error attribute
|
|
rspMsg->addAttribute(new YStunAttributeError(YStunError::Auth,
|
|
lookup(YStunError::Auth,YStunError::s_tokens)));
|
|
}
|
|
else {
|
|
// Add mapped address attribute
|
|
rspMsg->addAttribute(new YStunAttributeAddr(
|
|
YStunAttribute::MappedAddress,m_remoteAddr.host(),
|
|
m_remoteAddr.port()));
|
|
}
|
|
YStunUtils::sendMessage(socket(),rspMsg,m_remoteAddr,this);
|
|
rspMsg->deref();
|
|
}
|
|
|
|
void YStunSocketFilter::processBindResult(YStunMessage* msg)
|
|
{
|
|
// msg is 0: timeout
|
|
if (!msg) {
|
|
Debug(&iplugin,DebugNote,
|
|
"Filter: Bind request to '%s:%d' timed out. [%p]",
|
|
m_bindReq->m_addr.host().c_str(),m_bindReq->m_addr.port(),this);
|
|
// TODO: WHAT ????
|
|
return;
|
|
}
|
|
// Check username
|
|
if (m_useLocalUsername) {
|
|
String username;
|
|
YStunUtils::getAttrAuth(msg,YStunAttribute::Username,username);
|
|
if (username != m_localUsername) {
|
|
Debug(&iplugin,DebugInfo,
|
|
"Filter: Bind response with bad username from '%s:%d'. We expect '%s' and received '%s'. [%p]",
|
|
m_remoteAddr.host().c_str(),m_remoteAddr.port(),m_remoteUsername.c_str(),username.c_str(),this);
|
|
}
|
|
// Authenticated: notify RTP chan
|
|
else if (m_notFound) {
|
|
Debug(&iplugin,DebugNote,
|
|
"Filter: Response authenticated for '%s:%d' - notifying RTP. [%p]",
|
|
m_remoteAddr.host().c_str(),m_remoteAddr.port(),this);
|
|
m_notFound = false;
|
|
Message* m = new Message("chan.rtp");
|
|
m->addParam("direction","bidir");
|
|
m->addParam("remoteip",m_remoteAddr.host());
|
|
m->addParam("remoteport",String(m_remoteAddr.port()));
|
|
m->addParam("rtpid",m_userId);
|
|
Engine::enqueue(m);
|
|
}
|
|
}
|
|
if (msg->type() == YStunMessage::BindRsp) {
|
|
// Mandatory: MappedAddress, SourceAddress, ChangedAddress
|
|
// Conditional: ReflectedFrom
|
|
// Optional: MessageIntegrity
|
|
String mapped;
|
|
if (YStunUtils::getAttrAddr(msg,YStunAttribute::MappedAddress,mapped))
|
|
DDebug(&iplugin,DebugAll,
|
|
"Filter mapped address: '%s'. [%p]",mapped.c_str(),this);
|
|
else
|
|
Debug(&iplugin,DebugAll,
|
|
"Filter: Invalid message: No MAPPED-ADDRESS attribute. [%p]",
|
|
this);
|
|
}
|
|
else if (msg->type() == YStunMessage::BindErr) {
|
|
// Mandatory: ErrorCode
|
|
// Conditional: UnknownAttributes
|
|
String errStr;
|
|
if (YStunUtils::getAttrError(msg,errStr))
|
|
Debug(&iplugin,DebugAll,
|
|
"Filter: Received error: '%s'. [%p]",errStr.c_str(),this);
|
|
else
|
|
Debug(&iplugin,DebugAll,
|
|
"Filter: Invalid message (%p): No ERROR-CODE attribute. [%p]",
|
|
msg,this);
|
|
}
|
|
else
|
|
return;
|
|
// Result is: error, response
|
|
// TODO: WHAT ????
|
|
// Remove request
|
|
m_bindReq->deref();
|
|
m_bindReq = 0;
|
|
}
|
|
|
|
/**
|
|
* StunHandler
|
|
*/
|
|
bool StunHandler::received(Message& msg)
|
|
{
|
|
Socket* socket = static_cast<Socket*>(msg.userObject("Socket"));
|
|
if (!socket) {
|
|
Debug(&iplugin,DebugGoOn,"StunHandler: No socket to install filter for.");
|
|
return true;
|
|
}
|
|
YStunSocketFilter* filter = new YStunSocketFilter();
|
|
if (!filter->install(socket,&msg))
|
|
filter->destruct();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* YStunPlugin
|
|
*/
|
|
YStunPlugin::YStunPlugin()
|
|
: Module("stun","misc"),
|
|
m_bindInterval(STUN_BINDINTERVAL)
|
|
{
|
|
Output("Loaded module YSTUN");
|
|
}
|
|
|
|
YStunPlugin::~YStunPlugin()
|
|
{
|
|
Output("Unloading module YSTUN");
|
|
}
|
|
|
|
void YStunPlugin::initialize()
|
|
{
|
|
static bool notFirst = false;
|
|
Output("Initializing module YSTUN");
|
|
// Load configuration
|
|
s_cfg = Engine::configFile("ystunchan");
|
|
s_cfg.load();
|
|
// Bind request interval
|
|
m_bindInterval = s_cfg.getIntValue("filters","bindrequest_interval",
|
|
STUN_BINDINTERVAL);
|
|
if (m_bindInterval < STUN_BINDINTERVAL_MIN)
|
|
m_bindInterval = STUN_BINDINTERVAL_MIN;
|
|
else if (m_bindInterval > STUN_BINDINTERVAL_MAX)
|
|
m_bindInterval = STUN_BINDINTERVAL_MAX;
|
|
Debug(this,DebugAll,"Bind request interval set to %u msec.",
|
|
m_bindInterval);
|
|
// Do the first time jobs
|
|
if (notFirst)
|
|
return;
|
|
notFirst = true;
|
|
// Install message handlers
|
|
Engine::install(new StunHandler);
|
|
setup();
|
|
}
|
|
|
|
}; // anonymous namespace
|
|
|
|
/* vi: set ts=8 sw=4 sts=4 noet: */
|