aboutsummaryrefslogtreecommitdiffstats
path: root/openbsc
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2009-07-28 18:25:29 +0200
committerHarald Welte <laforge@gnumonks.org>2009-07-28 18:25:29 +0200
commit805f64486b490d8c7042235e69565755074b768e (patch)
treed9e4fd82232b2dd80a19ecba40a1509a8fac2501 /openbsc
parentaea9a98dc2dfbbad0daf434c97bc6d2a4d3384f2 (diff)
add RTP proxy mode for ip.access
Up until now, we only supported direct RTP streams between ip.access BTS. With this commit, the user can specify '-P' to the command line to enable a RTP/RTCP proxy inside OpenBSC. The nanoBTS will then send all their voice data to OpenBSC, which will relay it to the respective destination BTS (which can be the same BTS). The default behaviour remains unchanged. Without '-P' on the command line, RTP/RTCP is exchanged directly.
Diffstat (limited to 'openbsc')
-rw-r--r--openbsc/include/openbsc/Makefile.am2
-rw-r--r--openbsc/include/openbsc/gsm_data.h2
-rw-r--r--openbsc/src/Makefile.am2
-rw-r--r--openbsc/src/bsc_hack.c7
-rw-r--r--openbsc/src/gsm_04_08.c132
-rw-r--r--openbsc/src/rtp_proxy.c188
6 files changed, 296 insertions, 37 deletions
diff --git a/openbsc/include/openbsc/Makefile.am b/openbsc/include/openbsc/Makefile.am
index 86f056d26..adef573e5 100644
--- a/openbsc/include/openbsc/Makefile.am
+++ b/openbsc/include/openbsc/Makefile.am
@@ -2,4 +2,4 @@ noinst_HEADERS = abis_nm.h abis_rsl.h debug.h db.h gsm_04_08.h gsm_data.h \
gsm_subscriber.h linuxlist.h msgb.h select.h tlv.h gsm_04_11.h \
timer.h misdn.h chan_alloc.h telnet_interface.h paging.h \
subchan_demux.h trau_frame.h e1_input.h trau_mux.h signal.h \
- gsm_utils.h ipaccess.h rs232.h openbscdefines.h
+ gsm_utils.h ipaccess.h rs232.h openbscdefines.h rtp_proxy.h
diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h
index 5eac8a7d1..e6dffa8f2 100644
--- a/openbsc/include/openbsc/gsm_data.h
+++ b/openbsc/include/openbsc/gsm_data.h
@@ -98,6 +98,7 @@ struct gsm_bts_link {
struct gsm_lchan;
struct gsm_subscriber;
struct gsm_mncc;
+struct rtp_socket;
/* One transaction */
struct gsm_trans {
@@ -214,6 +215,7 @@ struct gsm_bts_trx_ts {
u_int16_t bound_port;
u_int8_t rtp_payload2;
u_int16_t conn_id;
+ struct rtp_socket *rtp_socket;
} abis_ip;
struct gsm_lchan lchan[TS_MAX_LCHAN];
diff --git a/openbsc/src/Makefile.am b/openbsc/src/Makefile.am
index c6e9dae53..ed63c8e31 100644
--- a/openbsc/src/Makefile.am
+++ b/openbsc/src/Makefile.am
@@ -10,7 +10,7 @@ libbsc_a_SOURCES = abis_rsl.c abis_nm.c gsm_04_08.c gsm_data.c mncc.c \
gsm_04_11.c telnet_interface.c subchan_demux.c \
trau_frame.c trau_mux.c paging.c e1_config.c e1_input.c tlv_parser.c \
input/misdn.c input/ipaccess.c signal.c gsm_utils.c talloc.c \
- transaction.c
+ transaction.c rtp_proxy.c
libvty_a_SOURCES = vty/buffer.c vty/command.c vty/vector.c vty/vty.c
diff --git a/openbsc/src/bsc_hack.c b/openbsc/src/bsc_hack.c
index 1bfb7068b..ee8bd1f6f 100644
--- a/openbsc/src/bsc_hack.c
+++ b/openbsc/src/bsc_hack.c
@@ -64,6 +64,7 @@ static int release_l2 = 0;
static enum gsm_bts_type BTS_TYPE = GSM_BTS_TYPE_BS11;
static enum gsm_band BAND = GSM_BAND_900;
static const char *database_name = "hlr.sqlite3";
+extern int ipacc_rtp_direct;
struct nano_bts_id {
struct llist_head entry;
@@ -1113,10 +1114,11 @@ static void handle_options(int argc, char** argv)
{"bts-id", 1, 0, 'i'},
{"tsc", 1, 0, 'S'},
{"bsic", 1, 0, 'B'},
+ {"rtp-proxy", 0, 0, 'P'},
{0, 0, 0, 0}
};
- c = getopt_long(argc, argv, "hc:n:d:sar:p:f:t:C:RL:l:Tb:i:S:B:",
+ c = getopt_long(argc, argv, "hc:n:d:sar:p:f:t:C:RL:l:Tb:i:S:B:P",
long_options, &option_index);
if (c == -1)
break;
@@ -1187,6 +1189,9 @@ static void handle_options(int argc, char** argv)
case 'B':
BSIC = atoi(optarg);
break;
+ case 'P':
+ ipacc_rtp_direct = 0;
+ break;
}
default:
/* ignore */
diff --git a/openbsc/src/gsm_04_08.c b/openbsc/src/gsm_04_08.c
index a9f2ebd8e..2d3a634e1 100644
--- a/openbsc/src/gsm_04_08.c
+++ b/openbsc/src/gsm_04_08.c
@@ -44,6 +44,7 @@
#include <openbsc/signal.h>
#include <openbsc/trau_frame.h>
#include <openbsc/trau_mux.h>
+#include <openbsc/rtp_proxy.h>
#include <openbsc/talloc.h>
#include <openbsc/transaction.h>
@@ -56,6 +57,10 @@
static void *tall_locop_ctx;
+/* should ip.access BTS use direct RTP streams between each other (1),
+ * or should OpenBSC always act as RTP relay/proxy in between (0) ? */
+int ipacc_rtp_direct = 1;
+
static const struct tlv_definition rsl_att_tlvdef = {
.def = {
[GSM48_IE_MOBILE_ID] = { TLV_TYPE_TLV },
@@ -399,17 +404,6 @@ static int gsm0408_handle_lchan_signal(unsigned int subsys, unsigned int signal,
return 0;
}
-/*
- * This will be ran by the linker when loading the DSO. We use it to
- * do system initialization, e.g. registration of signal handlers.
- */
-static __attribute__((constructor)) void on_dso_load_0408(void)
-{
- tall_locop_ctx = talloc_named_const(tall_bsc_ctx, 1,
- "loc_updating_oper");
- register_signal_handler(SS_LCHAN, gsm0408_handle_lchan_signal, NULL);
-}
-
static void to_bcd(u_int8_t *bcd, u_int16_t val)
{
bcd[2] = val % 10;
@@ -1973,12 +1967,80 @@ static int setup_trig_pag_evt(unsigned int hooknum, unsigned int event,
return 0;
}
+/* some other part of the code sends us a signal */
+static int handle_abisip_signal(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct gsm_lchan *lchan = signal_data;
+ struct gsm_bts_trx_ts *ts;
+ int rc;
+
+ if (subsys != SS_ABISIP)
+ return 0;
+
+ /* in case we use direct BTS-to-BTS RTP */
+ if (ipacc_rtp_direct)
+ return 0;
+
+ ts = lchan->ts;
+
+ switch (signal) {
+ case S_ABISIP_BIND_ACK:
+ /* the BTS has successfully bound a TCH to a local ip/port,
+ * which means we can connect our UDP socket to it */
+ if (ts->abis_ip.rtp_socket) {
+ rtp_socket_free(ts->abis_ip.rtp_socket);
+ ts->abis_ip.rtp_socket = NULL;
+ }
+
+ ts->abis_ip.rtp_socket = rtp_socket_create();
+ if (!ts->abis_ip.rtp_socket)
+ goto out_err;
+
+ rc = rtp_socket_connect(ts->abis_ip.rtp_socket,
+ ts->abis_ip.bound_ip,
+ ts->abis_ip.bound_port);
+ if (rc < 0)
+ goto out_err;
+ break;
+ case S_ABISIP_DISC_IND:
+ /* the BTS tells us a RTP stream has been disconnected */
+ if (ts->abis_ip.rtp_socket) {
+ rtp_socket_free(ts->abis_ip.rtp_socket);
+ ts->abis_ip.rtp_socket = NULL;
+ }
+ break;
+ }
+
+ return 0;
+out_err:
+ /* FIXME: do something */
+ return 0;
+}
+
+/* bind rtp proxy to local IP/port and tell BTS to connect to it */
+static int ipacc_connect_proxy_bind(struct gsm_lchan *lchan)
+{
+ struct gsm_bts_trx_ts *ts = lchan->ts;
+ struct rtp_socket *rs = ts->abis_ip.rtp_socket;
+ int rc;
+
+ rc = rsl_ipacc_connect(lchan, ntohl(rs->rtp.sin_local.sin_addr.s_addr),
+ ntohs(rs->rtp.sin_local.sin_port),
+ ts->abis_ip.conn_id,
+ /* FIXME: use RTP payload of bound socket, not BTS*/
+ ts->abis_ip.rtp_payload2);
+
+ return rc;
+}
+
/* map two ipaccess RTP streams onto each other */
static int tch_map(struct gsm_lchan *lchan, struct gsm_lchan *remote_lchan)
{
struct gsm_bts *bts = lchan->ts->trx->bts;
struct gsm_bts *remote_bts = remote_lchan->ts->trx->bts;
struct gsm_bts_trx_ts *ts;
+ int rc;
DEBUGP(DCC, "Setting up TCH map between (bts=%u,trx=%u,ts=%u) and (bts=%u,trx=%u,ts=%u)\n",
bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
@@ -1992,23 +2054,38 @@ static int tch_map(struct gsm_lchan *lchan, struct gsm_lchan *remote_lchan)
switch (bts->type) {
case GSM_BTS_TYPE_NANOBTS_900:
case GSM_BTS_TYPE_NANOBTS_1800:
- ts = remote_lchan->ts;
- rsl_ipacc_connect(lchan, ts->abis_ip.bound_ip,
- ts->abis_ip.bound_port,
- lchan->ts->abis_ip.conn_id,
- ts->abis_ip.rtp_payload2);
-
- ts = lchan->ts;
- rsl_ipacc_connect(remote_lchan, ts->abis_ip.bound_ip,
- ts->abis_ip.bound_port,
- remote_lchan->ts->abis_ip.conn_id,
- ts->abis_ip.rtp_payload2);
+ if (!ipacc_rtp_direct) {
+ /* connect the TCH's to our RTP proxy */
+ rc = ipacc_connect_proxy_bind(lchan);
+ if (rc < 0)
+ return rc;
+ rc = ipacc_connect_proxy_bind(remote_lchan);
+
+ /* connect them with each other */
+ rtp_socket_proxy(lchan->ts->abis_ip.rtp_socket,
+ remote_lchan->ts->abis_ip.rtp_socket);
+ } else {
+ /* directly connect TCH RTP streams to each other */
+ ts = remote_lchan->ts;
+ rc = rsl_ipacc_connect(lchan, ts->abis_ip.bound_ip,
+ ts->abis_ip.bound_port,
+ lchan->ts->abis_ip.conn_id,
+ ts->abis_ip.rtp_payload2);
+ if (rc < 0)
+ return rc;
+ ts = lchan->ts;
+ rc = rsl_ipacc_connect(remote_lchan, ts->abis_ip.bound_ip,
+ ts->abis_ip.bound_port,
+ remote_lchan->ts->abis_ip.conn_id,
+ ts->abis_ip.rtp_payload2);
+ }
break;
case GSM_BTS_TYPE_BS11:
trau_mux_map_lchan(lchan, remote_lchan);
break;
default:
DEBUGP(DCC, "Unknown BTS type %u\n", bts->type);
+ rc = -EINVAL;
break;
}
@@ -3732,3 +3809,14 @@ int bsc_upqueue(struct gsm_network *net)
return work;
}
+/*
+ * This will be ran by the linker when loading the DSO. We use it to
+ * do system initialization, e.g. registration of signal handlers.
+ */
+static __attribute__((constructor)) void on_dso_load_0408(void)
+{
+ tall_locop_ctx = talloc_named_const(tall_bsc_ctx, 1,
+ "loc_updating_oper");
+ register_signal_handler(SS_LCHAN, gsm0408_handle_lchan_signal, NULL);
+ register_signal_handler(SS_ABISIP, handle_abisip_signal, NULL);
+}
diff --git a/openbsc/src/rtp_proxy.c b/openbsc/src/rtp_proxy.c
index f7eb8b835..61fc38575 100644
--- a/openbsc/src/rtp_proxy.c
+++ b/openbsc/src/rtp_proxy.c
@@ -23,15 +23,21 @@
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
+#include <arpa/inet.h>
#include <openbsc/talloc.h>
#include <openbsc/gsm_data.h>
#include <openbsc/msgb.h>
#include <openbsc/select.h>
+#include <openbsc/debug.h>
#include <openbsc/rtp_proxy.h>
static LLIST_HEAD(rtp_sockets);
+/* should we mangle the CNAME inside SDES of RTCP packets? We disable
+ * this by default, as it seems to be not needed */
+static int mangle_rtcp_cname = 0;
+
enum rtp_bfd_priv {
RTP_PRIV_NONE,
RTP_PRIV_RTP,
@@ -40,6 +46,103 @@ enum rtp_bfd_priv {
#define RTP_ALLOC_SIZE 1500
+/* according to RFC 1889 */
+struct rtcp_hdr {
+ u_int8_t byte0;
+ u_int8_t type;
+ u_int16_t length;
+} __attribute__((packed));
+
+#define RTCP_TYPE_SDES 202
+
+#define RTCP_IE_CNAME 1
+
+/* iterate over all chunks in one RTCP message, kook for CNAME IEs and
+ * replace all of those with 'new_cname' */
+static int rtcp_sdes_cname_mangle(struct msgb *msg, struct rtcp_hdr *rh,
+ u_int16_t *rtcp_len, const char *new_cname)
+{
+ u_int8_t *rtcp_end;
+ u_int8_t *cur = (u_int8_t *) rh;
+ u_int8_t tag, len = 0;
+
+ rtcp_end = cur + *rtcp_len;
+ /* move cur to end of RTP header */
+ cur += sizeof(rh);
+
+ /* iterate over Chunks */
+ while (cur+4 < rtcp_end) {
+ /* skip four bytes SSRC/CSRC */
+ cur += 4;
+
+ /* iterate over IE's inside the chunk */
+ while (cur+1 < rtcp_end) {
+ tag = *cur++;
+ if (tag == 0) {
+ /* end of chunk, skip additional zero */
+ while (*cur++ == 0) { }
+ break;
+ }
+ len = *cur++;
+
+ if (tag == RTCP_IE_CNAME) {
+ /* we've found the CNAME, lets mangle it */
+ if (len < strlen(new_cname)) {
+ /* we need to make more space */
+ int increase = strlen(new_cname) - len;
+
+ msgb_push(msg, increase);
+ memmove(cur+len+increase, cur+len,
+ rtcp_end - (cur+len));
+ /* FIXME: we have to respect RTCP
+ * padding/alignment rules! */
+ len += increase;
+ *(cur-1) += increase;
+ rtcp_end += increase;
+ *rtcp_len += increase;
+ }
+ /* copy new CNAME into message */
+ memcpy(cur, new_cname, strlen(new_cname));
+ /* FIXME: zero the padding in case new CNAME
+ * is smaller than old one !!! */
+ }
+ cur += len;
+ }
+ }
+
+ return 0;
+}
+
+static int rtcp_mangle(struct msgb *msg, struct rtp_socket *rs)
+{
+ struct rtp_sub_socket *rss = &rs->rtcp;
+ struct rtcp_hdr *rtph;
+ u_int16_t old_len;
+ int rc;
+
+ if (!mangle_rtcp_cname)
+ return 0;
+
+ /* iterate over list of RTCP messages */
+ rtph = (struct rtcp_hdr *)msg->data;
+ while ((void *)rtph + sizeof(*rtph) < (void *)msg->data + msg->len) {
+ old_len = (ntohs(rtph->length) + 1) * 4;
+ if (rtph->type == RTCP_TYPE_SDES) {
+ char new_cname[255];
+ strncpy(new_cname, inet_ntoa(rss->sin_local.sin_addr),
+ sizeof(new_cname));
+ new_cname[sizeof(new_cname)-1] = '\0';
+ rc = rtcp_sdes_cname_mangle(msg, rtph, &old_len,
+ new_cname);
+ if (rc < 0)
+ return rc;
+ }
+ rtph = (void *)rtph + old_len;
+ }
+
+ return 0;
+}
+
/* read from incoming RTP/RTCP socket */
static int rtp_socket_read(struct rtp_socket *rs, struct rtp_sub_socket *rss)
{
@@ -60,13 +163,21 @@ static int rtp_socket_read(struct rtp_socket *rs, struct rtp_sub_socket *rss)
switch (rs->rx_action) {
case RTP_PROXY:
+ if (!rs->proxy.other_sock) {
+ rc = -EIO;
+ goto out_free;
+ }
if (rss->bfd.priv_nr == RTP_PRIV_RTP)
other_rss = &rs->proxy.other_sock->rtp;
- else if (rss->bfd.priv_nr == RTP_PRIV_RTCP)
+ else if (rss->bfd.priv_nr == RTP_PRIV_RTCP) {
other_rss = &rs->proxy.other_sock->rtcp;
- else {
- msgb_free(msg);
- return -EINVAL;
+ /* modify RTCP SDES CNAME */
+ rc = rtcp_mangle(msg, rs);
+ if (rc < 0)
+ goto out_free;
+ } else {
+ rc = -EINVAL;
+ goto out_free;
}
msgb_enqueue(&other_rss->tx_queue, msg);
other_rss->bfd.when |= BSC_FD_WRITE;
@@ -75,6 +186,10 @@ static int rtp_socket_read(struct rtp_socket *rs, struct rtp_sub_socket *rss)
}
return rc;
+
+out_free:
+ msgb_free(msg);
+ return rc;
}
/* write from tx_queue to RTP/RTCP socket */
@@ -143,6 +258,8 @@ struct rtp_socket *rtp_socket_create(void)
int rc;
struct rtp_socket *rs;
+ DEBUGP(DMUX, "rtp_socket_create(): ");
+
rs = talloc_zero(tall_bsc_ctx, struct rtp_socket);
if (!rs)
return NULL;
@@ -168,8 +285,16 @@ struct rtp_socket *rtp_socket_create(void)
if (rc < 0)
goto out_rtcp_socket;
+ DEBUGPC(DMUX, "success\n");
+
+ rc = rtp_socket_bind(rs, INADDR_ANY);
+ if (rc < 0)
+ goto out_rtcp_bfd;
+
return rs;
+out_rtcp_bfd:
+ bsc_unregister_fd(&rs->rtcp.bfd);
out_rtcp_socket:
close(rs->rtcp.bfd.fd);
out_rtp_bfd:
@@ -178,31 +303,48 @@ out_rtp_socket:
close(rs->rtp.bfd.fd);
out_free:
talloc_free(rs);
+ DEBUGPC(DMUX, "failed\n");
return NULL;
}
static int rtp_sub_socket_bind(struct rtp_sub_socket *rss, u_int32_t ip,
u_int16_t port)
{
+ int rc;
+ socklen_t alen = sizeof(rss->sin_local);
+
rss->sin_local.sin_family = AF_INET;
rss->sin_local.sin_addr.s_addr = htonl(ip);
rss->sin_local.sin_port = htons(port);
rss->bfd.when |= BSC_FD_READ;
- return bind(rss->bfd.fd, (struct sockaddr *)&rss->sin_local,
- sizeof(rss->sin_local));
+ rc = bind(rss->bfd.fd, (struct sockaddr *)&rss->sin_local,
+ sizeof(rss->sin_local));
+ if (rc < 0)
+ return rc;
+
+ /* retrieve the address we actually bound to, in case we
+ * passed INADDR_ANY as IP address */
+ return getsockname(rss->bfd.fd, (struct sockaddr *)&rss->sin_local,
+ &alen);
}
#define RTP_PORT_BASE 30000
-static u_int16_t next_udp_port = RTP_PORT_BASE;
+static unsigned int next_udp_port = RTP_PORT_BASE;
/* bind a RTP socket to a local address */
int rtp_socket_bind(struct rtp_socket *rs, u_int32_t ip)
{
- int rc;
+ int rc = -EIO;
+ struct in_addr ia;
+
+ ia.s_addr = htonl(ip);
+ DEBUGP(DMUX, "rtp_socket_bind(rs=%p, IP=%s): ", rs,
+ inet_ntoa(ia));
/* try to bind to a consecutive pair of ports */
- for (next_udp_port; next_udp_port < 0xffff; next_udp_port += 2) {
+ for (next_udp_port = next_udp_port % 0xffff;
+ next_udp_port < 0xffff; next_udp_port += 2) {
rc = rtp_sub_socket_bind(&rs->rtp, ip, next_udp_port);
if (rc != 0)
continue;
@@ -211,27 +353,45 @@ int rtp_socket_bind(struct rtp_socket *rs, u_int32_t ip)
if (rc == 0)
break;
}
- if (rc < 0)
+ if (rc < 0) {
+ DEBUGPC(DMUX, "failed\n");
return rc;
+ }
+ ia.s_addr = rs->rtp.sin_local.sin_addr.s_addr;
+ DEBUGPC(DMUX, "BOUND_IP=%s, BOUND_PORT=%u\n",
+ inet_ntoa(ia), ntohs(rs->rtp.sin_local.sin_port));
return ntohs(rs->rtp.sin_local.sin_port);
}
static int rtp_sub_socket_connect(struct rtp_sub_socket *rss,
u_int32_t ip, u_int16_t port)
{
+ int rc;
+ socklen_t alen = sizeof(rss->sin_local);
+
rss->sin_remote.sin_family = AF_INET;
rss->sin_remote.sin_addr.s_addr = htonl(ip);
rss->sin_remote.sin_port = htons(port);
- return connect(rss->bfd.fd, (struct sockaddr *) &rss->sin_remote,
- sizeof(rss->sin_remote));
+ rc = connect(rss->bfd.fd, (struct sockaddr *) &rss->sin_remote,
+ sizeof(rss->sin_remote));
+ if (rc < 0)
+ return rc;
+
+ return getsockname(rss->bfd.fd, (struct sockaddr *)&rss->sin_local,
+ &alen);
}
/* 'connect' a RTP socket to a remote peer */
int rtp_socket_connect(struct rtp_socket *rs, u_int32_t ip, u_int16_t port)
{
int rc;
+ struct in_addr ia;
+
+ ia.s_addr = htonl(ip);
+ DEBUGP(DMUX, "rtp_socket_connect(rs=%p, ip=%s, port=%u)\n",
+ rs, inet_ntoa(ia), port);
rc = rtp_sub_socket_connect(&rs->rtp, ip, port);
if (rc < 0)
@@ -243,6 +403,9 @@ int rtp_socket_connect(struct rtp_socket *rs, u_int32_t ip, u_int16_t port)
/* bind two RTP/RTCP sockets together */
int rtp_socket_proxy(struct rtp_socket *this, struct rtp_socket *other)
{
+ DEBUGP(DMUX, "rtp_socket_proxy(this=%p, other=%p)\n",
+ this, other);
+
this->rx_action = RTP_PROXY;
this->proxy.other_sock = other;
@@ -262,6 +425,7 @@ static void free_tx_queue(struct rtp_sub_socket *rss)
int rtp_socket_free(struct rtp_socket *rs)
{
+ DEBUGP(DMUX, "rtp_socket_free(rs=%p)\n", rs);
/* make sure we don't leave references dangling to us */
if (rs->rx_action == RTP_PROXY &&