/*
Copyright (C) 2005-2007 Michel de Boer <michel@twinklephone.com>
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <cstring>
#include "stun_transaction.h"
#include "events.h"
#include "log.h"
#include "phone.h"
#include "sys_settings.h"
#include "transaction_mgr.h"
#include "translator.h"
#include "util.h"
#include "audits/memman.h"
extern t_transaction_mgr *transaction_mgr;
extern t_event_queue *evq_trans_layer;
extern t_event_queue *evq_trans_mgr;
extern t_event_queue *evq_sender_udp;
extern t_phone *phone;
bool get_stun_binding(t_user *user_config, unsigned short src_port, unsigned long &mapped_ip,
unsigned short &mapped_port, int &err_code, string &err_reason)
{
list<t_ip_port> destinations =
user_config->get_stun_server().get_h_ip_srv("udp");
if (destinations.empty()) {
// Cannot resolve STUN server address.
log_file->write_header("::get_stun_binding", LOG_NORMAL, LOG_CRITICAL);
log_file->write_raw("Failed to resolve: ");
log_file->write_raw(user_config->get_stun_server().encode());
log_file->write_endl();
log_file->write_raw("Return internal STUN bind error: 404 Not Found");
log_file->write_endl();
log_file->write_footer();
err_code = 404;
err_reason = "Not Found";
return false;
}
int num_transmissions = 0;
int wait_intval = DUR_STUN_START_INTVAL;
t_socket_udp sock(src_port);
sock.connect(destinations.front().ipaddr, destinations.front().port);
// Build STUN request
char buf[STUN_MAX_MESSAGE_SIZE + 1];
StunMessage req_bind;
StunAtrString stun_null_str;
stun_null_str.sizeValue = 0;
stunBuildReqSimple(&req_bind, stun_null_str, false, false);
char req_msg[STUN_MAX_MESSAGE_SIZE];
int req_msg_size = stunEncodeMessage(req_bind, req_msg,
STUN_MAX_MESSAGE_SIZE, stun_null_str, false);
// Send STUN request and retransmit till a response is received.
while (num_transmissions < STUN_MAX_TRANSMISSIONS) {
bool ret;
try {
sock.send(req_msg, req_msg_size);
}
catch (int err) {
// Socket error (probably ICMP error)
// Failover to next destination
log_file->write_report("Send failed. Failover to next destination.",
"::get_stun_binding");
destinations.pop_front();
if (destinations.empty()) {
log_file->write_report("No next destination for failover.",
"::get_stun_binding");
break;
}
num_transmissions = 0;
wait_intval = DUR_STUN_START_INTVAL;
sock.connect(destinations.front().ipaddr, destinations.front().port);
continue;
}
log_file->write_header("::get_stun_binding", LOG_STUN);
log_file->write_raw("Send to: ");
log_file->write_raw(h_ip2str(destinations.front().ipaddr));
log_file->write_raw(":");
log_file->write_raw(destinations.front().port);
log_file->write_endl();
log_file->write_raw(stunMsg2Str(req_bind));
log_file->write_footer();
try {
ret = sock.select_read(wait_intval);
}
catch (int err) {
// Socket error (probably ICMP error)
// Failover to next destination
log_file->write_report("Select failed. Failover to next destination.",
"::get_stun_binding");
destinations.pop_front();
if (destinations.empty()) {
log_file->write_report("No next destination for failover.",
"::get_stun_binding");
break;
}
num_transmissions = 0;
wait_intval = DUR_STUN_START_INTVAL;
sock.connect(destinations.front().ipaddr, destinations.front().port);
continue;
}
if (!ret) {
// Time out
num_transmissions++;
if (wait_intval < DUR_STUN_MAX_INTVAL) {
wait_intval *= 2;
}
continue;
}
// A message has been received
int resp_msg_size;
try {
resp_msg_size = sock.recv(buf, STUN_MAX_MESSAGE_SIZE + 1);
}
catch (int err) {
// Socket error (probably ICMP error)
// Failover to next destination
log_file->write_report("Recv failed. Failover to next destination.",
"::get_stun_binding");
destinations.pop_front();
if (destinations.empty()) {
log_file->write_report("No next destination for failover.",
"::get_stun_binding");
break;
}
num_transmissions = 0;
wait_intval = DUR_STUN_START_INTVAL;
sock.connect(destinations.front().ipaddr, destinations.front().port);
continue;
}
StunMessage resp_bind;
if (!stunParseMessage(buf, resp_msg_size, resp_bind, false)) {
log_file->write_report(
"Received faulty STUN message", "::get_stun_binding",
LOG_STUN);
num_transmissions++;
if (wait_intval < DUR_STUN_MAX_INTVAL) {
wait_intval *= 2;
}
continue;
}
log_file->write_header("::get_stun_binding", LOG_STUN);
log_file->write_raw("Received from: ");
log_file->write_raw(h_ip2str(destinations.front().ipaddr));
log_file->write_raw(":");
log_file->write_raw(destinations.front().port);
log_file->write_endl();
log_file->write_raw(stunMsg2Str(resp_bind));
log_file->write_footer();
// Check if id in msgHdr matches
if (!stunEqualId(resp_bind, req_bind)) {
num_transmissions++;
if (wait_intval < DUR_STUN_MAX_INTVAL) {
wait_intval *= 2;
}
continue;
}
if (resp_bind.msgHdr.msgType == BindResponseMsg &&
resp_bind.hasMappedAddress) {
// Bind response received
mapped_ip = resp_bind.mappedAddress.ipv4.addr;
mapped_port = resp_bind.mappedAddress.ipv4.port;
return true;
}
if (resp_bind.msgHdr.msgType == BindErrorResponseMsg &&
resp_bind.hasErrorCode)
{
// Bind error received
err_code = resp_bind.errorCode.errorClass * 100 +
resp_bind.errorCode.number;
char s[STUN_MAX_STRING + 1];
strncpy(s, resp_bind.errorCode.reason, STUN_MAX_STRING);
s[STUN_MAX_STRING] = 0;
err_reason = s;
return false;
}
// A wrong response has been received.
log_file->write_report(
"Invalid STUN response received", "::get_stun_binding",
LOG_NORMAL);
err_code = 500;
err_reason = "Server Error";
return false;
}
// Request timed out
log_file->write_report("STUN request timeout", "::get_stun_binding",
LOG_NORMAL);
err_code = 408;
err_reason = "Request Timeout";
return false;
}
bool stun_discover_nat(t_phone_user *pu, string &err_msg) {
t_user *user_config = pu->get_user_profile();
pu->use_stun = false;
pu->use_nat_keepalive = false;
list<t_ip_port> destinations =
user_config->get_stun_server().get_h_ip_srv("udp");
if (destinations.empty()) {
// Cannot resolve STUN server address.
log_file->write_header("::main", LOG_NORMAL, LOG_CRITICAL);
log_file->write_raw("Failed to resolve: ");
log_file->write_raw(user_config->get_stun_server().encode());
log_file->write_endl();
log_file->write_footer();
err_msg = TRANSLATE("Cannot resolve STUN server: %1");
err_msg = replace_first(err_msg, "%1", user_config->get_stun_server().encode());
return false;
}
while (!destinations.empty()) {
StunAddress4 stun_ip4;
stun_ip4.addr = destinations.front().ipaddr;
stun_ip4.port = destinations.front().port;
NatType nat_type = stunNatType(stun_ip4, false);
log_file->write_header("::main");
log_file->write_raw("STUN NAT type discovery for ");
log_file->write_raw(user_config->get_profile_name());
log_file->write_endl();
log_file->write_raw("NAT type: ");
log_file->write_raw(stunNatType2Str(nat_type));
log_file->write_endl();
log_file->write_footer();
switch (nat_type) {
case StunTypeOpen:
// STUN is not needed.
return true;
case StunTypeSymNat:
err_msg = TRANSLATE("You are behind a symmetric NAT.\nSTUN will not work.\nConfigure a public IP address in the user profile\nand create the following static bindings (UDP) in your NAT.");
err_msg += "\n\n";
err_msg += TRANSLATE("public IP: %1 --> private IP: %2 (SIP signaling)");
err_msg = replace_first(err_msg, "%1", int2str(sys_config->get_sip_udp_port()));
err_msg = replace_first(err_msg, "%2", int2str(sys_config->get_sip_udp_port()));
err_msg += "\n";
err_msg += TRANSLATE("public IP: %1-%2 --> private IP: %3-%4 (RTP/RTCP)");
err_msg = replace_first(err_msg, "%1", int2str(sys_config->get_rtp_port()));
err_msg = replace_first(err_msg, "%2", int2str(sys_config->get_rtp_port() + 5));
err_msg = replace_first(err_msg, "%3", int2str(sys_config->get_rtp_port()));
err_msg = replace_first(err_msg, "%4", int2str(sys_config->get_rtp_port() + 5));
return false;
case StunTypeSymFirewall:
// STUN is not needed as we are on a pubic IP.
// NAT keep alive is needed however to keep the firewall open.
pu->use_nat_keepalive = true;
return true;
case StunTypeBlocked:
destinations.pop_front();
// The code for NAT type discovery does not handle
// ICMP errors. So if the conclusion is that the network
// connection is blocked, it might be due to a down STUN
// server. Try alternative destination if avaliable.
if (destinations.empty()) {
err_msg = TRANSLATE("Cannot reach the STUN server: %1");
err_msg = replace_first(err_msg, "%1",
user_config->get_stun_server().encode());
err_msg += "\n\n";
err_msg += TRANSLATE("If you are behind a firewall then you need to open the following UDP ports.");
err_msg += "\n";
err_msg += TRANSLATE("Port %1 (SIP signaling)");
err_msg = replace_first(err_msg, "%1",
int2str(sys_config->get_sip_udp_port()));
err_msg += "\n";
err_msg += TRANSLATE("Ports %1-%2 (RTP/RTCP)");
err_msg = replace_first(err_msg, "%1",
int2str(sys_config->get_rtp_port()));
err_msg = replace_first(err_msg, "%2",
int2str(sys_config->get_rtp_port() + 5));
return false;
}
log_file->write_report("Failover to next destination.",
"::stun_discover_nat");
break;
case StunTypeFailure:
destinations.pop_front();
log_file->write_report("Failover to next destination.",
"::stun_discover_nat");
break;
default:
// Use STUN.
pu->use_stun = true;
pu->use_nat_keepalive = true;
return true;
}
}
err_msg = TRANSLATE("NAT type discovery via STUN failed.");
return false;
}
// Main function for STUN listener thread for media STUN requests.
void *stun_listen_main(void *arg) {
char buf[STUN_MAX_MESSAGE_SIZE + 1];
int data_size;
t_socket_udp *sock = (t_socket_udp *)arg;
while(true) {
try {
data_size = sock->recv(buf, STUN_MAX_MESSAGE_SIZE + 1);
} catch (int err) {
string msg("Failed to receive STUN response for media.\n");
msg += get_error_str(err);
log_file->write_report(msg, "::stun_listen_main",
LOG_NORMAL, LOG_CRITICAL);
// The request will timeout, no need to send a response now.
return NULL;
}
StunMessage m;
if (!stunParseMessage(buf, data_size, m, false)) {
log_file->write_report("Faulty STUN message", "::stun_listen_main");
continue;
}
log_file->write_header("::stun_listen_main", LOG_STUN);
log_file->write_raw("Received: ");
log_file->write_raw(stunMsg2Str(m));
log_file->write_footer();
evq_trans_mgr->push_stun_response(&m, 0, 0);
}
}
//////////////////////////////////////////////
// Base STUN transaction
//////////////////////////////////////////////
t_mutex t_stun_transaction::mtx_class;
t_tid t_stun_transaction::next_id = 1;
t_stun_transaction::t_stun_transaction(t_user *user, StunMessage *r,
unsigned short _tuid, const list<t_ip_port> &dst)
{
mtx_class.lock();
id = next_id++;
if (next_id == 65535) next_id = 1;
mtx_class.unlock();
state = TS_NULL;
request = new StunMessage(*r);
MEMMAN_NEW(request);
tuid = _tuid;
dur_req_timeout = DUR_STUN_START_INTVAL;
num_transmissions = 0;
destinations = dst;
user_config = user->copy();
}
t_stun_transaction::~t_stun_transaction() {
MEMMAN_DELETE(request);
delete request;
MEMMAN_DELETE(user_config);
delete user_config;
}
t_tid t_stun_transaction::get_id(void) const {
return id;
}
t_trans_state t_stun_transaction::get_state(void) const {
return state;
}
void t_stun_transaction::start_timer_req_timeout(void) {
timer_req_timeout = transaction_mgr->start_stun_timer(dur_req_timeout,
STUN_TMR_REQ_TIMEOUT, id);
// RFC 3489 9.3
// Double the retransmision interval till a maximum
if (dur_req_timeout < DUR_STUN_MAX_INTVAL) {
dur_req_timeout = 2 * dur_req_timeout;
}
}
void t_stun_transaction::stop_timer_req_timeout(void) {
if (timer_req_timeout) {
transaction_mgr->stop_timer(timer_req_timeout);
timer_req_timeout = 0;
}
}
void t_stun_transaction::process_response(StunMessage *r) {
stop_timer_req_timeout();
evq_trans_layer->push_stun_response(r, tuid, id);
state = TS_TERMINATED;
}
void t_stun_transaction::process_icmp(const t_icmp_msg &icmp) {
stop_timer_req_timeout();
log_file->write_report("Failover to next destination.",
"t_stun_transaction::process_icmp");
destinations.pop_front();
if (destinations.empty()) {
log_file->write_report("No next destination for failover.",
"t_stun_transaction::process_icmp");
log_file->write_header("t_stun_transaction::process_icmp",
LOG_NORMAL, LOG_INFO);
log_file->write_raw("ICMP error received.\n\n");
log_file->write_raw("Send internal: 500 Server Error");
log_file->write_footer();
// No server could be reached, Notify the TU with 500 Server
// Error.
StunMessage *resp = stunBuildError(*request, 500, "Server Error");
evq_trans_layer->push_stun_response(resp, tuid, id);
MEMMAN_DELETE(resp);
delete resp;
state = TS_TERMINATED;
}
// Failover to next destination
evq_sender_udp->push_stun_request(user_config, request, TYPE_STUN_SIP, tuid, id,
destinations.front().ipaddr, destinations.front().port);
num_transmissions = 1;
dur_req_timeout = DUR_STUN_START_INTVAL;
start_timer_req_timeout();
}
void t_stun_transaction::timeout(t_stun_timer t) {
// RFC 3489 9.3
if (num_transmissions < STUN_MAX_TRANSMISSIONS) {
retransmit();
start_timer_req_timeout();
return;
}
// Report timeout to TU
StunMessage *timeout_resp;
timeout_resp = stunBuildError(*request, 408, "Request Timeout");
log_file->write_report("STUN request timeout", "t_stun_transaction::timeout",
LOG_NORMAL);
evq_trans_layer->push_stun_response(timeout_resp, tuid, id);
MEMMAN_DELETE(timeout_resp);
delete timeout_resp;
state = TS_TERMINATED;
}
bool t_stun_transaction::match(StunMessage *resp) const {
return stunEqualId(*resp, *request);
}
// An ICMP error matches a transaction when the destination IP address/port
// of the packet that caused the ICMP error equals the destination
// IP address/port of the transaction. Other information of the packet causing
// the ICMP error is not available.
// In theory when multiple transactions are open for the same destination, the
// wrong transaction may process the ICMP error. In practice this should rarely
// happen as the destination will be unreachable for all those transactions.
// If it happens a transaction gets aborted.
bool t_stun_transaction::match(const t_icmp_msg &icmp) const {
return (destinations.front().ipaddr == icmp.ipaddr &&
destinations.front().port == icmp.port);
}
//////////////////////////////////////////////
// SIP STUN transaction
//////////////////////////////////////////////
void t_sip_stun_trans::retransmit(void) {
// The SIP UDP sender will send out the STUN request.
evq_sender_udp->push_stun_request(user_config, request, TYPE_STUN_SIP, tuid, id,
destinations.front().ipaddr, destinations.front().port);
num_transmissions++;
}
t_sip_stun_trans::t_sip_stun_trans(t_user *user, StunMessage *r,
unsigned short _tuid, const list<t_ip_port> &dst) :
t_stun_transaction(user, r, _tuid, dst)
{
// The SIP UDP sender will send out the STUN request.
evq_sender_udp->push_stun_request(user_config, request, TYPE_STUN_SIP, tuid, id,
destinations.front().ipaddr, destinations.front().port);
num_transmissions++;
start_timer_req_timeout();
state = TS_PROCEEDING;
}
//////////////////////////////////////////////
// Media STUN transaction
//////////////////////////////////////////////
// TODO: this code is not used anymore. Remove?
void t_media_stun_trans::retransmit(void) {
// Retransmit the STUN request
StunAtrString stun_pass;
stun_pass.sizeValue = 0;
char m[STUN_MAX_MESSAGE_SIZE];
int msg_size = stunEncodeMessage(*request, m, STUN_MAX_MESSAGE_SIZE, stun_pass, false);
try {
sock->sendto(destinations.front().ipaddr, destinations.front().port,
m, msg_size);
} catch (int err) {
string msg("Failed to send STUN request for media.\n");
msg += get_error_str(err);
log_file->write_report(msg, "::t_media_stun_trans::retransmit",
LOG_NORMAL, LOG_CRITICAL);
StunMessage *resp;
resp = stunBuildError(*request, 500, "Could not send request");
evq_trans_layer->push_stun_response(resp, tuid, id);
MEMMAN_DELETE(resp);
delete resp;
return;
}
num_transmissions++;
}
t_media_stun_trans::t_media_stun_trans(t_user *user, StunMessage *r,
unsigned short _tuid, const list<t_ip_port> &dst,
unsigned short src_port) :
t_stun_transaction(user, r, _tuid, dst)
{
thr_listen = NULL;
try {
sock = new t_socket_udp(src_port);
MEMMAN_NEW(sock);
sock->connect(destinations.front().ipaddr, destinations.front().port);
} catch (int err) {
string msg("Failed to create a UDP socket (STUN) on port ");
msg += int2str(src_port);
msg += "\n";
msg += get_error_str(err);
log_file->write_report(msg, "t_media_stun_trans::t_media_stun_trans", LOG_NORMAL,
LOG_CRITICAL);
delete sock;
sock = NULL;
StunMessage *resp;
resp = stunBuildError(*request, 500, "Could not create socket");
evq_trans_layer->push_stun_response(resp, tuid, id);
MEMMAN_DELETE(resp);
delete resp;
return;
}
// Send STUN request
StunAtrString stun_pass;
stun_pass.sizeValue = 0;
char m[STUN_MAX_MESSAGE_SIZE];
int msg_size = stunEncodeMessage(*r, m, STUN_MAX_MESSAGE_SIZE, stun_pass, false);
try {
sock->send(m, msg_size);
} catch (int err) {
string msg("Failed to send STUN request for media.\n");
msg += get_error_str(err);
log_file->write_report(msg, "::t_media_stun_trans::t_media_stun_trans",
LOG_NORMAL, LOG_CRITICAL);
StunMessage *resp;
resp = stunBuildError(*request, 500, "Failed to send request");
evq_trans_layer->push_stun_response(resp, tuid, id);
MEMMAN_DELETE(resp);
delete resp;
return;
}
num_transmissions++;
try {
thr_listen = new t_thread(stun_listen_main, sock);
MEMMAN_NEW(thr_listen);
} catch (int) {
log_file->write_report("Failed to create STUN listener thread.",
"::t_media_stun_trans::t_media_stun_trans",
LOG_NORMAL, LOG_CRITICAL);
delete thr_listen;
thr_listen = NULL;
StunMessage *resp;
resp = stunBuildError(*request, 500, "Failed to create STUN listen thread");
evq_trans_layer->push_stun_response(resp, tuid, id);
MEMMAN_DELETE(resp);
delete resp;
return;
}
start_timer_req_timeout();
state = TS_PROCEEDING;
}
t_media_stun_trans::~t_media_stun_trans() {
if (sock) {
MEMMAN_DELETE(sock);
delete sock;
}
if (thr_listen) {
thr_listen->cancel();
thr_listen->join();
MEMMAN_DELETE(thr_listen);
delete thr_listen;
}
}
syntax highlighted by Code2HTML, v. 0.9.1