Skip to content

Instantly share code, notes, and snippets.

@roxlu
Created May 25, 2016 19:34
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save roxlu/af4a5310c14d60422893c71790d2b3d7 to your computer and use it in GitHub Desktop.
Save roxlu/af4a5310c14d60422893c71790d2b3d7 to your computer and use it in GitHub Desktop.
STUN + RendezVous Server / Cliet
#include <poly/Log.h>
#include <RendezVousClient.h>
#include <RendezVousTypes.h>
namespace poly {
/* -------------------------------------------------------------- */
RendezVousClientSettings::RendezVousClientSettings()
:rv_port(0)
,stun_port(0)
{
}
bool RendezVousClientSettings::isValid() {
if (0 == rv_port) {
SX_ERROR("RendezVous Server port not set.");
return false;
}
if (0 == stun_port) {
SX_ERROR("Stun Server port not set.");
return false;
}
if (0 == rv_host.size()) {
SX_ERROR("RendezVous Server host not set.");
return false;
}
if (0 == stun_host.size()) {
SX_ERROR("Stun Server host not set.");
return false;
}
return true;
}
/* -------------------------------------------------------------- */
RendezVousClient::RendezVousClient() {
}
RendezVousClient::~RendezVousClient() {
shutdown();
}
int RendezVousClient::init(RendezVousClientSettings cfg) {
StunResult stun_result;
StunClient stun_client;
if (false == cfg.isValid()) {
SX_ERROR("Invalid settings.");
return -1;
}
if (0 != out_buffer.ensureSpaceForWriting(256)) {
SX_ERROR("Failed to allocate some space in our out buffer.");
return -2;
}
if (0 != ssl.init(SSL_FLAG_IS_CLIENT)) {
SX_ERROR("Failed to initialize the ssl context.");
return -2;
}
if (0 != sock.init(&ssl, this)) {
SX_ERROR("Failed to initialize the ssl socket.");
return -3;
}
if (0 != sock.connect(cfg.rv_host, cfg.rv_port)) {
SX_ERROR("Failed to connect to the RendezVous server.");
return -4;
}
if (0 != stun_client.discover(cfg.stun_host, cfg.stun_port, stun_result)) {
SX_ERROR("Failed to discover stun connection type.");
return -5;
}
if (0 != registerWithRendezVousServer("roxlu", stun_result)) {
return -6;
}
stun_result.print();
return 0;
}
int RendezVousClient::shutdown() {
int r = 0;
if (0 != sock.shutdown()) {
SX_ERROR("Failed to cleanly shutdown the ssl socket.");
r -= 10;
}
if (0 != ssl.shutdown()) {
SX_ERROR("Failed to cleanly shutdown the ssl context.");
r -= 20;
}
return r;
}
/* -------------------------------------------------------------- */
int RendezVousClient::registerWithRendezVousServer(const std::string& name, StunResult& sr) {
out_buffer.reset();
out_buffer.appendBigEndianU16(RV_MSG_REGISTER);
out_buffer.appendBigEndianU16(2 + name.length() + 2 + sr.remote_ip.length() + 2);
out_buffer.appendBigEndianU16(name.length());
out_buffer.appendBytes((uint8_t*)name.data(), name.length());
out_buffer.appendBigEndianU16(sr.remote_ip.length());
out_buffer.appendBytes((uint8_t*)sr.remote_ip.data(), sr.remote_ip.length());
out_buffer.appendBigEndianU16(sr.remote_port);
SX_VERBOSE("SENDING: %zu", out_buffer.getNumBytesAvailableToRead());
return sock.send(out_buffer.getReadPtr(), out_buffer.getNumBytesAvailableToRead());
}
/* -------------------------------------------------------------- */
void RendezVousClient::onSslSocketApplicationData(char* data, size_t nbytes) {
SX_VERBOSE("RendezVous client received application data %zu bytes.", nbytes);
}
} /* namespace poly */
/* -*- c++ -*- */
/*
Rendez Vous Client
===================
GENERAL INFO:
The `RendezVousClient` uses STUN to detect what kind of
connection the user has. Then it registers itself with the
rendezvous server. This information for the connection will
reside on the server (until the server decides to drop it).
This information can be used to create a direct connection
between two endpoints.
*/
#ifndef POLY_RENDEZVOUS_CLIENT_H
#define POLY_RENDEZVOUS_CLIENT_H
#include <stdint.h>
#include <string>
#include <poly/Buffer.h>
#include <poly/Socket.h>
#include <poly/SslSocket.h>
#include <poly/SslSocketListener.h>
#include <poly/SslMbedContext.h>
#include <StunClient.h>
#include <StunTypes.h>
namespace poly {
/* -------------------------------------------------------------- */
class RendezVousClientSettings {
public:
RendezVousClientSettings();
bool isValid();
public:
std::string rv_host; /* RendezVous server host. */
uint16_t rv_port; /* RendezVous server port. */
std::string stun_host; /* Stun server host. */
uint16_t stun_port; /* Stun server port. */
};
/* -------------------------------------------------------------- */
class RendezVousClient : public SslSocketListener {
public:
RendezVousClient();
~RendezVousClient();
int init(RendezVousClientSettings cfg);
int shutdown();
void onSslSocketApplicationData(char* data, size_t nbytes);
private:
int registerWithRendezVousServer(const std::string& name, StunResult& sr); /* This will register ourself with the given name and stun result. */
private:
SslMbedContext ssl;
SslSocket sock;
Buffer out_buffer;
};
/* -------------------------------------------------------------- */
} /* namespace poly */
#endif
#include <RendezVousConnection.h>
namespace poly {
RendezVousConnection::RendezVousConnection(ServerSslConnection* conn)
:conn(conn)
,remote_port(0)
{
}
} /* namespace poly */
/*
RendezVous Connection
=======================
Represents a client connection to the RendezVousServer.
*/
#ifndef POLY_RENDEZ_VOUS_CONNECTION_H
#define POLY_RENDEZ_VOUS_CONNECTION_H
#include <string>
#include <poly/Log.h>
#include <poly/ServerSslConnection.h>
namespace poly {
class RendezVousConnection {
public:
RendezVousConnection(ServerSslConnection* conn);
public:
Buffer buffer;
ServerSslConnection* conn;
std::string name;
std::string remote_ip;
uint16_t remote_port;
};
} /* namespace poly */
#endif
#include <assert.h>
#include <poly/Log.h>
#include <RendezVousServer.h>
#include <RendezVousTypes.h>
namespace poly {
RendezVousServer::RendezVousServer() {
}
RendezVousServer::~RendezVousServer() {
shutdown();
}
int RendezVousServer::init(ServerSslSettings cfg) {
SX_DEBUG("Created RendezVousServer on port: %u", cfg.port);
if (NULL != cfg.listener) {
SX_ERROR("It seems like you've already set a listener; you can't.");
return -1;
}
cfg.listener = this;
if (0 != server.init(cfg)) {
SX_ERROR("Failed to initialize the ssl server.");
return -2;
}
return 0;
}
void RendezVousServer::update() {
server.update();
}
int RendezVousServer::shutdown() {
int r = 0;
if (0 != server.shutdown()) {
r -= 10;
}
return r;
}
/* -------------------------------------------------------------- */
int RendezVousServer::onServerSslClientConnected(ServerSslConnection* conn) {
SX_DEBUG("Ssl Client Connected.");
RendezVousConnection* rc = new RendezVousConnection(conn);
connections[conn] = rc;
return 0;
}
int RendezVousServer::onServerSslClientDisconnected(ServerSslConnection* conn) {
SX_DEBUG("Ssl Client Disconnected.");
std::map<ServerSslConnection*, RendezVousConnection*>::iterator it = connections.find(conn);
if (it == connections.end()) {
SX_ERROR("Failed to find a connection; not possible; serious bug.");
return -1;
}
if (NULL == it->second) {
SX_ERROR("Found rendezvous connection is NULL (?)");
}
else {
delete it->second;
it->second = NULL;
}
connections.erase(it);
return 0;
}
/*
This function will be called when the server read some data from a client. We parse
the data that consists of messages that are sent by a `RendezVousClient`.
*/
int RendezVousServer::onServerSslClientData(ServerSslConnection* conn, uint8_t* data, size_t len) {
SX_DEBUG("Ssl Client Data: %zu.", len);
std::map<ServerSslConnection*, RendezVousConnection*>::iterator it = connections.find(conn);
if (it == connections.end()) {
SX_ERROR("Failed to find the RendezVousConnection*.");
return -1;
}
RendezVousConnection* rc = it->second;
if (NULL == rc) {
SX_ERROR("Found rendez voud connection is NULL.");
return -2;
}
if (0 != rc->buffer.appendBytes(data, len)) {
SX_ERROR("Failed to append some bytes to our rendez vous connection buffer.");
return -2;
}
size_t avail = rc->buffer.getNumBytesAvailableToRead();
if (avail < 4) {
return 0;
}
uint16_t msg_type = rc->buffer.peekBigEndianU16();
switch (msg_type) {
case RV_MSG_REGISTER: {
return parseMessageRegister(rc);
}
default: {
SX_ERROR("Unhandled type.");
return -3;
}
}
SX_VERBOSE("msg_type: %u, %s", msg_type, rendezvous_msgtype_to_string(msg_type).c_str());
return 0;
}
/* -------------------------------------------------------------- */
/*
Register Message:
uint16_t msg_type - message type
uint16_t total_nbytes - total bytes in message
uint16_t name_nbytes - length of name
uint8_t * name_nbytes - name
uint16_t remote_ip_nbytes - length of remote ip string
uint8_t * remote_ip_nbytes - remote ip string
*/
int RendezVousServer::parseMessageRegister(RendezVousConnection* rc) {
uint16_t msg_len = 0;
uint16_t name_len = 0;
uint16_t ip_len = 0;
if (NULL == rc) {
SX_ERROR("Given rc is NULL.");
return -1;
}
msg_len = rc->buffer.peekBigEndianU16(2);
if (rc->buffer.getNumBytesAvailableToRead() < (msg_len + 4)) {
SX_DEBUG("Received an incomplete register command; waiting for more data.");
return 0;
}
if (0 != rc->buffer.addNumBytesRead(4)) {
SX_ERROR("Failed to add some bytes read.");
return -2;
}
name_len = rc->buffer.readBigEndianU16();
rc->name = rc->buffer.readString(name_len);
ip_len = rc->buffer.readBigEndianU16();
rc->remote_ip = rc->buffer.readString(ip_len);
rc->remote_port = rc->buffer.readBigEndianU16();
SX_DEBUG("Registered client: %s, %s:%u", rc->name.c_str(), rc->remote_ip.c_str(), rc->remote_port);
return 0;
}
} /* namespace poly */
#ifndef POLY_RENDEZVOUS_SERVER_H
#define POLY_RENDEZVOUS_SERVER_H
#include <stdint.h>
#include <map>
#include <poly/Socket.h>
#include <poly/ServerSsl.h>
#include <poly/ServerSslSettings.h>
#include <poly/ServerSslListener.h>
#include <RendezVousConnection.h>
namespace poly {
class RendezVousServer : public ServerSslListener {
public:
RendezVousServer();
~RendezVousServer();
int init(ServerSslSettings cfg);
void update();
int shutdown();
int onServerSslClientConnected(ServerSslConnection* conn);
int onServerSslClientDisconnected(ServerSslConnection* conn);
int onServerSslClientData(ServerSslConnection* con, uint8_t* data, size_t len);
private:
int parseMessageRegister(RendezVousConnection* rc);
private:
ServerSsl server;
std::map<ServerSslConnection*, RendezVousConnection*> connections;
};
} /* namespace poly */
#endif
#include <RendezVousTypes.h>
namespace poly {
std::string rendezvous_msgtype_to_string(uint16_t t) {
switch (t) {
case RV_MSG_NONE: { return "RV_MSG_NONE"; }
case RV_MSG_REGISTER: { return "RV_MSG_REGISTER"; }
default: { return "UNKNOWN"; }
}
}
} /* namespace poly */
#ifndef POLY_RENDEZ_VOUS_TYPES_H
#define POLY_RENDEZ_VOUS_TYPES_H
#define RV_MSG_NONE 0x0000
#define RV_MSG_REGISTER 0x0001
#include <string>
namespace poly {
std::string rendezvous_msgtype_to_string(uint16_t t);
} /* namespace poly */
#endif
#include <poly/NetworkInterfaces.h>
#include <StunClient.h>
namespace poly {
StunClient::StunClient()
:sock(SOCK_DGRAM)
,in_header(NULL)
,out_header(NULL)
{
memset((void*)&remote_addr, 0x00, sizeof(remote_addr));
srandom(time(NULL));
port = random() % (65535 - 1023) + 1023;
}
int StunClient::discover(const std::string& host, uint16_t remotePort, StunResult& result) {
if (0 == host.size()) {
SX_ERROR("Given host is empty.");
return -1;
}
if (0 != in_buffer.ensureSpaceForWriting(1024)) {
SX_ERROR("Failed to ensure some space that can be used to store received data.");
return -2;
}
if (0 != out_buffer.ensureSpaceForWriting(1024)) {
SX_ERROR("Failed to ensure some space that can be used to generate stun message.");
return -3;
}
if (0 != sock.resolveAddress(host, remotePort, &remote_addr)) {
SX_ERROR("Failed to get the socket address.");
return -4;
}
if (0 != sock.bind(port)) {
SX_ERROR("Failed to bind our UDP socket.");
return -5;
}
in_buffer.reset();
out_buffer.reset();
SX_WARNING("Bound on port: %u", port);
return executeTests(result);
}
/* -------------------------------------------------------------- */
int StunClient::executeTests(StunResult& result) {
std::string r_addr;
std::string r_ip;
uint16_t r_port = 0;
result.type = STUN_TYPE_NONE;
if (0 != executeTest1()) {
SX_VERBOSE("UPD blocked.");
return 0;
}
if (0 != getMappedAddress(r_addr, r_ip, r_port)) {
return -1;
}
if (true == isLocalIp(r_ip)) {
SX_VERBOSE("YES LOCAL IP: %s", r_ip.c_str());
/* @todo perform test 2. */
}
else {
if (0 != executeTest2()) {
SX_ERROR("Failed to execute test 2.");
return -3;
}
result.type = STUN_TYPE_FULL_CONE_NAT;
result.remote_ip = r_ip;
result.remote_port = r_port;
SX_VERBOSE("FULL CONE TEST SUCCEEDED");
}
return 0;
}
int StunClient::executeBinding(bool changeIp, bool changePort) {
if (0 != poly_stun_create_binding_request(out_buffer, &out_header, changeIp, changePort)) {
SX_ERROR("Failed to create the binding request.");
return -1;
}
if (0 != sendAndReceive()) {
SX_ERROR("Failed to sendAndReceive() test 1.");
return -2;
}
return 0;
}
/* -------------------------------------------------------------- */
int StunClient::sendAndReceive() {
int delay = 100;
int delay_total = 0;
if (0 != send()) {
SX_ERROR("Failed to sent output buffer.");
return -1;
}
while (delay_total < 9500) {
SX_VERBOSE("Total delay: %d", delay_total);
if (0 != receive(delay)) {
delay_total += delay;
if (delay < 1600) {
delay *= 2;
}
}
else {
SX_VERBOSE("Received some data.");
return 0;
}
}
SX_VERBOSE("Total delay: %d, timed out", delay_total);
return -2;
}
/* -------------------------------------------------------------- */
int StunClient::receive(int timeoutMillis) {
int r = 0;
if (0 != sock.canRead(0, timeoutMillis * 1e3)) {
SX_ERROR("Cannot read.");
return -1;
}
in_buffer.reset();
r = sock.recvfrom(&remote_addr,
(char*)in_buffer.getWritePtr(),
in_buffer.getNumBytesAvailableToWrite());
if (r < 0) {
SX_ERROR("Failed to read from remote host.");
return -2;
}
if (0 != in_buffer.addNumBytesWritten(r)) {
SX_ERROR("Failed to increment the number of bytes written.");
return -3;
}
if (0 != poly_stun_parse(in_buffer, &in_header, attributes, values)) {
SX_ERROR("Failed to parse the binding response.");
return -4;
}
return 0;
}
int StunClient::send() {
int r = 0;
r = sock.sendto(&remote_addr,
out_buffer.getReadPtr(),
out_buffer.getNumBytesAvailableToRead()
);
if (r < 0) {
SX_ERROR("Failed to send some bytes to the remote addr.");
return -2;
}
if (r != out_buffer.getNumBytesAvailableToRead()) {
SX_ERROR("Didn't sent all data.");
return -3;
}
return 0;
}
/* -------------------------------------------------------------- */
int StunClient::getMappedAddress(std::string& address, std::string& ip, uint16_t& port) {
StunAttributeAddress* addr = NULL;
if (0 != getAddressAttribute(STUN_ATTR_MAPPED_ADDRESS, &addr)) {
return -1;
}
if (NULL == addr) {
SX_ERROR("Returned address is NULL.");
return -2;
}
return addr->toString(address, ip, port);
}
bool StunClient::isLocalIp(const std::string& ip) {
std::vector<NetworkInterface> ifas;
if (0 != poly_get_network_interfaces(ifas)) {
SX_ERROR("Failed to get network interfaces. (exiting)");
exit(EXIT_FAILURE);
}
for (size_t i = 0; i < ifas.size(); ++i) {
if (ifas[i].ip == ip) {
return true;
}
}
return false;
}
/* -------------------------------------------------------------- */
} /* namespace poly */
/*
STUN RFC 3489
==============
GENERAL INFO:
This class implements the `STUN: Simple Traversof of User
Datagram Protocol (UDP) Through Network Address Translators
(NATs)` protocol as described by [rfc3489][0] You create an
instance and call `discover()` with the host and port of the
stun server. The output `result` that you pass into `discover()`
is set to the discovered connection type and filled with the
necessary information that can be used to setup a peer-2-peer
connection. See [this post][1] or the [rfc3489][0] for more
into about what kind of connection types can be found. From the
RFC, you can get these kind of NATs:
FULL CONE NAT:
A full cone NAT is one where all requests from the same
internal IP address and PORT are mapped to the same external
IP and PORT. Furthermore, any external host can send a packet
to the internal host by sending a packet to the mapped
external address.
RESTRICTED CONE NAT:
A restricted cone NAT is one where all requests from the same
internal IP address and PORT are mapped to the same external
IP and PORT (just like FULL CONE NAT). Unlike the a full cone
NAT, an external host (with IP address X) can only send a
packet to the internal host only if the internal host had
previously sent a packet to IP address X.
PORT RESTRICTED CONE NAT:
A port restricted cone NAT is like a restricted cone NAT, but
the restriction includes port numbers. Specifically, an
external host can send a packet, with source IP address X and
source port P, to the internal host only if the internal host
had previously sent a packet to IP address X and port P.
SYMMETRIC NAT:
A symmetric NAT is one where all requests from the same
internal IP address and PORT, to a specific destination IP
address and PORT are mapped to the same external IP address
and port. If the same host sends a packet with the same source
address and port, but to a different destination, a different
mapping is used. Futhermore, only the external host that
receives a packet can send a UDP packet back to the internal
host.
REFERENCES:
[0]: https://tools.ietf.org/html/rfc3489
[1]: https://hassan4u.wordpress.com/2009/05/19/nat-traversal-techniques/
*/
#ifndef POLY_STUN_CLIENT_H
#define POLY_STUN_CLIENT_H
#include <vector>
#include <poly/Socket.h>
#include <poly/Buffer.h>
#include <poly/Log.h>
#include <StunTypes.h>
#include <StunReader.h>
#include <StunWriter.h>
namespace poly {
class StunClient {
public:
StunClient();
int discover(const std::string& host, uint16_t port, StunResult& result);
private:
int sendAndReceive();
int receive(int timeoutMillis);
int send();
int executeTests(StunResult& result);
int executeTest1();
int executeTest2();
int executeTest3();
int executeBinding(bool changeIp, bool changePort);
int findAttribute(uint16_t type, StunAttribute** attr, StunAttributeValue** value);
int getAttribute(uint16_t type, StunAttribute** attr, StunAttributeValue** val);
int getAddressAttribute(uint16_t type, StunAttributeAddress** addr);
int getMappedAddress(std::string& address, std::string& ip, uint16_t& port);
bool isLocalIp(const std::string& ip);
private:
Socket sock;
Buffer in_buffer;
Buffer out_buffer;
StunHeader* in_header;
StunHeader* out_header;
struct sockaddr_in remote_addr;
std::vector<StunAttribute*> attributes;
std::vector<StunAttributeValue*> values;
uint16_t port; /* Port on which we've bound our udp socket. */
};
/* ------------------------------------------------------------------ */
inline int StunClient::findAttribute(uint16_t type, StunAttribute** attr, StunAttributeValue** value) {
if (NULL == attr) {
SX_ERROR("Given attribute result is NULL.");
return -1;
}
if (NULL == value) {
SX_ERROR("Given attribute value is NULL.");
return -2;
}
if (attributes.size() != values.size()) {
SX_ERROR("Parsing failed as attributes.size() != values.size().");
return -3;
}
for (size_t i = 0; i < attributes.size(); ++i) {
if (ntohs(attributes[i]->type) == type) {
*attr = attributes[i];
*value = values[i];
return 0;
}
}
return 0;
}
inline int StunClient::getAttribute(uint16_t type, StunAttribute** attr, StunAttributeValue** val) {
if (NULL == attr) {
SX_ERROR("Given output attr is NULL.");
return -1;
}
if (NULL == val) {
SX_ERROR("Given output val is NULL.");
return -2;
}
if (NULL == in_header
|| 0 == attributes.size()
|| 0 == values.size())
{
SX_ERROR("Failed to parse binding response.");
return -3;
}
if (0 != findAttribute(type, attr, val)) {
SX_ERROR("Failed to find the attribute.");
return -4;
}
return 0;
}
inline int StunClient::getAddressAttribute(uint16_t type, StunAttributeAddress** addr) {
StunAttribute* attr = NULL;
StunAttributeValue* val = NULL;
if (NULL == addr) {
SX_ERROR("Given addr out is NULL.");
return -1;
}
if (0 != getAttribute(type, &attr, &val)) {
return -1;
}
(*addr) = (StunAttributeAddress*)val;
return 0;
}
inline int StunClient::executeTest1() {
return executeBinding(false, false);
}
inline int StunClient::executeTest2() {
return executeBinding(true, true);
}
inline int StunClient::executeTest3() {
return executeBinding(false, true);
}
} /* namesepace poly */
#endif
#ifndef POLY_STUN_PROTOCOL_H
#define POLY_STUN_PROTOCOL_H
#endif
#include <poly/Buffer.h>
#include <StunReader.h>
#include <StunTypes.h>
namespace poly {
int poly_stun_parse(Buffer& buf,
StunHeader** headerRef,
std::vector<StunAttribute*>& attributes,
std::vector<StunAttributeValue*>& values
)
{
StunHeader* hdr = NULL;
uint8_t* ptr = buf.getReadPtr();
size_t nread = 0;
hdr = (StunHeader*)ptr;
*headerRef = hdr;
nread = 20;
if (NULL == headerRef) {
SX_ERROR("Given header is NULL.");
return -1;
}
if (buf.getNumBytesAvailableToRead() < 20) {
SX_ERROR("Not enough bytes in the buffer (%zu).", buf.getNumBytesAvailableToRead());
return -1;
}
if (htons(hdr->length) + 20 > buf.getNumBytesAvailableToRead()) {
SX_VERBOSE("not enough bytes in buffer.");
return -2;
}
attributes.clear();
values.clear();
while ((nread + 4) < buf.getNumBytesAvailableToRead()) {
StunAttribute* attr = (StunAttribute*)(ptr + nread);
if (nread + htons(attr->length) > buf.getNumBytesAvailableToRead()) {
SX_ERROR("Not enough bytes to read the attribute.");
return -3;
}
nread += 4;
switch (ntohs(attr->type)) {
case STUN_ATTR_MAPPED_ADDRESS:
case STUN_ATTR_CHANGED_ADDRESS:
case STUN_ATTR_SOURCE_ADDRESS: {
/*
Q: Here I should actually read from (ptr + nread + 1) as
the first byte of the an address attribute should be
ignored; though when I do tings, the IP address is
incorrect. Is this because of 4 byte alignment and is
the way I'm casting not safe?
*/
StunAttributeAddress* addr = (StunAttributeAddress*)(ptr + nread);
attributes.push_back(attr);
values.push_back(addr);
nread += 8;
break;
}
default: {
SX_DEBUG("Unhandled attribute: %s", stun_attribute_to_string(htons(attr->type)).c_str());
nread += ntohs(attr->length);
continue;
}
}
}
return 0;
}
} /* namespace poly */
#ifndef POLY_STUN_READER_H
#define POLY_STUN_READER_H
#include <vector>
#include <StunTypes.h>
namespace poly {
class Buffer;
int poly_stun_parse(Buffer& buf,
StunHeader** headerRef,
std::vector<StunAttribute*>& attributes,
std::vector<StunAttributeValue*>& values
);
} /* namespace poly */
#endif
#include <poly/Log.h>
#include <StunTypes.h>
namespace poly {
/* ------------------------------------------------------------------ */
std::string stun_attribute_to_string(uint16_t t) {
switch (t) {
case STUN_ATTR_MAPPED_ADDRESS: { return "STUN_ATTR_MAPPED_ADDRESS"; }
case STUN_ATTR_RESPONSE_ADDRESS: { return "STUN_ATTR_RESPONSE_ADDRESS"; }
case STUN_ATTR_CHANGE_REQUEST: { return "STUN_ATTR_CHANGE_REQUEST"; }
case STUN_ATTR_SOURCE_ADDRESS: { return "STUN_ATTR_SOURCE_ADDRESS"; }
case STUN_ATTR_CHANGED_ADDRESS: { return "STUN_ATTR_CHANGED_ADDRESS"; }
case STUN_ATTR_USERNAME: { return "STUN_ATTR_USERNAME"; }
case STUN_ATTR_PASSWORD: { return "STUN_ATTR_PASSWORD"; }
case STUN_ATTR_MESSAGE_INTEGRITY: { return "STUN_ATTR_MESSAGE_INTEGRITY"; }
case STUN_ATTR_ERRORCODE: { return "STUN_ATTR_ERRORCODE"; }
case STUN_ATTR_UNKNOWN_ATTRIBUTES: { return "STUN_ATTR_UNKNOWN_ATTRIBUTES"; }
case STUN_ATTR_REFLECTED_FROM: { return "STUN_ATTR_REFLECTED_FROM"; }
default: { return "UNKNOWN"; }
}
}
std::string stun_type_to_string(uint32_t t) {
switch (t) {
case STUN_TYPE_NONE: { return "STUN_TYPE_NONE"; }
case STUN_TYPE_OPEN_INTERNET: { return "STUN_TYPE_OPEN_INTERNET"; }
case STUN_TYPE_UDP_FIREWALL: { return "STUN_TYPE_UDP_FIREWALL"; }
case STUN_TYPE_FULL_CONE_NAT: { return "STUN_TYPE_FULL_CONE_NAT"; }
case STUN_TYPE_SYMMETRIC_NAT: { return "STUN_TYPE_SYMMETRIC_NAT"; }
case STUN_TYPE_PORT_RESTRICTED_NAT: { return "STUN_TYPE_PORT_RESTRICTED_NAT"; }
case STUN_TYPE_RESTRICTED_NAT: { return "STUN_TYPE_RESTRICTED_NAT"; }
default: { return "UNKNOWN"; }
}
}
/* ------------------------------------------------------------------ */
StunResult::StunResult()
:type(STUN_TYPE_NONE)
,remote_port(0)
{
}
void StunResult::print() {
SX_DEBUG("StunResult.type: %s", stun_type_to_string(type).c_str());
SX_DEBUG("StunResult.remote_port: %u", remote_port);
SX_DEBUG("StunResult.remote_ip: %s", remote_ip.c_str());
}
/* ------------------------------------------------------------------ */
} /* namespace poly */
#ifndef POLY_STUN_TYPES_H
#define POLY_STUN_TYPES_H
#if defined(_WIN32)
#else
# include <arpa/inet.h>
#endif
#include <string>
#include <sstream>
#include <poly/Socket.h>
#define STUN_BIND_REQUEST 0x0001
#define STUN_TYPE_NONE 0
#define STUN_TYPE_OPEN_INTERNET 1
#define STUN_TYPE_UDP_FIREWALL 2
#define STUN_TYPE_FULL_CONE_NAT 3
#define STUN_TYPE_SYMMETRIC_NAT 4
#define STUN_TYPE_PORT_RESTRICTED_NAT 5
#define STUN_TYPE_RESTRICTED_NAT 6
#define STUN_ATTR_MAPPED_ADDRESS 0x0001
#define STUN_ATTR_RESPONSE_ADDRESS 0x0002
#define STUN_ATTR_CHANGE_REQUEST 0x0003
#define STUN_ATTR_SOURCE_ADDRESS 0x0004
#define STUN_ATTR_CHANGED_ADDRESS 0x0005
#define STUN_ATTR_USERNAME 0x0006
#define STUN_ATTR_PASSWORD 0x0007
#define STUN_ATTR_MESSAGE_INTEGRITY 0x0008
#define STUN_ATTR_ERRORCODE 0x0009
#define STUN_ATTR_UNKNOWN_ATTRIBUTES 0x000a
#define STUN_ATTR_REFLECTED_FROM 0x000b
#define STUN_IP4 0x01
namespace poly {
/* ------------------------------------------------------------------ */
std::string stun_attribute_to_string(uint16_t t);
std::string stun_type_to_string(uint32_t t);
/* ------------------------------------------------------------------ */
class StunResult {
public:
StunResult();
void print();
public:
uint32_t type;
uint16_t remote_port;
std::string remote_ip;
};
/* ------------------------------------------------------------------ */
class StunHeader {
public:
uint16_t type;
uint16_t length;
uint32_t id[4];
};
/* ------------------------------------------------------------------ */
class StunAttribute {
public:
uint16_t type;
uint16_t length;
};
/* ------------------------------------------------------------------ */
class StunAttributeValue {};
class StunAttributeAddress : public StunAttributeValue {
public:
struct sockaddr_in getAddress();
int toString(std::string& address, std::string& ip, uint16_t& port);
public:
uint8_t family;
uint16_t port;
uint32_t address;
};
/* ------------------------------------------------------------------ */
inline struct sockaddr_in StunAttributeAddress::getAddress() {
struct sockaddr_in addr;
addr.sin_family = family;
addr.sin_port = port;
addr.sin_addr.s_addr = address;
return addr;
}
inline int StunAttributeAddress::toString(std::string& address,
std::string& ip,
uint16_t& port)
{
struct sockaddr_in addr = getAddress();
if (0 != poly_socket_get_address_info(&addr, ip, port)) {
return -1;
}
std::stringstream ss;
ss << ip.c_str() << ":" << port;
address = ss.str();
return 0;
}
} /* namespace poly */
#endif
#include <stdlib.h>
#include <time.h>
#include <StunWriter.h>
#include <poly/Log.h>
namespace poly {
int poly_stun_create_binding_request(Buffer& buffer, StunHeader** outHeader, bool changeIp, bool changePort) {
StunHeader* hdr = NULL;
buffer.reset();
srand(time(NULL));
if (NULL == outHeader) {
SX_ERROR("Given outHeader is NULL.");
return -1;
}
if (0 != buffer.ensureSpaceForWriting(20)) {
SX_ERROR("Failed to allocate space for writing in the output buffer.");
return -2;
}
*outHeader = (StunHeader*)buffer.getWritePtr();
hdr = *outHeader;
hdr->length = 0;
hdr->type = htons(STUN_BIND_REQUEST);
hdr->id[0] = rand();
hdr->id[1] = rand();
hdr->id[2] = rand();
hdr->id[3] = rand();
if (0 != buffer.addNumBytesWritten(20)) {
SX_ERROR("Failed to set the number of bytes.");
return -3;
}
uint32_t cr_value = 0;
cr_value |= (true == changeIp) ? 4 : 0;
cr_value |= (true == changePort) ? 2 : 0;
if (0 != cr_value) {
buffer.appendBigEndianU16(STUN_ATTR_CHANGE_REQUEST);
buffer.appendBigEndianU16(4);
buffer.appendBigEndianU32(cr_value);
hdr->length = htons(8);
}
return 0;
}
} /* namespace poly */
#ifndef POLY_STUN_WRITER_H
#define POLY_STUN_WRITER_H
#include <poly/Buffer.h>
#include <StunTypes.h>
namespace poly {
int poly_stun_create_binding_request(Buffer& buffer,
StunHeader** outHeader,
bool changeIp = false,
bool changePort = false);
} /* namespace poly */
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment