/* * (C) 2016 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 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 Affero General Public License * along with this program. If not, see . * */ #include "sdp.h" #include "call.h" #include "logging.h" #include "app.h" #include #include #include #include #include #include /* * Check if the media mode attribute exists in SDP, in this * case update the passed pointer with the media mode */ bool sdp_get_sdp_mode(const sip_t *sip, sdp_mode_t *mode) { const char *sdp_data; sdp_parser_t *parser; sdp_session_t *sdp; if (!sip->sip_payload || !sip->sip_payload->pl_data) { LOGP(DSIP, LOGL_ERROR, "No SDP file\n"); return false; } sdp_data = sip->sip_payload->pl_data; parser = sdp_parse(NULL, sdp_data, strlen(sdp_data), sdp_f_mode_0000); if (!parser) { LOGP(DSIP, LOGL_ERROR, "Failed to parse SDP\n"); return false; } sdp = sdp_session(parser); if (!sdp) { LOGP(DSIP, LOGL_ERROR, "No sdp session\n"); sdp_parser_free(parser); return false; } if (!sdp->sdp_media || !sdp->sdp_media->m_mode) { sdp_parser_free(parser); return false; } *mode = sdp->sdp_media->m_mode; sdp_parser_free(parser); return true; } /* * We want to decide on the audio codec later but we need to see * if it is even including some of the supported ones. */ bool sdp_screen_sdp(const sip_t *sip) { const char *sdp_data; sdp_parser_t *parser; sdp_session_t *sdp; sdp_media_t *media; if (!sip->sip_payload || !sip->sip_payload->pl_data) { LOGP(DSIP, LOGL_ERROR, "No SDP file\n"); return false; } sdp_data = sip->sip_payload->pl_data; parser = sdp_parse(NULL, sdp_data, strlen(sdp_data), 0); if (!parser) { LOGP(DSIP, LOGL_ERROR, "Failed to parse SDP\n"); return false; } sdp = sdp_session(parser); if (!sdp) { LOGP(DSIP, LOGL_ERROR, "No sdp session\n"); sdp_parser_free(parser); return false; } for (media = sdp->sdp_media; media; media = media->m_next) { sdp_rtpmap_t *map; if (media->m_proto != sdp_proto_rtp) continue; if (media->m_type != sdp_media_audio) continue; for (map = media->m_rtpmaps; map; map = map->rm_next) { if (strcasecmp(map->rm_encoding, "GSM") == 0) goto success; if (strcasecmp(map->rm_encoding, "GSM-EFR") == 0) goto success; if (strcasecmp(map->rm_encoding, "GSM-HR-08") == 0) goto success; if (strcasecmp(map->rm_encoding, "AMR") == 0) goto success; } } sdp_parser_free(parser); /* FIXME: osmo-sip-connector should not interfere in codecs at all */ return false; success: sdp_parser_free(parser); return true; } /* Extract RTP address, port and payload type from SDP received in SIP message, in order to populate the legacy MNCC * fields, for backwards compatibility. osmo-sip-connector now always sends the entire SDP info unchanged via MNCC, * which obsoletes the legacy fields. But for backwards compatibility, still populate the legacy fields. */ bool sdp_extract_sdp(struct sip_call_leg *leg, const sip_t *sip, bool any_codec) { sdp_connection_t *conn; sdp_session_t *sdp; sdp_parser_t *parser; sdp_media_t *media; const char *sdp_data; uint16_t port; bool found_conn = false, found_map = false; if (!sip->sip_payload || !sip->sip_payload->pl_data) { LOGP(DSIP, LOGL_ERROR, "leg(%p) but no SDP file\n", leg); return false; } sdp_data = sip->sip_payload->pl_data; parser = sdp_parse(NULL, sdp_data, strlen(sdp_data), 0); if (!parser) { LOGP(DSIP, LOGL_ERROR, "leg(%p) failed to parse SDP\n", leg); return false; } sdp = sdp_session(parser); if (!sdp) { LOGP(DSIP, LOGL_ERROR, "leg(%p) no sdp session\n", leg); sdp_parser_free(parser); return false; } for (conn = sdp->sdp_connection; conn; conn = conn->c_next) { switch (conn->c_addrtype) { case sdp_addr_ip4: if (inet_pton(AF_INET, conn->c_address, &((struct sockaddr_in*)&leg->base.addr)->sin_addr) != 1) continue; leg->base.addr.ss_family = AF_INET; break; case sdp_addr_ip6: if (inet_pton(AF_INET6, conn->c_address, &((struct sockaddr_in6*)&leg->base.addr)->sin6_addr) != 1) continue; leg->base.addr.ss_family = AF_INET6; break; default: continue; } found_conn = true; break; } for (media = sdp->sdp_media; media; media = media->m_next) { sdp_rtpmap_t *map; if (media->m_proto != sdp_proto_rtp) continue; if (media->m_type != sdp_media_audio) continue; for (map = media->m_rtpmaps; map; map = map->rm_next) { if (!any_codec && leg->wanted_codec && strcasecmp(map->rm_encoding, leg->wanted_codec) != 0) continue; port = media->m_port; leg->base.payload_type = map->rm_pt; found_map = true; break; } if (found_map) break; } if (!found_conn || !found_map) { LOGP(DSIP, LOGL_ERROR, "leg(%p) did not find %d/%d\n", leg, found_conn, found_map); sdp_parser_free(parser); /* FIXME: osmo-sip-connector should not interfere in codecs at all */ return false; } switch (leg->base.addr.ss_family) { case AF_INET: ((struct sockaddr_in*)&leg->base.addr)->sin_port = htons(port); break; case AF_INET6: ((struct sockaddr_in6*)&leg->base.addr)->sin6_port = htons(port); break; default: OSMO_ASSERT(0); } sdp_parser_free(parser); return true; } /* One leg has sent a SIP or MNCC message, which is now translated/forwarded to the counterpart MNCC or SIP. * Take as much from the source's SDP as possible, but make sure the connection mode reflects the 'mode' arg (sendrecv, * recvonly, sendonly, inactive). * For example, if the MSC sent an MNCC_SETUP_IND, the SDP from the MNCC is found in 'other', while 'leg' reflects the * SIP side that should receive this SDP in the SIP Invite that is being composed by the caller of this function. * \param leg The target for which the returned SDP is intended. * \param other The source of which we are to reflect the SDP. * \return SDP string, using 'leg' as talloc ctx. */ char *sdp_create_file(struct sip_call_leg *leg, struct call_leg *other, sdp_mode_t mode) { sdp_parser_t *parser; sdp_session_t *sdp; sdp_media_t *media; const char *sdp_data; sdp_printer_t *printer; char buf[1024]; const char *sdp_str; char *ret; sdp_data = other->rx_sdp; if (!*sdp_data) { /* Legacy compat: We have not received any SDP from the other call leg. Compose some original SDP from * the RTP information we have. */ char *fmtp_str = NULL; char *mode_attribute; char ip_addr[INET6_ADDRSTRLEN]; char ipv; osmo_sockaddr_ntop((const struct sockaddr *)&other->addr, ip_addr); ipv = other->addr.ss_family == AF_INET6 ? '6' : '4'; leg->wanted_codec = app_media_name(other->payload_msg_type); if (strcmp(leg->wanted_codec, "AMR") == 0) fmtp_str = talloc_asprintf(leg, "a=fmtp:%d octet-align=1\r\n", other->payload_type); switch (mode) { case sdp_inactive: mode_attribute = "a=inactive\r\n"; break; case sdp_sendrecv: mode_attribute = "a=sendrecv\r\n"; break; case sdp_sendonly: mode_attribute = "a=sendonly\r\n"; break; case sdp_recvonly: mode_attribute = "a=recvonly\r\n"; break; default: OSMO_ASSERT(false); break; } return talloc_asprintf(leg, "v=0\r\n" "o=Osmocom 0 0 IN IP%c %s\r\n" "s=GSM Call\r\n" "c=IN IP%c %s\r\n" "t=0 0\r\n" "m=audio %d RTP/AVP %d\r\n" "%s" "a=rtpmap:%d %s/8000\r\n" "%s", ipv, ip_addr, ipv, ip_addr, osmo_sockaddr_port((const struct sockaddr *)&other->addr), other->payload_type, fmtp_str ? fmtp_str : "", other->payload_type, leg->wanted_codec, mode_attribute); } /* We have received SDP from the other call leg. Forward this as-is, only apply the mode the caller requests: * parse SDP, set media mode, recompose. */ /* TODO: currently, we often detect the media mode from parsing SDP, and then forward the same SDP, applying the * same mode to it below. It may make sense to completely skip parsing and composition: the mode is usually * already in the received SDP. * However, there are some invocations of this function (sdp_create_file()) with hardcoded modes. Take a look if * that is really necessary, and if not, just drop below parsing + recomposition and use the sdp_data as is. */ parser = sdp_parse(NULL, sdp_data, strlen(sdp_data), 0); if (!parser) { LOGP(DSIP, LOGL_ERROR, "leg(%p) failed to parse SDP\n", other); return talloc_strdup(leg, sdp_data); } sdp = sdp_session(parser); if (!sdp) { LOGP(DSIP, LOGL_INFO, "leg(%p) no SDP session in %s, returning SDP unchanged\n", other, osmo_quote_str(sdp_data, -1)); sdp_parser_free(parser); return talloc_strdup(leg, sdp_data); } for (media = sdp->sdp_media; media; media = media->m_next) media->m_mode = mode; printer = sdp_print(NULL, sdp, buf, sizeof(buf), sdp_f_mode_always); if (!printer) { LOGP(DSIP, LOGL_ERROR, "leg(%p) failed to print SDP\n", other); sdp_parser_free(parser); return talloc_strdup(leg, sdp_data); } sdp_str = sdp_message(printer); if (!sdp_str) { LOGP(DSIP, LOGL_ERROR, "leg(%p) failed to print SDP: %s\n", other, sdp_printing_error(printer)); sdp_str = sdp_data; } ret = talloc_strdup(leg, sdp_str); sdp_parser_free(parser); sdp_printer_free(printer); return ret; }