diff --git a/README.md b/README.md index 58be0284f..06012edd4 100644 --- a/README.md +++ b/README.md @@ -1056,6 +1056,8 @@ a string and determines the type of message. Currently the following commands ar * query * start recording * stop recording +* block DTMF +* unblock DTMF The response dictionary must contain at least one key called `result`. The value can be either `ok` or `error`. For the `ping` command, the additional value `pong` is allowed. If the result is `error`, then another key @@ -1893,3 +1895,12 @@ The `stop recording` message must contain the key `call-id` as defined above. Th no additional keys. Disables call recording for the call. This can be sent during a call to imediatley stop recording it. + +`block DTMF` and `unblock DTMF` Messages +---------------------------------------- +These message types must include the key `call-id` in the message. They enable and disable blocking of DTMF +events (RFC 4733 type packets) for a call, respectively. + +When DTMF blocking is enabled for a call, DTMF event packets will not be forwarded to the receiving peer. +If DTMF logging is enabled, DTMF events will still be logged to syslog while blocking is enabled. Blocking +of DTMF events happens for an entire call and can be enabled and disabled at any time during call runtime. diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 4a65bfd34..dec2a3cc6 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -1355,6 +1355,45 @@ const char *call_stop_recording_ng(bencode_item_t *input, bencode_item_t *output return NULL; } +const char *call_block_dtmf_ng(bencode_item_t *input, bencode_item_t *output) { + str callid; + struct call *call; + + if (!bencode_dictionary_get_str(input, "call-id", &callid)) + return "No call-id in message"; + call = call_get_opmode(&callid, OP_OTHER); + if (!call) + return "Unknown call-id"; + + ilog(LOG_INFO, "Blocking DTMF"); + call->block_dtmf = 1; + + rwlock_unlock_w(&call->master_lock); + obj_put(call); + + return NULL; +} + +const char *call_unblock_dtmf_ng(bencode_item_t *input, bencode_item_t *output) { + str callid; + struct call *call; + + if (!bencode_dictionary_get_str(input, "call-id", &callid)) + return "No call-id in message"; + call = call_get_opmode(&callid, OP_OTHER); + if (!call) + return "Unknown call-id"; + + ilog(LOG_INFO, "Unblocking DTMF"); + call->block_dtmf = 0; + + rwlock_unlock_w(&call->master_lock); + obj_put(call); + + return NULL; +} + + int call_interfaces_init() { const char *errptr; int erroff; diff --git a/daemon/cli.c b/daemon/cli.c index 72291a329..2d5fb131f 100644 --- a/daemon/cli.c +++ b/daemon/cli.c @@ -461,8 +461,8 @@ static void cli_incoming_list_totals(str *instr, struct streambuf *replybuffer) streambuf_printf(replybuffer, "\n\n"); streambuf_printf(replybuffer, "Control statistics:\n\n"); - streambuf_printf(replybuffer, " %20s | %10s | %10s | %10s | %10s | %10s | %10s | %10s | %10s | %10s \n", - "Proxy", "Offer", "Answer", "Delete", "Ping", "List", "Query", "StartRec", "StopRec", "Errors"); + streambuf_printf(replybuffer, " %20s | %10s | %10s | %10s | %10s | %10s | %10s | %10s | %10s | %10s | %10s | %10s \n", + "Proxy", "Offer", "Answer", "Delete", "Ping", "List", "Query", "StartRec", "StopRec", "Errors", "BlkDTMF", "UnblkDTMF"); mutex_lock(&rtpe_cngs_lock); GList *list = g_hash_table_get_values(rtpe_cngs_hash); @@ -472,7 +472,7 @@ static void cli_incoming_list_totals(str *instr, struct streambuf *replybuffer) } for (GList *l = list; l; l = l->next) { struct control_ng_stats* cur = l->data; - streambuf_printf(replybuffer, " %20s | %10u | %10u | %10u | %10u | %10u | %10u | %10u | %10u | %10u \n", + streambuf_printf(replybuffer, " %20s | %10u | %10u | %10u | %10u | %10u | %10u | %10u | %10u | %10u | %10u | %10u \n", sockaddr_print_buf(&cur->proxy), cur->offer, cur->answer, @@ -482,7 +482,9 @@ static void cli_incoming_list_totals(str *instr, struct streambuf *replybuffer) cur->query, cur->start_recording, cur->stop_recording, - cur->errors); + cur->errors, + cur->block_dtmf, + cur->unblock_dtmf); } streambuf_printf(replybuffer, "\n\n"); mutex_unlock(&rtpe_cngs_lock); diff --git a/daemon/codec.c b/daemon/codec.c index 9be8a7176..53d756001 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -647,13 +647,15 @@ static int packet_dtmf(struct codec_ssrc_handler *ch, struct transcode_packet *p } } - packet_dtmf_fwd(ch, packet, mp, 1); + if (!mp->call->block_dtmf) + packet_dtmf_fwd(ch, packet, mp, 1); return 0; } static void packet_dtmf_dup(struct codec_ssrc_handler *ch, struct transcode_packet *packet, struct media_packet *mp) { - packet_dtmf_fwd(ch, packet, mp, 0); + if (!mp->call->block_dtmf) + packet_dtmf_fwd(ch, packet, mp, 0); } static int handler_func_dtmf(struct codec_handler *h, struct media_packet *mp) { diff --git a/daemon/control_ng.c b/daemon/control_ng.c index 63400970c..cd5607a1c 100644 --- a/daemon/control_ng.c +++ b/daemon/control_ng.c @@ -216,6 +216,14 @@ static void control_ng_incoming(struct obj *obj, str *buf, const endpoint_t *sin errstr = call_stop_recording_ng(dict, resp); g_atomic_int_inc(&cur->stop_recording); } + else if (!str_cmp(&cmd, "block DTMF")) { + errstr = call_block_dtmf_ng(dict, resp); + g_atomic_int_inc(&cur->block_dtmf); + } + else if (!str_cmp(&cmd, "unblock DTMF")) { + errstr = call_unblock_dtmf_ng(dict, resp); + g_atomic_int_inc(&cur->unblock_dtmf); + } else { errstr = "Unrecognized command"; diff --git a/daemon/redis.c b/daemon/redis.c index 902008f32..5f28d9c9f 100644 --- a/daemon/redis.c +++ b/daemon/redis.c @@ -1579,6 +1579,8 @@ static void json_restore_call(struct redis *r, const str *callid, enum call_type c->created_from = call_strdup(c, id.s); if (!redis_hash_get_str(&id, &call, "created_from_addr")) sockaddr_parse_any_str(&c->created_from_addr, &id); + if (!redis_hash_get_int(&i, &call, "block_dtmf")) + c->block_dtmf = i ? 1 : 0; err = "missing 'redis_hosted_db' value"; if (redis_hash_get_unsigned((unsigned int *) &c->redis_hosted_db, &call, "redis_hosted_db")) @@ -1859,6 +1861,7 @@ char* redis_encode_json(struct call *c) { JSON_SET_SIMPLE_CSTR("created_from_addr",sockaddr_print_buf(&c->created_from_addr)); JSON_SET_SIMPLE("redis_hosted_db","%u",c->redis_hosted_db); JSON_SET_SIMPLE_STR("recording_metadata",&c->metadata); + JSON_SET_SIMPLE("block_dtmf","%i",c->block_dtmf ? 1 : 0); if ((rec = c->recording)) { JSON_SET_SIMPLE_CSTR("recording_meta_prefix",rec->meta_prefix); diff --git a/include/call.h b/include/call.h index 3a1aa3e88..c646277c1 100644 --- a/include/call.h +++ b/include/call.h @@ -399,6 +399,8 @@ struct call { struct recording *recording; str metadata; + + int block_dtmf:1; }; diff --git a/include/call_interfaces.h b/include/call_interfaces.h index 824e0cc18..d735710b3 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -94,6 +94,8 @@ const char *call_query_ng(bencode_item_t *, bencode_item_t *); const char *call_list_ng(bencode_item_t *, bencode_item_t *); const char *call_start_recording_ng(bencode_item_t *, bencode_item_t *); const char *call_stop_recording_ng(bencode_item_t *, bencode_item_t *); +const char *call_block_dtmf_ng(bencode_item_t *, bencode_item_t *); +const char *call_unblock_dtmf_ng(bencode_item_t *, bencode_item_t *); void ng_call_stats(struct call *call, const str *fromtag, const str *totag, bencode_item_t *output, struct call_stats *totals); diff --git a/include/control_ng.h b/include/control_ng.h index d38815ecc..0d2bec3a8 100644 --- a/include/control_ng.h +++ b/include/control_ng.h @@ -19,6 +19,8 @@ struct control_ng_stats { int list; int start_recording; int stop_recording; + int block_dtmf; + int unblock_dtmf; int errors; }; diff --git a/t/transcode-test.c b/t/transcode-test.c index 5a50e670b..fcb950034 100644 --- a/t/transcode-test.c +++ b/t/transcode-test.c @@ -806,5 +806,168 @@ int main() { packet_seq_exp(A, 0, PCMU_payload, 1000960, 212, 0, PCMU_payload, 5); // expected seq is 207+5 for PT 8 end(); + // plain DTMF passthrough w/o transcoding - blocking + start(); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + offer(); + expect(A, recv, ""); + expect(A, send, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 101/telephone-event/8000"); + packet_seq(A, 8, PCMA_payload, 1000000, 200, 8, PCMA_payload); + // start with marker + packet_seq(A, 101 | 0x80, "\x08\x0a\x00\xa0", 1000160, 201, 101 | 0x80, "\x08\x0a\x00\xa0"); + dtmf(""); + // continuous event with increasing length + // XXX check output ts, seq, ssrc + packet_seq(A, 101, "\x08\x0a\x01\x40", 1000160, 202, 101, "\x08\x0a\x01\x40"); + packet_seq(A, 101, "\x08\x0a\x01\xe0", 1000160, 203, 101, "\x08\x0a\x01\xe0"); + packet_seq(A, 101, "\x08\x0a\x02\x80", 1000160, 204, 101, "\x08\x0a\x02\x80"); + dtmf(""); + // end + packet_seq(A, 101, "\x08\x8a\x03\x20", 1000160, 205, 101, "\x08\x8a\x03\x20"); + dtmf("{\"callid\":\"test-call\",\"source_tag\":\"tag_A\",\"tags\":[],\"type\":\"DTMF\",\"timestamp\":0,\"source_ip\":\"(null)\",\"event\":8,\"duration\":100,\"volume\":10}"); + packet_seq_exp(A, 101, "\x08\x8a\x03\x20", 1000160, 205, 101, "\x08\x8a\x03\x20", 0); + packet_seq_exp(A, 101, "\x08\x8a\x03\x20", 1000160, 205, 101, "\x08\x8a\x03\x20", 0); + dtmf(""); + // send some more audio + packet_seq_exp(A, 8, PCMA_payload, 1000960, 206, 8, PCMA_payload, 6); // expected seq is 200+6 for PT 8 + packet_seq(A, 8, PCMA_payload, 1001120, 207, 8, PCMA_payload); + // enable blocking + call.block_dtmf = 1; + // start with marker + packet_seq_exp(A, 101 | 0x80, "\x05\x0a\x00\xa0", 1001280, 208, -1, "", 0); + dtmf(""); + // continuous event with increasing length + packet_seq(A, 101, "\x05\x0a\x01\x40", 1001280, 209, -1, ""); + packet_seq(A, 101, "\x05\x0a\x01\xe0", 1001280, 210, -1, ""); + dtmf(""); + // end + packet_seq(A, 101, "\x05\x8a\x02\x80", 1001280, 211, -1, ""); + dtmf("{\"callid\":\"test-call\",\"source_tag\":\"tag_A\",\"tags\":[],\"type\":\"DTMF\",\"timestamp\":0,\"source_ip\":\"(null)\",\"event\":5,\"duration\":80,\"volume\":10}"); + packet_seq_exp(A, 101, "\x05\x8a\x02\x80", 1001280, 211, -1, "", 0); + packet_seq_exp(A, 101, "\x05\x8a\x02\x80", 1001280, 211, -1, "", 0); + dtmf(""); + // final audio RTP test + packet_seq_exp(A, 8, PCMA_payload, 1000960, 212, 8, PCMA_payload, 5); // expected seq is 207+5 for PT 8 + end(); + + // DTMF passthrough w/ transcoding - blocking + start(); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + transcode(PCMU); + offer(); + expect(A, recv, ""); + expect(A, send, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000 0/PCMU/8000"); + expect(B, send, ""); + sdp_pt(0, PCMU, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, recv, "101/telephone-event/8000 0/PCMU/8000"); + expect(B, send, "0/PCMU/8000 101/telephone-event/8000"); + packet_seq(A, 8, PCMA_payload, 1000000, 200, 0, PCMU_payload); + // start with marker + packet_seq(A, 101 | 0x80, "\x08\x0a\x00\xa0", 1000160, 201, 101 | 0x80, "\x08\x0a\x00\xa0"); + dtmf(""); + // continuous event with increasing length + // XXX check output ts, seq, ssrc + packet_seq(A, 101, "\x08\x0a\x01\x40", 1000160, 202, 101, "\x08\x0a\x01\x40"); + packet_seq(A, 101, "\x08\x0a\x01\xe0", 1000160, 203, 101, "\x08\x0a\x01\xe0"); + packet_seq(A, 101, "\x08\x0a\x02\x80", 1000160, 204, 101, "\x08\x0a\x02\x80"); + dtmf(""); + // end + packet_seq(A, 101, "\x08\x8a\x03\x20", 1000160, 205, 101, "\x08\x8a\x03\x20"); + dtmf("{\"callid\":\"test-call\",\"source_tag\":\"tag_A\",\"tags\":[],\"type\":\"DTMF\",\"timestamp\":0,\"source_ip\":\"(null)\",\"event\":8,\"duration\":100,\"volume\":10}"); + packet_seq_exp(A, 101, "\x08\x8a\x03\x20", 1000160, 205, 101, "\x08\x8a\x03\x20", 0); + packet_seq_exp(A, 101, "\x08\x8a\x03\x20", 1000160, 205, 101, "\x08\x8a\x03\x20", 0); + dtmf(""); + // send some more audio + packet_seq_exp(A, 8, PCMA_payload, 1000960, 206, 0, PCMU_payload, 6); // expected seq is 200+6 for PT 8 + packet_seq(A, 8, PCMA_payload, 1001120, 207, 0, PCMU_payload); + // enable blocking + call.block_dtmf = 1; + // start with marker + packet_seq_exp(A, 101 | 0x80, "\x05\x0a\x00\xa0", 1001280, 208, -1, "", 0); + dtmf(""); + // continuous event with increasing length + packet_seq(A, 101, "\x05\x0a\x01\x40", 1001280, 209, -1, ""); + packet_seq(A, 101, "\x05\x0a\x01\xe0", 1001280, 210, -1, ""); + dtmf(""); + // end + packet_seq(A, 101, "\x05\x8a\x02\x80", 1001280, 211, -1, ""); + dtmf("{\"callid\":\"test-call\",\"source_tag\":\"tag_A\",\"tags\":[],\"type\":\"DTMF\",\"timestamp\":0,\"source_ip\":\"(null)\",\"event\":5,\"duration\":80,\"volume\":10}"); + packet_seq_exp(A, 101, "\x05\x8a\x02\x80", 1001280, 211, -1, "", 0); + packet_seq_exp(A, 101, "\x05\x8a\x02\x80", 1001280, 211, -1, "", 0); + dtmf(""); + // final audio RTP test + packet_seq_exp(A, 8, PCMA_payload, 1000960, 212, 0, PCMU_payload, 1); // expected seq is 207+1 for PT 8 + end(); + + // plain DTMF passthrough w/o transcoding w/ implicit primary payload type - blocking + start(); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + offer(); + expect(A, recv, ""); + expect(A, send, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 101/telephone-event/8000"); + packet_seq(A, 0, PCMU_payload, 1000000, 200, 0, PCMU_payload); + // start with marker + packet_seq(A, 101 | 0x80, "\x08\x0a\x00\xa0", 1000160, 201, 101 | 0x80, "\x08\x0a\x00\xa0"); + dtmf(""); + // continuous event with increasing length + // XXX check output ts, seq, ssrc + packet_seq(A, 101, "\x08\x0a\x01\x40", 1000160, 202, 101, "\x08\x0a\x01\x40"); + packet_seq(A, 101, "\x08\x0a\x01\xe0", 1000160, 203, 101, "\x08\x0a\x01\xe0"); + packet_seq(A, 101, "\x08\x0a\x02\x80", 1000160, 204, 101, "\x08\x0a\x02\x80"); + dtmf(""); + // end + packet_seq(A, 101, "\x08\x8a\x03\x20", 1000160, 205, 101, "\x08\x8a\x03\x20"); + dtmf("{\"callid\":\"test-call\",\"source_tag\":\"tag_A\",\"tags\":[],\"type\":\"DTMF\",\"timestamp\":0,\"source_ip\":\"(null)\",\"event\":8,\"duration\":100,\"volume\":10}"); + packet_seq_exp(A, 101, "\x08\x8a\x03\x20", 1000160, 205, 101, "\x08\x8a\x03\x20", 0); + packet_seq_exp(A, 101, "\x08\x8a\x03\x20", 1000160, 205, 101, "\x08\x8a\x03\x20", 0); + dtmf(""); + // send some more audio + packet_seq_exp(A, 0, PCMU_payload, 1000960, 206, 0, PCMU_payload, 6); // expected seq is 200+6 for PT 8 + packet_seq(A, 0, PCMU_payload, 1001120, 207, 0, PCMU_payload); + // enable blocking + call.block_dtmf = 1; + // start with marker + packet_seq_exp(A, 101 | 0x80, "\x05\x0a\x00\xa0", 1001280, 208, -1, "", 0); + dtmf(""); + // continuous event with increasing length + packet_seq(A, 101, "\x05\x0a\x01\x40", 1001280, 209, -1, ""); + packet_seq(A, 101, "\x05\x0a\x01\xe0", 1001280, 210, -1, ""); + dtmf(""); + // end + packet_seq(A, 101, "\x05\x8a\x02\x80", 1001280, 211, -1, ""); + dtmf("{\"callid\":\"test-call\",\"source_tag\":\"tag_A\",\"tags\":[],\"type\":\"DTMF\",\"timestamp\":0,\"source_ip\":\"(null)\",\"event\":5,\"duration\":80,\"volume\":10}"); + packet_seq_exp(A, 101, "\x05\x8a\x02\x80", 1001280, 211, -1, "", 0); + packet_seq_exp(A, 101, "\x05\x8a\x02\x80", 1001280, 211, -1, "", 0); + dtmf(""); + // final audio RTP test + packet_seq_exp(A, 0, PCMU_payload, 1000960, 212, 0, PCMU_payload, 5); // expected seq is 207+5 for PT 8 + end(); + return 0; }