diff --git a/README.md b/README.md index da4bcee08..aaee16548 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ the following additional features are available: + AES-CM and AES-F8 ciphers, both in userspace and in kernel + HMAC-SHA1 packet authentication + Bridging between RTP and SRTP user agents + + Opportunistic SRTP (RFC 8643) - Support for RTCP profile with feedback extensions (RTP/AVPF, RFC 4585 and 5124) - Arbitrary bridging between any of the supported RTP profiles (RTP/AVP, RTP/AVPF, RTP/SAVP, RTP/SAVPF) @@ -940,6 +941,21 @@ Optionally included keys are: Add the key lifetime parameter `2^31` to each crypto key. +* `OSRTP` + + Similar to `SDES` but controls OSRTP behaviour. Default behaviour is to pass through + OSRTP negotiations. Supported options: + + - `offer` + + When processing a non-OSRTP offer, convert it to an OSRTP offer. Will result + in RTP/SRTP transcoding if the OSRTP offer is accepted. + + - `accept` + + When processing a non-OSRTP answer in response to an OSRTP offer, accept the + OSRTP offer anyway. Results in RTP/SRTP transcoding. + * `record call` Contains one of the strings `yes`, `no`, `on` or `off`. This tells the rtpengine diff --git a/daemon/call.c b/daemon/call.c index e54415c13..86ac6df6b 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -1484,7 +1484,7 @@ static void __sdes_accept(struct call_media *media, const struct sdp_ng_flags *f crypto_params_sdes_free(offered_cps); l = next; } -} + } struct crypto_params_sdes *cps_in = media->sdes_in.head->data; GList *l = media->sdes_out.head; @@ -1848,8 +1848,14 @@ static void __update_media_protocol(struct call_media *media, struct call_media other_media->protocol = sp->protocol; /* if the endpoint changes the protocol, we reset the other side's * protocol as well. this lets us remember our previous overrides, - * but also lets endpoints re-negotiate. */ - media->protocol = NULL; + * but also lets endpoints re-negotiate. + * Exception: OSRTP answer/accept. */ + if (flags && flags->opmode == OP_ANSWER && other_media->protocol && other_media->protocol->rtp + && !other_media->protocol->srtp + && media->protocol && media->protocol->osrtp && flags->osrtp_accept) + ; + else + media->protocol = NULL; } /* default is to leave the protocol unchanged */ if (!media->protocol) @@ -1859,6 +1865,13 @@ static void __update_media_protocol(struct call_media *media, struct call_media if (!flags) return; + // OSRTP offer requested? + if (media->protocol && media->protocol->rtp && !media->protocol->srtp + && media->protocol->osrtp_proto && flags->osrtp_offer && flags->opmode == OP_OFFER) + { + media->protocol = &transport_protocols[media->protocol->osrtp_proto]; + } + // T.38 decoder? if (other_media->type_id == MT_IMAGE && proto_is(other_media->protocol, PROTO_UDPTL) && flags->t38_decode) diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 48a881f6f..16d05e394 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -550,6 +550,20 @@ INLINE void ng_sdes_option(struct sdp_ng_flags *out, str *s, void *dummy) { } } +INLINE void ng_osrtp_option(struct sdp_ng_flags *out, str *s, void *dummy) { + switch (__csh_lookup(s)) { + case CSH_LOOKUP("accept"): + out->osrtp_accept = 1; + break; + case CSH_LOOKUP("offer"): + out->osrtp_offer = 1; + break; + default: + ilog(LOG_WARN, "Unknown 'OSRTP' flag encountered: '" STR_FORMAT "'", + STR_FMT(s)); + } +} + #ifdef WITH_TRANSCODING INLINE void ng_t38_option(struct sdp_ng_flags *out, str *s, void *dummy) { @@ -801,6 +815,8 @@ static void call_ng_flags_flags(struct sdp_ng_flags *out, str *s, void *dummy) { return; if (call_ng_flags_prefix(out, s, "SDES-", ng_sdes_option, NULL)) return; + if (call_ng_flags_prefix(out, s, "OSRTP-", ng_osrtp_option, NULL)) + return; if (out->opmode == OP_OFFER) { if (call_ng_flags_prefix(out, s, "codec-strip-", call_ng_flags_str_ht, &out->codec_strip)) @@ -906,6 +922,7 @@ static void call_ng_process_flags(struct sdp_ng_flags *out, bencode_item_t *inpu call_ng_flags_list(out, input, "rtcp-mux", call_ng_flags_rtcp_mux, NULL); call_ng_flags_list(out, input, "SDES", ng_sdes_option, NULL); + call_ng_flags_list(out, input, "OSRTP", ng_osrtp_option, NULL); #ifdef WITH_TRANSCODING call_ng_flags_list(out, input, "T38", ng_t38_option, NULL); call_ng_flags_list(out, input, "T.38", ng_t38_option, NULL); diff --git a/daemon/media_socket.c b/daemon/media_socket.c index 8565f0883..a0b09802d 100644 --- a/daemon/media_socket.c +++ b/daemon/media_socket.c @@ -95,6 +95,7 @@ const struct transport_protocol transport_protocols[] = { .index = PROTO_RTP_AVP, .name = "RTP/AVP", .avpf_proto = PROTO_RTP_AVPF, + .osrtp_proto = PROTO_RTP_SAVP_OSRTP, .rtp = 1, .srtp = 0, .avpf = 0, @@ -112,6 +113,7 @@ const struct transport_protocol transport_protocols[] = { [PROTO_RTP_AVPF] = { .index = PROTO_RTP_AVPF, .name = "RTP/AVPF", + .osrtp_proto = PROTO_RTP_SAVPF_OSRTP, .rtp = 1, .srtp = 0, .avpf = 1, @@ -150,6 +152,25 @@ const struct transport_protocol transport_protocols[] = { .avpf = 0, .tcp = 0, }, + [PROTO_RTP_SAVP_OSRTP] = { + .index = PROTO_RTP_SAVP_OSRTP, + .name = "RTP/AVP", + .avpf_proto = PROTO_RTP_SAVPF_OSRTP, + .rtp = 1, + .srtp = 1, + .osrtp = 1, + .avpf = 0, + .tcp = 0, + }, + [PROTO_RTP_SAVPF_OSRTP] = { + .index = PROTO_RTP_SAVPF_OSRTP, + .name = "RTP/AVPF", + .rtp = 1, + .srtp = 1, + .osrtp = 1, + .avpf = 1, + .tcp = 0, + }, }; const int num_transport_protocols = G_N_ELEMENTS(transport_protocols); @@ -244,6 +265,8 @@ static const struct streamhandler * const __sh_matrix_in_rtp_avp[__PROTO_LAST] = [PROTO_UDP_TLS_RTP_SAVP] = &__sh_avp2savp, [PROTO_UDP_TLS_RTP_SAVPF] = &__sh_avp2savp, [PROTO_UDPTL] = &__sh_noop, + [PROTO_RTP_SAVP_OSRTP] = &__sh_avp2savp, + [PROTO_RTP_SAVPF_OSRTP] = &__sh_avp2savp, }; static const struct streamhandler * const __sh_matrix_in_rtp_avpf[__PROTO_LAST] = { [PROTO_RTP_AVP] = &__sh_avpf2avp, @@ -253,6 +276,8 @@ static const struct streamhandler * const __sh_matrix_in_rtp_avpf[__PROTO_LAST] [PROTO_UDP_TLS_RTP_SAVP] = &__sh_avpf2savp, [PROTO_UDP_TLS_RTP_SAVPF] = &__sh_avp2savp, [PROTO_UDPTL] = &__sh_noop, + [PROTO_RTP_SAVP_OSRTP] = &__sh_avpf2savp, + [PROTO_RTP_SAVPF_OSRTP] = &__sh_avp2savp, }; static const struct streamhandler * const __sh_matrix_in_rtp_savp[__PROTO_LAST] = { [PROTO_RTP_AVP] = &__sh_savp2avp, @@ -262,6 +287,8 @@ static const struct streamhandler * const __sh_matrix_in_rtp_savp[__PROTO_LAST] [PROTO_UDP_TLS_RTP_SAVP] = &__sh_savp2savp_rtcp_only, [PROTO_UDP_TLS_RTP_SAVPF] = &__sh_savp2savp_rtcp_only, [PROTO_UDPTL] = &__sh_noop, + [PROTO_RTP_SAVP_OSRTP] = &__sh_savp2savp_rtcp_only, + [PROTO_RTP_SAVPF_OSRTP] = &__sh_savp2savp_rtcp_only, }; static const struct streamhandler * const __sh_matrix_in_rtp_savpf[__PROTO_LAST] = { [PROTO_RTP_AVP] = &__sh_savpf2avp, @@ -271,6 +298,8 @@ static const struct streamhandler * const __sh_matrix_in_rtp_savpf[__PROTO_LAST] [PROTO_UDP_TLS_RTP_SAVP] = &__sh_savpf2savp, [PROTO_UDP_TLS_RTP_SAVPF] = &__sh_savp2savp_rtcp_only, [PROTO_UDPTL] = &__sh_noop, + [PROTO_RTP_SAVP_OSRTP] = &__sh_savpf2savp, + [PROTO_RTP_SAVPF_OSRTP] = &__sh_savp2savp_rtcp_only, }; static const struct streamhandler * const __sh_matrix_in_rtp_savp_recrypt[__PROTO_LAST] = { [PROTO_RTP_AVP] = &__sh_savp2avp, @@ -280,6 +309,8 @@ static const struct streamhandler * const __sh_matrix_in_rtp_savp_recrypt[__PROT [PROTO_UDP_TLS_RTP_SAVP] = &__sh_savp2savp, [PROTO_UDP_TLS_RTP_SAVPF] = &__sh_savp2savp, [PROTO_UDPTL] = &__sh_noop, + [PROTO_RTP_SAVP_OSRTP] = &__sh_savp2savp, + [PROTO_RTP_SAVPF_OSRTP] = &__sh_savp2savp, }; static const struct streamhandler * const __sh_matrix_in_rtp_savpf_recrypt[__PROTO_LAST] = { [PROTO_RTP_AVP] = &__sh_savpf2avp, @@ -289,6 +320,8 @@ static const struct streamhandler * const __sh_matrix_in_rtp_savpf_recrypt[__PRO [PROTO_UDP_TLS_RTP_SAVP] = &__sh_savpf2savp, [PROTO_UDP_TLS_RTP_SAVPF] = &__sh_savp2savp, [PROTO_UDPTL] = &__sh_noop, + [PROTO_RTP_SAVP_OSRTP] = &__sh_savpf2savp, + [PROTO_RTP_SAVPF_OSRTP] = &__sh_savp2savp, }; static const struct streamhandler * const __sh_matrix_noop[__PROTO_LAST] = { // non-RTP protocols [PROTO_RTP_AVP] = &__sh_noop, @@ -298,6 +331,8 @@ static const struct streamhandler * const __sh_matrix_noop[__PROTO_LAST] = { // [PROTO_UDP_TLS_RTP_SAVP] = &__sh_noop, [PROTO_UDP_TLS_RTP_SAVPF] = &__sh_noop, [PROTO_UDPTL] = &__sh_noop, + [PROTO_RTP_SAVP_OSRTP] = &__sh_noop, + [PROTO_RTP_SAVPF_OSRTP] = &__sh_noop, }; /* ********** */ @@ -310,6 +345,8 @@ static const struct streamhandler * const * const __sh_matrix[__PROTO_LAST] = { [PROTO_UDP_TLS_RTP_SAVP] = __sh_matrix_in_rtp_savp, [PROTO_UDP_TLS_RTP_SAVPF] = __sh_matrix_in_rtp_savpf, [PROTO_UDPTL] = __sh_matrix_noop, + [PROTO_RTP_SAVP_OSRTP] = __sh_matrix_in_rtp_savp, + [PROTO_RTP_SAVPF_OSRTP] = __sh_matrix_in_rtp_savpf, }; /* special case for DTLS as we can't pass through SRTP<>SRTP */ static const struct streamhandler * const * const __sh_matrix_recrypt[__PROTO_LAST] = { @@ -320,6 +357,8 @@ static const struct streamhandler * const * const __sh_matrix_recrypt[__PROTO_LA [PROTO_UDP_TLS_RTP_SAVP] = __sh_matrix_in_rtp_savp_recrypt, [PROTO_UDP_TLS_RTP_SAVPF] = __sh_matrix_in_rtp_savpf_recrypt, [PROTO_UDPTL] = __sh_matrix_noop, + [PROTO_RTP_SAVP_OSRTP] = __sh_matrix_in_rtp_savp_recrypt, + [PROTO_RTP_SAVPF_OSRTP] = __sh_matrix_in_rtp_savpf_recrypt, }; /* ********** */ diff --git a/daemon/sdp.c b/daemon/sdp.c index 7d3231f28..608ef14cd 100644 --- a/daemon/sdp.c +++ b/daemon/sdp.c @@ -1521,6 +1521,14 @@ int sdp_streams(const GQueue *sessions, GQueue *streams, struct sdp_ng_flags *fl sp->fingerprint.hash_func->num_bytes); } + // OSRTP (RFC 8643) + if (sp->protocol && sp->protocol->rtp && !sp->protocol->srtp + && sp->protocol->osrtp_proto) + { + if (sp->fingerprint.hash_func || sp->sdes_params.length) + sp->protocol = &transport_protocols[sp->protocol->osrtp_proto]; + } + // a=mid attr = attr_get_by_id(&media->attributes, ATTR_MID); if (attr) diff --git a/include/call_interfaces.h b/include/call_interfaces.h index b219bd056..b8d772175 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -67,6 +67,8 @@ struct sdp_ng_flags { strict_source:1, media_handover:1, dtls_passive:1, + osrtp_accept:1, + osrtp_offer:1, reset:1, all:1, fragment:1, diff --git a/include/media_socket.h b/include/media_socket.h index d3f9df3eb..58dfab330 100644 --- a/include/media_socket.h +++ b/include/media_socket.h @@ -34,6 +34,8 @@ enum transport_protocol_index { PROTO_UDP_TLS_RTP_SAVP, PROTO_UDP_TLS_RTP_SAVPF, PROTO_UDPTL, + PROTO_RTP_SAVP_OSRTP, + PROTO_RTP_SAVPF_OSRTP, __PROTO_LAST, }; @@ -41,8 +43,10 @@ struct transport_protocol { enum transport_protocol_index index; const char *name; enum transport_protocol_index avpf_proto; + enum transport_protocol_index osrtp_proto; int rtp:1; /* also set to 1 for SRTP */ int srtp:1; + int osrtp:1; int avpf:1; int tcp:1; }; diff --git a/t/auto-daemon-tests.pl b/t/auto-daemon-tests.pl index 2ed8a0000..ea250df38 100755 --- a/t/auto-daemon-tests.pl +++ b/t/auto-daemon-tests.pl @@ -13,7 +13,532 @@ autotest_start(qw(--config-file=none -t -1 -i 203.0.113.1 -i 2001:db8:4321::1 or die; -my ($sock_a, $sock_b, $port_a, $port_b, $ssrc, $resp, $srtp_ctx_a, $srtp_ctx_b, @ret1, @ret2); +my ($sock_a, $sock_b, $port_a, $port_b, $ssrc, $resp, + $srtp_ctx_a, $srtp_ctx_b, $srtp_ctx_a_rev, $srtp_ctx_b_rev, + @ret1, @ret2, $srtp_key_a, $srtp_key_b); + + + + +# SRTP control - accept diff suite from offer + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 3328)], [qw(198.51.100.3 3330)]); + +($port_a, undef, $srtp_key_a) = offer('reg SRTP offer, accept, diff suite', + { ICE => 'remove', replace => ['origin'], DTLS => 'off' }, < 'remove', replace => ['origin'], DTLS => 'off' }, < $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_32}, + key => 'Kl3GFJ5Gqz5x07xYkoyHODkVkSpiplZnXsQIw+Q7', +}; +$srtp_ctx_a_rev = { + cs => $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_32}, + key => $srtp_key_a, +}; +$srtp_ctx_b = { + cs => $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_80}, + key => 'Qk0TvVeyfqfjFd/YebnyyklqSEhJntpVKV1KAhHa', +}; +$srtp_ctx_b_rev = { + cs => $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_80}, + key => $srtp_key_b, +}; + +srtp_snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 160), $srtp_ctx_b); +srtp_rcv($sock_b, $port_a, rtpm(0, 1000, 3000, 0x1234, "\x00" x 160), $srtp_ctx_a_rev); +srtp_snd($sock_b, $port_a, rtp(0, 2000, 4000, 0x3456, "\x00" x 160), $srtp_ctx_a); +srtp_rcv($sock_a, $port_b, rtpm(0, 2000, 4000, 0x3456, "\x00" x 160), $srtp_ctx_b_rev); + + + + + +# OSRTP + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 3316)], [qw(198.51.100.3 3318)]); + +($port_a) = offer('OSRTP offer, accept, same suite', + { ICE => 'remove', replace => ['origin'], DTLS => 'off' }, < 'remove', replace => ['origin'], DTLS => 'off' }, < $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_80}, + key => 'Kl3GFJ5Gqz5x07xYkoyHODkVkSpiplZnXsQIw+Q7', +}; +$srtp_ctx_b = { + cs => $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_80}, + key => 'Qk0TvVeyfqfjFd/YebnyyklqSEhJntpVKV1KAhHa', +}; + +srtp_snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 160), $srtp_ctx_a); +srtp_rcv($sock_b, $port_a, rtpm(0, 1000, 3000, 0x1234, "\x00" x 160), $srtp_ctx_a); +srtp_snd($sock_b, $port_a, rtp(0, 2000, 4000, 0x3456, "\x00" x 160), $srtp_ctx_b); +srtp_rcv($sock_a, $port_b, rtpm(0, 2000, 4000, 0x3456, "\x00" x 160), $srtp_ctx_b); + + + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 3320)], [qw(198.51.100.3 3322)]); + +($port_a, undef, $srtp_key_a) = offer('OSRTP offer, accept, diff suite', + { ICE => 'remove', replace => ['origin'], DTLS => 'off' }, < 'remove', replace => ['origin'], DTLS => 'off' }, < $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_32}, + key => 'Kl3GFJ5Gqz5x07xYkoyHODkVkSpiplZnXsQIw+Q7', +}; +$srtp_ctx_a_rev = { + cs => $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_32}, + key => $srtp_key_a, +}; +$srtp_ctx_b = { + cs => $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_80}, + key => 'Qk0TvVeyfqfjFd/YebnyyklqSEhJntpVKV1KAhHa', +}; +$srtp_ctx_b_rev = { + cs => $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_80}, + key => $srtp_key_b, +}; + +srtp_snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 160), $srtp_ctx_b); +srtp_rcv($sock_b, $port_a, rtpm(0, 1000, 3000, 0x1234, "\x00" x 160), $srtp_ctx_a_rev); +srtp_snd($sock_b, $port_a, rtp(0, 2000, 4000, 0x3456, "\x00" x 160), $srtp_ctx_a); +srtp_rcv($sock_a, $port_b, rtpm(0, 2000, 4000, 0x3456, "\x00" x 160), $srtp_ctx_b_rev); + + + + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 3324)], [qw(198.51.100.3 3326)]); + +($port_a) = offer('OSRTP offer, reject', + { ICE => 'remove', replace => ['origin'], DTLS => 'off' }, < 'remove', replace => ['origin'], DTLS => 'off' }, < 'remove', replace => ['origin'], DTLS => 'off' }, < 'remove', replace => ['origin'], DTLS => 'off', OSRTP => ['accept'] }, < $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_80}, + key => $srtp_key_a, +}; +$srtp_ctx_b = { + cs => $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_80}, + key => 'Qk0TvVeyfqfjFd/YebnyyklqSEhJntpVKV1KAhHa', +}; + +srtp_snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 160), $srtp_ctx_b); +rcv($sock_b, $port_a, rtpm(0, 1000, 3000, 0x1234, "\x00" x 160)); +snd($sock_b, $port_a, rtp(0, 2000, 4000, 0x3456, "\x00" x 160)); +srtp_rcv($sock_a, $port_b, rtpm(0, 2000, 4000, 0x3456, "\x00" x 160), $srtp_ctx_a); + + + + + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 3336)], [qw(198.51.100.3 3338)]); + +($port_a, undef, $srtp_key_a) = offer('non-OSRTP offer with offer flag, accept', + { ICE => 'remove', replace => ['origin'], DTLS => 'off', OSRTP => ['offer'] }, < 'remove', replace => ['origin'], DTLS => 'off', }, < $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_80}, + key => 'Kl3GFJ5Gqz5x07xYkoyHODkVkSpiplZnXsQIw+Q7', +}; +$srtp_ctx_b = { + cs => $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_80}, + key => $srtp_key_a, +}; + +snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 160)); +srtp_rcv($sock_b, $port_a, rtpm(0, 1000, 3000, 0x1234, "\x00" x 160), $srtp_ctx_b); +srtp_snd($sock_b, $port_a, rtp(0, 2000, 4000, 0x3456, "\x00" x 160), $srtp_ctx_a); +rcv($sock_a, $port_b, rtpm(0, 2000, 4000, 0x3456, "\x00" x 160)); + + + + + + + + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 3340)], [qw(198.51.100.3 3342)]); + +($port_a, undef, $srtp_key_a) = offer('non-OSRTP offer with offer flag, reject', + { ICE => 'remove', replace => ['origin'], DTLS => 'off', OSRTP => ['offer'] }, < 'remove', replace => ['origin'], DTLS => 'off', }, < \$options{'db-id'}, 'T38=s@' => \$options{'T.38'}, 'code=s' => \$options{'code'}, + 'OSRTP' => \$options{'OSRTP'}, ) or die; my $cmd = shift(@ARGV) or die; @@ -85,7 +86,7 @@ for my $x (split(/,/, 'from-tag,to-tag,call-id,transport protocol,media address, for my $x (split(/,/, 'TOS,delete-delay')) { defined($options{$x}) and $packet{$x} = $options{$x}; } -for my $x (split(/,/, 'trust address,symmetric,asymmetric,unidirectional,force,strict source,media handover,sip source address,reset,port latching,no rtcp attribute,full rtcp attribute,loop protect,record call,always transcode,all,pad crypto,generate mid,fragment,original sendrecv')) { +for my $x (split(/,/, 'trust address,symmetric,asymmetric,unidirectional,force,strict source,media handover,sip source address,reset,port latching,no rtcp attribute,full rtcp attribute,loop protect,record call,always transcode,all,pad crypto,generate mid,fragment,original sendrecv,OSRTP')) { defined($options{$x}) and push(@{$packet{flags}}, $x); } for my $x (split(/,/, 'origin,session connection')) {