diff options
author | Holger Hans Peter Freyther <zecke@selfish.org> | 2012-11-12 10:46:11 +0100 |
---|---|---|
committer | Holger Hans Peter Freyther <zecke@selfish.org> | 2012-11-12 10:46:11 +0100 |
commit | 88c06bcd64adedcbf738991c15203f646ff01d86 (patch) | |
tree | 28fe81ac75a26689f7edbc346bc2eab6c6030d08 | |
parent | de1674ab02d7c30f7f40f03d6942b7933d9d6d58 (diff) | |
parent | cb306a689e4bef5d2f82315ff4c694517750519f (diff) |
Merge branch 'zecke/mgcp-rtp-statistics'
-rw-r--r-- | openbsc/include/openbsc/mgcp.h | 1 | ||||
-rw-r--r-- | openbsc/include/openbsc/mgcp_internal.h | 18 | ||||
-rw-r--r-- | openbsc/src/libmgcp/mgcp_network.c | 121 | ||||
-rw-r--r-- | openbsc/src/libmgcp/mgcp_protocol.c | 53 | ||||
-rw-r--r-- | openbsc/src/libmgcp/mgcp_vty.c | 5 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc_mgcp/Makefile.am | 2 | ||||
-rw-r--r-- | openbsc/tests/mgcp/mgcp_test.c | 66 | ||||
-rw-r--r-- | openbsc/tests/mgcp/mgcp_test.ok | 2 |
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 |