Newer
Older
/* Don't forward RFC2833 if we're not supposed to */
if (f && (f->frametype == AST_FRAME_DTMF) &&
(ast_test_flag(&p->flags[0], SIP_DTMF) != SIP_DTMF_RFC2833))
return &ast_null_frame;
/* We already hold the channel lock */
if (!p->owner || (f && f->frametype != AST_FRAME_VOICE))
if (f && f->subclass != (p->owner->nativeformats & AST_FORMAT_AUDIO_MASK)) {
if (!(f->subclass & p->jointcapability)) {
ast_debug(1, "Bogus frame of format '%s' received from '%s'!\n",
ast_getformatname(f->subclass), p->owner->name);
return &ast_null_frame;
}
ast_debug(1, "Oooh, format changed to %d %s\n",
f->subclass, ast_getformatname(f->subclass));
p->owner->nativeformats = (p->owner->nativeformats & (AST_FORMAT_VIDEO_MASK | AST_FORMAT_TEXT_MASK)) | f->subclass;
ast_set_read_format(p->owner, p->owner->readformat);
ast_set_write_format(p->owner, p->owner->writeformat);
}
if (f && (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) && p->vad) {
f = ast_dsp_process(p->owner, p->vad, f);
if (f && f->frametype == AST_FRAME_DTMF) {
if (ast_test_flag(&p->t38.t38support, SIP_PAGE2_T38SUPPORT_UDPTL) && f->subclass == 'f') {
ast_debug(1, "Fax CNG detected on %s\n", ast->name);
} else {
ast_debug(1, "* Detected inband DTMF '%c'\n", f->subclass);
/*! \brief Read SIP RTP from channel */
static struct ast_frame *sip_read(struct ast_channel *ast)
{
struct ast_frame *fr;
struct sip_pvt *p = ast->tech_pvt;
sip_pvt_lock(p);
fr = sip_rtp_read(ast, p, &faxdetected);
p->lastrtprx = time(NULL);
/* If we are NOT bridged to another channel, and we have detected fax tone we issue T38 re-invite to a peer */
/* If we are bridged then it is the responsibility of the SIP device to issue T38 re-invite if it detects CNG or fax preamble */
if (faxdetected && ast_test_flag(&p->t38.t38support, SIP_PAGE2_T38SUPPORT_UDPTL) && (p->t38.state == T38_DISABLED) && !(ast_bridged_channel(ast))) {
if (!ast_test_flag(&p->flags[0], SIP_GOTREFER)) {
if (!p->pendinginvite) {
ast_debug(3, "Sending reinvite on SIP (%s) for T.38 negotiation.\n",ast->name);
p->t38.state = T38_LOCAL_REINVITE;
transmit_reinvite_with_sdp(p, TRUE);
ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, ast->name);
}
} else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
ast_debug(3, "Deferring reinvite on SIP (%s) - it will be re-negotiated for T.38\n", ast->name);
ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
}
}
sip_pvt_unlock(p);
Olle Johansson
committed
/*! \brief Generate 32 byte random string for callid's etc */
static char *generate_random_string(char *buf, size_t size)
Tilghman Lesher
committed
long val[4];
for (x=0; x<4; x++)
Tilghman Lesher
committed
val[x] = ast_random();
snprintf(buf, size, "%08lx%08lx%08lx%08lx", val[0], val[1], val[2], val[3]);
Olle Johansson
committed
return buf;
}
/*! \brief Build SIP Call-ID value for a non-REGISTER transaction */
static void build_callid_pvt(struct sip_pvt *pvt)
{
char buf[33];
const char *host = S_OR(pvt->fromdomain, ast_inet_ntoa(pvt->ourip.sin_addr));
Olle Johansson
committed
ast_string_field_build(pvt, callid, "%s@%s", generate_random_string(buf, sizeof(buf)), host);
}
/*! \brief Build SIP Call-ID value for a REGISTER transaction */
static void build_callid_registry(struct sip_registry *reg, struct in_addr ourip, const char *fromdomain)
{
Olle Johansson
committed
char buf[33];
Russell Bryant
committed
const char *host = S_OR(fromdomain, ast_inet_ntoa(ourip));
Olle Johansson
committed
ast_string_field_build(reg, callid, "%s@%s", generate_random_string(buf, sizeof(buf)), host);
/*! \brief Make our SIP dialog tag */
Kevin P. Fleming
committed
static void make_our_tag(char *tagbuf, size_t len)
{
Tilghman Lesher
committed
snprintf(tagbuf, len, "as%08lx", ast_random());
Kevin P. Fleming
committed
}
/*! \brief Allocate sip_pvt structure, set defaults and link in the container.
* Returns a reference to the object so whoever uses it later must
* remember to release the reference.
*/
static struct sip_pvt *sip_alloc(ast_string_field callid, struct sockaddr_in *sin,
int useglobal_nat, const int intended_method)
if (!(p = ast_calloc(1, sizeof(*p))))
Kevin P. Fleming
committed
if (ast_string_field_init(p, 512)) {
Tilghman Lesher
committed
ast_free(p);
return NULL;
}
ast_mutex_init(&p->pvt_lock);
Kevin P. Fleming
committed
p->method = intended_method;
Kevin P. Fleming
committed
p->subscribed = NONE;
p->stateid = -1;
Olle Johansson
committed
p->prefs = default_prefs; /* Set default codecs for this call */
Olle Johansson
committed
if (intended_method != SIP_OPTIONS) { /* Peerpoke has it's own system */
p->timer_t1 = global_t1; /* Default SIP retransmission timer T1 (RFC 3261) */
p->timer_b = global_timer_b; /* Default SIP transaction timer B (RFC 3261) */
}
if (!sin)
p->ourip = internip;
else {
p->sa = *sin;
ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip);
/* Copy global flags to this PVT at setup. */
ast_copy_flags(&p->flags[0], &global_flags[0], SIP_FLAGS_TO_COPY);
ast_copy_flags(&p->flags[1], &global_flags[1], SIP_PAGE2_FLAGS_TO_COPY);
Kevin P. Fleming
committed
p->do_history = recordhistory;
Tilghman Lesher
committed
p->branch = ast_random();
Kevin P. Fleming
committed
make_our_tag(p->tag, sizeof(p->tag));
p->ocseq = INITIAL_CSEQ;
Kevin P. Fleming
committed
if (sip_methods[intended_method].need_rtp) {
p->rtp = ast_rtp_new_with_bindaddr(sched, io, 1, 0, bindaddr.sin_addr);
/* If the global videosupport flag is on, we always create a RTP interface for video */
if (ast_test_flag(&p->flags[1], SIP_PAGE2_VIDEOSUPPORT))
Kevin P. Fleming
committed
p->vrtp = ast_rtp_new_with_bindaddr(sched, io, 1, 0, bindaddr.sin_addr);
if (ast_test_flag(&p->flags[1], SIP_PAGE2_TEXTSUPPORT))
p->trtp = ast_rtp_new_with_bindaddr(sched, io, 1, 0, bindaddr.sin_addr);
if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT))
p->udptl = ast_udptl_new_with_bindaddr(sched, io, 0, bindaddr.sin_addr);
if (!p->rtp|| (ast_test_flag(&p->flags[1], SIP_PAGE2_VIDEOSUPPORT) && !p->vrtp)
|| (ast_test_flag(&p->flags[1], SIP_PAGE2_TEXTSUPPORT) && !p->trtp)) {
ast_log(LOG_WARNING, "Unable to create RTP audio %s%ssession: %s\n",
ast_test_flag(&p->flags[1], SIP_PAGE2_VIDEOSUPPORT) ? "and video " : "",
ast_test_flag(&p->flags[1], SIP_PAGE2_TEXTSUPPORT) ? "and text " : "", strerror(errno));
ast_mutex_destroy(&p->pvt_lock);
Kevin P. Fleming
committed
ast_variables_destroy(p->chanvars);
p->chanvars = NULL;
}
Tilghman Lesher
committed
ast_free(p);
Kevin P. Fleming
committed
return NULL;
ast_rtp_setqos(p->rtp, global_tos_audio, global_cos_audio, "SIP RTP");
ast_rtp_setdtmf(p->rtp, ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833);
ast_rtp_setdtmfcompensate(p->rtp, ast_test_flag(&p->flags[1], SIP_PAGE2_RFC2833_COMPENSATE));
ast_rtp_set_rtptimeout(p->rtp, global_rtptimeout);
ast_rtp_set_rtpholdtimeout(p->rtp, global_rtpholdtimeout);
ast_rtp_set_rtpkeepalive(p->rtp, global_rtpkeepalive);
ast_rtp_setqos(p->vrtp, global_tos_video, global_cos_video, "SIP VRTP");
ast_rtp_setdtmf(p->vrtp, 0);
ast_rtp_setdtmfcompensate(p->vrtp, 0);
ast_rtp_set_rtptimeout(p->vrtp, global_rtptimeout);
ast_rtp_set_rtpholdtimeout(p->vrtp, global_rtpholdtimeout);
ast_rtp_set_rtpkeepalive(p->vrtp, global_rtpkeepalive);
ast_rtp_setqos(p->trtp, global_tos_text, global_cos_text, "SIP TRTP");
ast_rtp_setdtmf(p->trtp, 0);
ast_rtp_setdtmfcompensate(p->trtp, 0);
}
ast_udptl_setqos(p->udptl, global_tos_audio, global_cos_audio);
Kevin P. Fleming
committed
Mark Spencer
committed
if (useglobal_nat && sin) {
/* Setup NAT structure according to global settings if we have an address */
ast_copy_flags(&p->flags[0], &global_flags[0], SIP_NAT);
p->recv = *sin;
do_setnat(p, ast_test_flag(&p->flags[0], SIP_NAT) & SIP_NAT_ROUTE);
Kevin P. Fleming
committed
if (p->method != SIP_REGISTER)
ast_string_field_set(p, fromdomain, default_fromdomain);
build_via(p);
build_callid_pvt(p);
ast_string_field_set(p, callid, callid);
Kevin P. Fleming
committed
ast_string_field_set(p, mohinterpret, default_mohinterpret);
ast_string_field_set(p, mohsuggest, default_mohsuggest);
Mark Spencer
committed
p->capability = global_capability;
p->allowtransfer = global_allowtransfer;
if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833) ||
(ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO))
if (p->udptl) {
Joshua Colp
committed
ast_copy_flags(&p->t38.t38support, &p->flags[1], SIP_PAGE2_T38SUPPORT);
set_t38_capabilities(p);
p->t38.jointcapability = p->t38.capability;
}
ast_string_field_set(p, context, default_context);
Kevin P. Fleming
committed
/* Add to active dialog list */
Olle Johansson
committed
p->next = dialoglist;
dialoglist = dialog_ref(p);
ast_debug(1, "Allocating new SIP dialog for %s - %s (%s)\n", callid ? callid : "(No Call-ID)", sip_methods[intended_method].text, p->rtp ? "With RTP" : "No RTP");
/*! \brief find or create a dialog structure for an incoming SIP message.
* Connect incoming SIP message to current dialog or create new dialog structure
* Returns a reference to the sip_pvt object, remember to give it back once done.
* Called by handle_incoming(), sipsock_read
Kevin P. Fleming
committed
static struct sip_pvt *find_call(struct sip_request *req, struct sockaddr_in *sin, const int intended_method)
char *tag = ""; /* note, tag is never NULL */
char totag[128];
char fromtag[128];
const char *callid = get_header(req, "Call-ID");
Olle Johansson
committed
const char *from = get_header(req, "From");
const char *to = get_header(req, "To");
const char *cseq = get_header(req, "Cseq");
/* Call-ID, to, from and Cseq are required by RFC 3261. (Max-forwards and via too - ignored now) */
/* get_header always returns non-NULL so we must use ast_strlen_zero() */
if (ast_strlen_zero(callid) || ast_strlen_zero(to) ||
ast_strlen_zero(from) || ast_strlen_zero(cseq))
Olle Johansson
committed
return NULL; /* Invalid packet */
/* In principle Call-ID's uniquely identify a call, but with a forking SIP proxy
we need more to identify a branch - so we have to check branch, from
and to tags to identify a call leg.
For Asterisk to behave correctly, you need to turn on pedanticsipchecking
in sip.conf
*/
if (gettag(req, "To", totag, sizeof(totag)))
req->has_to_tag = 1; /* Used in handle_request/response */
gettag(req, "From", fromtag, sizeof(fromtag));
tag = (req->method == SIP_RESPONSE) ? totag : fromtag;
ast_debug(5, "= Looking for Call ID: %s (Checking %s) --From tag %s --To-tag %s \n", callid, req->method==SIP_RESPONSE ? "To" : "From", fromtag, totag);
Olle Johansson
committed
/* All messages must always have From: tag */
if (ast_strlen_zero(fromtag)) {
ast_debug(5, "%s request has no from tag, dropping callid: %s from: %s\n", sip_methods[req->method].text , callid, from );
Olle Johansson
committed
return NULL;
}
/* reject requests that must always have a To: tag */
if (ast_strlen_zero(totag) && (req->method == SIP_ACK || req->method == SIP_BYE || req->method == SIP_INFO )) {
ast_debug(5, "%s must have a to tag. dropping callid: %s from: %s\n", sip_methods[req->method].text , callid, from );
Olle Johansson
committed
return NULL;
}
Olle Johansson
committed
for (p = dialoglist; p; p = p->next) {
/* In pedantic, we do not want packets with bad syntax to be connected to a PVT */
if (ast_strlen_zero(p->callid))
continue;
if (req->method == SIP_REGISTER)
found = (!strcmp(p->callid, callid));
else
found = (!strcmp(p->callid, callid) &&
(!pedanticsipchecking || !tag || ast_strlen_zero(p->theirtag) || !strcmp(p->theirtag, tag))) ;
ast_debug(5, "= %s Their Call ID: %s Their Tag %s Our tag: %s\n", found ? "Found" : "No match", p->callid, p->theirtag, p->tag);
/* If we get a new request within an existing to-tag - check the to tag as well */
if (pedanticsipchecking && found && req->method != SIP_RESPONSE) { /* SIP Request */
if (p->tag[0] == '\0' && totag[0]) {
/* We have no to tag, but they have. Wrong dialog */
} else if (totag[0]) { /* Both have tags, compare them */
if (strcmp(totag, p->tag)) {
found = FALSE; /* This is not our packet */
if (!found)
ast_debug(5, "= Being pedantic: This is not our match on request: Call ID: %s Ourtag <null> Totag %s Method %s\n", p->callid, totag, sip_methods[req->method].text);
if (found) {
sip_pvt_lock(p);
/* See if the method is capable of creating a dialog */
if (sip_methods[intended_method].can_create == CAN_CREATE_DIALOG) {
if (intended_method == SIP_REFER) {
/* We do support REFER, but not outside of a dialog yet */
transmit_response_using_temp(callid, sin, 1, intended_method, req, "603 Declined (no dialog)");
} else if (intended_method == SIP_NOTIFY) {
/* We do not support out-of-dialog NOTIFY either,
like voicemail notification, so cancel that early */
transmit_response_using_temp(callid, sin, 1, intended_method, req, "489 Bad event");
} else {
/* Ok, time to create a new SIP dialog object, a pvt */
Olle Johansson
committed
if ((p = sip_alloc(callid, sin, 1, intended_method))) {
/* Ok, we've created a dialog, let's go and process it */
sip_pvt_lock(p);
Olle Johansson
committed
} else {
/* We have a memory or file/socket error (can't allocate RTP sockets or something) so we're not
getting a dialog from sip_alloc.
Without a dialog we can't retransmit and handle ACKs and all that, but at least
send an error message.
Sorry, we apologize for the inconvienience
*/
transmit_response_using_temp(callid, sin, 1, intended_method, req, "500 Server internal error");
ast_debug(4, "Failed allocating SIP dialog, sending 500 Server internal error and giving up\n");
Olle Johansson
committed
}
} else if( sip_methods[intended_method].can_create == CAN_CREATE_DIALOG_UNSUPPORTED_METHOD) {
/* A method we do not support, let's take it on the volley */
transmit_response_using_temp(callid, sin, 1, intended_method, req, "501 Method Not Implemented");
ast_debug(2, "Got a request with unsupported SIP method.\n");
} else if (intended_method != SIP_RESPONSE && intended_method != SIP_ACK) {
/* This is a request outside of a dialog that we don't know about */
transmit_response_using_temp(callid, sin, 1, intended_method, req, "481 Call leg/transaction does not exist");
ast_debug(2, "That's odd... Got a request in unknown dialog. Callid %s\n", callid ? callid : "<unknown>");
/* We do not respond to responses for dialogs that we don't know about, we just drop
the session quickly */
if (intended_method == SIP_RESPONSE)
ast_debug(2, "That's odd... Got a response on a call we dont know about. Callid %s\n", callid ? callid : "<unknown>");
/*! \brief Parse register=> line in sip.conf and add to registry */
static int sip_register(const char *value, int lineno)
int portnum = 0;
char username[256] = "";
char *hostname=NULL, *secret=NULL, *authuser=NULL;
Olle Johansson
committed
char *callback=NULL;
ast_copy_string(username, value, sizeof(username));
/* First split around the last '@' then parse the two components. */
hostname = strrchr(username, '@'); /* allow @ in the first part */
if (hostname)
*hostname++ = '\0';
if (ast_strlen_zero(username) || ast_strlen_zero(hostname)) {
ast_log(LOG_WARNING, "Format for registration is user[:secret[:authuser]]@host[:port][/contact] at line %d\n", lineno);
/* split user[:secret[:authuser]] */
secret = strchr(username, ':');
if (secret) {
*secret++ = '\0';
authuser = strchr(secret, ':');
if (authuser)
*authuser++ = '\0';
Olle Johansson
committed
callback = strchr(hostname, '/');
if (callback)
*callback++ = '\0';
if (ast_strlen_zero(callback))
callback = "s";
porta = strchr(hostname, ':');
if (porta) {
*porta++ = '\0';
portnum = atoi(porta);
if (portnum == 0) {
ast_log(LOG_WARNING, "%s is not a valid port number at line %d\n", porta, lineno);
return -1;
}
if (!(reg = ast_calloc(1, sizeof(*reg)))) {
ast_log(LOG_ERROR, "Out of memory. Can't allocate SIP registry entry\n");
if (ast_string_field_init(reg, 256)) {
ast_log(LOG_ERROR, "Out of memory. Can't allocate SIP registry strings\n");
Tilghman Lesher
committed
ast_free(reg);
return -1;
}
regobjs++;
ASTOBJ_INIT(reg);
Olle Johansson
committed
ast_string_field_set(reg, callback, callback);
Joshua Colp
committed
if (!ast_strlen_zero(username))
ast_string_field_set(reg, username, username);
ast_string_field_set(reg, hostname, hostname);
ast_string_field_set(reg, authuser, authuser);
ast_string_field_set(reg, secret, secret);
Olle Johansson
committed
reg->expiry = default_expiry;
reg->timeout = -1;
reg->refresh = default_expiry;
reg->ocseq = INITIAL_CSEQ;
ASTOBJ_CONTAINER_LINK(®l, reg); /* Add the new registry entry to the list */
registry_unref(reg); /* release the reference given by ASTOBJ_INIT. The container has another reference */
/*! \brief Parse multiline SIP headers into one header
This is enabled if pedanticsipchecking is enabled */
static int lws2sws(char *msgbuf, int len)
int h = 0, t = 0;
int lws = 0;
for (; h < len;) {
/* Eliminate all CRs */
if (msgbuf[h] == '\r') {
h++;
continue;
}
/* Check for end-of-line */
if (msgbuf[h] == '\n') {
if (h + 1 == len)
break;
/* Check for a continuation line */
if (msgbuf[h + 1] == ' ' || msgbuf[h + 1] == '\t') {
/* Merge continuation line */
h++;
continue;
}
/* Propagate LF and start new line */
msgbuf[t++] = msgbuf[h++];
lws = 0;
continue;
}
if (msgbuf[h] == ' ' || msgbuf[h] == '\t') {
if (lws) {
h++;
continue;
}
msgbuf[t++] = msgbuf[h++];
lws = 1;
continue;
}
msgbuf[t++] = msgbuf[h++];
}
msgbuf[t] = '\0';
return t;
}
Olle Johansson
committed
/*! \brief Parse a SIP message
\note this function is used both on incoming and outgoing packets
*/
static void parse_request(struct sip_request *req)
char *c = req->data, **dst = req->header;
int i = 0, lim = SIP_MAX_HEADERS - 1;
req->header[0] = c;
req->headers = -1; /* mark that we are working on the header */
for (; *c; c++) {
if (*c == '\r') /* remove \r */
*c = '\0';
else if (*c == '\n') { /* end of this line */
*c = '\0';
if (sipdebug)
ast_debug(4, "%7s %2d [%3d]: %s\n",
req->headers < 0 ? "Header" : "Body",
i, (int)strlen(dst[i]), dst[i]);
if (ast_strlen_zero(dst[i]) && req->headers < 0) {
req->headers = i; /* record number of header lines */
dst = req->line; /* start working on the body */
i = 0;
lim = SIP_MAX_LINES - 1;
} else { /* move to next line, check for overflows */
if (i++ >= lim)
break;
dst[i] = c + 1; /* record start of next line */
Olle Johansson
committed
/* Check for last header without CRLF. The RFC for SDP requires CRLF,
but since some devices send without, we'll be generous in what we accept.
*/
if (!ast_strlen_zero(dst[i])) {
if (sipdebug)
ast_debug(4, "%7s %2d [%3d]: %s\n",
Olle Johansson
committed
req->headers < 0 ? "Header" : "Body",
i, (int)strlen(dst[i]), dst[i]);
i++;
}
/* update count of header or body lines */
if (req->headers >= 0) /* we are in the body */
req->lines = i;
else { /* no body */
req->headers = i;
req->lines = 0;
req->line[0] = "";
if (*c)
ast_log(LOG_WARNING, "Too many lines, skipping <%s>\n", c);
/* Split up the first line parts */
determine_firstline_parts(req);
/*!
\brief Determine whether a SIP message contains an SDP in its body
\param req the SIP request to process
\return 1 if SDP found, 0 if not found
Also updates req->sdp_start and req->sdp_end to indicate where the SDP
lives in the message body.
*/
static int find_sdp(struct sip_request *req)
{
const char *content_type;
const char *search;
char *boundary;
unsigned int x;
int boundaryisquoted = FALSE;
int found_application_sdp = FALSE;
int found_end_of_headers = FALSE;
content_type = get_header(req, "Content-Type");
/* if the body contains only SDP, this is easy */
if (!strcasecmp(content_type, "application/sdp")) {
req->sdp_start = 0;
req->sdp_end = req->lines;
}
/* if it's not multipart/mixed, there cannot be an SDP */
if (strncasecmp(content_type, "multipart/mixed", 15))
return 0;
/* if there is no boundary marker, it's invalid */
if ((search = strcasestr(content_type, ";boundary=")))
search += 10;
else if ((search = strcasestr(content_type, "; boundary=")))
search += 11;
else
return 0;
if (ast_strlen_zero(search))
return 0;
/* If the boundary is quoted with ", remove quote */
if (*search == '\"') {
search++;
boundaryisquoted = TRUE;
}
/* make a duplicate of the string, with two extra characters
at the beginning */
boundary = ast_strdupa(search - 2);
boundary[0] = boundary[1] = '-';
/* Remove final quote */
if (boundaryisquoted)
boundary[strlen(boundary) - 1] = '\0';
/* search for the boundary marker, the empty line delimiting headers from
sdp part and the end boundry if it exists */
for (x = 0; x < (req->lines ); x++) {
if(!strncasecmp(req->line[x], boundary, strlen(boundary))){
if(found_application_sdp && found_end_of_headers){
req->sdp_end = x-1;
return 1;
}
found_application_sdp = FALSE;
}
if(!strcasecmp(req->line[x], "Content-Type: application/sdp"))
found_application_sdp = TRUE;
if(strlen(req->line[x]) == 0 ){
if(found_application_sdp && !found_end_of_headers){
req->sdp_start = x;
found_end_of_headers = TRUE;
if(found_application_sdp && found_end_of_headers) {
req->sdp_end = x;
return TRUE;
}
return FALSE;
/*! \brief Process SIP SDP offer, select formats and activate RTP channels
If offer is rejected, we will not change any properties of the call
Return 0 on success, a negative value on errors.
Must be called after find_sdp().
*/
static int process_sdp(struct sip_pvt *p, struct sip_request *req)
{
int portno = -1; /*!< RTP Audio port number */
int vportno = -1; /*!< RTP Video port number */
int tportno = -1; /*!< RTP Text port number */
int udptlportno = -1;
int peert38capability = 0;
char s[256];
int old = 0;
/* Peer capability is the capability in the SDP, non codec is RFC2833 DTMF (101) */
Joshua Colp
committed
int peercapability = 0, peernoncodeccapability = 0;
int vpeercapability = 0, vpeernoncodeccapability = 0;
int tpeercapability = 0, tpeernoncodeccapability = 0;
struct sockaddr_in sin; /*!< media socket address */
struct sockaddr_in vsin; /*!< Video socket address */
struct sockaddr_in tsin; /*!< Text socket address */
struct hostent *hp; /*!< RTP Audio host IP */
struct hostent *vhp = NULL; /*!< RTP video host IP */
struct hostent *thp = NULL; /*!< RTP text host IP */
struct ast_hostent audiohp;
struct ast_hostent videohp;
struct ast_hostent texthp;
int destiterator = 0;
int numberofports;
struct ast_rtp *newaudiortp, *newvideortp, *newtextrtp; /* Buffers for codec handling */
int newjointcapability; /* Negotiated capability */
int newpeercapability;
int newnoncodeccapability;
int numberofmediastreams = 0;
int debug = sip_debug_test_pvt(p);
int found_rtpmap_codecs[32];
int last_rtpmap_codec=0;
char buf[BUFSIZ];
if (!p->rtp) {
ast_log(LOG_ERROR, "Got SDP but have no RTP session allocated.\n");
return -1;
}
/* Initialize the temporary RTP structures we use to evaluate the offer from the peer */
newaudiortp = alloca(ast_rtp_alloc_size());
memset(newaudiortp, 0, ast_rtp_alloc_size());
Joshua Colp
committed
ast_rtp_pt_clear(newaudiortp);
newvideortp = alloca(ast_rtp_alloc_size());
memset(newvideortp, 0, ast_rtp_alloc_size());
Joshua Colp
committed
ast_rtp_pt_clear(newvideortp);
newtextrtp = alloca(ast_rtp_alloc_size());
memset(newtextrtp, 0, ast_rtp_alloc_size());
ast_rtp_new_init(newtextrtp);
ast_rtp_pt_clear(newtextrtp);
/* Update our last rtprx when we receive an SDP, too */
p->lastrtprx = p->lastrtptx = time(NULL); /* XXX why both ? */
/* Try to find first media stream */
destiterator = req->sdp_start;
c = get_sdp_iterate(&destiterator, req, "c");
if (ast_strlen_zero(m) || ast_strlen_zero(c)) {
ast_log(LOG_WARNING, "Insufficient information for SDP (m = '%s', c = '%s')\n", m, c);
return -1;
}
/* Check for IPv4 address (not IPv6 yet) */
if (sscanf(c, "IN IP4 %256s", host) != 1) {
ast_log(LOG_WARNING, "Invalid host in c= line, '%s'\n", c);
return -1;
}
/* XXX This could block for a long time, and block the main thread! XXX */
hp = ast_gethostbyname(host, &audiohp);
if (!hp) {
ast_log(LOG_WARNING, "Unable to lookup host in c= line, '%s'\n", c);
return -1;
}
vhp = hp; /* Copy to video address as default too */
thp = hp; /* Copy to text address as default too */
iterator = req->sdp_start;
/* default: novideo and notext set */
p->novideo = TRUE;
p->notext = TRUE;
if (p->vrtp)
ast_rtp_pt_clear(newvideortp); /* Must be cleared in case no m=video line exists */
if (p->trtp)
ast_rtp_pt_clear(newtextrtp); /* Must be cleared in case no m=text line exists */
/* Find media streams in this SDP offer */
while ((m = get_sdp_iterate(&iterator, req, "m"))[0] != '\0') {
int x;
int audio = FALSE;
int video = FALSE;
int text = FALSE;
numberofports = 1;
if ((sscanf(m, "audio %d/%d RTP/AVP %n", &x, &numberofports, &len) == 2) ||
(sscanf(m, "audio %d RTP/AVP %n", &x, &len) == 1)) {
audio = TRUE;
Olle Johansson
committed
numberofmediastreams++;
/* Found audio stream in this media definition */
portno = x;
/* Scan through the RTP payload types specified in a "m=" line: */
for (codecs = m + len; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) {
if (sscanf(codecs, "%d%n", &codec, &len) != 1) {
ast_log(LOG_WARNING, "Error in codec string '%s'\n", codecs);
return -1;
}
ast_verbose("Found RTP audio format %d\n", codec);
ast_rtp_set_m_type(newaudiortp, codec);
} else if ((sscanf(m, "video %d/%d RTP/AVP %n", &x, &numberofports, &len) == 2) ||
(sscanf(m, "video %d RTP/AVP %n", &x, &len) == 1)) {
p->novideo = FALSE;
Olle Johansson
committed
numberofmediastreams++;
vportno = x;
/* Scan through the RTP payload types specified in a "m=" line: */
for (codecs = m + len; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) {
if (sscanf(codecs, "%d%n", &codec, &len) != 1) {
ast_log(LOG_WARNING, "Error in codec string '%s'\n", codecs);
return -1;
}
ast_verbose("Found RTP video format %d\n", codec);
ast_rtp_set_m_type(newvideortp, codec);
} else if ((sscanf(m, "text %d/%d RTP/AVP %n", &x, &numberofports, &len) == 2) ||
(sscanf(m, "text %d RTP/AVP %n", &x, &len) == 1)) {
text = TRUE;
p->notext = FALSE;
numberofmediastreams++;
tportno = x;
/* Scan through the RTP payload types specified in a "m=" line: */
for (codecs = m + len; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) {
if (sscanf(codecs, "%d%n", &codec, &len) != 1) {
ast_log(LOG_WARNING, "Error in codec string '%s'\n", codecs);
return -1;
}
if (debug)
ast_verbose("Found RTP text format %d\n", codec);
ast_rtp_set_m_type(newtextrtp, codec);
}
} else if (p->udptl && ( (sscanf(m, "image %d udptl t38%n", &x, &len) == 1) ||
(sscanf(m, "image %d UDPTL t38%n", &x, &len) == 1) )) {
if (debug)
ast_verbose("Got T.38 offer in SDP in dialog %s\n", p->callid);
udptlportno = x;
Olle Johansson
committed
numberofmediastreams++;
if (p->owner && p->lastinvite) {
p->t38.state = T38_PEER_REINVITE; /* T38 Offered in re-invite from remote party */
ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>" );
} else {
p->t38.state = T38_PEER_DIRECT; /* T38 Offered directly from peer in first invite */
ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>");
} else
ast_log(LOG_WARNING, "Unsupported SDP media type in offer: %s\n", m);
if (numberofports > 1)
ast_log(LOG_WARNING, "SDP offered %d ports for media, not supported by Asterisk. Will try anyway...\n", numberofports);
/* Check for Media-description-level-address for audio */
c = get_sdp_iterate(&destiterator, req, "c");
if (!ast_strlen_zero(c)) {
if (sscanf(c, "IN IP4 %256s", host) != 1) {
ast_log(LOG_WARNING, "Invalid secondary host in c= line, '%s'\n", c);
} else {
/* XXX This could block for a long time, and block the main thread! XXX */
if (audio) {
if ( !(hp = ast_gethostbyname(host, &audiohp))) {
ast_log(LOG_WARNING, "Unable to lookup RTP Audio host in secondary c= line, '%s'\n", c);
if (!(vhp = ast_gethostbyname(host, &videohp))) {
ast_log(LOG_WARNING, "Unable to lookup RTP video host in secondary c= line, '%s'\n", c);
if (!(thp = ast_gethostbyname(host, &texthp))) {
ast_log(LOG_WARNING, "Unable to lookup RTP text host in secondary c= line, '%s'\n", c);
if (portno == -1 && vportno == -1 && udptlportno == -1 && tportno == -1)
/* No acceptable offer found in SDP - we have no ports */
/* Do not change RTP or VRTP if this is a re-invite */
return -2;
if (numberofmediastreams > 3)
/* We have too many fax, audio and/or video and/or text media streams, fail this offer */
return -3;
/* RTP addresses and ports for audio and video */
vsin.sin_family = AF_INET;
tsin.sin_family = AF_INET;
memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
if (vhp)
memcpy(&vsin.sin_addr, vhp->h_addr, sizeof(vsin.sin_addr));
if (thp)
memcpy(&tsin.sin_addr, thp->h_addr, sizeof(tsin.sin_addr));
/* Setup UDPTL port number */
if (p->udptl) {
if (udptlportno > 0) {
sin.sin_port = htons(udptlportno);
ast_udptl_set_peer(p->udptl, &sin);
if (debug)
ast_debug(1,"Peer T.38 UDPTL is at port %s:%d\n",ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
} else {
ast_udptl_stop(p->udptl);
if (debug)
ast_debug(1, "Peer doesn't provide T.38 UDPTL\n");
}
}
if (p->rtp) {
if (portno > 0) {
sin.sin_port = htons(portno);
ast_rtp_set_peer(p->rtp, &sin);
if (debug)
ast_verbose("Peer audio RTP is at port %s:%d\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
} else {
if (udptlportno > 0) {
if (debug)
ast_verbose("Got T.38 Re-invite without audio. Keeping RTP active during T.38 session. Callid %s\n", p->callid);
} else {
ast_rtp_stop(p->rtp);
if (debug)
ast_verbose("Peer doesn't provide audio. Callid %s\n", p->callid);
}
}
}
/* Setup video port number, assumes we have audio */
if (vportno != -1)
vsin.sin_port = htons(vportno);
/* Setup text port number, assumes we have audio */
if (tportno != -1)
tsin.sin_port = htons(tportno);
/* Next, scan through each "a=xxxx:" line, noting each
* specified RTP payload type (with corresponding MIME subtype):
*/
/* XXX This needs to be done per media stream, since it's media stream specific */
iterator = req->sdp_start;
while ((a = get_sdp_iterate(&iterator, req, "a"))[0] != '\0') {
char* mimeSubtype = ast_strdupa(a); /* ensures we have enough space */
if (option_debug > 1) {
int breakout = FALSE;
/* If we're debugging, check for unsupported sdp options */
Olle Johansson
committed
if (!strncasecmp(a, "rtcp:", (size_t) 5)) {
if (debug)
ast_verbose("Got unsupported a:rtcp in SDP offer \n");
breakout = TRUE;
} else if (!strncasecmp(a, "fmtp:", (size_t) 5)) {
/* Format parameters: Not supported */
/* Note: This is used for codec parameters, like bitrate for
G722 and video formats for H263 and H264
See RFC2327 for an example */
if (debug)
ast_verbose("Got unsupported a:fmtp in SDP offer \n");
breakout = TRUE;
} else if (!strncasecmp(a, "framerate:", (size_t) 10)) {
/* Video stuff: Not supported */
if (debug)
ast_verbose("Got unsupported a:framerate in SDP offer \n");
breakout = TRUE;
} else if (!strncasecmp(a, "maxprate:", (size_t) 9)) {
/* Video stuff: Not supported */
if (debug)
ast_verbose("Got unsupported a:maxprate in SDP offer \n");
breakout = TRUE;
} else if (!strncasecmp(a, "crypto:", (size_t) 7)) {
/* SRTP stuff, not yet supported */
if (debug)
ast_verbose("Got unsupported a:crypto in SDP offer \n");
breakout = TRUE;
}
if (breakout) /* We have a match, skip to next header */
continue;
}
if (!strcasecmp(a, "sendonly")) {
if (sendonly == -1)
sendonly = 1;
continue;
Olle Johansson
committed
} else if (!strcasecmp(a, "inactive")) {
if (sendonly == -1)
sendonly = 2;
Olle Johansson
committed
continue;
} else if (!strcasecmp(a, "sendrecv")) {
if (sendonly == -1)
sendonly = 0;
continue;
} else if (strlen(a) > 5 && !strncasecmp(a, "ptime", 5)) {
char *tmp = strrchr(a, ':');
long int framing = 0;
if (tmp) {
tmp++;
framing = strtol(tmp, NULL, 10);
if (framing == LONG_MIN || framing == LONG_MAX) {
framing = 0;
ast_debug(1, "Can't read framing from SDP: %s\n", a);
}
}
if (framing && last_rtpmap_codec) {
struct ast_codec_pref *pref = ast_rtp_codec_getpref(p->rtp);
int codec_n;
int format = 0;
for (codec_n = 0; codec_n < last_rtpmap_codec; codec_n++) {
format = ast_rtp_codec_getformat(found_rtpmap_codecs[codec_n]);
if (!format) /* non-codec or not found */
continue;
ast_debug(1, "Setting framing for %d to %ld\n", format, framing);
ast_codec_pref_setsize(pref, format, framing);
}
ast_rtp_codec_setpref(p->rtp, pref);
}
}
memset(&found_rtpmap_codecs, 0, sizeof(found_rtpmap_codecs));
last_rtpmap_codec = 0;
continue;
} else if (sscanf(a, "rtpmap: %u %[^/]/", &codec, mimeSubtype) == 2) {
/* We have a rtpmap to handle */
/* Note: should really look at the 'freq' and '#chans' params too */
/* Note: This should all be done in the context of the m= above */