diff --git a/daemon/aux.c b/daemon/aux.c index cbb645024..459f781a6 100644 --- a/daemon/aux.c +++ b/daemon/aux.c @@ -212,3 +212,11 @@ int g_tree_find_all_cmp(void *k, void *v, void *d) { g_queue_push_tail(q, v); return FALSE; } +unsigned int uint32_hash(const void *p) { + const u_int32_t *a = p; + return *a; +} +int uint32_eq(const void *a, const void *b) { + const u_int32_t *A = a, *B = b; + return (*A == *B) ? TRUE : FALSE; +} diff --git a/daemon/aux.h b/daemon/aux.h index 80a731457..615a4474d 100644 --- a/daemon/aux.h +++ b/daemon/aux.h @@ -67,6 +67,8 @@ INLINE void strdupfree(char **, const char *); char *get_thread_buf(void); unsigned int in6_addr_hash(const void *p); int in6_addr_eq(const void *a, const void *b); +unsigned int uint32_hash(const void *p); +int uint32_eq(const void *a, const void *b); diff --git a/daemon/call.c b/daemon/call.c index b66a174d1..fbaee346d 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -161,7 +161,6 @@ static int call_avpf2avp_rtcp(str *s, struct packet_stream *); static int call_savpf2avp_rtcp(str *s, struct packet_stream *); //static int call_savpf2savp_rtcp(str *s, struct packet_stream *); - /* ********** */ static const struct streamhandler_io __shio_noop = { @@ -435,6 +434,7 @@ void kernelize(struct packet_stream *stream) { reti.src_addr.family = reti.dst_addr.family; reti.src_addr.port = sink->sfd->fd.localport; + reti.ssrc = sink->crypto.ssrc; ifa = g_atomic_pointer_get(&sink->media->local_address); if (reti.src_addr.family == AF_INET) @@ -764,14 +764,16 @@ loop_ok: mutex_lock(&out_srtp->out_lock); /* return values are: 0 = forward packet, -1 = error/dont forward, - * 1 = forward and push update to redis */ + * 1 = forward and push update to redis and kernel */ if (rwf_in) handler_ret = rwf_in(s, in_srtp); if (handler_ret >= 0 && rwf_out) handler_ret += rwf_out(s, out_srtp); - if (handler_ret > 0) + if (handler_ret > 0) { + __unkernelize(stream); update = 1; + } mutex_unlock(&out_srtp->out_lock); mutex_unlock(&in_srtp->in_lock); diff --git a/daemon/crypto.h b/daemon/crypto.h index 54aa37c62..b723857f4 100644 --- a/daemon/crypto.h +++ b/daemon/crypto.h @@ -7,6 +7,7 @@ #include #include "compat.h" #include "str.h" +#include "aux.h" @@ -86,12 +87,15 @@ struct crypto_context { /* ? */ void *session_key_ctx[2]; + GHashTable *ssrc_hash; int have_session_key:1; }; - - +struct rtp_ssrc_entry { + u_int32_t ssrc; + u_int64_t index; +}; extern const struct crypto_suite crypto_suites[]; extern const int num_crypto_suites; @@ -102,7 +106,11 @@ const struct crypto_suite *crypto_find_suite(const str *); int crypto_gen_session_key(struct crypto_context *, str *, unsigned char, int); void crypto_dump_keys(struct crypto_context *in, struct crypto_context *out); - +INLINE struct rtp_ssrc_entry *find_ssrc(u_int32_t, GHashTable *); +INLINE void add_ssrc_entry(struct rtp_ssrc_entry *, GHashTable *); +INLINE struct rtp_ssrc_entry *create_ssrc_entry(u_int32_t, u_int64_t); +INLINE void free_ssrc_table(GHashTable **); +INLINE GHashTable *create_ssrc_table(void); INLINE int crypto_encrypt_rtp(struct crypto_context *c, struct rtp_header *rtp, @@ -136,6 +144,7 @@ INLINE void crypto_params_cleanup(struct crypto_params *p) { } INLINE void crypto_cleanup(struct crypto_context *c) { crypto_params_cleanup(&c->params); + free_ssrc_table(&c->ssrc_hash); if (!c->params.crypto_suite) return; if (c->params.crypto_suite->session_key_cleanup) @@ -190,4 +199,31 @@ INLINE int crypto_params_cmp(const struct crypto_params *a, const struct crypto_ +INLINE struct rtp_ssrc_entry *find_ssrc(u_int32_t ssrc, GHashTable *ht) { + return g_hash_table_lookup(ht, &ssrc); +} +INLINE void add_ssrc_entry(struct rtp_ssrc_entry *ent, GHashTable *ht) { + g_hash_table_insert(ht, &ent->ssrc, ent); +} +INLINE struct rtp_ssrc_entry *create_ssrc_entry(u_int32_t ssrc, u_int64_t index) { + struct rtp_ssrc_entry *ent; + ent = g_slice_alloc(sizeof(struct rtp_ssrc_entry)); + ent->ssrc = ssrc; + ent->index = index; + return ent; +} +INLINE void free_ssrc_table(GHashTable **ht) { + if (!*ht) + return; + g_hash_table_destroy(*ht); + *ht = NULL; +} +INLINE void free_ssrc_entry(void *p) { + g_slice_free1(sizeof(struct rtp_ssrc_entry), p); +} +INLINE GHashTable *create_ssrc_table(void) { + return g_hash_table_new_full(uint32_hash, uint32_eq, free_ssrc_entry, NULL); +} + + #endif diff --git a/daemon/rtp.c b/daemon/rtp.c index a70544bee..561651879 100644 --- a/daemon/rtp.c +++ b/daemon/rtp.c @@ -137,8 +137,6 @@ error: static u_int64_t packet_index(struct crypto_context *c, struct rtp_header *rtp) { u_int16_t seq; - u_int64_t index; - long long int diff; seq = ntohs(rtp->seq_num); /* rfc 3711 section 3.3.1 */ @@ -146,24 +144,24 @@ static u_int64_t packet_index(struct crypto_context *c, struct rtp_header *rtp) c->last_index = seq; /* rfc 3711 appendix A, modified, and sections 3.3 and 3.3.1 */ - index = (c->last_index & 0xffffffff0000ULL) | seq; - diff = index - c->last_index; - if (diff >= 0) { - if (diff < 0x8000) - c->last_index = index; - else if (index >= 0x10000) - index -= 0x10000; - } - else { - if (diff >= -0x8000) - ; - else { - index += 0x10000; - c->last_index = index; - } + u_int16_t s_l = (c->last_index & 0x00000000ffffULL); + u_int32_t roc = (c->last_index & 0xffffffff0000ULL) >> 16; + u_int32_t v = 0; + + if (s_l < 0x8000) { + if (((seq - s_l) > 0x8000) && roc > 0) + v = (roc - 1) % 0x10000; + else + v = roc; + } else { + if ((s_l - 0x8000) > seq) + v = (roc + 1) % 0x10000; + else + v = roc; } - return index; + c->last_index = (u_int64_t)(((v << 16) | seq) & 0xffffffffffffULL); + return c->last_index; } void rtp_append_mki(str *s, struct crypto_context *c) { @@ -178,29 +176,59 @@ void rtp_append_mki(str *s, struct crypto_context *c) { s->len += c->params.mki_len; } +static int rtp_ssrc_check(const struct rtp_header *rtp, struct crypto_context *c) { + struct rtp_ssrc_entry *cur_ssrc; + + /* check last known SSRC */ + if (G_LIKELY(rtp->ssrc == c->ssrc)) + return 0; + if (!c->ssrc) { + c->ssrc = rtp->ssrc; + return 1; + } + + /* SSRC mismatch. stash away last know info */ + ilog(LOG_DEBUG, "SSRC changed, updating SRTP crypto contexts"); + if (G_UNLIKELY(!c->ssrc_hash)) + c->ssrc_hash = create_ssrc_table(); + + // Find the entry for the last SSRC. + cur_ssrc = find_ssrc(c->ssrc, c->ssrc_hash); + // If it doesn't exist, create a new entry. + if (G_UNLIKELY(!cur_ssrc)) { + cur_ssrc = create_ssrc_entry(c->ssrc, c->last_index); + add_ssrc_entry(cur_ssrc, c->ssrc_hash); + } + else + cur_ssrc->index = c->last_index; + + // New SSRC, set the crypto context. + c->ssrc = rtp->ssrc; + cur_ssrc = find_ssrc(rtp->ssrc, c->ssrc_hash); + if (G_UNLIKELY(!cur_ssrc)) + c->last_index = 0; + else + c->last_index = cur_ssrc->index; + + return 1; +} + /* rfc 3711, section 3.3 */ int rtp_avp2savp(str *s, struct crypto_context *c) { struct rtp_header *rtp; str payload, to_auth; u_int64_t index; + int ret = 0; if (rtp_payload(&rtp, &payload, s)) return -1; if (check_session_keys(c)) return -1; - /* SSRC is part of the crypto context and ROC must be reset when it changes */ - if (G_UNLIKELY(!c->ssrc)) - c->ssrc = rtp->ssrc; - else if (G_UNLIKELY(c->ssrc != rtp->ssrc)) { - c->last_index = 0; - c->ssrc = rtp->ssrc; - } - + ret = rtp_ssrc_check(rtp, c); index = packet_index(c, rtp); /* rfc 3711 section 3.1 */ - if (!c->params.session_params.unencrypted_srtp && crypto_encrypt_rtp(c, rtp, &payload, index)) return -1; @@ -213,7 +241,7 @@ int rtp_avp2savp(str *s, struct crypto_context *c) { s->len += c->params.crypto_suite->srtp_auth_tag; } - return 0; + return ret; } /* rfc 3711, section 3.3 */ @@ -222,12 +250,14 @@ int rtp_savp2avp(str *s, struct crypto_context *c) { u_int64_t index; str payload, to_auth, to_decrypt, auth_tag; char hmac[20]; + int ret = 0; if (rtp_payload(&rtp, &payload, s)) return -1; if (check_session_keys(c)) return -1; + ret = rtp_ssrc_check(rtp, c); index = packet_index(c, rtp); if (srtp_payloads(&to_auth, &to_decrypt, &auth_tag, NULL, c->params.session_params.unauthenticated_srtp ? 0 : c->params.crypto_suite->srtp_auth_tag, @@ -271,7 +301,7 @@ decrypt: *s = to_auth; - return 0; + return ret; error: ilog(LOG_WARNING | LOG_FLAG_LIMIT, "Discarded invalid SRTP packet: authentication failed"); diff --git a/kernel-module/xt_RTPENGINE.c b/kernel-module/xt_RTPENGINE.c index 713c34997..b67688307 100644 --- a/kernel-module/xt_RTPENGINE.c +++ b/kernel-module/xt_RTPENGINE.c @@ -1888,8 +1888,10 @@ static u_int64_t packet_index(struct re_crypto_context *c, { u_int16_t seq; u_int64_t index; - long long int diff; unsigned long flags; + u_int16_t s_l; + u_int32_t roc; + u_int32_t v; seq = ntohs(rtp->seq_num); @@ -1900,24 +1902,26 @@ static u_int64_t packet_index(struct re_crypto_context *c, s->last_index = seq; /* rfc 3711 appendix A, modified, and sections 3.3 and 3.3.1 */ - index = ((u_int64_t) c->roc << 16) | seq; - diff = index - s->last_index; - if (diff >= 0) { - if (diff < 0x8000) - s->last_index = index; - else if (index >= 0x10000) - index -= 0x10000; - } - else { - if (diff >= -0x8000) - ; - else { - index += 0x10000; - c->roc++; - s->last_index = index; - } + s_l = (s->last_index & 0x00000000ffffULL); + roc = (s->last_index & 0xffffffff0000ULL) >> 16; + v = 0; + + if (s_l < 0x8000) { + if (((seq - s_l) > 0x8000) && roc > 0) + v = (roc - 1) % 0x10000; + else + v = roc; + } else { + if ((s_l - 0x8000) > seq) + v = (roc + 1) % 0x10000; + else + v = roc; } + index = (v << 16) | seq; + s->last_index = index; + c->roc = v; + spin_unlock_irqrestore(&c->lock, flags); return index; @@ -2060,26 +2064,28 @@ static int srtp_auth_validate(struct re_crypto_context *c, if (srtp_hash(hmac, c, s, r, pkt_idx)) return -1; if (!memcmp(auth_tag, hmac, s->auth_tag_len)) - goto ok; + goto ok_update; /* or maybe we did a rollover too many */ if (pkt_idx >= 0x20000) { pkt_idx -= 0x20000; if (srtp_hash(hmac, c, s, r, pkt_idx)) return -1; if (!memcmp(auth_tag, hmac, s->auth_tag_len)) - goto ok; + goto ok_update; } /* last guess: reset ROC to zero */ pkt_idx &= 0xffff; if (srtp_hash(hmac, c, s, r, pkt_idx)) return -1; if (!memcmp(auth_tag, hmac, s->auth_tag_len)) - goto ok; + goto ok_update; return -1; -ok: +ok_update: *pkt_idx_p = pkt_idx; + update_packet_index(c, s, pkt_idx); +ok: return 0; } @@ -2192,7 +2198,7 @@ static unsigned int rtpengine46(struct sk_buff *skb, struct rtpengine_table *t, unsigned int datalen; u_int32_t *u32; struct rtp_parsed rtp; - u_int64_t pkt_idx = 0, pkt_idx_u; + u_int64_t pkt_idx; skb_reset_transport_header(skb); uh = udp_hdr(skb); @@ -2264,11 +2270,13 @@ src_check_ok: rtp_pt_idx = rtp_payload_type(rtp.header, &g->target); - pkt_idx_u = pkt_idx = packet_index(&g->decrypt, &g->target.decrypt, rtp.header); + // Pass to userspace if SSRC has changed. + if (unlikely((g->target.ssrc) && (g->target.ssrc != rtp.header->ssrc))) + goto skip_error; + + pkt_idx = packet_index(&g->decrypt, &g->target.decrypt, rtp.header); if (srtp_auth_validate(&g->decrypt, &g->target.decrypt, &rtp, &pkt_idx)) goto skip_error; - if (pkt_idx != pkt_idx_u) - update_packet_index(&g->decrypt, &g->target.decrypt, pkt_idx); if (srtp_decrypt(&g->decrypt, &g->target.decrypt, &rtp, pkt_idx)) goto skip_error; @@ -2291,6 +2299,7 @@ not_rtp: } if (rtp.ok) { + pkt_idx = packet_index(&g->encrypt, &g->target.encrypt, rtp.header); srtp_encrypt(&g->encrypt, &g->target.encrypt, &rtp, pkt_idx); skb_put(skb, g->target.encrypt.mki_len + g->target.encrypt.auth_tag_len); srtp_authenticate(&g->encrypt, &g->target.encrypt, &rtp, pkt_idx); diff --git a/kernel-module/xt_RTPENGINE.h b/kernel-module/xt_RTPENGINE.h index 361a56fb0..0dd2bd8e1 100644 --- a/kernel-module/xt_RTPENGINE.h +++ b/kernel-module/xt_RTPENGINE.h @@ -82,6 +82,7 @@ struct rtpengine_target_info { struct rtpengine_srtp decrypt; struct rtpengine_srtp encrypt; + u_int32_t ssrc; // Expose the SSRC to userspace when we resync. unsigned char payload_types[NUM_PAYLOAD_TYPES]; /* must be sorted */ unsigned int num_payload_types; diff --git a/tests/simulator-ng.pl b/tests/simulator-ng.pl index ecd2ca146..8a93fbc4d 100755 --- a/tests/simulator-ng.pl +++ b/tests/simulator-ng.pl @@ -17,7 +17,8 @@ use SRTP; my ($NUM, $RUNTIME, $STREAMS, $PAYLOAD, $INTERVAL, $RTCP_INTERVAL, $STATS_INTERVAL) = (1000, 30, 1, 160, 20, 5, 5); -my ($NODEL, $IP, $IPV6, $KEEPGOING, $REINVITES, $PROTOS, $DEST, $SUITES, $NOENC, $RTCPMUX, $BUNDLE, $LAZY); +my ($NODEL, $IP, $IPV6, $KEEPGOING, $REINVITES, $PROTOS, $DEST, $SUITES, $NOENC, $RTCPMUX, $BUNDLE, $LAZY, + $CHANGE_SSRC); GetOptions( 'no-delete' => \$NODEL, 'num-calls=i' => \$NUM, @@ -38,6 +39,7 @@ GetOptions( 'rtcp-mux' => \$RTCPMUX, 'bundle' => \$BUNDLE, 'lazy-params' => \$LAZY, + 'change-ssrc' => \$CHANGE_SSRC, ) or die; ($IP || $IPV6) or die("at least one of --local-ip or --local-ipv6 must be given"); @@ -154,6 +156,7 @@ sub rtp_encrypt { my ($pkt, $roc) = SRTP::encrypt_rtp(@$dctx{qw(crypto_suite rtp_session_key rtp_session_salt rtp_session_auth_key rtp_roc rtp_mki rtp_mki_len unenc_srtp unauth_srtp)}, $r); + $roc == ($$dctx{rtp_roc} // 0) or print("ROC is now $roc\n"); $$dctx{rtp_roc} = $roc; $NOENC{rtp_packet} = $pkt; @@ -271,9 +274,10 @@ sub rtcp_savpf { sub rtp { my ($ctx) = @_; + my $ssrc = $$ctx{ssrc} // ($$ctx{ssrc} = rand(2**32)); my $seq = $$ctx{rtp_seqnum}; defined($seq) or $seq = int(rand(0xfffe)) + 1; - my $hdr = pack("CCnNN", 0x80, 0x00, $seq, rand(2**32), rand(2**32)); + my $hdr = pack("CCnNN", 0x80, 0x00, $seq, rand(2**32), $ssrc); my $pack = $hdr . rand_str($PAYLOAD); $$ctx{rtp_seqnum} = (++$seq & 0xffff); return $pack; @@ -777,6 +781,21 @@ while (time() < $end) { offer($c, 0, 1); answer($c, 1, 0); } + + if ($CHANGE_SSRC && rand() < .001) { + my $c = $calls[rand(@calls)]; + my $s = $$c{sides}[rand(2)]; + my $st = rand($$s{num_streams}); + my $d = (qw(in out))[rand(2)]; + my $stc = $$s{trans_contexts}[$st]; + my $ct = $$stc{$d}; + if (defined($$ct{rtp_roc}) && $$stc{ssrc}) { + my $nssrc = rand(2 ** 32); + print("change SSRC from $$stc{ssrc} to $nssrc\n"); + $$stc{ssrc} = $nssrc; + $$ct{roc} = 0; + } + } } if (!$NODEL) {