diff options
author | Alfred E. Heggestad <aeh@db.org> | 2016-07-19 18:37:38 +0200 |
---|---|---|
committer | Alfred E. Heggestad <aeh@db.org> | 2016-07-19 18:37:38 +0200 |
commit | 9da1970131662ad480dae589618411734dcf4905 (patch) | |
tree | 44577fcffff35352d432c3aa0c9b0dd69e33c1a0 | |
parent | e27e37b547b709d8ecf8be306c2b5137b7affad3 (diff) |
add support for rtp_timeout and redial
1. Added support for RTP timeout. The feature is disabled by default
and can be enabled with config "rtp_timeout N" where N is the number
of seconds of RTP inactivity. If this is detected, the call is
closed with a "special" SIP reason code of 701.
2. Added support for automatic re-connect in the menu module.
This can be enabled by setting the 2 config items:
redial_attempts 3
redial_delay 5
This work was contributed by Sveriges Radio. Thanks
goes to Ola Palm and Jim Eld.
-rw-r--r-- | include/baresip.h | 2 | ||||
-rw-r--r-- | modules/menu/menu.c | 107 | ||||
-rw-r--r-- | src/call.c | 47 | ||||
-rw-r--r-- | src/config.c | 12 | ||||
-rw-r--r-- | src/core.h | 13 | ||||
-rw-r--r-- | src/stream.c | 93 | ||||
-rw-r--r-- | test/call.c | 48 | ||||
-rw-r--r-- | test/main.c | 1 | ||||
-rw-r--r-- | test/test.h | 1 |
9 files changed, 320 insertions, 4 deletions
diff --git a/include/baresip.h b/include/baresip.h index ae73cae..22c75ea 100644 --- a/include/baresip.h +++ b/include/baresip.h @@ -100,6 +100,7 @@ struct list *call_streaml(const struct call *call); struct ua *call_get_ua(const struct call *call); bool call_is_onhold(const struct call *call); bool call_is_outgoing(const struct call *call); +void call_enable_rtp_timeout(struct call *call, uint32_t timeout_ms); /* @@ -201,6 +202,7 @@ struct config_avt { bool rtcp_mux; /**< RTP/RTCP multiplexing */ struct range jbuf_del; /**< Delay, number of frames */ bool rtp_stats; /**< Enable RTP statistics */ + uint32_t rtp_timeout; /**< RTP Timeout in seconds (0=off) */ }; /* Network */ diff --git a/modules/menu/menu.c b/modules/menu/menu.c index 3c98c8f..b24038a 100644 --- a/modules/menu/menu.c +++ b/modules/menu/menu.c @@ -36,6 +36,11 @@ static struct le *le_cur; /**< Current User-Agent (struct ua) */ static struct { struct play *play; bool bell; + + struct tmr tmr_redial; + uint32_t redial_delay; /* Redial delay in [seconds] */ + uint32_t redial_attempts; /* Number of re-dial attempts */ + uint32_t current_attempts; } menu; @@ -44,6 +49,13 @@ static void update_callstatus(void); static void alert_stop(void); +static void redial_reset(void) +{ + tmr_cancel(&menu.tmr_redial); + menu.current_attempts = 0; +} + + static const char *translate_errorcode(uint16_t scode) { switch (scode) { @@ -790,6 +802,42 @@ static void alert_stop(void) } +static void redial_handler(void *arg) +{ + char *uri = NULL; + int err; + (void)arg; + + info("now: redialing now. current_attempts=%u, max_attempts=%u\n", + menu.current_attempts, + menu.redial_attempts); + + if (menu.current_attempts > menu.redial_attempts) { + + info("menu: redial: too many attemptes -- giving up\n"); + return; + } + + if (dialbuf->end == 0) { + warning("menu: redial: dialbuf is empty\n"); + return; + } + + dialbuf->pos = 0; + err = mbuf_strdup(dialbuf, &uri, dialbuf->end); + if (err) + return; + + err = ua_connect(uag_cur(), NULL, NULL, uri, NULL, VIDMODE_ON); + if (err) { + warning("menu: redial: ua_connect failed (%m)\n", err); + } + + mem_deref(uri); + +} + + static void ua_event_handler(struct ua *ua, enum ua_event ev, struct call *call, const char *prm, void *arg) { @@ -842,6 +890,10 @@ static void ua_event_handler(struct ua *ua, enum ua_event ev, menu.play = mem_deref(menu.play); alert_stop(); + + /* We must stop the re-dialing if the call was + established */ + redial_reset(); break; case UA_EVENT_CALL_CLOSED: @@ -856,6 +908,35 @@ static void ua_event_handler(struct ua *ua, enum ua_event ev, } alert_stop(); + + /* Activate the re-dialing if: + * + * - redial_attempts must be enabled in config + * - the closed call must be of outgoing direction + * - the closed call must fail with special code 701 + */ + if (menu.redial_attempts) { + + if (menu.current_attempts + || + (call_is_outgoing(call) && + call_scode(call) == 701)) { + + info("menu: call closed" + " -- redialing in %u seconds\n", + menu.redial_delay); + + ++menu.current_attempts; + + tmr_start(&menu.tmr_redial, + menu.redial_delay*1000, + redial_handler, NULL); + } + else { + info("menu: call closed -- not redialing\n"); + } + } + break; case UA_EVENT_REGISTER_OK: @@ -889,10 +970,31 @@ static void message_handler(const struct pl *peer, const struct pl *ctype, static int module_init(void) { + struct pl val; int err; + /* + * Read the config values + */ conf_get_bool(conf_cur(), "menu_bell", &menu.bell); + if (0 == conf_get(conf_cur(), "redial_attempts", &val) && + 0 == pl_strcasecmp(&val, "inf")) { + menu.redial_attempts = (uint32_t)-1; + } + else { + conf_get_u32(conf_cur(), "redial_attempts", + &menu.redial_attempts); + } + conf_get_u32(conf_cur(), "redial_delay", &menu.redial_delay); + + if (menu.redial_attempts) { + info("menu: redial enabled with %u attempts and" + " %u seconds delay\n", + menu.redial_attempts, + menu.redial_delay); + } + dialbuf = mbuf_alloc(64); if (!dialbuf) return ENOMEM; @@ -913,6 +1015,9 @@ static int module_init(void) static int module_close(void) { + debug("menu: close (redial current_attempts=%d)\n", + menu.current_attempts); + message_close(); uag_event_unregister(ua_event_handler); cmd_unregister(cmdv); @@ -926,6 +1031,8 @@ static int module_close(void) menu.play = mem_deref(menu.play); + tmr_cancel(&menu.tmr_redial); + return 0; } @@ -78,6 +78,8 @@ struct call { struct config_avt config_avt; struct config_call config_call; + + uint32_t rtp_timeout_ms; /**< RTP Timeout in [ms] */ }; @@ -420,6 +422,22 @@ static void menc_error_handler(int err, void *arg) } +static void stream_error_handler(struct stream *strm, int err, void *arg) +{ + struct call *call = arg; + MAGIC_CHECK(call); + + info("call: error in \"%s\" rtp stream (%m)\n", + sdp_media_name(stream_sdpmedia(strm)), err); + + call->scode = 701; + set_state(call, STATE_TERMINATED); + + call_stream_stop(call); + call_event_handler(call, CALL_EVENT_CLOSED, "rtp stream error"); +} + + /** * Allocate a new Call state object * @@ -445,6 +463,7 @@ int call_alloc(struct call **callp, const struct config *cfg, struct list *lst, call_event_h *eh, void *arg) { struct call *call; + struct le *le; enum vidmode vidmode = prm ? prm->vidmode : VIDMODE_OFF; bool use_video = true, got_offer = false; int label = 0; @@ -567,6 +586,15 @@ int call_alloc(struct call **callp, const struct config *cfg, struct list *lst, call->not = mem_ref(xcall->not); } + FOREACH_STREAM { + struct stream *strm = le->data; + stream_set_error_handler(strm, stream_error_handler, call); + } + + if (cfg->avt.rtp_timeout) { + call_enable_rtp_timeout(call, cfg->avt.rtp_timeout*1000); + } + list_append(lst, &call->le, call); out: @@ -1033,6 +1061,16 @@ static void sipsess_estab_handler(const struct sip_msg *msg, void *arg) call_stream_start(call, true); + if (call->rtp_timeout_ms) { + + struct le *le; + + FOREACH_STREAM { + struct stream *strm = le->data; + stream_enable_rtp_timeout(strm, call->rtp_timeout_ms); + } + } + /* the transferor will hangup this call */ if (call->not) { (void)call_notify_sipfrag(call, 200, "OK"); @@ -1716,3 +1754,12 @@ bool call_is_outgoing(const struct call *call) { return call ? call->outgoing : false; } + + +void call_enable_rtp_timeout(struct call *call, uint32_t timeout_ms) +{ + if (!call) + return; + + call->rtp_timeout_ms = timeout_ms; +} diff --git a/src/config.c b/src/config.c index 77a4411..899575d 100644 --- a/src/config.c +++ b/src/config.c @@ -70,7 +70,8 @@ static struct config core_config = { true, false, {5, 10}, - false + false, + 0 }, /* Network */ @@ -223,6 +224,7 @@ int config_parse_conf(struct config *cfg, const struct conf *conf) (void)conf_get_range(conf, "jitter_buffer_delay", &cfg->avt.jbuf_del); (void)conf_get_bool(conf, "rtp_stats", &cfg->avt.rtp_stats); + (void)conf_get_u32(conf, "rtp_timeout", &cfg->avt.rtp_timeout); if (err) { warning("config: configure parse error (%m)\n", err); @@ -289,6 +291,7 @@ int config_print(struct re_printf *pf, const struct config *cfg) "rtcp_mux\t\t%s\n" "jitter_buffer_delay\t%H\n" "rtp_stats\t\t%s\n" + "rtp_timeout\t\t%u # in seconds\n" "\n" "# Network\n" "net_interface\t\t%s\n" @@ -327,6 +330,7 @@ int config_print(struct re_printf *pf, const struct config *cfg) cfg->avt.rtcp_mux ? "yes" : "no", range_print, &cfg->avt.jbuf_del, cfg->avt.rtp_stats ? "yes" : "no", + cfg->avt.rtp_timeout, cfg->net.ifname @@ -465,6 +469,7 @@ static int core_config_template(struct re_printf *pf, const struct config *cfg) "rtcp_mux\t\tno\n" "jitter_buffer_delay\t%u-%u\t\t# frames\n" "rtp_stats\t\tno\n" + "#rtp_timeout\t\t60\n" "\n# Network\n" "#dns_server\t\t10.0.0.1:53\n" "#net_interface\t\t%H\n", @@ -749,6 +754,11 @@ int config_write_template(const char *file, const struct config *cfg) "ice_nomination\t\tregular\t# {regular,aggressive}\n" "ice_mode\t\tfull\t# {full,lite}\n"); + (void)re_fprintf(f, + "\n# Menu\n" + "#redial_attempts\t\t3 # Num or <inf>\n" + "#redial_delay\t\t5 # Delay in seconds\n"); + if (f) (void)fclose(f); @@ -281,6 +281,9 @@ typedef void (stream_rtp_h)(const struct rtp_header *hdr, struct mbuf *mb, void *arg); typedef void (stream_rtcp_h)(struct rtcp_msg *msg, void *arg); +typedef void (stream_error_h)(struct stream *strm, int err, void *arg); + + /** Defines a generic media stream */ struct stream { struct le le; /**< Linked list element */ @@ -307,6 +310,13 @@ struct stream { stream_rtp_h *rtph; /**< Stream RTP handler */ stream_rtcp_h *rtcph; /**< Stream RTCP handler */ void *arg; /**< Handler argument */ + + stream_error_h *errorh; + void *errorh_arg; + struct tmr tmr_rtp; + uint64_t ts_last; + bool terminated; + uint32_t rtp_timeout_ms; /* [ms] */ }; int stream_alloc(struct stream **sp, const struct config_avt *cfg, @@ -327,8 +337,11 @@ void stream_set_srate(struct stream *s, uint32_t srate_tx, uint32_t srate_rx); void stream_send_fir(struct stream *s, bool pli); void stream_reset(struct stream *s); void stream_set_bw(struct stream *s, uint32_t bps); +void stream_set_error_handler(struct stream *strm, + stream_error_h *errorh, void *arg); int stream_debug(struct re_printf *pf, const struct stream *s); int stream_print(struct re_printf *pf, const struct stream *s); +void stream_enable_rtp_timeout(struct stream *strm, uint32_t timeout_ms); /* diff --git a/src/stream.c b/src/stream.c index 9dc572f..9250fe2 100644 --- a/src/stream.c +++ b/src/stream.c @@ -12,9 +12,63 @@ enum { RTP_RECV_SIZE = 8192, + RTP_CHECK_INTERVAL = 1000 /* how often to check for RTP [ms] */ }; +static void stream_close(struct stream *strm, int err) +{ + strm->terminated = true; + + if (strm->errorh) { + strm->errorh(strm, err, strm->errorh_arg); + strm->errorh = NULL; + } +} + + +/* TODO: should we check RTP per stream, or should we have the + * check in struct call instead? + */ +static void check_rtp_handler(void *arg) +{ + struct stream *strm = arg; + const uint64_t now = tmr_jiffies(); + int diff_ms; + + tmr_start(&strm->tmr_rtp, RTP_CHECK_INTERVAL, + check_rtp_handler, strm); + + /* If no RTP was received at all, check later */ + if (!strm->ts_last) + return; + + /* We are in sendrecv mode, check when the last RTP packet + * was received. + */ + if (sdp_media_dir(strm->sdp) == SDP_SENDRECV) { + + diff_ms = (int)(now - strm->ts_last); + + debug("stream: last \"%s\" RTP packet: %d milliseconds\n", + sdp_media_name(strm->sdp), diff_ms); + + if (diff_ms > (int)strm->rtp_timeout_ms) { + + info("stream: no %s RTP packets received for" + " %d milliseconds\n", + sdp_media_name(strm->sdp), diff_ms); + + stream_close(strm, ETIMEDOUT); + } + } + else { + re_printf("check_rtp: not checking (dir=%s)\n", + sdp_dir_name(sdp_media_dir(strm->sdp))); + } +} + + static inline int lostcalc(struct stream *s, uint16_t seq) { const uint16_t delta = seq - s->pseq; @@ -79,6 +133,7 @@ static void stream_destructor(void *arg) metric_reset(&s->metric_tx); metric_reset(&s->metric_rx); + tmr_cancel(&s->tmr_rtp); list_unlink(&s->le); mem_deref(s->rtpkeep); mem_deref(s->sdp); @@ -98,6 +153,8 @@ static void rtp_recv(const struct sa *src, const struct rtp_header *hdr, bool flush = false; int err; + s->ts_last = tmr_jiffies(); + if (!mbuf_get_left(mb)) return; @@ -446,6 +503,9 @@ void stream_update(struct stream *s) void stream_update_encoder(struct stream *s, int pt_enc) { + if (!s) + return; + if (pt_enc >= 0) s->pt_enc = pt_enc; } @@ -534,6 +594,37 @@ void stream_set_bw(struct stream *s, uint32_t bps) } +void stream_enable_rtp_timeout(struct stream *strm, uint32_t timeout_ms) +{ + if (!strm) + return; + + strm->rtp_timeout_ms = timeout_ms; + + tmr_cancel(&strm->tmr_rtp); + + if (timeout_ms) { + + info("stream: Enable RTP timeout (%u milliseconds)\n", + timeout_ms); + + strm->ts_last = tmr_jiffies(); + tmr_start(&strm->tmr_rtp, 10, check_rtp_handler, strm); + } +} + + +void stream_set_error_handler(struct stream *strm, + stream_error_h *errorh, void *arg) +{ + if (!strm) + return; + + strm->errorh = errorh; + strm->errorh_arg = arg; +} + + int stream_debug(struct re_printf *pf, const struct stream *s) { struct sa rrtcp; @@ -567,5 +658,3 @@ int stream_print(struct re_printf *pf, const struct stream *s) s->metric_tx.cur_bitrate, s->metric_rx.cur_bitrate); } - - diff --git a/test/call.c b/test/call.c index 0c168ad..06c7bf3 100644 --- a/test/call.c +++ b/test/call.c @@ -20,7 +20,8 @@ enum behaviour { enum action { ACTION_RECANCEL = 0, ACTION_HANGUP_A, - ACTION_HANGUP_B + ACTION_HANGUP_B, + ACTION_NOTHING }; struct agent { @@ -164,6 +165,10 @@ static void event_handler(struct ua *ua, enum ua_event ev, f->b.failed = true; ua_hangup(f->b.ua, NULL, 0, 0); break; + + case ACTION_NOTHING: + /* Do nothing, wait */ + break; } } break; @@ -364,3 +369,44 @@ int test_call_answer_hangup_b(void) return err; } + + +int test_call_rtp_timeout(void) +{ +#define RTP_TIMEOUT_MS 1 + struct fixture fix, *f = &fix; + struct call *call; + int err = 0; + + fixture_init(f); + + f->behaviour = BEHAVIOUR_ANSWER; + f->estab_action = ACTION_NOTHING; + + /* Make a call from A to B */ + err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF); + TEST_ERR(err); + + call = ua_call(f->a.ua); + ASSERT_TRUE(call != NULL); + + call_enable_rtp_timeout(call, RTP_TIMEOUT_MS); + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + ASSERT_EQ(1, fix.a.n_established); + ASSERT_EQ(1, fix.a.n_closed); + ASSERT_EQ(701, fix.a.close_scode); /* verify timeout */ + + ASSERT_EQ(1, fix.b.n_established); + ASSERT_EQ(1, fix.b.n_closed); + ASSERT_EQ(0, fix.b.close_scode); + + out: + fixture_close(f); + + return err; +} diff --git a/test/main.c b/test/main.c index 061f8ff..d403084 100644 --- a/test/main.c +++ b/test/main.c @@ -24,6 +24,7 @@ static const struct test tests[] = { TEST(test_call_answer_hangup_a), TEST(test_call_answer_hangup_b), TEST(test_call_reject), + TEST(test_call_rtp_timeout), TEST(test_cmd), TEST(test_cplusplus), TEST(test_mos), diff --git a/test/test.h b/test/test.h index fe9cd4c..15629ba 100644 --- a/test/test.h +++ b/test/test.h @@ -102,6 +102,7 @@ int test_call_reject(void); int test_call_af_mismatch(void); int test_call_answer_hangup_a(void); int test_call_answer_hangup_b(void); +int test_call_rtp_timeout(void); #ifdef __cplusplus |