aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHolger Hans Peter Freyther <zecke@selfish.org>2012-11-12 10:46:11 +0100
committerHolger Hans Peter Freyther <zecke@selfish.org>2012-11-12 10:46:11 +0100
commit88c06bcd64adedcbf738991c15203f646ff01d86 (patch)
tree28fe81ac75a26689f7edbc346bc2eab6c6030d08
parentde1674ab02d7c30f7f40f03d6942b7933d9d6d58 (diff)
parentcb306a689e4bef5d2f82315ff4c694517750519f (diff)
Merge branch 'zecke/mgcp-rtp-statistics'
-rw-r--r--openbsc/include/openbsc/mgcp.h1
-rw-r--r--openbsc/include/openbsc/mgcp_internal.h18
-rw-r--r--openbsc/src/libmgcp/mgcp_network.c121
-rw-r--r--openbsc/src/libmgcp/mgcp_protocol.c53
-rw-r--r--openbsc/src/libmgcp/mgcp_vty.c5
-rw-r--r--openbsc/src/osmo-bsc_mgcp/Makefile.am2
-rw-r--r--openbsc/tests/mgcp/mgcp_test.c66
-rw-r--r--openbsc/tests/mgcp/mgcp_test.ok2
8 files changed, 240 insertions, 28 deletions
diff --git a/openbsc/include/openbsc/mgcp.h b/openbsc/include/openbsc/mgcp.h
index 751943ada..d618f3cdb 100644
--- a/openbsc/include/openbsc/mgcp.h
+++ b/openbsc/include/openbsc/mgcp.h
@@ -168,6 +168,7 @@ int mgcp_vty_init(void);
int mgcp_endpoints_allocate(struct mgcp_trunk_config *cfg);
void mgcp_free_endp(struct mgcp_endpoint *endp);
int mgcp_reset_transcoder(struct mgcp_config *cfg);
+void mgcp_format_stats(struct mgcp_endpoint *endp, char *stats, size_t size);
/*
* format helper functions
diff --git a/openbsc/include/openbsc/mgcp_internal.h b/openbsc/include/openbsc/mgcp_internal.h
index 6e0451ebf..025b813a5 100644
--- a/openbsc/include/openbsc/mgcp_internal.h
+++ b/openbsc/include/openbsc/mgcp_internal.h
@@ -1,8 +1,8 @@
/* MGCP Private Data */
/*
- * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
- * (C) 2009-2011 by On-Waves
+ * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2012 by On-Waves
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
@@ -46,16 +46,22 @@ struct mgcp_rtp_state {
uint32_t orig_ssrc;
uint32_t ssrc;
- uint16_t seq_no;
- int lost_no;
+
+ uint16_t base_seq;
+ uint16_t max_seq;
int seq_offset;
+ int cycles;
+
uint32_t last_timestamp;
int32_t timestamp_offset;
+ uint32_t jitter;
+ int32_t transit;
};
struct mgcp_rtp_end {
/* statistics */
unsigned int packets;
+ unsigned int octets;
struct in_addr addr;
/* in network byte order */
@@ -147,5 +153,9 @@ static inline int endp_back_channel(int endpoint)
struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int index);
struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index);
+void mgcp_state_calc_loss(struct mgcp_rtp_state *s, struct mgcp_rtp_end *,
+ uint32_t *expected, int *loss);
+uint32_t mgcp_state_calc_jitter(struct mgcp_rtp_state *);
+
#endif
diff --git a/openbsc/src/libmgcp/mgcp_network.c b/openbsc/src/libmgcp/mgcp_network.c
index 8824dc835..03d0f35f6 100644
--- a/openbsc/src/libmgcp/mgcp_network.c
+++ b/openbsc/src/libmgcp/mgcp_network.c
@@ -2,8 +2,8 @@
/* The protocol implementation */
/*
- * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
- * (C) 2009-2011 by On-Waves
+ * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2012 by On-Waves
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
@@ -40,6 +40,7 @@
/* attempt to determine byte order */
#include <sys/param.h>
#include <limits.h>
+#include <time.h>
#ifndef __BYTE_ORDER
# ifdef __APPLE__
@@ -73,6 +74,10 @@ struct rtp_hdr {
uint32_t ssrc;
} __attribute__((packed));
+#define RTP_SEQ_MOD (1 << 16)
+#define RTP_MAX_DROPOUT 3000
+#define RTP_MAX_MISORDER 100
+
enum {
DEST_NETWORK = 0,
@@ -87,6 +92,29 @@ enum {
#define DUMMY_LOAD 0x23
+/**
+ * This does not need to be a precision timestamp and
+ * is allowed to wrap quite fast. The returned value is
+ * milli seconds now.
+ */
+uint32_t get_current_ts(void)
+{
+ struct timespec tp;
+ uint64_t ret;
+
+ memset(&tp, 0, sizeof(tp));
+ if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0)
+ LOGP(DMGCP, LOGL_NOTICE,
+ "Getting the clock failed.\n");
+
+ /* convert it to useconds */
+ ret = tp.tv_sec;
+ ret *= 1000;
+ ret += tp.tv_nsec / 1000 / 1000;
+
+ return ret;
+}
+
static int udp_send(int fd, struct in_addr *addr, int port, char *buf, int len)
{
struct sockaddr_in out;
@@ -105,10 +133,20 @@ int mgcp_send_dummy(struct mgcp_endpoint *endp)
endp->net_end.rtp_port, buf, 1);
}
+/**
+ * The RFC 3550 Appendix A assumes there are multiple sources but
+ * some of the supported endpoints (e.g. the nanoBTS) can only handle
+ * one source and this code will patch packages to appear as if there
+ * is only one source.
+ * There is also no probation period for new sources. Every package
+ * we receive will be seen as a switch in streams.
+ */
static void patch_and_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state,
int payload, struct sockaddr_in *addr, char *data, int len)
{
- uint16_t seq;
+ uint32_t arrival_time;
+ int32_t transit, d;
+ uint16_t seq, udelta;
uint32_t timestamp;
struct rtp_hdr *rtp_hdr;
@@ -118,15 +156,19 @@ static void patch_and_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *s
rtp_hdr = (struct rtp_hdr *) data;
seq = ntohs(rtp_hdr->sequence);
timestamp = ntohl(rtp_hdr->timestamp);
+ arrival_time = get_current_ts();
if (!state->initialized) {
- state->seq_no = seq - 1;
+ state->base_seq = seq;
+ state->max_seq = seq - 1;
state->ssrc = state->orig_ssrc = rtp_hdr->ssrc;
state->initialized = 1;
state->last_timestamp = timestamp;
+ state->jitter = 0;
+ state->transit = arrival_time - timestamp;
} else if (state->ssrc != rtp_hdr->ssrc) {
state->ssrc = rtp_hdr->ssrc;
- state->seq_offset = (state->seq_no + 1) - seq;
+ state->seq_offset = (state->max_seq + 1) - seq;
state->timestamp_offset = state->last_timestamp - timestamp;
state->patch = endp->allow_patch;
LOGP(DMGCP, LOGL_NOTICE,
@@ -145,11 +187,35 @@ static void patch_and_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *s
rtp_hdr->timestamp = htonl(timestamp);
}
- /* seq changed, now compare if we have lost something */
- if (state->seq_no + 1u != seq)
- state->lost_no = abs(seq - (state->seq_no + 1));
- state->seq_no = seq;
+ /*
+ * The below takes the shape of the validation from Appendix A. Check
+ * if there is something weird with the sequence number, otherwise check
+ * for a wrap around in the sequence number.
+ */
+ udelta = seq - state->max_seq;
+ if (udelta < RTP_MAX_DROPOUT) {
+ if (seq < state->max_seq)
+ state->cycles += RTP_SEQ_MOD;
+ } else if (udelta <= RTP_SEQ_MOD + RTP_MAX_MISORDER) {
+ LOGP(DMGCP, LOGL_NOTICE,
+ "RTP seqno made a very large jump on 0x%x delta: %u\n",
+ ENDPOINT_NUMBER(endp), udelta);
+ }
+ /*
+ * calculate the jitter between the two packages. The TS should be
+ * taken closer to the read function. This was taken from the
+ * Appendix A of RFC 3550. The local timestamp has a usec resolution.
+ */
+ transit = arrival_time - timestamp;
+ d = transit - state->transit;
+ state->transit = transit;
+ if (d < 0)
+ d = -d;
+ state->jitter += d - ((state->jitter + 8) >> 4);
+
+
+ state->max_seq = seq;
state->last_timestamp = timestamp;
if (payload < 0)
@@ -300,6 +366,7 @@ static int rtp_data_net(struct osmo_fd *fd, unsigned int what)
proto = fd == &endp->net_end.rtp ? PROTO_RTP : PROTO_RTCP;
endp->net_end.packets += 1;
+ endp->net_end.octets += rc;
forward_data(fd->fd, &endp->taps[MGCP_TAP_NET_IN], buf, rc);
if (endp->is_transcoded)
@@ -378,6 +445,7 @@ static int rtp_data_bts(struct osmo_fd *fd, unsigned int what)
/* do this before the loop handling */
endp->bts_end.packets += 1;
+ endp->bts_end.octets += rc;
forward_data(fd->fd, &endp->taps[MGCP_TAP_BTS_IN], buf, rc);
if (endp->is_transcoded)
@@ -581,3 +649,38 @@ int mgcp_free_rtp_port(struct mgcp_rtp_end *end)
return 0;
}
+
+
+void mgcp_state_calc_loss(struct mgcp_rtp_state *state,
+ struct mgcp_rtp_end *end, uint32_t *expected,
+ int *loss)
+{
+ *expected = state->cycles + state->max_seq;
+ *expected = *expected - state->base_seq + 1;
+
+ if (!state->initialized) {
+ *expected = 0;
+ *loss = 0;
+ return;
+ }
+
+ /*
+ * Make sure the sign is correct and use the biggest
+ * positive/negative number that fits.
+ */
+ *loss = *expected - end->packets;
+ if (*expected < end->packets) {
+ if (*loss > 0)
+ *loss = INT_MIN;
+ } else {
+ if (*loss < 0)
+ *loss = INT_MAX;
+ }
+}
+
+uint32_t mgcp_state_calc_jitter(struct mgcp_rtp_state *state)
+{
+ if (!state->initialized)
+ return 0;
+ return state->jitter >> 4;
+}
diff --git a/openbsc/src/libmgcp/mgcp_protocol.c b/openbsc/src/libmgcp/mgcp_protocol.c
index ac7dea19a..4b0222ffb 100644
--- a/openbsc/src/libmgcp/mgcp_protocol.c
+++ b/openbsc/src/libmgcp/mgcp_protocol.c
@@ -107,9 +107,9 @@ static struct msgb *mgcp_msgb_alloc(void)
return msg;
}
-struct msgb *mgcp_create_response_with_data(int code, const char *txt,
- const char *msg, const char *trans,
- const char *data)
+static struct msgb *create_resp(int code, const char *txt, const char *msg,
+ const char *trans, const char *param,
+ const char *sdp)
{
int len;
struct msgb *res;
@@ -118,10 +118,12 @@ struct msgb *mgcp_create_response_with_data(int code, const char *txt,
if (!res)
return NULL;
- if (data) {
- len = snprintf((char *) res->data, 2048, "%d %s%s\r\n%s", code, trans, txt, data);
- } else {
- len = snprintf((char *) res->data, 2048, "%d %s%s\r\n", code, trans, txt);
+ len = snprintf((char *) res->data, 2048, "%d %s%s%s\r\n%s",
+ code, trans, txt, param ? param : "", sdp ? sdp : "");
+ if (len < 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to sprintf MGCP response.\n");
+ msgb_free(res);
+ return NULL;
}
res->l2h = msgb_put(res, len);
@@ -129,9 +131,22 @@ struct msgb *mgcp_create_response_with_data(int code, const char *txt,
return res;
}
+struct msgb *mgcp_create_response_with_data(int code, const char *txt,
+ const char *msg, const char *trans,
+ const char *data)
+{
+ return create_resp(code, txt, msg, trans, NULL, data);
+}
+
+static struct msgb *create_ok_resp_with_param(int code, const char *msg,
+ const char *trans, const char *param)
+{
+ return create_resp(code, " OK", msg, trans, param, NULL);
+}
+
static struct msgb *create_ok_response(int code, const char *msg, const char *trans)
{
- return mgcp_create_response_with_data(code, " OK", msg, trans, NULL);
+ return create_ok_resp_with_param(code, msg, trans, NULL);
}
static struct msgb *create_err_response(int code, const char *msg, const char *trans)
@@ -707,6 +722,7 @@ static struct msgb *handle_delete_con(struct mgcp_config *cfg, struct msgb *msg)
int error_code = 400;
int silent = 0;
char *line, *save;
+ char stats[1048];
for_each_line((char *) msg->l3h, line, save) {
/* skip first line */
@@ -769,6 +785,9 @@ static struct msgb *handle_delete_con(struct mgcp_config *cfg, struct msgb *msg)
LOGP(DMGCP, LOGL_DEBUG, "Deleted endpoint on: 0x%x Server: %s:%u\n",
ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port));
+ /* save the statistics of the current call */
+ mgcp_format_stats(endp, stats, sizeof(stats));
+
delete_transcoder(endp);
mgcp_free_endp(endp);
if (cfg->change_cb)
@@ -776,7 +795,7 @@ static struct msgb *handle_delete_con(struct mgcp_config *cfg, struct msgb *msg)
if (silent)
goto out_silent;
- return create_ok_response(250, "DLCX", trans_id);
+ return create_ok_resp_with_param(250, "DLCX", trans_id, stats);
error3:
return create_err_response(error_code, "DLCX", trans_id);
@@ -897,6 +916,7 @@ static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end)
}
end->packets = 0;
+ end->octets = 0;
memset(&end->addr, 0, sizeof(end->addr));
end->rtp_port = end->rtcp_port = 0;
end->payload_type = -1;
@@ -1098,3 +1118,18 @@ int mgcp_reset_transcoder(struct mgcp_config *cfg)
return send_trans(cfg, mgcp_reset, sizeof mgcp_reset -1);
}
+
+void mgcp_format_stats(struct mgcp_endpoint *endp, char *msg, size_t size)
+{
+ uint32_t expected, jitter;
+ int ploss;
+ mgcp_state_calc_loss(&endp->net_state, &endp->net_end,
+ &expected, &ploss);
+ jitter = mgcp_state_calc_jitter(&endp->net_state);
+
+ snprintf(msg, size, "\r\nP: PS=%u, OS=%u, PR=%u, OR=%u, PL=%d, JI=%d",
+ endp->bts_end.packets, endp->bts_end.octets,
+ endp->net_end.packets, endp->net_end.octets,
+ ploss, jitter);
+ msg[size - 1] = '\0';
+}
diff --git a/openbsc/src/libmgcp/mgcp_vty.c b/openbsc/src/libmgcp/mgcp_vty.c
index 314faa809..122fa8437 100644
--- a/openbsc/src/libmgcp/mgcp_vty.c
+++ b/openbsc/src/libmgcp/mgcp_vty.c
@@ -128,13 +128,12 @@ static void dump_trunk(struct vty *vty, struct mgcp_trunk_config *cfg)
struct mgcp_endpoint *endp = &cfg->endpoints[i];
vty_out(vty,
" Endpoint 0x%.2x: CI: %d net: %u/%u bts: %u/%u on %s "
- "traffic received bts: %u/%u remote: %u/%u transcoder: %u/%u%s",
+ "traffic received bts: %u remote: %u transcoder: %u/%u%s",
i, endp->ci,
ntohs(endp->net_end.rtp_port), ntohs(endp->net_end.rtcp_port),
ntohs(endp->bts_end.rtp_port), ntohs(endp->bts_end.rtcp_port),
inet_ntoa(endp->bts_end.addr),
- endp->bts_end.packets, endp->bts_state.lost_no,
- endp->net_end.packets, endp->net_state.lost_no,
+ endp->bts_end.packets, endp->net_end.packets,
endp->trans_net.packets, endp->trans_bts.packets,
VTY_NEWLINE);
}
diff --git a/openbsc/src/osmo-bsc_mgcp/Makefile.am b/openbsc/src/osmo-bsc_mgcp/Makefile.am
index 166e83dbb..3f58d5db7 100644
--- a/openbsc/src/osmo-bsc_mgcp/Makefile.am
+++ b/openbsc/src/osmo-bsc_mgcp/Makefile.am
@@ -6,5 +6,5 @@ bin_PROGRAMS = osmo-bsc_mgcp
osmo_bsc_mgcp_SOURCES = mgcp_main.c
osmo_bsc_mgcp_LDADD = $(top_builddir)/src/libcommon/libcommon.a \
- $(top_builddir)/src/libmgcp/libmgcp.a \
+ $(top_builddir)/src/libmgcp/libmgcp.a -lrt \
$(LIBOSMOVTY_LIBS) $(LIBOSMOCORE_LIBS)
diff --git a/openbsc/tests/mgcp/mgcp_test.c b/openbsc/tests/mgcp/mgcp_test.c
index d151ebb1f..9b65666f4 100644
--- a/openbsc/tests/mgcp/mgcp_test.c
+++ b/openbsc/tests/mgcp/mgcp_test.c
@@ -1,6 +1,6 @@
/*
- * (C) 2011 by Holger Hans Peter Freyther <zecke@selfish.org>
- * (C) 2011 by On-Waves
+ * (C) 2011-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2011-2012 by On-Waves
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
#include <osmocom/core/application.h>
#include <osmocom/core/talloc.h>
#include <string.h>
+#include <limits.h>
#define AUEP1 "AUEP 158663169 ds/e1-1/2@172.16.6.66 MGCP 1.0\r\n"
#define AUEP1_RET "200 158663169 OK\r\n"
@@ -67,6 +68,13 @@
"m=audio 5904 RTP/AVP 97\r" \
"a=rtpmap:97 GSM-EFR/8000\r"
+#define DLCX "DLCX 7 1@mgw MGCP 1.0\r\n" \
+ "C: 2\r\n"
+
+#define DLCX_RET "250 7 OK\r\n" \
+ "P: PS=0, OS=0, PR=0, OR=0, PL=0, JI=0\r\n"
+
+
struct mgcp_test {
const char *name;
const char *req;
@@ -83,6 +91,7 @@ const struct mgcp_test tests[] = {
{ "SHORT2", SHORT2, SHORT2_RET },
{ "SHORT3", SHORT3, SHORT2_RET },
{ "SHORT4", SHORT4, SHORT2_RET },
+ { "DLCX", DLCX, DLCX_RET },
};
static struct msgb *create_msg(const char *str)
@@ -128,11 +137,64 @@ static void test_messages(void)
talloc_free(cfg);
}
+struct pl_test {
+ int cycles;
+ uint16_t base_seq;
+ uint16_t max_seq;
+ uint32_t packets;
+
+ uint32_t expected;
+ int loss;
+};
+
+static const struct pl_test pl_test_dat[] = {
+ /* basic.. just one package */
+ { .cycles = 0, .base_seq = 0, .max_seq = 0, .packets = 1, .expected = 1, .loss = 0},
+ /* some packages and a bit of loss */
+ { .cycles = 0, .base_seq = 0, .max_seq = 100, .packets = 100, .expected = 101, .loss = 1},
+ /* wrap around */
+ { .cycles = 1<<16, .base_seq = 0xffff, .max_seq = 2, .packets = 4, .expected = 4, .loss = 0},
+ /* min loss */
+ { .cycles = 0, .base_seq = 0, .max_seq = 0, .packets = UINT_MAX, .expected = 1, .loss = INT_MIN },
+ /* max loss, with wrap around on expected max */
+ { .cycles = INT_MAX, .base_seq = 0, .max_seq = UINT16_MAX, .packets = 0, .expected = ((uint32_t)(INT_MAX) + UINT16_MAX + 1), .loss = INT_MAX },
+};
+
+static void test_packet_loss_calc(void)
+{
+ int i;
+ printf("Testing packet loss calculation.\n");
+
+ for (i = 0; i < ARRAY_SIZE(pl_test_dat); ++i) {
+ uint32_t expected;
+ int loss;
+ struct mgcp_rtp_state state;
+ struct mgcp_rtp_end rtp;
+ memset(&state, 0, sizeof(state));
+ memset(&rtp, 0, sizeof(rtp));
+
+ state.initialized = 1;
+ state.base_seq = pl_test_dat[i].base_seq;
+ state.max_seq = pl_test_dat[i].max_seq;
+ state.cycles = pl_test_dat[i].cycles;
+
+ rtp.packets = pl_test_dat[i].packets;
+ mgcp_state_calc_loss(&state, &rtp, &expected, &loss);
+
+ if (loss != pl_test_dat[i].loss || expected != pl_test_dat[i].expected) {
+ printf("FAIL: Wrong exp/loss at idx(%d) Loss(%d vs. %d) Exp(%u vs. %u)\n",
+ i, loss, pl_test_dat[i].loss,
+ expected, pl_test_dat[i].expected);
+ }
+ }
+}
+
int main(int argc, char **argv)
{
osmo_init_logging(&log_info);
test_messages();
+ test_packet_loss_calc();
printf("Done\n");
return EXIT_SUCCESS;
diff --git a/openbsc/tests/mgcp/mgcp_test.ok b/openbsc/tests/mgcp/mgcp_test.ok
index 45882a57f..e61c0bc15 100644
--- a/openbsc/tests/mgcp/mgcp_test.ok
+++ b/openbsc/tests/mgcp/mgcp_test.ok
@@ -7,4 +7,6 @@ Testing SHORT1
Testing SHORT2
Testing SHORT3
Testing SHORT4
+Testing DLCX
+Testing packet loss calculation.
Done