diff --git a/daemon/.ycm_extra_conf.py b/daemon/.ycm_extra_conf.py index abf7069ef..dff1894df 100644 --- a/daemon/.ycm_extra_conf.py +++ b/daemon/.ycm_extra_conf.py @@ -21,6 +21,7 @@ flags = [ '-pthread', '-I../kernel-module/', '-D_GNU_SOURCE', +'-D__DEBUG=1', '-DMEDIAPROXY_VERSION="dummy"', '-DMP_PLUGIN_DIR="/usr/lib/mediaproxy-ng"', '-O2', diff --git a/daemon/Makefile b/daemon/Makefile index 5049b54fe..93aff86b4 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -47,7 +47,7 @@ endif SRCS= main.c kernel.c poller.c aux.c control_tcp.c streambuf.c call.c control_udp.c redis.c \ bencode.c cookie_cache.c udp_listener.c control_ng.c sdp.c str.c stun.c rtcp.c \ - crypto.c rtp.c + crypto.c rtp.c call_interfaces.c dtls.c log.c OBJS= $(SRCS:.c=.o) diff --git a/daemon/aux.h b/daemon/aux.h index 178cb8a5d..2cdb967bb 100644 --- a/daemon/aux.h +++ b/daemon/aux.h @@ -15,6 +15,13 @@ #include #include #include +#include + + + +#if 0 && defined(__DEBUG) +#define __THREAD_DEBUG 1 +#endif @@ -65,6 +72,16 @@ static inline int g_hash_table_contains(GHashTable *h, const void *k) { } #endif +static inline void g_queue_move(GQueue *dst, GQueue *src) { + GList *l; + while ((l = g_queue_pop_head_link(src))) + g_queue_push_tail_link(dst, l); +} +static inline void g_queue_truncate(GQueue *q, unsigned int len) { + while (q->length > len) + g_queue_pop_tail(q); +} + static inline void strmove(char **d, char **s) { if (*d) @@ -192,31 +209,131 @@ static inline int strmemcmp(const void *mem, int len, const char *str) { return memcmp(mem, str, len); } +/* XXX replace with better source of randomness */ +static inline void random_string(unsigned char *buf, int len) { + while (len--) + *buf++ = random() % 0x100; +} + typedef pthread_mutex_t mutex_t; typedef pthread_rwlock_t rwlock_t; typedef pthread_cond_t cond_t; -#define mutex_init(m) pthread_mutex_init(m, NULL) -#define mutex_destroy(m) pthread_mutex_destroy(m) -#define mutex_lock(m) pthread_mutex_lock(m) -#define mutex_trylock(m) pthread_mutex_trylock(m) -#define mutex_unlock(m) pthread_mutex_unlock(m) +#define mutex_init(m) __debug_mutex_init(m, __FILE__, __LINE__) +#define mutex_destroy(m) __debug_mutex_destroy(m, __FILE__, __LINE__) +#define mutex_lock(m) __debug_mutex_lock(m, __FILE__, __LINE__) +#define mutex_trylock(m) __debug_mutex_trylock(m, __FILE__, __LINE__) +#define mutex_unlock(m) __debug_mutex_unlock(m, __FILE__, __LINE__) #define MUTEX_STATIC_INIT PTHREAD_MUTEX_INITIALIZER -#define rwlock_init(l) pthread_rwlock_init(l, NULL) -#define rwlock_lock_r(l) pthread_rwlock_rdlock(l) -#define rwlock_unlock_r(l) pthread_rwlock_unlock(l) -#define rwlock_lock_w(l) pthread_rwlock_wrlock(l) -#define rwlock_unlock_w(l) pthread_rwlock_unlock(l) - -#define cond_init(c) pthread_cond_init(c, NULL) -#define cond_wait(c,m) pthread_cond_wait(c,m) -#define cond_signal(c) pthread_cond_signal(c) -#define cond_broadcast(c) pthread_cond_broadcast(c) +#define rwlock_init(l) __debug_rwlock_init(l, __FILE__, __LINE__) +#define rwlock_destroy(l) __debug_rwlock_destroy(l, __FILE__, __LINE__) +#define rwlock_lock_r(l) __debug_rwlock_lock_r(l, __FILE__, __LINE__) +#define rwlock_unlock_r(l) __debug_rwlock_unlock_r(l, __FILE__, __LINE__) +#define rwlock_lock_w(l) __debug_rwlock_lock_w(l, __FILE__, __LINE__) +#define rwlock_unlock_w(l) __debug_rwlock_unlock_w(l, __FILE__, __LINE__) + +#define cond_init(c) __debug_cond_init(c, __FILE__, __LINE__) +#define cond_wait(c,m) __debug_cond_wait(c,m, __FILE__, __LINE__) +#define cond_signal(c) __debug_cond_signal(c, __FILE__, __LINE__) +#define cond_broadcast(c) __debug_cond_broadcast(c, __FILE__, __LINE__) #define COND_STATIC_INIT PTHREAD_COND_INITIALIZER +#ifndef __THREAD_DEBUG + +#define __debug_mutex_init(m, F, L) pthread_mutex_init(m, NULL) +#define __debug_mutex_destroy(m, F, L) pthread_mutex_destroy(m) +#define __debug_mutex_lock(m, F, L) pthread_mutex_lock(m) +#define __debug_mutex_trylock(m, F, L) pthread_mutex_trylock(m) +#define __debug_mutex_unlock(m, F, L) pthread_mutex_unlock(m) + +#define __debug_rwlock_init(l, F, L) pthread_rwlock_init(l, NULL) +#define __debug_rwlock_destroy(l, F, L) pthread_rwlock_destroy(l) +#define __debug_rwlock_lock_r(l, F, L) pthread_rwlock_rdlock(l) +#define __debug_rwlock_unlock_r(l, F, L) pthread_rwlock_unlock(l) +#define __debug_rwlock_lock_w(l, F, L) pthread_rwlock_wrlock(l) +#define __debug_rwlock_unlock_w(l, F, L) pthread_rwlock_unlock(l) + +#define __debug_cond_init(c, F, L) pthread_cond_init(c, NULL) +#define __debug_cond_wait(c, m, F, L) pthread_cond_wait(c,m) +#define __debug_cond_signal(c, F, L) pthread_cond_signal(c) +#define __debug_cond_broadcast(c, F, L) pthread_cond_broadcast(c) + +#else + + +#include "log.h" + + + +static inline int __debug_mutex_init(mutex_t *m, const char *file, unsigned int line) { + mylog(LOG_DEBUG, "mutex_init(%p) at %s:%u", m, file, line); + return pthread_mutex_init(m, NULL); +} +static inline int __debug_mutex_destroy(mutex_t *m, const char *file, unsigned int line) { + mylog(LOG_DEBUG, "mutex_destroy(%p) at %s:%u", m, file, line); + return pthread_mutex_destroy(m); +} +static inline int __debug_mutex_lock(mutex_t *m, const char *file, unsigned int line) { + int ret; + mylog(LOG_DEBUG, "mutex_lock(%p) at %s:%u ...", m, file, line); + ret = pthread_mutex_lock(m); + mylog(LOG_DEBUG, "mutex_lock(%p) at %s:%u returning %i", m, file, line, ret); + return ret; +} +static inline int __debug_mutex_trylock(mutex_t *m, const char *file, unsigned int line) { + int ret; + mylog(LOG_DEBUG, "mutex_trylock(%p) at %s:%u ...", m, file, line); + ret = pthread_mutex_trylock(m); + mylog(LOG_DEBUG, "mutex_trylock(%p) at %s:%u returning %i", m, file, line, ret); + return ret; +} +static inline int __debug_mutex_unlock(mutex_t *m, const char *file, unsigned int line) { + mylog(LOG_DEBUG, "mutex_unlock(%p) at %s:%u", m, file, line); + return pthread_mutex_unlock(m); +} + +static inline int __debug_rwlock_init(rwlock_t *m, const char *file, unsigned int line) { + mylog(LOG_DEBUG, "rwlock_init(%p) at %s:%u", m, file, line); + return pthread_rwlock_init(m, NULL); +} +static inline int __debug_rwlock_destroy(rwlock_t *m, const char *file, unsigned int line) { + mylog(LOG_DEBUG, "rwlock_destroy(%p) at %s:%u", m, file, line); + return pthread_rwlock_destroy(m); +} +static inline int __debug_rwlock_lock_r(rwlock_t *m, const char *file, unsigned int line) { + int ret; + mylog(LOG_DEBUG, "rwlock_lock_r(%p) at %s:%u ...", m, file, line); + ret = pthread_rwlock_rdlock(m); + mylog(LOG_DEBUG, "rwlock_lock_r(%p) at %s:%u returning %i", m, file, line, ret); + return ret; +} +static inline int __debug_rwlock_lock_w(rwlock_t *m, const char *file, unsigned int line) { + int ret; + mylog(LOG_DEBUG, "rwlock_lock_w(%p) at %s:%u ...", m, file, line); + ret = pthread_rwlock_wrlock(m); + mylog(LOG_DEBUG, "rwlock_lock_w(%p) at %s:%u returning %i", m, file, line, ret); + return ret; +} +static inline int __debug_rwlock_unlock_r(rwlock_t *m, const char *file, unsigned int line) { + mylog(LOG_DEBUG, "rwlock_unlock_r(%p) at %s:%u", m, file, line); + return pthread_rwlock_unlock(m); +} +static inline int __debug_rwlock_unlock_w(rwlock_t *m, const char *file, unsigned int line) { + mylog(LOG_DEBUG, "rwlock_unlock_w(%p) at %s:%u", m, file, line); + return pthread_rwlock_unlock(m); +} + +#define __debug_cond_init(c, F, L) pthread_cond_init(c, NULL) +#define __debug_cond_wait(c, m, F, L) pthread_cond_wait(c,m) +#define __debug_cond_signal(c, F, L) pthread_cond_signal(c) +#define __debug_cond_broadcast(c, F, L) pthread_cond_broadcast(c) + +#endif + + void threads_join_all(int); void thread_create_detach(void (*)(void *), void *); diff --git a/daemon/bencode.c b/daemon/bencode.c index 835f13025..9044d2a05 100644 --- a/daemon/bencode.c +++ b/daemon/bencode.c @@ -96,7 +96,7 @@ int bencode_buffer_init(bencode_buffer_t *buf) { return 0; } -static void *__bencode_alloc(bencode_buffer_t *buf, unsigned int size) { +void *bencode_buffer_alloc(bencode_buffer_t *buf, unsigned int size) { struct __bencode_buffer_piece *piece; void *ret; @@ -143,7 +143,7 @@ void bencode_buffer_free(bencode_buffer_t *buf) { static bencode_item_t *__bencode_item_alloc(bencode_buffer_t *buf, unsigned int payload) { bencode_item_t *ret; - ret = __bencode_alloc(buf, sizeof(struct bencode_item) + payload); + ret = bencode_buffer_alloc(buf, sizeof(struct bencode_item) + payload); if (!ret) return NULL; ret->buffer = buf; @@ -218,7 +218,7 @@ static bencode_item_t *__bencode_string_alloc(bencode_buffer_t *buf, const void } bencode_item_t *bencode_string_len_dup(bencode_buffer_t *buf, const char *s, int len) { - char *sd = __bencode_alloc(buf, len); + char *sd = bencode_buffer_alloc(buf, len); if (!sd) return NULL; memcpy(sd, s, len); @@ -361,7 +361,7 @@ struct iovec *bencode_iovec(bencode_item_t *root, int *cnt, unsigned int head, u assert(cnt != NULL); assert(root->iov_cnt > 0); - ret = __bencode_alloc(root->buffer, sizeof(*ret) * (root->iov_cnt + head + tail)); + ret = bencode_buffer_alloc(root->buffer, sizeof(*ret) * (root->iov_cnt + head + tail)); if (!ret) return NULL; *cnt = __bencode_iovec_dump(ret + head, root); @@ -376,7 +376,7 @@ char *bencode_collapse(bencode_item_t *root, int *len) { return NULL; assert(root->str_len > 0); - ret = __bencode_alloc(root->buffer, root->str_len + 1); + ret = bencode_buffer_alloc(root->buffer, root->str_len + 1); if (!ret) return NULL; l = __bencode_str_dump(ret, root); @@ -693,7 +693,7 @@ void bencode_buffer_destroy_add(bencode_buffer_t *buf, free_func_t func, void *p if (!p) return; - li = __bencode_alloc(buf, sizeof(*li)); + li = bencode_buffer_alloc(buf, sizeof(*li)); if (!li) return; li->ptr = p; diff --git a/daemon/bencode.h b/daemon/bencode.h index 5628db960..22defa170 100644 --- a/daemon/bencode.h +++ b/daemon/bencode.h @@ -72,6 +72,9 @@ struct bencode_buffer { * Returns 0 on success or -1 on failure (if no memory could be allocated). */ int bencode_buffer_init(bencode_buffer_t *buf); +/* Allocate a piece of memory from the given buffer object */ +void *bencode_buffer_alloc(bencode_buffer_t *, unsigned int); + /* Destroys a previously initialized bencode_buffer_t object. All memory used by the object is freed * and all objects created through it become invalid. */ void bencode_buffer_free(bencode_buffer_t *buf); diff --git a/daemon/call.c b/daemon/call.c index 059d2000f..1c2c1ea0b 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -29,31 +29,18 @@ #include "stun.h" #include "rtcp.h" #include "rtp.h" +#include "call_interfaces.h" -#ifdef __DEBUG -#define DBG(x...) mylog(LOG_DEBUG, x) -#else -#define DBG(x...) ((void)0) -#endif - -#define LOG_PREFIX_C "[%.*s] " -#define LOG_PREFIX_CI "[%.*s - %.*s] " -#define LOG_PARAMS_C(c) STR_FMT(&(c)->callid) -#define LOG_PARAMS_CI(c) STR_FMT(&(c)->callid), STR_FMT0(log_info) - - - -static __thread const str *log_info; - +typedef int (*rewrite_func)(str *, struct packet_stream *); /* also serves as array index for callstream->peers[] */ struct iterator_helper { GSList *del; - struct streamrelay *ports[0x10000]; + struct stream_fd *ports[0x10000]; }; struct xmlrpc_helper { GStringChunk *c; @@ -61,124 +48,212 @@ struct xmlrpc_helper { GSList *tags; }; +struct streamhandler_io { + rewrite_func rtp; + rewrite_func rtcp; + int (*kernel)(struct mediaproxy_srtp *, struct packet_stream *); +}; +struct streamhandler { + const struct streamhandler_io *in; + const struct streamhandler_io *out; +}; + +const struct transport_protocol transport_protocols[] = { + [PROTO_RTP_AVP] = { + .index = PROTO_RTP_AVP, + .name = "RTP/AVP", + .srtp = 0, + .avpf = 0, + }, + [PROTO_RTP_SAVP] = { + .index = PROTO_RTP_SAVP, + .name = "RTP/SAVP", + .srtp = 1, + .avpf = 0, + }, + [PROTO_RTP_AVPF] = { + .index = PROTO_RTP_AVPF, + .name = "RTP/AVPF", + .srtp = 0, + .avpf = 1, + }, + [PROTO_RTP_SAVPF] = { + .index = PROTO_RTP_SAVPF, + .name = "RTP/SAVPF", + .srtp = 1, + .avpf = 1, + }, + [PROTO_UDP_TLS_RTP_SAVP] = { + .index = PROTO_UDP_TLS_RTP_SAVP, + .name = "UDP/TLS/RTP/SAVP", + .srtp = 1, + .avpf = 0, + }, + [PROTO_UDP_TLS_RTP_SAVPF] = { + .index = PROTO_UDP_TLS_RTP_SAVPF, + .name = "UDP/TLS/RTP/SAVPF", + .srtp = 1, + .avpf = 1, + }, +}; +const int num_transport_protocols = G_N_ELEMENTS(transport_protocols); -struct callmaster { - struct obj obj; - rwlock_t hashlock; - GHashTable *callhash; - mutex_t portlock; - u_int16_t lastport; - BIT_ARRAY_DECLARE(ports_used, 0x10000); - mutex_t statspslock; - struct stats statsps; /* per second stats, running timer */ - mutex_t statslock; - struct stats stats; /* copied from statsps once a second */ +static void determine_handler(struct packet_stream *in, const struct packet_stream *out); - struct poller *poller; - pcre *info_re; - pcre_extra *info_ree; - pcre *streams_re; - pcre_extra *streams_ree; +static int __k_null(struct mediaproxy_srtp *s, struct packet_stream *); +static int __k_srtp_encrypt(struct mediaproxy_srtp *s, struct packet_stream *); +static int __k_srtp_decrypt(struct mediaproxy_srtp *s, struct packet_stream *); - struct callmaster_config conf; -}; +static int call_avp2savp_rtp(str *s, struct packet_stream *); +static int call_savp2avp_rtp(str *s, struct packet_stream *); +static int call_avp2savp_rtcp(str *s, struct packet_stream *); +static int call_savp2avp_rtcp(str *s, struct packet_stream *); +static int call_avpf2avp_rtcp(str *s, struct packet_stream *); +//static int call_avpf2savp_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 *); -struct call_stats { - time_t newest; - struct stats totals[4]; /* rtp in, rtcp in, rtp out, rtcp out */ -}; -struct streamhandler { - int (*rewrite)(str *, struct streamrelay *); - int (*kernel_decrypt)(struct mediaproxy_srtp *, struct streamrelay *); - int (*kernel_encrypt)(struct mediaproxy_srtp *, struct streamrelay *); -}; +/* ********** */ -static char *rtp_codecs[] = { - [0] = "G711u", - [1] = "1016", - [2] = "G721", - [3] = "GSM", - [4] = "G723", - [5] = "DVI4", - [6] = "DVI4", - [7] = "LPC", - [8] = "G711a", - [9] = "G722", - [10] = "L16", - [11] = "L16", - [14] = "MPA", - [15] = "G728", - [18] = "G729", - [25] = "CelB", - [26] = "JPEG", - [28] = "nv", - [31] = "H261", - [32] = "MPV", - [33] = "MP2T", - [34] = "H263", +static const struct streamhandler_io __shio_noop = { + .kernel = __k_null, }; -const char *transport_protocol_strings[__PROTO_LAST] = { - [PROTO_RTP_AVP] = "RTP/AVP", - [PROTO_RTP_SAVP] = "RTP/SAVP", - [PROTO_RTP_AVPF] = "RTP/AVPF", - [PROTO_RTP_SAVPF] = "RTP/SAVPF", +static const struct streamhandler_io __shio_decrypt = { + .kernel = __k_srtp_decrypt, + .rtp = call_savp2avp_rtp, + .rtcp = call_savp2avp_rtcp, +}; +static const struct streamhandler_io __shio_encrypt = { + .kernel = __k_srtp_encrypt, + .rtp = call_avp2savp_rtp, + .rtcp = call_avp2savp_rtcp, +}; +static const struct streamhandler_io __shio_avpf_strip = { + .kernel = __k_null, + .rtcp = call_avpf2avp_rtcp, +}; +static const struct streamhandler_io __shio_decrypt_avpf_strip = { + .kernel = __k_srtp_decrypt, + .rtp = call_savp2avp_rtp, + .rtcp = call_savpf2avp_rtcp, }; - - - -static void determine_handler(struct streamrelay *in); - -static int __k_null(struct mediaproxy_srtp *s, struct streamrelay *r); -static int __k_srtp_encrypt(struct mediaproxy_srtp *s, struct streamrelay *r); -static int __k_srtp_decrypt(struct mediaproxy_srtp *s, struct streamrelay *r); - -static int call_avp2savp_rtp(str *s, struct streamrelay *r); -static int call_savp2avp_rtp(str *s, struct streamrelay *r); -static int call_avp2savp_rtcp(str *s, struct streamrelay *r); -static int call_savp2avp_rtcp(str *s, struct streamrelay *r); -static int call_avpf2avp_rtcp(str *s, struct streamrelay *r); -static int call_avpf2savp_rtcp(str *s, struct streamrelay *r); -static int call_savpf2avp_rtcp(str *s, struct streamrelay *r); -static int call_savpf2savp_rtcp(str *s, struct streamrelay *t); +/* ********** */ static const struct streamhandler __sh_noop = { - .kernel_decrypt = __k_null, - .kernel_encrypt = __k_null, + .in = &__shio_noop, + .out = &__shio_noop, +}; +static const struct streamhandler __sh_savp2avp = { + .in = &__shio_decrypt, + .out = &__shio_noop, +}; +static const struct streamhandler __sh_avp2savp = { + .in = &__shio_noop, + .out = &__shio_encrypt, +}; +static const struct streamhandler __sh_avpf2avp = { + .in = &__shio_avpf_strip, + .out = &__shio_noop, +}; +static const struct streamhandler __sh_avpf2savp = { + .in = &__shio_avpf_strip, + .out = &__shio_encrypt, +}; +static const struct streamhandler __sh_savpf2avp = { + .in = &__shio_decrypt_avpf_strip, + .out = &__shio_noop, +}; +static const struct streamhandler __sh_savp2savp = { + .in = &__shio_decrypt, + .out = &__shio_encrypt, +}; +static const struct streamhandler __sh_savpf2savpf = { + .in = &__shio_decrypt, + .out = &__shio_encrypt, +}; +static const struct streamhandler __sh_savpf2savp = { + .in = &__shio_decrypt_avpf_strip, + .out = &__shio_encrypt, }; -static const struct streamhandler __sh_rtp_avp2savp = { - .rewrite = call_avp2savp_rtp, - .kernel_decrypt = __k_null, - .kernel_encrypt = __k_srtp_encrypt, + +/* ********** */ + +static const struct streamhandler *__sh_matrix_in_rtp_avp[] = { + [PROTO_RTP_AVP] = &__sh_noop, + [PROTO_RTP_AVPF] = &__sh_noop, + [PROTO_RTP_SAVP] = &__sh_avp2savp, + [PROTO_RTP_SAVPF] = &__sh_avp2savp, + [PROTO_UDP_TLS_RTP_SAVP] = &__sh_avp2savp, + [PROTO_UDP_TLS_RTP_SAVPF] = &__sh_avp2savp, }; -static const struct streamhandler __sh_rtp_savp2avp = { - .rewrite = call_savp2avp_rtp, - .kernel_decrypt = __k_srtp_decrypt, - .kernel_encrypt = __k_null, +static const struct streamhandler *__sh_matrix_in_rtp_avpf[] = { + [PROTO_RTP_AVP] = &__sh_avpf2avp, + [PROTO_RTP_AVPF] = &__sh_noop, + [PROTO_RTP_SAVP] = &__sh_avpf2savp, + [PROTO_RTP_SAVPF] = &__sh_avp2savp, + [PROTO_UDP_TLS_RTP_SAVP] = &__sh_avpf2savp, + [PROTO_UDP_TLS_RTP_SAVPF] = &__sh_avp2savp, }; -static const struct streamhandler __sh_rtcp_avp2savp = { - .rewrite = call_avp2savp_rtcp, +static const struct streamhandler *__sh_matrix_in_rtp_savp[] = { + [PROTO_RTP_AVP] = &__sh_savp2avp, + [PROTO_RTP_AVPF] = &__sh_savp2avp, + [PROTO_RTP_SAVP] = &__sh_noop, + [PROTO_RTP_SAVPF] = &__sh_noop, + [PROTO_UDP_TLS_RTP_SAVP] = &__sh_noop, + [PROTO_UDP_TLS_RTP_SAVPF] = &__sh_noop, }; -static const struct streamhandler __sh_rtcp_savp2avp = { - .rewrite = call_savp2avp_rtcp, +static const struct streamhandler *__sh_matrix_in_rtp_savpf[] = { + [PROTO_RTP_AVP] = &__sh_savpf2avp, + [PROTO_RTP_AVPF] = &__sh_savp2avp, + [PROTO_RTP_SAVP] = &__sh_savpf2savp, + [PROTO_RTP_SAVPF] = &__sh_noop, + [PROTO_UDP_TLS_RTP_SAVP] = &__sh_savpf2savp, + [PROTO_UDP_TLS_RTP_SAVPF] = &__sh_noop, }; -static const struct streamhandler __sh_rtcp_avpf2avp = { - .rewrite = call_avpf2avp_rtcp, +static const struct streamhandler *__sh_matrix_in_rtp_savp_dtls[] = { + [PROTO_RTP_AVP] = &__sh_savp2avp, + [PROTO_RTP_AVPF] = &__sh_savp2avp, + [PROTO_RTP_SAVP] = &__sh_savp2savp, + [PROTO_RTP_SAVPF] = &__sh_savp2savp, + [PROTO_UDP_TLS_RTP_SAVP] = &__sh_savp2savp, + [PROTO_UDP_TLS_RTP_SAVPF] = &__sh_savp2savp, }; -static const struct streamhandler __sh_rtcp_avpf2savp = { - .rewrite = call_avpf2savp_rtcp, +static const struct streamhandler *__sh_matrix_in_rtp_savpf_dtls[] = { + [PROTO_RTP_AVP] = &__sh_savpf2avp, + [PROTO_RTP_AVPF] = &__sh_savp2avp, + [PROTO_RTP_SAVP] = &__sh_savpf2savp, + [PROTO_RTP_SAVPF] = &__sh_savp2savp, + [PROTO_UDP_TLS_RTP_SAVP] = &__sh_savpf2savp, + [PROTO_UDP_TLS_RTP_SAVPF] = &__sh_savp2savp, }; -static const struct streamhandler __sh_rtcp_savpf2avp = { - .rewrite = call_savpf2avp_rtcp, + +/* ********** */ + +static const struct streamhandler **__sh_matrix[] = { + [PROTO_RTP_AVP] = __sh_matrix_in_rtp_avp, + [PROTO_RTP_AVPF] = __sh_matrix_in_rtp_avpf, + [PROTO_RTP_SAVP] = __sh_matrix_in_rtp_savp, + [PROTO_RTP_SAVPF] = __sh_matrix_in_rtp_savpf, + [PROTO_UDP_TLS_RTP_SAVP] = __sh_matrix_in_rtp_savp, + [PROTO_UDP_TLS_RTP_SAVPF] = __sh_matrix_in_rtp_savpf, }; -static const struct streamhandler __sh_rtcp_savpf2savp = { - .rewrite = call_savpf2savp_rtcp, +/* special case for DTLS as we can't pass through SRTP<>SRTP */ +static const struct streamhandler **__sh_matrix_dtls[] = { + [PROTO_RTP_AVP] = __sh_matrix_in_rtp_avp, + [PROTO_RTP_AVPF] = __sh_matrix_in_rtp_avpf, + [PROTO_RTP_SAVP] = __sh_matrix_in_rtp_savp_dtls, + [PROTO_RTP_SAVPF] = __sh_matrix_in_rtp_savpf_dtls, + [PROTO_UDP_TLS_RTP_SAVP] = __sh_matrix_in_rtp_savp_dtls, + [PROTO_UDP_TLS_RTP_SAVPF] = __sh_matrix_in_rtp_savpf_dtls, }; +/* ********** */ + static const struct mediaproxy_srtp __mps_null = { .cipher = MPC_NULL, .hmac = MPH_NULL, @@ -190,31 +265,26 @@ static const struct mediaproxy_srtp __mps_null = { static void call_destroy(struct call *); -static void unkernelize(struct peer *); -static void unconfirm(struct peer *); -static void unconfirm_cs(struct callstream *); -static void relays_cache_port_used(struct relays_cache *c); -static void ng_call_stats(struct call *call, const str *fromtag, const str *totag, bencode_item_t *output); +static void unkernelize(struct packet_stream *); -static void stream_closed(int fd, void *p, uintptr_t u) { - struct callstream *cs = p; - struct streamrelay *r; +/* called lock-free */ +static void stream_fd_closed(int fd, void *p, uintptr_t u) { + struct stream_fd *sfd = p; struct call *c; int i; socklen_t j; - mutex_lock(&cs->lock); - r = &cs->peers[u >> 1].rtps[u & 1]; - assert(r->fd.fd == fd); - mutex_unlock(&cs->lock); - c = cs->call; + assert(sfd->fd.fd == fd); + c = sfd->call; + if (!c) + return; j = sizeof(i); getsockopt(fd, SOL_SOCKET, SO_ERROR, &i, &j); - mylog(LOG_WARNING, LOG_PREFIX_C "Read error on RTP socket: %i (%s) -- closing call", LOG_PARAMS_C(c), i, strerror(i)); + ilog(LOG_WARNING, "Read error on media socket: %i (%s) -- closing call", i, strerror(i)); call_destroy(c); } @@ -222,289 +292,181 @@ static void stream_closed(int fd, void *p, uintptr_t u) { -/* called with callstream->lock held */ -void kernelize(struct callstream *c) { - int i, j; - struct peer *p, *pp; - struct streamrelay *r, *rp; +/* called with in_lock held */ +void kernelize(struct packet_stream *stream) { struct mediaproxy_target_info mpt; - struct callmaster *cm = c->call->callmaster; + struct call *call = stream->call; + struct callmaster *cm = call->callmaster; + struct packet_stream *sink = NULL; - if (cm->conf.kernelfd < 0 || cm->conf.kernelid == -1) + if (stream->kernelized) return; + if (cm->conf.kernelfd < 0 || cm->conf.kernelid == -1) + goto no_kernel; + if (!stream->rtp) + goto no_kernel; + if (!stream->sfd) + goto no_kernel; - mylog(LOG_DEBUG, LOG_PREFIX_C "Kernelizing RTP streams", LOG_PARAMS_C(c->call)); + ilog(LOG_DEBUG, "Kernelizing media stream with local port %u", + stream->sfd->fd.localport); + + sink = packet_stream_sink(stream); + if (!sink) { + ilog(LOG_WARNING, "Attempt to kernelize stream without sink"); + goto no_kernel; + } ZERO(mpt); - for (i = 0; i < 2; i++) { - p = &c->peers[i]; - pp = &c->peers[i ^ 1]; + determine_handler(stream, sink); - if (p->kernelized) - continue; + if (is_addr_unspecified(&sink->advertised_endpoint.ip46) + || !sink->advertised_endpoint.port) + goto no_kernel; + if (!stream->handler->in->kernel + || !stream->handler->out->kernel) + goto no_kernel; - for (j = 0; j < 2; j++) { - r = &p->rtps[j]; - rp = &pp->rtps[j]; - - determine_handler(r); - - if (is_addr_unspecified(&r->peer_advertised.ip46) - || !r->peer_advertised.port) - goto no_kernel_stream; - if (!r->handler->kernel_decrypt - || !r->handler->kernel_encrypt) - goto no_kernel_stream; - - mpt.target_port = r->fd.localport; - mpt.tos = cm->conf.tos; - mpt.src_addr.port = rp->fd.localport; - mpt.dst_addr.port = r->peer.port; - mpt.rtcp_mux = r->rtcp_mux; - - if (IN6_IS_ADDR_V4MAPPED(&r->peer.ip46)) { - mpt.src_addr.family = AF_INET; - mpt.src_addr.ipv4 = cm->conf.ipv4; - mpt.dst_addr.family = AF_INET; - mpt.dst_addr.ipv4 = r->peer.ip46.s6_addr32[3]; - } - else { - mpt.src_addr.family = AF_INET6; - memcpy(mpt.src_addr.ipv6, &cm->conf.ipv6, sizeof(mpt.src_addr.ipv6)); - mpt.dst_addr.family = AF_INET6; - memcpy(mpt.dst_addr.ipv6, &r->peer.ip46, sizeof(mpt.src_addr.ipv6)); - } + mutex_lock(&sink->out_lock); + + mpt.target_port = stream->sfd->fd.localport; + mpt.tos = cm->conf.tos; + mpt.src_addr.port = sink->sfd->fd.localport; + mpt.dst_addr.port = sink->endpoint.port; + mpt.rtcp_mux = stream->media->rtcp_mux; + mpt.dtls = stream->media->dtls; + + if (IN6_IS_ADDR_V4MAPPED(&sink->endpoint.ip46)) { + mpt.src_addr.family = AF_INET; + mpt.src_addr.ipv4 = cm->conf.ipv4; + mpt.dst_addr.family = AF_INET; + mpt.dst_addr.ipv4 = sink->endpoint.ip46.s6_addr32[3]; + } + else { + mpt.src_addr.family = AF_INET6; + memcpy(mpt.src_addr.ipv6, &cm->conf.ipv6, sizeof(mpt.src_addr.ipv6)); + mpt.dst_addr.family = AF_INET6; + memcpy(mpt.dst_addr.ipv6, &sink->endpoint.ip46, sizeof(mpt.src_addr.ipv6)); + } - if (r->handler->kernel_decrypt(&mpt.decrypt, r)) - goto no_kernel_stream; - if (r->handler->kernel_encrypt(&mpt.encrypt, r)) - goto no_kernel_stream; + stream->handler->in->kernel(&mpt.decrypt, stream); + stream->handler->out->kernel(&mpt.encrypt, sink); - if (!mpt.encrypt.cipher || !mpt.encrypt.hmac) - goto no_kernel_stream; - if (!mpt.decrypt.cipher || !mpt.decrypt.hmac) - goto no_kernel_stream; + mutex_unlock(&sink->out_lock); - ZERO(r->kstats); + if (!mpt.encrypt.cipher || !mpt.encrypt.hmac) + goto no_kernel; + if (!mpt.decrypt.cipher || !mpt.decrypt.hmac) + goto no_kernel; - kernel_add_stream(cm->conf.kernelfd, &mpt, 0); - - continue; + ZERO(stream->kernel_stats); -no_kernel_stream: - r->no_kernel_support = 1; - } + kernel_add_stream(cm->conf.kernelfd, &mpt, 0); + stream->kernelized = 1; - p->kernelized = 1; - } + return; + +no_kernel: + stream->kernelized = 1; + stream->no_kernel_support = 1; } /* returns: 0 = not a muxed stream, 1 = muxed, RTP, 2 = muxed, RTCP */ -static int rtcp_demux(str *s, struct streamrelay *r) { - if (r->idx != 0) - return 0; - if (!r->rtcp_mux) +static int rtcp_demux(str *s, struct call_media *media) { + if (!media->rtcp_mux) return 0; return rtcp_demux_is_rtcp(s) ? 2 : 1; } -static int call_avpf2avp_rtcp(str *s, struct streamrelay *r) { +static int call_avpf2avp_rtcp(str *s, struct packet_stream *stream) { return rtcp_avpf2avp(s); } -static int call_avp2savp_rtp(str *s, struct streamrelay *r) { - return rtp_avp2savp(s, &r->other->crypto.out); +static int call_avp2savp_rtp(str *s, struct packet_stream *stream) { + return rtp_avp2savp(s, &stream->crypto); } -static int call_avp2savp_rtcp(str *s, struct streamrelay *r) { - return rtcp_avp2savp(s, &r->other->crypto.out); +static int call_avp2savp_rtcp(str *s, struct packet_stream *stream) { + return rtcp_avp2savp(s, &stream->crypto); } -static int call_savp2avp_rtp(str *s, struct streamrelay *r) { - return rtp_savp2avp(s, &r->crypto.in); +static int call_savp2avp_rtp(str *s, struct packet_stream *stream) { + return rtp_savp2avp(s, &stream->sfd->crypto); } -static int call_savp2avp_rtcp(str *s, struct streamrelay *r) { - return rtcp_savp2avp(s, &r->crypto.in); +static int call_savp2avp_rtcp(str *s, struct packet_stream *stream) { + return rtcp_savp2avp(s, &stream->sfd->crypto); } -static int call_avpf2savp_rtcp(str *s, struct streamrelay *r) { +static int call_savpf2avp_rtcp(str *s, struct packet_stream *stream) { int ret; - ret = rtcp_avpf2avp(s); - if (ret < 0) - return ret; - return rtcp_avp2savp(s, &r->other->crypto.out); -} -static int call_savpf2avp_rtcp(str *s, struct streamrelay *r) { - int ret; - ret = rtcp_savp2avp(s, &r->crypto.in); + ret = rtcp_savp2avp(s, &stream->sfd->crypto); if (ret < 0) return ret; return rtcp_avpf2avp(s); } -static int call_savpf2savp_rtcp(str *s, struct streamrelay *r) { - int ret; - ret = rtcp_savp2avp(s, &r->crypto.in); - if (ret < 0) - return ret; - ret = rtcp_avpf2avp(s); - if (ret < 0) - return ret; - return rtcp_avp2savp(s, &r->other->crypto.out); -} -static int __k_null(struct mediaproxy_srtp *s, struct streamrelay *r) { +static int __k_null(struct mediaproxy_srtp *s, struct packet_stream *stream) { *s = __mps_null; return 0; } static int __k_srtp_crypt(struct mediaproxy_srtp *s, struct crypto_context *c) { - if (!c->crypto_suite) + if (!c->params.crypto_suite) return -1; *s = (struct mediaproxy_srtp) { - .cipher = c->crypto_suite->kernel_cipher, - .hmac = c->crypto_suite->kernel_hmac, - .mki = c->mki, - .mki_len = c->mki_len, + .cipher = c->params.crypto_suite->kernel_cipher, + .hmac = c->params.crypto_suite->kernel_hmac, + .mki_len = c->params.mki_len, .last_index = c->last_index, - .auth_tag_len = c->crypto_suite->srtp_auth_tag, + .auth_tag_len = c->params.crypto_suite->srtp_auth_tag, }; - memcpy(s->master_key, c->master_key, c->crypto_suite->master_key_len); - memcpy(s->master_salt, c->master_salt, c->crypto_suite->master_salt_len); + if (c->params.mki_len) + memcpy(s->mki, c->params.mki, c->params.mki_len); + memcpy(s->master_key, c->params.master_key, c->params.crypto_suite->master_key_len); + memcpy(s->master_salt, c->params.master_salt, c->params.crypto_suite->master_salt_len); return 0; } -static int __k_srtp_encrypt(struct mediaproxy_srtp *s, struct streamrelay *r) { - return __k_srtp_crypt(s, &r->other->crypto.out); +static int __k_srtp_encrypt(struct mediaproxy_srtp *s, struct packet_stream *stream) { + return __k_srtp_crypt(s, &stream->crypto); } -static int __k_srtp_decrypt(struct mediaproxy_srtp *s, struct streamrelay *r) { - return __k_srtp_crypt(s, &r->crypto.in); -} - -static const struct streamhandler *determine_handler_rtp(struct streamrelay *in) { - switch (in->peer.protocol) { - case PROTO_RTP_AVP: - case PROTO_RTP_AVPF: - switch (in->peer_advertised.protocol) { - case PROTO_RTP_AVP: - case PROTO_RTP_AVPF: - return NULL; - - case PROTO_RTP_SAVP: - case PROTO_RTP_SAVPF: - return &__sh_rtp_avp2savp; - - default: - abort(); - } - - case PROTO_RTP_SAVP: - case PROTO_RTP_SAVPF: - switch (in->peer_advertised.protocol) { - case PROTO_RTP_AVP: - case PROTO_RTP_AVPF: - return &__sh_rtp_savp2avp; - - case PROTO_RTP_SAVPF: - case PROTO_RTP_SAVP: - return NULL; - - default: - abort(); - } - - default: - abort(); - } +static int __k_srtp_decrypt(struct mediaproxy_srtp *s, struct packet_stream *stream) { + return __k_srtp_crypt(s, &stream->sfd->crypto); } -static const struct streamhandler *determine_handler_rtcp(struct streamrelay *in) { - switch (in->peer.protocol) { - case PROTO_RTP_AVP: - switch (in->peer_advertised.protocol) { - case PROTO_RTP_AVPF: - return NULL; - - case PROTO_RTP_SAVP: - case PROTO_RTP_SAVPF: - return &__sh_rtcp_avp2savp; - - default: - abort(); - } - case PROTO_RTP_SAVP: - switch (in->peer_advertised.protocol) { - case PROTO_RTP_AVP: - case PROTO_RTP_AVPF: - return &__sh_rtcp_savp2avp; - - case PROTO_RTP_SAVPF: - return NULL; - - default: - abort(); - } - - case PROTO_RTP_AVPF: - switch (in->peer_advertised.protocol) { - case PROTO_RTP_AVP: - return &__sh_rtcp_avpf2avp; - - case PROTO_RTP_SAVP: - return &__sh_rtcp_avpf2savp; - - case PROTO_RTP_SAVPF: - return &__sh_rtcp_avp2savp; - - default: - abort(); - } - - case PROTO_RTP_SAVPF: - switch (in->peer_advertised.protocol) { - case PROTO_RTP_AVP: - return &__sh_rtcp_savpf2avp; - - case PROTO_RTP_AVPF: - return &__sh_rtcp_savp2avp; - - case PROTO_RTP_SAVP: - return &__sh_rtcp_savpf2savp; - - default: - abort(); - } - - default: - abort(); - } -} -static void determine_handler(struct streamrelay *in) { - const struct streamhandler *ret; +/* must be called with call->master_lock held in R, and in->in_lock held */ +static void determine_handler(struct packet_stream *in, const struct packet_stream *out) { + const struct streamhandler **sh_pp, *sh; + const struct streamhandler ***matrix; - if (in->handler) + if (in->has_handler) return; - if (in->peer.protocol == in->peer_advertised.protocol) - goto dummy; - if (in->peer.protocol == PROTO_UNKNOWN) - goto dummy; - if (in->peer_advertised.protocol == PROTO_UNKNOWN) - goto dummy; - - if (in->rtcp) - ret = determine_handler_rtcp(in); - else - ret = determine_handler_rtp(in); + if (!in->media->protocol) + goto err; + if (!out->media->protocol) + goto err; - if (!ret) - goto dummy; + matrix = __sh_matrix; + if (in->media->dtls && out->media->dtls) + matrix = __sh_matrix_dtls; - in->handler = ret; + sh_pp = matrix[in->media->protocol->index]; + if (!sh_pp) + goto err; + sh = sh_pp[out->media->protocol->index]; + if (!sh) + goto err; + in->handler = sh; +done: + in->has_handler = 1; return; -dummy: +err: + ilog(LOG_WARNING, "Unknown transport protocol encountered"); in->handler = &__sh_noop; + goto done; } void callmaster_msg_mh_src(struct callmaster *cm, struct msghdr *mh) { @@ -542,136 +504,182 @@ void callmaster_msg_mh_src(struct callmaster *cm, struct msghdr *mh) { } } -/* called with r->up (== cs) locked */ -static int stream_packet(struct streamrelay *sr_incoming, str *s, struct sockaddr_in6 *fsin) { - struct streamrelay *sr_outgoing, *sr_out_rtcp, *sr_in_rtcp; - struct peer *p_incoming, *p_outgoing; - struct callstream *cs_incoming; - int ret, update = 0, stun_ret = 0, handler_ret = 0, muxed_rtcp = 0; +/* called lock-free */ +static int stream_packet(struct stream_fd *sfd, str *s, struct sockaddr_in6 *fsin) { + struct packet_stream *stream = sfd->stream, + *sink = NULL, + *in_srtp, *out_srtp; + struct call_media *media; + int ret = 0, update = 0, stun_ret = 0, handler_ret = 0, muxed_rtcp = 0, rtcp = 0; struct sockaddr_in6 sin6; struct msghdr mh; struct iovec iov; unsigned char buf[256]; - struct call *c; - struct callmaster *m; - unsigned char cc; + struct call *call; + struct callmaster *cm; + /*unsigned char cc;*/ char addr[64]; - struct stream s_copy; - - p_incoming = sr_incoming->up; - cs_incoming = p_incoming->up; - p_outgoing = p_incoming->other; - sr_outgoing = sr_incoming->other; - c = cs_incoming->call; - m = c->callmaster; + struct endpoint endpoint; + rewrite_func rwf_in, rwf_out; + + assert(stream != NULL); + media = stream->media; + call = stream->call; + cm = call->callmaster; smart_ntop_port(addr, fsin, sizeof(addr)); - if (sr_incoming->stun && is_stun(s)) { - stun_ret = stun(s, sr_incoming, fsin); + rwlock_lock_r(&call->master_lock); + mutex_lock(&stream->in_lock); + + if (!stream->sfd) + goto done; + + if (media->dtls && is_dtls(s)) { + ret = dtls(stream, s, fsin); + if (!ret) + goto done; + } + + if (stream->stun && is_stun(s)) { + stun_ret = stun(s, stream, fsin); if (!stun_ret) - return 0; + goto done; if (stun_ret == 1) /* use candidate */ goto use_cand; else /* not an stun packet */ stun_ret = 0; } - if (sr_outgoing->fd.fd == -1) { - mylog(LOG_WARNING, LOG_PREFIX_C "RTP packet to port %u discarded from %s", - LOG_PARAMS_C(c), sr_incoming->fd.localport, addr); - sr_incoming->stats.errors++; - mutex_lock(&m->statspslock); - m->statsps.errors++; - mutex_unlock(&m->statspslock); - return 0; + mutex_unlock(&stream->in_lock); + + in_srtp = stream; + sink = stream->rtp_sink; + if (!sink && stream->rtcp) { + sink = stream->rtcp_sink; + rtcp = 1; + } + else if (stream->rtcp_sink) { + muxed_rtcp = rtcp_demux(s, media); + if (muxed_rtcp == 2) { + sink = stream->rtcp_sink; + rtcp = 1; + in_srtp = stream->rtcp_sibling; + } + } + out_srtp = sink; + if (rtcp && sink && sink->rtcp_sibling) + out_srtp = sink->rtcp_sibling; + + if (!sink || !sink->sfd || !out_srtp->sfd || !in_srtp->sfd) { + ilog(LOG_WARNING, "RTP packet to port %u discarded from %s", + sfd->fd.localport, addr); + mutex_lock(&stream->in_lock); + stream->stats.errors++; + mutex_lock(&cm->statspslock); + cm->statsps.errors++; + mutex_unlock(&cm->statspslock); + goto done; + } + + mutex_lock(&in_srtp->in_lock); + + determine_handler(in_srtp, sink); + + if (!rtcp) { + rwf_in = in_srtp->handler->in->rtp; + rwf_out = in_srtp->handler->out->rtp; + } + else { + rwf_in = in_srtp->handler->in->rtcp; + rwf_out = in_srtp->handler->out->rtcp; } - sr_in_rtcp = sr_incoming; - muxed_rtcp = rtcp_demux(s, sr_incoming); - if (muxed_rtcp == 2) - sr_in_rtcp = &p_incoming->rtps[1]; + mutex_lock(&out_srtp->out_lock); - determine_handler(sr_in_rtcp); - if (sr_in_rtcp->handler->rewrite) - handler_ret = sr_in_rtcp->handler->rewrite(s, sr_in_rtcp); - /* return values are: 0 = forward packet, -1 = error/dont forward, - * 1 = forward and push update to redis */ + /* return values are: 0 = forward packet, -1 = error/dont forward, + * 1 = forward and push update to redis */ + 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) update = 1; + mutex_unlock(&out_srtp->out_lock); + mutex_unlock(&in_srtp->in_lock); + + mutex_lock(&stream->in_lock); + use_cand: - if (!p_incoming->filled || sr_incoming->idx != 0) + if (!stream->filled) goto forward; - if (p_incoming->confirmed) + if (media->asymmetric) + stream->confirmed = 1; + + if (stream->confirmed) goto kernel_check; - if (!c->lookup_done || poller_now <= c->lookup_done + 3) + if (!call->last_signal || poller_now <= call->last_signal + 3) goto peerinfo; - mylog(LOG_DEBUG, LOG_PREFIX_C "Confirmed peer information for port %u - %s", - LOG_PARAMS_C(c), sr_incoming->fd.localport, addr); + ilog(LOG_DEBUG, "Confirmed peer information for port %u - %s", + sfd->fd.localport, addr); - p_incoming->confirmed = 1; + stream->confirmed = 1; update = 1; peerinfo: - if (!stun_ret && !p_incoming->codec && s->len >= 2) { + /* + if (!stun_ret && !stream->codec && s->len >= 2) { cc = s->s[1]; cc &= 0x7f; if (cc < G_N_ELEMENTS(rtp_codecs)) - p_incoming->codec = rtp_codecs[cc] ? : "unknown"; + stream->codec = rtp_codecs[cc] ? : "unknown"; else - p_incoming->codec = "unknown"; + stream->codec = "unknown"; } + */ - sr_out_rtcp = &p_outgoing->rtps[1]; /* sr_incoming->idx == 0 */ - s_copy = sr_outgoing->peer; - sr_outgoing->peer.ip46 = fsin->sin6_addr; - sr_outgoing->peer.port = ntohs(fsin->sin6_port); - if (memcmp(&s_copy, &sr_outgoing->peer, sizeof(s_copy))) { - sr_out_rtcp->peer.ip46 = sr_outgoing->peer.ip46; - sr_out_rtcp->peer.port = sr_outgoing->peer.port + 1; /* sr_out_rtcp->idx == 1 */ + mutex_lock(&stream->out_lock); + endpoint = stream->endpoint; + stream->endpoint.ip46 = fsin->sin6_addr; + stream->endpoint.port = ntohs(fsin->sin6_port); + if (memcmp(&endpoint, &stream->endpoint, sizeof(endpoint))) update = 1; - } + mutex_unlock(&stream->out_lock); kernel_check: - if (sr_incoming->no_kernel_support) + if (stream->no_kernel_support) goto forward; - if (p_incoming->confirmed && p_outgoing->confirmed && p_outgoing->filled) - kernelize(cs_incoming); + if (stream->confirmed && sink && sink->confirmed && sink->filled) + kernelize(stream); forward: - if (is_addr_unspecified(&sr_incoming->peer_advertised.ip46) - || !sr_incoming->peer_advertised.port + if (sink) + mutex_lock(&sink->out_lock); + + if (!sink || is_addr_unspecified(&sink->advertised_endpoint.ip46) + || !sink->advertised_endpoint.port || stun_ret || handler_ret < 0) goto drop; - if (muxed_rtcp == 2) { - /* demux */ - sr_incoming = sr_in_rtcp; - sr_outgoing = sr_incoming->other; - } - else if (sr_incoming->idx == 1 && sr_outgoing->rtcp_mux) { - /* mux */ - sr_incoming = &p_incoming->rtps[0]; - sr_outgoing = sr_incoming->other; - } - ZERO(mh); mh.msg_control = buf; mh.msg_controllen = sizeof(buf); ZERO(sin6); sin6.sin6_family = AF_INET6; - sin6.sin6_addr = sr_incoming->peer.ip46; - sin6.sin6_port = htons(sr_incoming->peer.port); + sin6.sin6_addr = sink->endpoint.ip46; + sin6.sin6_port = htons(sink->endpoint.port); mh.msg_name = &sin6; mh.msg_namelen = sizeof(sin6); - callmaster_msg_mh_src(m, &mh); + mutex_unlock(&sink->out_lock); + + callmaster_msg_mh_src(cm, &mh); ZERO(iov); iov.iov_base = s->s; @@ -680,39 +688,46 @@ forward: mh.msg_iov = &iov; mh.msg_iovlen = 1; - ret = sendmsg(sr_outgoing->fd.fd, &mh, 0); + ret = sendmsg(sink->sfd->fd.fd, &mh, 0); if (ret == -1) { - sr_incoming->stats.errors++; - mutex_lock(&m->statspslock); - m->statsps.errors++; - mutex_unlock(&m->statspslock); + stream->stats.errors++; + mutex_lock(&cm->statspslock); + cm->statsps.errors++; + mutex_unlock(&cm->statspslock); goto out; } + sink = NULL; + drop: + if (sink) + mutex_unlock(&sink->out_lock); ret = 0; - sr_incoming->stats.packets++; - sr_incoming->stats.bytes += s->len; - sr_incoming->last = poller_now; - mutex_lock(&m->statspslock); - m->statsps.packets++; - m->statsps.bytes += s->len; - mutex_unlock(&m->statspslock); + stream->stats.packets++; + stream->stats.bytes += s->len; + stream->last_packet = poller_now; + mutex_lock(&cm->statspslock); + cm->statsps.packets++; + cm->statsps.bytes += s->len; + mutex_unlock(&cm->statspslock); out: if (ret == 0 && update) ret = 1; +done: + mutex_unlock(&stream->in_lock); + rwlock_unlock_r(&call->master_lock); + return ret; } -static void stream_readable(int fd, void *p, uintptr_t u) { - struct callstream *cs = p; - struct streamrelay *r; +static void stream_fd_readable(int fd, void *p, uintptr_t u) { + struct stream_fd *sfd = p; char buf[RTP_BUFFER_SIZE]; int ret; struct sockaddr_storage ss; @@ -724,11 +739,11 @@ static void stream_readable(int fd, void *p, uintptr_t u) { struct call *ca; str s; - mutex_lock(&cs->lock); - r = &cs->peers[u >> 1].rtps[u & 1]; - if (r->fd.fd != fd) + if (sfd->fd.fd != fd) goto out; + log_info_stream_fd(sfd); + for (;;) { sinlen = sizeof(ss); ret = recvfrom(fd, buf + RTP_BUFFER_HEAD_ROOM, MAX_RTP_PACKET_SIZE, @@ -739,12 +754,11 @@ static void stream_readable(int fd, void *p, uintptr_t u) { continue; if (errno == EAGAIN || errno == EWOULDBLOCK) break; - mutex_unlock(&cs->lock); - stream_closed(fd, r, 0); - return; + stream_fd_closed(fd, sfd, 0); + goto done; } if (ret >= MAX_RTP_PACKET_SIZE) - mylog(LOG_WARNING, "UDP packet possibly truncated"); + ilog(LOG_WARNING, "UDP packet possibly truncated"); sinp = &ss; if (ss.ss_family == AF_INET) { @@ -757,11 +771,10 @@ static void stream_readable(int fd, void *p, uintptr_t u) { } str_init_len(&s, buf + RTP_BUFFER_HEAD_ROOM, ret); - ret = stream_packet(r, &s, sinp); + ret = stream_packet(sfd, &s, sinp); if (ret == -1) { - mylog(LOG_WARNING, "Write error on RTP socket"); - mutex_unlock(&cs->lock); - call_destroy(cs->call); + ilog(LOG_WARNING, "Write error on RTP socket"); + call_destroy(sfd->call); return; } if (ret == 1) @@ -769,73 +782,15 @@ static void stream_readable(int fd, void *p, uintptr_t u) { } out: - ca = cs->call; - mutex_unlock(&cs->lock); - - if (update) - redis_update(ca, ca->callmaster->conf.redis); -} - - - - - -static int info_parse_func(char **a, void **ret, void *p) { - GHashTable *ih = p; - - g_hash_table_replace(ih, a[0], a[1]); - - return -1; -} - - -static void info_parse(const char *s, GHashTable *ih, struct callmaster *m) { - pcre_multi_match(m->info_re, m->info_ree, s, 2, info_parse_func, ih, NULL); -} - - -static int streams_parse_func(char **a, void **ret, void *p) { - struct stream_input *st; - u_int32_t ip; - int *i; + ca = sfd->call ? : NULL; - i = p; - st = g_slice_alloc0(sizeof(*st)); - - ip = inet_addr(a[0]); - if (ip == -1) - goto fail; - - in4_to_6(&st->stream.ip46, ip); - st->stream.port = atoi(a[1]); - st->stream.num = ++(*i); - st->consecutive_num = 1; - - if (!st->stream.port && strcmp(a[1], "0")) - goto fail; - - *ret = st; - return 0; - -fail: - mylog(LOG_WARNING, "Failed to parse a media stream: %s:%s", a[0], a[1]); - g_slice_free1(sizeof(*st), st); - return -1; -} - - -static void streams_parse(const char *s, struct callmaster *m, GQueue *q) { - int i; - i = 0; - pcre_multi_match(m->streams_re, m->streams_ree, s, 3, streams_parse_func, &i, q); + if (ca && update) + redis_update(ca, sfd->call->callmaster->conf.redis); +done: + log_info_clear(); } -static void streams_free(GQueue *q) { - struct stream_input *s; - while ((s = g_queue_pop_head(q))) - g_slice_free1(sizeof(*s), s); -} @@ -843,66 +798,62 @@ static void streams_free(GQueue *q) { static void call_timer_iterator(void *key, void *val, void *ptr) { struct call *c = val; struct iterator_helper *hlp = ptr; - GList *it; - struct callstream *cs; - int i, j; - struct peer *p; + GSList *it; struct callmaster *cm; unsigned int check; - struct streamrelay *sr; int good = 0; + struct packet_stream *ps; + struct stream_fd *sfd; - mutex_lock(&c->lock); + rwlock_lock_r(&c->master_lock); - if (!c->callstreams->head) + if (!c->streams) goto drop; cm = c->callmaster; - for (it = c->callstreams->head; it; it = it->next) { - cs = it->data; - mutex_lock(&cs->lock); + for (it = c->streams; it; it = it->next) { + ps = it->data; + mutex_lock(&ps->in_lock); - for (i = 0; i < 2; i++) { - p = &cs->peers[i]; - for (j = 0; j < 2; j++) { - sr = &p->rtps[j]; - if (!sr->fd.localport) - continue; - if (hlp->ports[sr->fd.localport]) - abort(); - hlp->ports[sr->fd.localport] = sr; - obj_hold(cs); + sfd = ps->sfd; + if (!sfd || !ps->media) + goto next; - if (good) - continue; + if (ps->media->dtls && sfd->dtls.init && !sfd->dtls.connected) + dtls(ps, NULL, NULL); - check = cm->conf.timeout; - if (!sr->peer_advertised.port) - check = cm->conf.silent_timeout; - else if (is_addr_unspecified(&sr->peer_advertised.ip46)) - check = cm->conf.silent_timeout; + if (hlp->ports[sfd->fd.localport]) + goto next; + hlp->ports[sfd->fd.localport] = sfd; + obj_hold(sfd); - if (poller_now - sr->last < check) - good = 1; - } - } - mutex_unlock(&cs->lock); + if (good) + goto next; + + check = cm->conf.timeout; + if (!ps->media->recv || !ps->sfd) + check = cm->conf.silent_timeout; + + if (poller_now - ps->last_packet < check) + good = 1; + +next: + mutex_unlock(&ps->in_lock); } if (good) goto out; - mylog(LOG_INFO, LOG_PREFIX_C "Closing call branch due to timeout", - LOG_PARAMS_C(c)); + ilog(LOG_INFO, "Closing call branch due to timeout"); drop: - mutex_unlock(&c->lock); + rwlock_unlock_r(&c->master_lock); hlp->del = g_slist_prepend(hlp->del, obj_get(c)); return; out: - mutex_unlock(&c->lock); + rwlock_unlock_r(&c->master_lock); } void xmlrpc_kill_calls(void *p) { @@ -919,7 +870,7 @@ void xmlrpc_kill_calls(void *p) { while (xh->tags) { tag = xh->tags->data; - mylog(LOG_INFO, "Forking child to close call with tag %.*s via XMLRPC", STR_FMT(tag)); + ilog(LOG_INFO, "Forking child to close call with tag "STR_FORMAT" via XMLRPC", STR_FMT(tag)); pid = fork(); if (pid) { @@ -932,7 +883,7 @@ retry: else { if (pid == -1 && errno == EINTR) goto retry; - mylog(LOG_INFO, "XMLRPC child exited with status %i", status); + ilog(LOG_INFO, "XMLRPC child exited with status %i", status); i++; } continue; @@ -950,7 +901,7 @@ retry: close(i); openlog("mediaproxy-ng/child", LOG_PID | LOG_NDELAY, LOG_DAEMON); - mylog(LOG_INFO, "Initiating XMLRPC call for tag %.*s", STR_FMT(tag)); + ilog(LOG_INFO, "Initiating XMLRPC call for tag "STR_FORMAT"", STR_FMT(tag)); alarm(5); @@ -976,7 +927,7 @@ retry: _exit(0); fault: - mylog(LOG_WARNING, "XMLRPC fault occurred: %s", e.fault_string); + ilog(LOG_WARNING, "XMLRPC fault occurred: %s", e.fault_string); _exit(1); } @@ -986,8 +937,8 @@ fault: void kill_calls_timer(GSList *list, struct callmaster *m) { struct call *ca; - GList *csl; - struct callstream *cs; + GSList *csl; + struct call_monologue *cm; const char *url; struct xmlrpc_helper *xh = NULL; @@ -1006,26 +957,28 @@ void kill_calls_timer(GSList *list, struct callmaster *m) { while (list) { ca = list->data; + log_info_call(ca); if (!url) goto destroy; - mutex_lock(&ca->lock); + rwlock_lock_r(&ca->master_lock); - for (csl = ca->callstreams->head; csl; csl = csl->next) { - cs = csl->data; - mutex_lock(&cs->lock); - if (!cs->peers[1].tag.s || !cs->peers[1].tag.len) + for (csl = ca->monologues; csl; csl = csl->next) { + cm = csl->data; + if (!cm->tag.s || !cm->tag.len) goto next; - xh->tags = g_slist_prepend(xh->tags, str_chunk_insert(xh->c, &cs->peers[1].tag)); + xh->tags = g_slist_prepend(xh->tags, str_chunk_insert(xh->c, &cm->tag)); next: - mutex_unlock(&cs->lock); + ; } - mutex_unlock(&ca->lock); + + rwlock_unlock_r(&ca->master_lock); destroy: call_destroy(ca); obj_put(ca); list = g_slist_delete_link(list, list); + log_info_clear(); } if (xh) @@ -1034,13 +987,13 @@ destroy: #define DS(x) do { \ - mutex_lock(&cs->lock); \ - if (ke->stats.x < sr->kstats.x) \ + mutex_lock(&ps->in_lock); \ + if (ke->stats.x < ps->kernel_stats.x) \ d = 0; \ else \ - d = ke->stats.x - sr->kstats.x; \ - sr->stats.x += d; \ - mutex_unlock(&cs->lock); \ + d = ke->stats.x - ps->kernel_stats.x; \ + ps->stats.x += d; \ + mutex_unlock(&ps->in_lock); \ mutex_lock(&m->statspslock); \ m->statsps.x += d; \ mutex_unlock(&m->statspslock); \ @@ -1050,11 +1003,11 @@ static void callmaster_timer(void *ptr) { struct iterator_helper hlp; GList *i; struct mediaproxy_list_entry *ke; - struct streamrelay *sr; + struct packet_stream *ps, *sink; u_int64_t d; struct stats tmpstats; - struct callstream *cs; int j, update; + struct stream_fd *sfd; ZERO(hlp); @@ -1074,53 +1027,69 @@ static void callmaster_timer(void *ptr) { while (i) { ke = i->data; - cs = NULL; - sr = hlp.ports[ke->target.target_port]; - if (!sr) + sfd = hlp.ports[ke->target.target_port]; + if (!sfd) + goto next; + + rwlock_lock_r(&sfd->call->master_lock); + + ps = sfd->stream; + if (!ps || ps->sfd != sfd) { + rwlock_unlock_r(&sfd->call->master_lock); goto next; - cs = sr->up->up; + } DS(packets); DS(bytes); DS(errors); - mutex_lock(&cs->lock); - if (ke->stats.packets != sr->kstats.packets) - sr->last = poller_now; + mutex_lock(&ps->in_lock); + + if (ke->stats.packets != ps->kernel_stats.packets) + ps->last_packet = poller_now; - sr->kstats.packets = ke->stats.packets; - sr->kstats.bytes = ke->stats.bytes; - sr->kstats.errors = ke->stats.errors; + ps->kernel_stats.packets = ke->stats.packets; + ps->kernel_stats.bytes = ke->stats.bytes; + ps->kernel_stats.errors = ke->stats.errors; update = 0; - if (sr->other->crypto.out.crypto_suite - && ke->target.encrypt.last_index - sr->other->crypto.out.last_index > 0x4000) { - sr->other->crypto.out.last_index = ke->target.encrypt.last_index; + sink = packet_stream_sink(ps); + + if (sink) + mutex_lock(&sink->out_lock); + + /* XXX this only works if the kernel module actually gets to see the packets. */ + if (sink && sink->crypto.params.crypto_suite + && ke->target.encrypt.last_index - sink->crypto.last_index > 0x4000) { + sink->crypto.last_index = ke->target.encrypt.last_index; update = 1; } - if (sr->crypto.in.crypto_suite - && ke->target.decrypt.last_index - sr->crypto.in.last_index > 0x4000) { - sr->crypto.in.last_index = ke->target.decrypt.last_index; + if (sfd->crypto.params.crypto_suite + && ke->target.decrypt.last_index - sfd->crypto.last_index > 0x4000) { + sfd->crypto.last_index = ke->target.decrypt.last_index; update = 1; } - mutex_unlock(&cs->lock); + if (sink) + mutex_unlock(&sink->out_lock); + mutex_unlock(&ps->in_lock); + rwlock_unlock_r(&sfd->call->master_lock); if (update) - redis_update(cs->call, m->conf.redis); + redis_update(ps->call, m->conf.redis); next: hlp.ports[ke->target.target_port] = NULL; g_slice_free1(sizeof(*ke), ke); i = g_list_delete_link(i, i); - if (cs) - obj_put(cs); + if (sfd) + obj_put(sfd); } for (j = 0; j < (sizeof(hlp.ports) / sizeof(*hlp.ports)); j++) if (hlp.ports[j]) - obj_put(hlp.ports[j]->up->up); + obj_put(hlp.ports[j]); if (!hlp.del) return; @@ -1155,7 +1124,6 @@ struct callmaster *callmaster_new(struct poller *p) { poller_add_timer(p, callmaster_timer, &c->obj); - obj_put(c); return c; fail: @@ -1240,12 +1208,14 @@ static void release_port(struct udp_fd *r, struct callmaster *m) { r->localport = 0; } -static int get_consecutive_ports(struct udp_fd *array, int array_len, int wanted_start_port, struct call *c) { +static int __get_consecutive_ports(struct udp_fd *array, int array_len, int wanted_start_port, struct call *c) { int i, j, cycle = 0; struct udp_fd *it; u_int16_t port; struct callmaster *m = c->callmaster; + memset(array, -1, sizeof(*array) * array_len); + if (wanted_start_port > 0) port = wanted_start_port; else { @@ -1289,529 +1259,606 @@ release_restart: m->lastport = port; mutex_unlock(&m->portlock); - mylog(LOG_DEBUG, LOG_PREFIX_CI "Opened ports %u..%u for RTP", - LOG_PARAMS_CI(c), array[0].localport, array[array_len - 1].localport); + ilog(LOG_DEBUG, "Opened ports %u..%u for media relay", + array[0].localport, array[array_len - 1].localport); return 0; fail: - mylog(LOG_ERR, LOG_PREFIX_CI "Failed to get RTP port pair", LOG_PARAMS_CI(c)); + ilog(LOG_ERR, "Failed to get %u consecutive UDP ports for relay", + array_len); return -1; } -static void setup_stream_families(struct callstream *cs, struct stream_input *s, int idx) { - int i; +static struct call_media *__get_media(struct call_monologue *ml, GList **it, const struct stream_params *sp) { + struct call_media *med; - for (i = 0; i < 2; i++) { - switch (s->direction[i]) { - case DIR_INTERNAL: - cs->peers[i ^ idx].desired_family = AF_INET; - break; - case DIR_EXTERNAL: - cs->peers[i ^ idx].desired_family = AF_INET6; - break; - default: - break; + /* iterator points to last seen element, or NULL if uninitialized */ + if (!*it) + *it = ml->medias.head; + else + *it = (*it)->next; + + /* possible incremental update, hunt for correct media struct */ + while (*it) { + med = (*it)->data; + if (med->index == sp->index) { + __C_DBG("found existing call_media for stream #%u", sp->index); + return med; } + *it = (*it)->next; } -} -/* caller is responsible for appropriate locking */ -static int setup_peer(struct peer *p, struct stream_input *s, const str *tag) { - struct streamrelay *a, *b; - struct callstream *cs; - struct call *ca; - - cs = p->up; - ca = cs->call; - a = &p->rtps[0]; - b = &p->rtps[1]; + __C_DBG("allocating new call_media for stream #%u", sp->index); + med = g_slice_alloc0(sizeof(*med)); + med->monologue = ml; + med->call = ml->call; + med->index = sp->index; + call_str_cpy(ml->call, &med->type, &sp->type); - if (a->peer_advertised.port != s->stream.port - || !IN6_ARE_ADDR_EQUAL(&a->peer_advertised.ip46, &s->stream.ip46)) - unconfirm_cs(cs); + g_queue_push_tail(&ml->medias, med); + *it = ml->medias.tail; - a->peer = s->stream; - b->peer = s->stream; - if (b->peer.port) - b->peer.port++; - a->peer_advertised = a->peer; - b->peer_advertised = b->peer; - a->rtcp = s->is_rtcp; - b->rtcp = 1; - a->other->rtcp_mux = s->rtcp_mux; - a->other->crypto.in = s->crypto; - b->other->crypto.in = s->crypto; + return med; +} - setup_stream_families(cs, s, p->idx); +static void stream_fd_free(void *p) { + struct stream_fd *f = p; + struct callmaster *m = f->call->callmaster; - call_str_cpy(ca, &p->tag, tag); - p->filled = 1; + release_port(&f->fd, m); + crypto_cleanup(&f->crypto); + dtls_connection_cleanup(&f->dtls); - return 0; + obj_put(f->call); } -/* caller is responsible for appropriate locking */ -static void steal_peer(struct peer *dest, struct peer *src) { - struct streamrelay *r; - int i; +static struct endpoint_map *__get_endpoint_map(struct call_media *media, unsigned int num_ports, + const struct endpoint *ep) +{ + GSList *l; + struct endpoint_map *em; + struct udp_fd fd_arr[16]; + unsigned int i; + struct stream_fd *sfd; + struct call *call = media->call; struct poller_item pi; - struct streamrelay *sr, *srs; - struct call *c; - struct callmaster *m; - struct poller *po; - - ZERO(pi); - r = &src->rtps[0]; - c = src->up->call; - m = c->callmaster; - po = m->poller; - - mylog(LOG_DEBUG, LOG_PREFIX_CI "Re-using existing open RTP port %u", - LOG_PARAMS_CI(c), r->fd.localport); - - unconfirm(dest); - unconfirm(src); - - dest->filled = src->filled; - dest->tag = src->tag; - src->tag = STR_NULL; - dest->desired_family = src->desired_family; - dest->ice_ufrag = src->ice_ufrag; - dest->ice_pwd = src->ice_pwd; - - for (i = 0; i < 2; i++) { - sr = &dest->rtps[i]; - srs = &src->rtps[i]; - - if (sr->fd.fd != -1) { - mylog(LOG_DEBUG, LOG_PREFIX_CI "Closing port %u in favor of re-use", - LOG_PARAMS_CI(c), sr->fd.localport); - poller_del_item(po, sr->fd.fd); - release_port(&sr->fd, m); + struct poller *po = call->callmaster->poller; + + for (l = media->endpoint_maps; l; l = l->next) { + em = l->data; + if (em->wildcard && em->sfds.length >= num_ports) { + __C_DBG("found a wildcard endpoint map%s", ep ? " and filling it in" : ""); + if (ep) { + em->endpoint = *ep; + em->wildcard = 0; + } + return em; } + if (!ep) /* creating wildcard map */ + break; + if (memcmp(&em->endpoint, ep, sizeof(*ep))) + continue; + if (em->sfds.length >= num_ports) + return em; + /* endpoint matches, but not enough ports. flush existing ports + * and allocate a new set. */ + __C_DBG("endpoint matches, doesn't have enough ports"); + g_queue_clear(&em->sfds); + goto alloc; + } + + __C_DBG("allocating new %sendpoint map", ep ? "" : "wildcard "); + em = g_slice_alloc0(sizeof(*em)); + if (ep) + em->endpoint = *ep; + else + em->wildcard = 1; + g_queue_init(&em->sfds); + media->endpoint_maps = g_slist_prepend(media->endpoint_maps, em); - sr->fd = srs->fd; - sr->peer = srs->peer; - sr->peer_advertised = srs->peer_advertised; - sr->stun = srs->stun; - sr->rtcp = srs->rtcp; - sr->rtcp_mux = srs->rtcp_mux; - crypto_context_move(&sr->other->crypto.in, &srs->other->crypto.in); - crypto_context_move(&sr->crypto.out, &srs->crypto.out); +alloc: + if (num_ports > G_N_ELEMENTS(fd_arr)) + return NULL; + if (__get_consecutive_ports(fd_arr, num_ports, 0, media->call)) + return NULL; + + __C_DBG("allocating stream_fds for %u ports", num_ports); + for (i = 0; i < num_ports; i++) { + sfd = obj_alloc0("stream_fd", sizeof(*sfd), stream_fd_free); + sfd->fd = fd_arr[i]; + sfd->call = obj_get(call); + g_queue_push_tail(&em->sfds, sfd); /* not referenced */ + call->stream_fds = g_slist_prepend(call->stream_fds, sfd); /* hand over ref */ + + ZERO(pi); + pi.fd = sfd->fd.fd; + pi.obj = &sfd->obj; + pi.readable = stream_fd_readable; + pi.closed = stream_fd_closed; + poller_add_item(po, &pi); + } - srs->fd.fd = -1; - srs->fd.localport = 0; - ZERO(srs->peer); - ZERO(srs->peer_advertised); + return em; +} - pi.fd = sr->fd.fd; - pi.obj = &sr->up->up->obj; - pi.uintp = i | (dest->idx << 1); - pi.readable = stream_readable; - pi.closed = stream_closed; +static void __assign_stream_fds(struct call_media *media, GList *sfds) { + GList *l; + struct packet_stream *ps; + struct stream_fd *sfd; - poller_update_item(po, &pi); + for (l = media->streams.head; l; l = l->next) { + assert(sfds != NULL); + ps = l->data; + sfd = sfds->data; + ps->sfd = sfd; + sfd->stream = ps; + sfds = sfds->next; } } +static int __wildcard_endpoint_map(struct call_media *media, unsigned int num_ports) { + struct endpoint_map *em; -void callstream_init(struct callstream *s, struct relays_cache *rc) { - int i, j; - struct peer *p; - struct streamrelay *r; - struct udp_fd *relay_AB; - struct poller_item pi; - struct call *c = s->call; - struct poller *po = c->callmaster->poller; + em = __get_endpoint_map(media, num_ports, NULL); + if (!em) + return -1; + + __assign_stream_fds(media, em->sfds.head); + + return 0; +} - ZERO(pi); +static int __num_media_streams(struct call_media *media, unsigned int num_ports) { + struct packet_stream *stream; + struct call *call = media->call; + int ret = 0; - for (i = 0; i < 2; i++) { - p = &s->peers[i]; - relay_AB = rc ? rc->array_ptrs[i] : NULL; + __C_DBG("allocating %i new packet_streams", num_ports - media->streams.length); + while (media->streams.length < num_ports) { + stream = g_slice_alloc0(sizeof(*stream)); + mutex_init(&stream->in_lock); + mutex_init(&stream->out_lock); + stream->call = call; + stream->media = media; + stream->last_packet = poller_now; + g_queue_push_tail(&media->streams, stream); + call->streams = g_slist_prepend(call->streams, stream); + ret++; + } - p->idx = i; - p->up = s; - p->other = &s->peers[i ^ 1]; - p->tag = STR_NULL; + g_queue_truncate(&media->streams, num_ports); - for (j = 0; j < 2; j++) { - r = &p->rtps[j]; + return ret; +} - r->fd.fd = -1; - r->idx = j; - r->up = p; - r->other = &p->other->rtps[j]; - r->last = poller_now; +static void __fill_stream(struct packet_stream *ps, const struct endpoint *ep, unsigned int port_off) { + ps->endpoint = *ep; + ps->endpoint.port += port_off; + /* we SHOULD remember the crypto contexts of previously used endpoints, + * but instead we reset it every time it changes, which is incompatible + * with what we're doing on our side (remembers in the stream_fd) */ + if (memcmp(&ps->advertised_endpoint, &ps->endpoint, sizeof(ps->endpoint))) + crypto_reset(&ps->crypto); + ps->advertised_endpoint = ps->endpoint; + ps->filled = 1; +} - if (relay_AB && relay_AB[j].fd != -1) { - r->fd = relay_AB[j]; +static int __init_stream(struct packet_stream *ps) { + struct call_media *media = ps->media; + struct call *call = ps->call; + int active; - pi.fd = r->fd.fd; - pi.obj = &s->obj; - pi.uintp = (i << 1) | j; - pi.readable = stream_readable; - pi.closed = stream_closed; + if (ps->sfd) { + if (media->sdes) + crypto_init(&ps->sfd->crypto, &media->sdes_in.params); - poller_add_item(po, &pi); + if (media->dtls && !ps->fallback_rtcp) { + active = (ps->filled && media->setup_active); + dtls_connection_init(ps, active, call->dtls_cert); - relay_AB[j].fd = -1; + if (!ps->fingerprint_verified && media->fingerprint.hash_func && ps->dtls_cert) { + if (dtls_verify_cert(ps)) + return -1; } } } - if (rc) - relays_cache_port_used(rc); + if (media->sdes) + crypto_init(&ps->crypto, &media->sdes_out.params); + + return 0; } +static int __init_streams(struct call_media *A, struct call_media *B, const struct stream_params *sp) { + GList *la, *lb; + struct packet_stream *a, *ax, *b; + unsigned int port_off = 0; + + la = A->streams.head; + lb = B->streams.head; + while (la) { + assert(lb != NULL); + a = la->data; + b = lb->data; -static void callstream_free(void *ptr) { - struct callstream *s = ptr; - struct callmaster *m = s->call->callmaster; - int i, j; - struct peer *p; - struct streamrelay *r; + /* RTP */ + a->rtp_sink = b; + a->rtp = 1; - for (i = 0; i < 2; i++) { - p = &s->peers[i]; + if (sp) + __fill_stream(a, &sp->rtp_endpoint, port_off); + if (__init_stream(a)) + return -1; - for (j = 0; j < 2; j++) { - r = &p->rtps[j]; - release_port(&r->fd, m); + /* RTCP */ + if (!B->rtcp_mux) { + lb = lb->next; + assert(lb != NULL); + b = lb->data; } - } - mutex_destroy(&s->lock); - obj_put(s->call); -} -void relays_cache_init(struct relays_cache *c) { - memset(c, -1, sizeof(*c)); - c->relays_open = 0; - c->array_ptrs[0] = c->relays_A; - c->array_ptrs[1] = c->relays_B; -} + if (!A->rtcp_mux) { + a->rtcp_sink = NULL; + a->rtcp = 0; + } + else { + a->rtcp_sink = b; + a->rtcp = 1; + a->implicit_rtcp = 0; + } -int relays_cache_want_ports(struct relays_cache *c, int portA, int portB, struct call *call) { - if (c->relays_open + 2 > G_N_ELEMENTS(c->relays_A)) - return -1; - if (get_consecutive_ports(&c->relays_A[c->relays_open], 2, portA, call)) - return -1; - if (get_consecutive_ports(&c->relays_B[c->relays_open], 2, portB, call)) - return -1; - c->relays_open += 2; - return 0; -} + ax = a; -static int relays_cache_get_ports(struct relays_cache *c, int num, struct call *call) { - num *= 2; - if (c->relays_open >= num) - return 0; + /* if muxing, this is the fallback RTCP port. it also contains the RTCP + * crypto context */ + la = la->next; + assert(la != NULL); + a = la->data; - if (c->relays_open + num > G_N_ELEMENTS(c->relays_A)) - return -1; - if (get_consecutive_ports(&c->relays_A[c->relays_open], num, 0, call)) - return -1; - if (get_consecutive_ports(&c->relays_B[c->relays_open], num, 0, call)) - return -1; - c->relays_open += num; - return 0; -} + a->rtp_sink = NULL; + a->rtcp_sink = b; + a->rtp = 0; + a->rtcp = 1; + a->rtcp_sibling = NULL; + a->fallback_rtcp = ax->rtcp; -static void relays_cache_port_used(struct relays_cache *c) { - if (c->relays_open < 2) - return; + ax->rtcp_sibling = a; - c->relays_open -= 2; - if (c->relays_open) { - memmove(&c->relays_A[0], &c->relays_A[2], c->relays_open * sizeof(*c->relays_A)); - memmove(&c->relays_B[0], &c->relays_B[2], c->relays_open * sizeof(*c->relays_B)); - } - c->relays_A[c->relays_open].fd = -1; - c->relays_B[c->relays_open].fd = -1; - c->relays_A[c->relays_open + 1].fd = -1; - c->relays_B[c->relays_open + 1].fd = -1; -} + if (sp) { + if (!sp->implicit_rtcp) { + __fill_stream(a, &sp->rtcp_endpoint, port_off); + a->implicit_rtcp = 0; + } + else { + __fill_stream(a, &sp->rtp_endpoint, port_off + 1); + a->implicit_rtcp = 1; + } + } + if (__init_stream(a)) + return -1; -void relays_cache_cleanup(struct relays_cache *c, struct callmaster *m) { - int i; + la = la->next; + lb = lb->next; - for (i = 0; i < G_N_ELEMENTS(c->relays_A); i++) { - if (c->relays_A[i].fd == -1) - break; - release_port(&c->relays_A[i], m); + port_off += 2; } - for (i = 0; i < G_N_ELEMENTS(c->relays_B); i++) { - if (c->relays_B[i].fd == -1) - break; - release_port(&c->relays_B[i], m); - } -} -/* called with call->lock held */ -static int call_streams(struct call *c, GQueue *s, const str *tag, enum call_opmode opmode) { - GQueue *q; - GList *i, *l; - struct stream_input *t; - int x; - struct streamrelay *matched_relay; - struct callstream *cs, *cs_o; - struct peer *p, *p2; - int ret = 1; - struct relays_cache relays_cache; + return 0; +} - q = g_queue_new(); /* new callstreams list */ - relays_cache_init(&relays_cache); +/* generates SDES parametes for outgoing SDP, which is our media "out" direction */ +static void __generate_crypto(const struct sdp_ng_flags *flags, struct call_media *this, + struct call_media *other) +{ + struct crypto_params *cp = &this->sdes_out.params, + *cp_in = &this->sdes_in.params; - for (i = s->head; i; i = i->next) { - t = i->data; + if (!flags) + return; - p = NULL; + if (!this->protocol || !this->protocol->srtp) { + cp->crypto_suite = NULL; + this->dtls = 0; + this->sdes = 0; + this->setup_passive = 0; + this->setup_active = 0; + return; + } - /* look for an existing call stream with identical parameters */ - for (l = c->callstreams->head; l; l = l->next) { - cs_o = l->data; - mutex_lock(&cs_o->lock); - for (x = 0; x < 2; x++) { - matched_relay = &cs_o->peers[x].rtps[0]; - if (matched_relay->peer.num != t->stream.num) - continue; - DBG("comparing new ["IP6F"]:%u/%.*s to old ["IP6F"]:%u/%.*s", - IP6P(&t->stream.ip46), t->stream.port, STR_FMT(tag), - IP6P(&matched_relay->peer_advertised.ip46), - matched_relay->peer_advertised.port, STR_FMT(&cs_o->peers[x].tag)); - - if (!IN6_ARE_ADDR_EQUAL(&matched_relay->peer_advertised.ip46, &t->stream.ip46) - && !is_addr_unspecified(&matched_relay->peer_advertised.ip46) - && !is_addr_unspecified(&t->stream.ip46)) - continue; - if (matched_relay->peer_advertised.port != t->stream.port - && matched_relay->peer_advertised.port - && t->stream.port) - continue; - if (str_cmp_str0(&cs_o->peers[x].tag, tag)) - continue; - DBG("found existing call stream to steal"); - unconfirm_cs(cs_o); - goto found; - } - mutex_unlock(&cs_o->lock); + if (flags->opmode == OP_OFFER) { + /* we use this to remember the peer's preference DTLS vs SDES */ + if (!this->initialized) { + this->dtls = 1; + this->sdes = 1; + } + /* we always offer actpass */ + this->setup_passive = 1; + this->setup_active = 1; + } + else { + /* if we can be active, we will, otherwise we'll be passive */ + if (this->setup_active) + this->setup_passive = 0; + + /* if we're answering and doing DTLS, then skip the SDES stuff */ + if (this->dtls) { + this->sdes = 0; + goto skip_sdes; } + } - /* not found */ - matched_relay = NULL; - cs_o = NULL; - l = NULL; - -found: - /* cs_o remains locked if set */ - if (opmode == OP_OFFER) { - DBG("creating new callstream"); - - cs = callstream_new(c, t->stream.num); - mutex_lock(&cs->lock); - - if (!matched_relay) { - /* nothing found to re-use, use new ports */ - relays_cache_get_ports(&relays_cache, t->consecutive_num, c); - callstream_init(cs, &relays_cache); - p = &cs->peers[0]; - setup_peer(p, t, tag); - } - else { - /* re-use, so don't use new ports */ - callstream_init(cs, NULL); - if (matched_relay->up->idx == 0) { - /* request/lookup came in the same order as before */ - steal_peer(&cs->peers[0], &cs_o->peers[0]); - steal_peer(&cs->peers[1], &cs_o->peers[1]); - } - else { - /* reversed request/lookup */ - steal_peer(&cs->peers[0], &cs_o->peers[1]); - steal_peer(&cs->peers[1], &cs_o->peers[0]); - } - setup_stream_families(cs, t, 0); - mutex_unlock(&cs_o->lock); - } + /* for answer case, otherwise defaults to zero */ + this->sdes_out.tag = this->sdes_in.tag; - mutex_unlock(&cs->lock); - g_queue_push_tail(q, cs); /* hand over the ref of new cs */ - ZERO(c->lookup_done); - continue; - } + if (other->sdes_in.params.crypto_suite) { + /* SRTP <> SRTP case, copy from other stream */ + crypto_params_copy(cp, &other->sdes_in.params); + return; + } - /* lookup */ - for (l = c->callstreams->head; l; l = l->next) { - cs = l->data; - if (cs != cs_o) - mutex_lock(&cs->lock); - DBG("hunting for callstream, %i <> %i", cs->num, t->stream.num); - if (cs->num == t->stream.num) - goto got_cs; - if (cs != cs_o) - mutex_unlock(&cs->lock); - } + if (cp->crypto_suite) + return; - mylog(LOG_WARNING, LOG_PREFIX_CI "Got LOOKUP, but no usable callstreams found", - LOG_PARAMS_CI(c)); - if (cs_o) - mutex_unlock(&cs_o->lock); - break; + cp->crypto_suite = cp_in->crypto_suite; + if (!cp->crypto_suite) + cp->crypto_suite = &crypto_suites[0]; + random_string((unsigned char *) cp->master_key, + cp->crypto_suite->master_key_len); + random_string((unsigned char *) cp->master_salt, + cp->crypto_suite->master_salt_len); + /* mki = mki_len = 0 */ -got_cs: - /* cs and cs_o remain locked, and maybe cs == cs_o */ - /* matched_relay == peer[x].rtp[0] of cs_o */ - unconfirm_cs(cs); - g_queue_delete_link(c->callstreams, l); /* steal cs ref */ - p = &cs->peers[1]; - p2 = &cs->peers[0]; - - if (c->lookup_done && matched_relay - && t->stream.port == matched_relay->peer_advertised.port - && IN6_ARE_ADDR_EQUAL(&t->stream.ip46, &matched_relay->peer_advertised.ip46)) - { - /* duplicate/stray lookup. don't do anything except replying with something - we already have. check whether the direction is reversed or not and return - the appropriate details. if no matching stream was found, results are - undefined. */ - DBG("double lookup"); - if (p == matched_relay->up) { - DBG("forward direction"); - goto skip; - } - if (p2 == matched_relay->up) { - DBG("backward direction"); - ret = -1; - goto skip; - } - } +skip_sdes: + ; +} - if (matched_relay && p == matched_relay->up) { - /* best case, nothing to do */ - DBG("case 1"); - /* ... unless we (un)silenced the stream, in which case - we need to copy the new information */ - if (!IN6_ARE_ADDR_EQUAL(&matched_relay->peer_advertised.ip46, &t->stream.ip46) - || matched_relay->peer_advertised.port != t->stream.port) - setup_peer(p, t, tag); - } - else if (matched_relay && cs_o != cs) { - /* found something, but it's linked to a different stream */ - DBG("case 2"); - steal_peer(p, matched_relay->up); - } - else if (!matched_relay && !p->filled) { - /* nothing found to steal, but this end is open */ - DBG("case 3"); - setup_peer(p, t, tag); - } - else { - /* nothing found to steal and this end is used */ - /* need a new call stream after all */ - DBG("case 4"); - if (cs_o && cs_o != cs) - mutex_unlock(&cs_o->lock); - cs_o = cs; - cs = callstream_new(c, t->stream.num); - mutex_lock(&cs->lock); - relays_cache_get_ports(&relays_cache, t->consecutive_num, c); - callstream_init(cs, &relays_cache); - steal_peer(&cs->peers[0], &cs_o->peers[0]); - p = &cs->peers[1]; - setup_peer(p, t, tag); - g_queue_push_tail(c->callstreams, cs_o); /* hand over ref to original cs */ - } +static void __disable_streams(struct call_media *media, unsigned int num_ports) { + GList *l; + struct packet_stream *ps; + + __num_media_streams(media, num_ports); -skip: - time(&c->lookup_done); - g_queue_push_tail(q, p->up); /* hand over ref to cs */ - mutex_unlock(&cs->lock); - if (cs_o && cs_o != cs) - mutex_unlock(&cs_o->lock); + for (l = media->streams.head; l; l = l->next) { + ps = l->data; + ps->sfd = NULL; } +} + +static void __rtcp_mux_logic(const struct sdp_ng_flags *flags, struct call_media *media, + struct call_media *other_media) +{ + if (!flags) + return; - ret = ret * q->length; + if (flags->opmode == OP_ANSWER) { + /* default is to go with the client's choice, unless we were instructed not + * to do that in the offer (see below) */ + if (!media->rtcp_mux_override) + media->rtcp_mux = other_media->rtcp_mux; - if (!q->head) - g_queue_free(q); - else { - if (c->callstreams->head) { - q->tail->next = c->callstreams->head; - c->callstreams->head->prev = q->tail; - q->tail = c->callstreams->tail; - q->length += c->callstreams->length; - c->callstreams->head = c->callstreams->tail = NULL; - c->callstreams->length = 0; - } - g_queue_free(c->callstreams); - c->callstreams = q; + return; } - relays_cache_cleanup(&relays_cache, c->callmaster); - return ret; -} + if (flags->opmode != OP_OFFER) + return; + /* default is to pass through the client's choice, unless our peer is already + * talking rtcp-mux, then we stick to that */ + if (!media->rtcp_mux) + media->rtcp_mux = other_media->rtcp_mux; + /* in our offer, we can override the client's choice */ + if (flags->rtcp_mux_offer) + media->rtcp_mux = 1; + else if (flags->rtcp_mux_demux) + media->rtcp_mux = 0; + + /* we can also control what's going to happen in the answer. it + * depends on what was offered, but by default we go with the other + * client's choice */ + other_media->rtcp_mux_override = 0; + if (other_media->rtcp_mux) { + if (!media->rtcp_mux) { + /* rtcp-mux was offered, but we don't offer it ourselves. + * the answer will not accept rtcp-mux (wasn't offered). + * the default is to accept the offer, unless we want to + * explicitly reject it. */ + other_media->rtcp_mux_override = 1; + if (flags->rtcp_mux_reject) + other_media->rtcp_mux = 0; + } + else { + /* rtcp-mux was offered and we offer it too. default is + * to go with the other client's choice, unless we want to + * either explicitly accept it (possibly demux) or reject + * it (possible reverse demux). */ + if (flags->rtcp_mux_accept) + other_media->rtcp_mux_override = 1; + else if (flags->rtcp_mux_reject) { + other_media->rtcp_mux_override = 1; + other_media->rtcp_mux = 0; + } + } + } + else { + /* rtcp-mux was not offered. we may offer it, but since it wasn't + * offered to us, we must not accept it. */ + other_media->rtcp_mux_override = 1; + } +} +static void __unverify_fingerprint(struct call_media *m) { + GList *l; + struct packet_stream *ps; -static void unconfirm(struct peer *p) { - p->confirmed = 0; - unkernelize(p); -} -static void unconfirm_cs(struct callstream *cs) { - unconfirm(&cs->peers[0]); - unconfirm(&cs->peers[1]); + for (l = m->streams.head; l; l = l->next) { + ps = l->data; + ps->fingerprint_verified = 0; + } } -static void unkernelize(struct peer *p) { - struct streamrelay *r; - int i; +/* called with call->master_lock held in W */ +int monologue_offer_answer(struct call_monologue *monologue, GQueue *streams, + const struct sdp_ng_flags *flags) +{ + struct stream_params *sp; + GList *media_iter, *ml_media, *other_ml_media; + struct call_media *media, *other_media; + unsigned int num_ports; + struct call_monologue *other_ml = monologue->active_dialogue; + struct endpoint_map *em; + + monologue->call->last_signal = poller_now; + + /* we must have a complete dialogue, even though the to-tag (other_ml->tag) + * may not be known yet */ + if (!other_ml) + return -1; - if (!p->kernelized) - return; + ml_media = other_ml_media = NULL; + + for (media_iter = streams->head; media_iter; media_iter = media_iter->next) { + sp = media_iter->data; + __C_DBG("processing media stream #%u", sp->index); + + /* first, check for existance of call_media struct on both sides of + * the dialogue */ + media = __get_media(monologue, &ml_media, sp); + other_media = __get_media(other_ml, &other_ml_media, sp); + /* THIS side corresponds to what's being sent to the recipient of the + * offer/answer. The OTHER side corresponds to what WILL BE sent to the + * offerer or WAS sent to the answerer. */ + + /* deduct protocol from stream parameters received */ + if (!other_media->protocol) { + other_media->protocol = sp->protocol; + if (!other_media->protocol) + other_media->protocol = &transport_protocols[PROTO_RTP_AVP]; + } + /* allow override of outgoing protocol even if we know it already */ + if (flags && flags->transport_protocol) + media->protocol = flags->transport_protocol; + else if (!media->protocol) + media->protocol = other_media->protocol; + + /* copy parameters advertised by the sender of this message */ + other_media->rtcp_mux = sp->rtcp_mux; + crypto_params_copy(&other_media->sdes_in.params, &sp->crypto); + other_media->sdes_in.tag = sp->sdes_tag; + other_media->asymmetric = sp->asymmetric; + + other_media->recv = media->send = sp->send; + other_media->send = media->recv = sp->recv; + + other_media->setup_passive = sp->setup_active; + other_media->setup_active = sp->setup_passive; + if (memcmp(&other_media->fingerprint, &sp->fingerprint, sizeof(sp->fingerprint))) { + __unverify_fingerprint(other_media); + other_media->fingerprint = sp->fingerprint; + } + other_media->dtls = 0; + if ((other_media->setup_passive || other_media->setup_active) + && other_media->fingerprint.hash_func) + other_media->dtls = 1; + + /* control rtcp-mux */ + __rtcp_mux_logic(flags, media, other_media); + + /* SDES and DTLS */ + __generate_crypto(flags, media, other_media); + + /* deduct address family from stream parameters received */ + other_media->desired_family = AF_INET; + if (!IN6_IS_ADDR_V4MAPPED(&sp->rtp_endpoint.ip46)) + other_media->desired_family = AF_INET6; + /* for outgoing SDP, use "direction"/DF or default to IPv4 (?) */ + if (!media->desired_family) + media->desired_family = AF_INET; + if (sp->desired_family) + media->desired_family = sp->desired_family; + else if (sp->direction[1] == DIR_EXTERNAL) + media->desired_family = AF_INET6; + + + /* mark initial offer/answer as done */ + media->initialized = 1; + + + /* determine number of consecutive ports needed locally. + * XXX only do *=2 for RTP streams? */ + num_ports = sp->consecutive_ports; + num_ports *= 2; + + + if (!sp->rtp_endpoint.port || is_addr_unspecified(&sp->rtp_endpoint.ip46)) { + /* Zero port or endpoint: stream has been rejected. + * RFC 3264, chapter 6: + * If a stream is rejected, the offerer and answerer MUST NOT + * generate media (or RTCP packets) for that stream. */ + __disable_streams(media, num_ports); + __disable_streams(other_media, num_ports); + goto init; + } - for (i = 0; i < 2; i++) { - r = &p->rtps[i]; - if (r->no_kernel_support) - continue; - kernel_del_stream(p->up->call->callmaster->conf.kernelfd, r->fd.localport); - r->no_kernel_support = 0; - } - p->kernelized = 0; -} + /* get that many ports for each side, and one packet stream for each port, then + * assign the ports to the streams */ + em = __get_endpoint_map(media, num_ports, &sp->rtp_endpoint); + if (!em) + goto error; + __num_media_streams(media, num_ports); + __assign_stream_fds(media, em->sfds.head); -/* called with callstream->lock held */ -static void kill_callstream(struct callstream *s) { - int i, j; - struct peer *p; - struct streamrelay *r; + if (__num_media_streams(other_media, num_ports)) { + /* new streams created on OTHER side. normally only happens in + * initial offer. create a wildcard endpoint_map to be filled in + * when the answer comes. */ + if (__wildcard_endpoint_map(other_media, num_ports)) + goto error; + } + +init: + if (__init_streams(media, other_media, NULL)) + return -1; + if (__init_streams(other_media, media, sp)) + return -1; + } - for (i = 0; i < 2; i++) { - p = &s->peers[i]; + return 0; - unkernelize(p); +error: + ilog(LOG_ERR, "Error allocating media ports"); + return -1; +} - for (j = 0; j < 2; j++) { - r = &p->rtps[j]; +/* must be called with in_lock held or call->master_lock held in W */ +static void unkernelize(struct packet_stream *p) { + if (!p->kernelized) + return; + if (p->no_kernel_support) + return; - if (r->fd.fd != -1) - poller_del_item(s->call->callmaster->poller, r->fd.fd); + kernel_del_stream(p->call->callmaster->conf.kernelfd, p->sfd->fd.localport); - crypto_cleanup(&r->crypto.in); - crypto_cleanup(&r->crypto.out); - } - } + p->kernelized = 0; } +/* called lock-free, but must hold a reference to the call */ static void call_destroy(struct call *c) { struct callmaster *m = c->callmaster; - struct callstream *s; + struct packet_stream *ps; + struct stream_fd *sfd; + struct poller *p = m->poller; + GSList *l; int ret; + struct call_monologue *ml; + struct call_media *md; + GList *k, *o; + char buf[64]; rwlock_lock_w(&m->hashlock); ret = g_hash_table_remove(m->callhash, &c->callid); @@ -1824,46 +1871,72 @@ static void call_destroy(struct call *c) { redis_delete(c, m->conf.redis); - mutex_lock(&c->lock); - /* at this point, no more callstreams can be added */ - mylog(LOG_INFO, LOG_PREFIX_C "Final packet stats:", LOG_PARAMS_C(c)); - while (c->callstreams->head) { - s = g_queue_pop_head(c->callstreams); - mutex_unlock(&c->lock); - mutex_lock(&s->lock); - mylog(LOG_INFO, LOG_PREFIX_C - "--- " - "side A: " - "RTP[%u] %lu p, %lu b, %lu e; " - "RTCP[%u] %lu p, %lu b, %lu e; " - "side B: " - "RTP[%u] %lu p, %lu b, %lu e; " - "RTCP[%u] %lu p, %lu b, %lu e", - LOG_PARAMS_C(c), - s->peers[0].rtps[0].fd.localport, s->peers[0].rtps[0].stats.packets, - s->peers[0].rtps[0].stats.bytes, s->peers[0].rtps[0].stats.errors, - s->peers[0].rtps[1].fd.localport, s->peers[0].rtps[1].stats.packets, - s->peers[0].rtps[1].stats.bytes, s->peers[0].rtps[1].stats.errors, - s->peers[1].rtps[0].fd.localport, s->peers[1].rtps[0].stats.packets, - s->peers[1].rtps[0].stats.bytes, s->peers[1].rtps[0].stats.errors, - s->peers[1].rtps[1].fd.localport, s->peers[1].rtps[1].stats.packets, - s->peers[1].rtps[1].stats.bytes, s->peers[1].rtps[1].stats.errors); - kill_callstream(s); - mutex_unlock(&s->lock); - obj_put(s); - mutex_lock(&c->lock); - } - mutex_unlock(&c->lock); -} - - - -typedef int (*csa_func)(char *o, struct peer *p, enum stream_address_format format, int *len); - -static int call_stream_address4(char *o, struct peer *p, enum stream_address_format format, int *len) { - struct callstream *cs = p->up; + rwlock_lock_w(&c->master_lock); + /* at this point, no more packet streams can be added */ + + ilog(LOG_INFO, "Final packet stats:"); + + for (l = c->monologues; l; l = l->next) { + ml = l->data; + ilog(LOG_INFO, "--- Tag '"STR_FORMAT"', created " + "%u:%02u ago, in dialogue with '"STR_FORMAT"'", + STR_FMT(&ml->tag), + (unsigned int) (poller_now - ml->created) / 60, + (unsigned int) (poller_now - ml->created) % 60, + ml->active_dialogue ? ml->active_dialogue->tag.len : 6, + ml->active_dialogue ? ml->active_dialogue->tag.s : "(none)"); + + for (k = ml->medias.head; k; k = k->next) { + md = k->data; + + for (o = md->streams.head; o; o = o->next) { + ps = o->data; + + if (ps->fallback_rtcp) + continue; + + smart_ntop_p(buf, &ps->endpoint.ip46, sizeof(buf)); + ilog(LOG_INFO, "------ Media #%u, port %5u <> %15s:%-5hu%s, " + "%lu p, %lu b, %lu e", + md->index, + (unsigned int) (ps->sfd ? ps->sfd->fd.localport : 0), + buf, ps->endpoint.port, + (!ps->rtp && ps->rtcp) ? " (RTCP)" : "", + ps->stats.packets, + ps->stats.bytes, + ps->stats.errors); + } + } + } + + for (l = c->streams; l; l = l->next) { + ps = l->data; + + unkernelize(ps); + ps->sfd = NULL; + crypto_cleanup(&ps->crypto); + + ps->rtp_sink = NULL; + ps->rtcp_sink = NULL; + } + + while (c->stream_fds) { + sfd = c->stream_fds->data; + c->stream_fds = g_slist_delete_link(c->stream_fds, c->stream_fds); + poller_del_item(p, sfd->fd.fd); + obj_put(sfd); + } + + rwlock_unlock_w(&c->master_lock); +} + + + +typedef int (*csa_func)(char *o, struct packet_stream *ps, enum stream_address_format format, int *len); + +static int call_stream_address4(char *o, struct packet_stream *ps, enum stream_address_format format, int *len) { u_int32_t ip4; - struct callmaster *m = cs->call->callmaster; + struct callmaster *m = ps->call->callmaster; int l = 0; if (format == SAF_NG) { @@ -1871,7 +1944,7 @@ static int call_stream_address4(char *o, struct peer *p, enum stream_address_for l = 4; } - ip4 = p->rtps[0].peer_advertised.ip46.s6_addr32[3]; + ip4 = ps->advertised_endpoint.ip46.s6_addr32[3]; if (!ip4) { strcpy(o + l, "0.0.0.0"); l += 7; @@ -1885,8 +1958,8 @@ static int call_stream_address4(char *o, struct peer *p, enum stream_address_for return AF_INET; } -static int call_stream_address6(char *o, struct peer *p, enum stream_address_format format, int *len) { - struct callmaster *m = p->up->call->callmaster; +static int call_stream_address6(char *o, struct packet_stream *ps, enum stream_address_format format, int *len) { + struct callmaster *m = ps->call->callmaster; int l = 0; if (format == SAF_NG) { @@ -1894,7 +1967,7 @@ static int call_stream_address6(char *o, struct peer *p, enum stream_address_for l += 4; } - if (is_addr_unspecified(&p->rtps[0].peer_advertised.ip46)) { + if (is_addr_unspecified(&ps->advertised_endpoint.ip46)) { strcpy(o + l, "::"); l += 2; } @@ -1910,16 +1983,18 @@ static int call_stream_address6(char *o, struct peer *p, enum stream_address_for return AF_INET6; } -static csa_func __call_stream_address(struct peer *p, int variant) { +static csa_func __call_stream_address(struct packet_stream *ps, int variant) { struct callmaster *m; - struct peer *other; + struct packet_stream *sink; + struct call_media *sink_media; csa_func variants[2]; assert(variant >= 0); assert(variant < G_N_ELEMENTS(variants)); - m = p->up->call->callmaster; - other = p->other; + m = ps->call->callmaster; + sink = packet_stream_sink(ps); + sink_media = sink->media; variants[0] = call_stream_address4; variants[1] = call_stream_address6; @@ -1928,11 +2003,11 @@ static csa_func __call_stream_address(struct peer *p, int variant) { variants[1] = NULL; goto done; } - if (other->desired_family == AF_INET) + if (sink_media->desired_family == AF_INET) goto done; - if (other->desired_family == 0 && IN6_IS_ADDR_V4MAPPED(&other->rtps[0].peer.ip46)) + if (sink_media->desired_family == 0 && IN6_IS_ADDR_V4MAPPED(&sink->endpoint.ip46)) goto done; - if (other->desired_family == 0 && is_addr_unspecified(&other->rtps[0].peer_advertised.ip46)) + if (sink_media->desired_family == 0 && is_addr_unspecified(&sink->advertised_endpoint.ip46)) goto done; variants[0] = call_stream_address6; @@ -1943,109 +2018,88 @@ done: return variants[variant]; } -int call_stream_address(char *o, struct peer *p, enum stream_address_format format, int *len) { +int call_stream_address(char *o, struct packet_stream *ps, enum stream_address_format format, int *len) { csa_func f; - f = __call_stream_address(p, 0); - return f(o, p, format, len); + ps = packet_stream_sink(ps); + f = __call_stream_address(ps, 0); + return f(o, ps, format, len); } -int call_stream_address_alt(char *o, struct peer *p, enum stream_address_format format, int *len) { +int call_stream_address_alt(char *o, struct packet_stream *ps, enum stream_address_format format, int *len) { csa_func f; - f = __call_stream_address(p, 1); - return f ? f(o, p, format, len) : -1; -} - -int callmaster_has_ipv6(struct callmaster *m) { - return is_addr_unspecified(&m->conf.ipv6) ? 0 : 1; -} - -static int call_stream_address_gstring(GString *o, struct peer *p, enum stream_address_format format) { - int len, ret; - char buf[64]; /* 64 bytes ought to be enough for anybody */ - - ret = call_stream_address(buf, p, format, &len); - g_string_append_len(o, buf, len); - return ret; + ps = packet_stream_sink(ps); + f = __call_stream_address(ps, 1); + return f ? f(o, ps, format, len) : -1; } +static void __call_free(void *p) { + struct call *c = p; + struct call_monologue *m; + struct call_media *md; + struct packet_stream *ps; + struct endpoint_map *em; + GList *it; -static str *streams_print(GQueue *s, int num, enum call_opmode opmode, const char *prefix, enum stream_address_format format) { - GString *o; - int i, off; - GList *l; - struct callstream *t; - struct streamrelay *x; - int af; - - off = opmode; /* 0 or 1 */ - if (num < 0) - off ^= 1; /* 1 or 0 */ - num = abs(num); - - o = g_string_new_str(); - if (prefix) - g_string_append_printf(o, "%s ", prefix); - - if (!s->head) - goto out; - - t = s->head->data; - mutex_lock(&t->lock); - - if (format == SAF_TCP) - call_stream_address_gstring(o, &t->peers[off], format); - - for (i = 0, l = s->head; i < num && l; i++, l = l->next) { - t = l->data; - x = &t->peers[off].rtps[0]; - g_string_append_printf(o, (format == 1) ? "%u " : " %u", x->fd.localport); - } + call_buffer_free(&c->buffer); + mutex_destroy(&c->buffer_lock); + rwlock_destroy(&c->master_lock); + obj_put(c->dtls_cert); + + while (c->monologues) { + m = c->monologues->data; + c->monologues = g_slist_delete_link(c->monologues, c->monologues); + + g_hash_table_destroy(m->other_tags); + + for (it = m->medias.head; it; it = it->next) { + md = it->data; + g_queue_clear(&md->streams); + while (md->endpoint_maps) { + em = md->endpoint_maps->data; + md->endpoint_maps = g_slist_delete_link(md->endpoint_maps, md->endpoint_maps); + g_queue_clear(&em->sfds); + g_slice_free1(sizeof(*em), em); + } + g_slice_free1(sizeof(*md), md); + } + g_queue_clear(&m->medias); - if (format == SAF_UDP) { - af = call_stream_address_gstring(o, &t->peers[off], format); - g_string_append_printf(o, " %c", (af == AF_INET) ? '4' : '6'); + g_slice_free1(sizeof(*m), m); } - mutex_unlock(&t->lock); + g_hash_table_destroy(c->tags); -out: - g_string_append(o, "\n"); - - return g_string_free_str(o); -} - -static void call_free(void *p) { - struct call *c = p; + while (c->streams) { + ps = c->streams->data; + c->streams = g_slist_delete_link(c->streams, c->streams); + g_slice_free1(sizeof(*ps), ps); + } - g_hash_table_destroy(c->branches); - g_queue_free(c->callstreams); - mutex_destroy(&c->lock); - mutex_destroy(&c->chunk_lock); - g_string_chunk_free(c->chunk); + assert(c->stream_fds == NULL); } static struct call *call_create(const str *callid, struct callmaster *m) { struct call *c; - mylog(LOG_NOTICE, LOG_PREFIX_C "Creating new call", + ilog(LOG_NOTICE, "["STR_FORMAT"] Creating new call", STR_FMT(callid)); /* XXX will spam syslog on recovery from DB */ - c = obj_alloc0("call", sizeof(*c), call_free); + c = obj_alloc0("call", sizeof(*c), __call_free); c->callmaster = m; - c->chunk = g_string_chunk_new(256); - mutex_init(&c->chunk_lock); + mutex_init(&c->buffer_lock); + call_buffer_init(&c->buffer); + rwlock_init(&c->master_lock); + c->tags = g_hash_table_new(str_hash, str_equal); call_str_cpy(c, &c->callid, callid); - c->callstreams = g_queue_new(); c->created = poller_now; - c->branches = g_hash_table_new(str_hash, str_equal); - mutex_init(&c->lock); + c->dtls_cert = dtls_cert(); return c; } -/* returns call with lock held */ -struct call *call_get_or_create(const str *callid, const str *viabranch, struct callmaster *m) { +/* returns call with master_lock held in W */ +struct call *call_get_or_create(const str *callid, struct callmaster *m) { struct call *c; restart: @@ -2063,25 +2117,21 @@ restart: goto restart; } g_hash_table_insert(m->callhash, &c->callid, obj_get(c)); - mutex_lock(&c->lock); + rwlock_lock_w(&c->master_lock); rwlock_unlock_w(&m->hashlock); } else { obj_hold(c); - mutex_lock(&c->lock); + rwlock_lock_w(&c->master_lock); rwlock_unlock_r(&m->hashlock); } - if (viabranch && viabranch->s && viabranch->len - && !g_hash_table_lookup(c->branches, viabranch)) - g_hash_table_insert(c->branches, call_str_dup(c, viabranch), - (void *) 0x1); - + log_info_call(c); return c; } -/* returns call with lock held, or NULL if not found */ -static struct call *call_get(const str *callid, const str *viabranch, struct callmaster *m) { +/* returns call with master_lock held in W, or NULL if not found */ +static struct call *call_get(const str *callid, struct callmaster *m) { struct call *ret; rwlock_lock_r(&m->hashlock); @@ -2091,452 +2141,328 @@ static struct call *call_get(const str *callid, const str *viabranch, struct cal return NULL; } - mutex_lock(&ret->lock); + rwlock_lock_w(&ret->master_lock); obj_hold(ret); rwlock_unlock_r(&m->hashlock); - if (viabranch && viabranch->s && viabranch->len) { - if (!g_hash_table_lookup(ret->branches, viabranch)) - g_hash_table_insert(ret->branches, call_str_dup(ret, viabranch), (void *) 0x1); - } - + log_info_call(ret); return ret; } -/* returns call with lock held, or possibly NULL iff opmode == OP_ANSWER */ -static struct call *call_get_opmode(const str *callid, const str *viabranch, struct callmaster *m, enum call_opmode opmode) { +/* returns call with master_lock held in W, or possibly NULL iff opmode == OP_ANSWER */ +struct call *call_get_opmode(const str *callid, struct callmaster *m, enum call_opmode opmode) { if (opmode == OP_OFFER) - return call_get_or_create(callid, viabranch, m); - return call_get(callid, viabranch, m); + return call_get_or_create(callid, m); + return call_get(callid, m); } -static int addr_parse_udp(struct stream_input *st, char **out) { - u_int32_t ip4; - const char *cp; - char c; - int i; +/* must be called with call->master_lock held in W */ +static struct call_monologue *__monologue_create(struct call *call) { + struct call_monologue *ret; - ZERO(*st); - if (out[RE_UDP_UL_ADDR4] && *out[RE_UDP_UL_ADDR4]) { - ip4 = inet_addr(out[RE_UDP_UL_ADDR4]); - if (ip4 == -1) - goto fail; - in4_to_6(&st->stream.ip46, ip4); - } - else if (out[RE_UDP_UL_ADDR6] && *out[RE_UDP_UL_ADDR6]) { - if (inet_pton(AF_INET6, out[RE_UDP_UL_ADDR6], &st->stream.ip46) != 1) - goto fail; - } - else - goto fail; + __C_DBG("creating new monologue"); + ret = g_slice_alloc0(sizeof(*ret)); - st->stream.port = atoi(out[RE_UDP_UL_PORT]); - if (!st->stream.port && strcmp(out[RE_UDP_UL_PORT], "0")) - goto fail; + ret->call = call; + ret->created = poller_now; + ret->other_tags = g_hash_table_new(str_hash, str_equal); + g_queue_init(&ret->medias); - if (out[RE_UDP_UL_FLAGS] && *out[RE_UDP_UL_FLAGS]) { - i = 0; - for (cp =out[RE_UDP_UL_FLAGS]; *cp && i < 2; cp++) { - c = chrtoupper(*cp); - if (c == 'E') - st->direction[i++] = DIR_EXTERNAL; - else if (c == 'I') - st->direction[i++] = DIR_INTERNAL; - } - } + call->monologues = g_slist_prepend(call->monologues, ret); - if (out[RE_UDP_UL_NUM] && *out[RE_UDP_UL_NUM]) - st->stream.num = atoi(out[RE_UDP_UL_NUM]); - if (!st->stream.num) - st->stream.num = 1; - st->consecutive_num = 1; + return ret; +} - return 0; -fail: - return -1; +/* must be called with call->master_lock held in W */ +static void __monologue_tag(struct call_monologue *ml, const str *tag) { + struct call *call = ml->call; + + __C_DBG("tagging monologue with '"STR_FORMAT"'", STR_FMT(tag)); + call_str_cpy(call, &ml->tag, tag); + g_hash_table_insert(call->tags, &ml->tag, ml); } -static str *call_update_lookup_udp(char **out, struct callmaster *m, enum call_opmode opmode, int tagidx) { - struct call *c; - GQueue q = G_QUEUE_INIT; - struct stream_input st; - int num; - str *ret, callid, viabranch, tag; +static void __stream_unkernelize(struct packet_stream *ps) { + unkernelize(ps); + ps->confirmed = 0; + ps->has_handler = 0; +} - str_init(&callid, out[RE_UDP_UL_CALLID]); - str_init(&viabranch, out[RE_UDP_UL_VIABRANCH]); - str_init(&tag, out[tagidx]); +/* must be called with call->master_lock held in W */ +static void __monologue_unkernelize(struct call_monologue *monologue) { + GList *l, *m; + struct call_media *media; + struct packet_stream *stream; - c = call_get_opmode(&callid, &viabranch, m, opmode); - if (!c) { - mylog(LOG_WARNING, LOG_PREFIX_CI "Got UDP LOOKUP for unknown call-id", - STR_FMT(&callid), STR_FMT(&viabranch)); - return str_sprintf("%s 0 " IPF "\n", out[RE_UDP_COOKIE], IPP(m->conf.ipv4)); - } - log_info = &viabranch; + if (!monologue) + return; - if (addr_parse_udp(&st, out)) - goto fail; + for (l = monologue->medias.head; l; l = l->next) { + media = l->data; + + for (m = media->streams.head; m; m = m->next) { + stream = m->data; + __stream_unkernelize(stream); + if (stream->rtp_sink) + __stream_unkernelize(stream->rtp_sink); + if (stream->rtcp_sink) + __stream_unkernelize(stream->rtcp_sink); + } + } +} - g_queue_push_tail(&q, &st); - num = call_streams(c, &q, &tag, opmode); - g_queue_clear(&q); +/* must be called with call->master_lock held in W */ +static void __monologue_destroy(struct call_monologue *monologue) { + struct call *call; + struct call_monologue *dialogue; + GList *l; - ret = streams_print(c->callstreams, num, opmode, out[RE_UDP_COOKIE], SAF_UDP); - mutex_unlock(&c->lock); + call = monologue->call; - redis_update(c, m->conf.redis); + g_hash_table_remove(call->tags, &monologue->tag); - mylog(LOG_INFO, LOG_PREFIX_CI "Returning to SIP proxy: %.*s", LOG_PARAMS_CI(c), STR_FMT(ret)); - goto out; + l = g_hash_table_get_values(monologue->other_tags); -fail: - mutex_unlock(&c->lock); - mylog(LOG_WARNING, "Failed to parse a media stream: %s/%s:%s", out[RE_UDP_UL_ADDR4], out[RE_UDP_UL_ADDR6], out[RE_UDP_UL_PORT]); - ret = str_sprintf("%s E8\n", out[RE_UDP_COOKIE]); -out: - log_info = NULL; - obj_put(c); - return ret; + while (l) { + dialogue = l->data; + l = g_list_delete_link(l, l); + g_hash_table_remove(dialogue->other_tags, &monologue->tag); + if (!g_hash_table_size(dialogue->other_tags)) + __monologue_destroy(dialogue); + } } -str *call_update_udp(char **out, struct callmaster *m) { - return call_update_lookup_udp(out, m, OP_OFFER, RE_UDP_UL_FROMTAG); -} -str *call_lookup_udp(char **out, struct callmaster *m) { - return call_update_lookup_udp(out, m, OP_ANSWER, RE_UDP_UL_TOTAG); -} +/* must be called with call->master_lock held in W */ +static struct call_monologue *call_get_monologue(struct call *call, const str *fromtag) { + struct call_monologue *ret; -static str *call_request_lookup_tcp(char **out, struct callmaster *m, enum call_opmode opmode, const char *tagstr) { - struct call *c; - GQueue s = G_QUEUE_INIT; - int num; - str *ret = NULL, callid, tag; - GHashTable *infohash; - - str_init(&callid, out[RE_TCP_RL_CALLID]); - infohash = g_hash_table_new(g_str_hash, g_str_equal); - c = call_get_opmode(&callid, NULL, m, opmode); - if (!c) { - mylog(LOG_WARNING, LOG_PREFIX_C "Got LOOKUP for unknown call-id", STR_FMT(&callid)); - goto out; + __C_DBG("getting monologue for tag '"STR_FORMAT"' in call '"STR_FORMAT"'", + STR_FMT(fromtag), STR_FMT(&call->callid)); + ret = g_hash_table_lookup(call->tags, fromtag); + if (ret) { + __C_DBG("found existing monologue"); + __monologue_unkernelize(ret); + __monologue_unkernelize(ret->active_dialogue); + return ret; } - info_parse(out[RE_TCP_RL_INFO], infohash, m); - streams_parse(out[RE_TCP_RL_STREAMS], m, &s); - str_init(&tag, g_hash_table_lookup(infohash, tagstr)); - num = call_streams(c, &s, &tag, opmode); - - ret = streams_print(c->callstreams, num, opmode, NULL, SAF_TCP); - mutex_unlock(&c->lock); + ret = __monologue_create(call); + __monologue_tag(ret, fromtag); + /* we need both sides of the dialogue even in the initial offer, so create + * another monologue without to-tag (to be filled in later) */ + ret->active_dialogue = __monologue_create(call); - streams_free(&s); + return ret; +} - redis_update(c, m->conf.redis); +/* must be called with call->master_lock held in W */ +static struct call_monologue *call_get_dialogue(struct call *call, const str *fromtag, const str *totag) { + struct call_monologue *ft, *ret, *tt; - mylog(LOG_INFO, LOG_PREFIX_CI "Returning to SIP proxy: %.*s", LOG_PARAMS_CI(c), STR_FMT(ret)); - obj_put(c); + __C_DBG("getting dialogue for tags '"STR_FORMAT"'<>'"STR_FORMAT"' in call '"STR_FORMAT"'", + STR_FMT(fromtag), STR_FMT(totag), STR_FMT(&call->callid)); + /* if the to-tag is known already, return that */ + tt = g_hash_table_lookup(call->tags, totag); + if (tt) { + __C_DBG("found existing dialogue"); + __monologue_unkernelize(tt); + __monologue_unkernelize(tt->active_dialogue); -out: - g_hash_table_destroy(infohash); - return ret; -} + /* make sure that the dialogue is actually intact */ + if (!str_cmp_str(fromtag, &tt->active_dialogue->tag)) + return tt; + } -str *call_request_tcp(char **out, struct callmaster *m) { - return call_request_lookup_tcp(out, m, OP_OFFER, "fromtag"); -} -str *call_lookup_tcp(char **out, struct callmaster *m) { - return call_request_lookup_tcp(out, m, OP_ANSWER, "totag"); -} + /* otherwise, at least the from-tag has to be known. it's an error if it isn't */ + ft = g_hash_table_lookup(call->tags, fromtag); + if (!ft) + return NULL; -static int tags_match(const struct peer *p, const struct peer *px, const str *fromtag, const str *totag) { - if (!fromtag || !fromtag->len) - return 1; - if (str_cmp_str(&p->tag, fromtag)) - return 0; - if (!totag || !totag->len) - return 1; - if (str_cmp_str(&px->tag, totag)) - return 0; - return 1; -} + __monologue_unkernelize(ft); -/* cs must be unlocked */ -static int tags_match_cs(struct callstream *cs, const str *fromtag, const str *totag) { - int i; + /* check for a half-complete dialogue and fill in the missing half if possible */ + ret = ft->active_dialogue; + __monologue_unkernelize(ret); - mutex_lock(&cs->lock); + if (!ret->tag.s) + goto tag; - for (i = 0; i < 2; i++) { - if (tags_match(&cs->peers[i], &cs->peers[i ^ 1], fromtag, totag)) { - mutex_unlock(&cs->lock); - return 1; - } + /* we may have seen both tags previously and they just need to be linked up */ + if (tt) { + ret = tt; + goto link; } - mutex_unlock(&cs->lock); - return 0; + /* this is an additional dialogue created from a single from-tag */ + ret = __monologue_create(call); + +tag: + __monologue_tag(ret, totag); +link: + g_hash_table_insert(ret->other_tags, &ft->tag, ft); + g_hash_table_insert(ft->other_tags, &ret->tag, ret); + ret->active_dialogue = ft; + ft->active_dialogue = ret; + + return ret; +} + +struct call_monologue *call_get_mono_dialogue(struct call *call, const str *fromtag, const str *totag) { + if (!totag || !totag->s) /* offer, not answer */ + return call_get_monologue(call, fromtag); + return call_get_dialogue(call, fromtag, totag); } -static int call_delete_branch(struct callmaster *m, const str *callid, const str *branch, + +int call_delete_branch(struct callmaster *m, const str *callid, const str *branch, const str *fromtag, const str *totag, bencode_item_t *output) { struct call *c; - GList *l; + struct call_monologue *ml; int ret; + const str *match_tag; - c = call_get(callid, NULL, m); + c = call_get(callid, m); if (!c) { - mylog(LOG_INFO, LOG_PREFIX_C "Call-ID to delete not found", STR_FMT(callid)); + ilog(LOG_INFO, "["STR_FORMAT"] Call-ID to delete not found", STR_FMT(callid)); goto err; } - log_info = branch; + if (!fromtag || !fromtag->s || !fromtag->len) + goto del_all; - for (l = c->callstreams->head; l; l = l->next) { - if (tags_match_cs(l->data, fromtag, totag)) - goto tag_match; - } + match_tag = (totag && totag->s && totag->len) ? totag : fromtag; - mylog(LOG_INFO, LOG_PREFIX_C "Tags didn't match for delete message, ignoring", LOG_PARAMS_C(c)); - goto err; + ml = g_hash_table_lookup(c->tags, match_tag); + if (!ml) { + ilog(LOG_INFO, "Tag '"STR_FORMAT"' in delete message not found, ignoring", + STR_FMT(match_tag)); + goto err; + } -tag_match: if (output) ng_call_stats(c, fromtag, totag, output); +/* if (branch && branch->len) { if (!g_hash_table_remove(c->branches, branch)) { - mylog(LOG_INFO, LOG_PREFIX_CI "Branch to delete doesn't exist", STR_FMT(&c->callid), STR_FMT(branch)); + ilog(LOG_INFO, LOG_PREFIX_CI "Branch to delete doesn't exist", STR_FMT(&c->callid), STR_FMT(branch)); goto err; } - mylog(LOG_INFO, LOG_PREFIX_CI "Branch deleted", LOG_PARAMS_CI(c)); + ilog(LOG_INFO, LOG_PREFIX_CI "Branch deleted", LOG_PARAMS_CI(c)); if (g_hash_table_size(c->branches)) goto success_unlock; else DBG("no branches left, deleting full call"); } +*/ + + __monologue_destroy(ml); + if (g_hash_table_size(c->tags)) { + ilog(LOG_INFO, "Call branch deleted (other branches still active)"); + goto success_unlock; + } - mutex_unlock(&c->lock); - mylog(LOG_INFO, LOG_PREFIX_C "Deleting full call", LOG_PARAMS_C(c)); +del_all: + rwlock_unlock_w(&c->master_lock); + ilog(LOG_INFO, "Deleting full call"); call_destroy(c); goto success; success_unlock: - mutex_unlock(&c->lock); + rwlock_unlock_w(&c->master_lock); success: ret = 0; goto out; err: if (c) - mutex_unlock(&c->lock); + rwlock_unlock_w(&c->master_lock); ret = -1; goto out; out: - log_info = NULL; if (c) obj_put(c); return ret; } -str *call_delete_udp(char **out, struct callmaster *m) { - str callid, branch, fromtag, totag; - - DBG("got delete for callid '%s' and viabranch '%s'", - out[RE_UDP_DQ_CALLID], out[RE_UDP_DQ_VIABRANCH]); - - str_init(&callid, out[RE_UDP_DQ_CALLID]); - str_init(&branch, out[RE_UDP_DQ_VIABRANCH]); - str_init(&fromtag, out[RE_UDP_DQ_FROMTAG]); - str_init(&totag, out[RE_UDP_DQ_TOTAG]); - - if (call_delete_branch(m, &callid, &branch, &fromtag, &totag, NULL)) - return str_sprintf("%s E8\n", out[RE_UDP_COOKIE]); - - return str_sprintf("%s 0\n", out[RE_UDP_COOKIE]); -} #define SSUM(x) \ - stats->totals[0].x += p->rtps[0].stats.x; \ - stats->totals[1].x += p->rtps[1].stats.x; \ - stats->totals[2].x += px->rtps[0].stats.x; \ - stats->totals[3].x += px->rtps[1].stats.x -/* call must be locked */ -static void stats_query(struct call *call, const str *fromtag, const str *totag, struct call_stats *stats, - void (*cb)(struct peer *, struct peer *, void *), void *arg) + stats->totals[0].x += stream->stats.x; +/* call->master_lock must be held in W */ +/* XXX possibly eliminate W lock, should work with R only */ +void stats_query(struct call *call, const str *fromtag, const str *totag, struct call_stats *stats, + void (*cb)(struct packet_stream *, void *), void *arg) { - GList *l; - struct callstream *cs; - int i; - struct peer *p, *px; + const str *match_tag; + struct call_monologue *ml; + struct call_media *media; + GList *l, *k; + GSList *ml_l = NULL; + struct packet_stream *stream; ZERO(*stats); - for (l = call->callstreams->head; l; l = l->next) { - cs = l->data; - mutex_lock(&cs->lock); + match_tag = (totag && totag->s && totag->len) ? totag : fromtag; + if (!match_tag) { + ml_l = call->monologues; + if (!ml_l) + goto out; + ml = ml_l->data; + } + else + ml = g_hash_table_lookup(call->tags, match_tag); - for (i = 0; i < 2; i++) { - p = &cs->peers[i]; - px = &cs->peers[i ^ 1]; + while (ml) { + l = ml->medias.head; + for (l = ml->medias.head; l; l = l->next) { + media = l->data; - if (p->rtps[0].last > stats->newest) - stats->newest = p->rtps[0].last; - if (p->rtps[1].last > stats->newest) - stats->newest = p->rtps[1].last; + for (k = media->streams.head; k; k = k->next) { + stream = k->data; - if (!tags_match(p, px, fromtag, totag)) - continue; + if (stream->last_packet > stats->newest) + stats->newest = stream->last_packet; - if (cb) - cb(p, px, arg); + if (cb) + cb(stream, arg); - SSUM(packets); - SSUM(bytes); - SSUM(errors); + SSUM(packets); + SSUM(bytes); + SSUM(errors); - break; + /* XXX more meaningful stats */ + } } - mutex_unlock(&cs->lock); - } -} - -str *call_query_udp(char **out, struct callmaster *m) { - struct call *c; - str *ret, callid, fromtag, totag; - struct call_stats stats; - - DBG("got query for callid '%s'", out[RE_UDP_DQ_CALLID]); - - str_init(&callid, out[RE_UDP_DQ_CALLID]); - str_init(&fromtag, out[RE_UDP_DQ_FROMTAG]); - str_init(&totag, out[RE_UDP_DQ_TOTAG]); - - c = call_get_opmode(&callid, NULL, m, OP_OTHER); - if (!c) { - mylog(LOG_INFO, LOG_PREFIX_C "Call-ID to query not found", STR_FMT(&callid)); - goto err; + if (!ml_l) + break; + ml_l = ml_l->next; + if (!ml_l) + break; + ml = ml_l->data; } - stats_query(c, &fromtag, &totag, &stats, NULL, NULL); - - mutex_unlock(&c->lock); - - ret = str_sprintf("%s %lld "UINT64F" "UINT64F" "UINT64F" "UINT64F"\n", out[RE_UDP_COOKIE], - (long long int) m->conf.silent_timeout - (poller_now - stats.newest), - stats.totals[0].packets, stats.totals[1].packets, - stats.totals[2].packets, stats.totals[3].packets); - goto out; - -err: - if (c) - mutex_unlock(&c->lock); - ret = str_sprintf("%s E8\n", out[RE_UDP_COOKIE]); - goto out; - out: - if (c) - obj_put(c); - return ret; -} - -void call_delete_tcp(char **out, struct callmaster *m) { - str callid; - - str_init(&callid, out[RE_TCP_D_CALLID]); - call_delete_branch(m, &callid, NULL, NULL, NULL, NULL); + ; } - -static void call_status_iterator(struct call *c, struct control_stream *s) { - GList *l; - struct callstream *cs; - struct peer *p; - struct streamrelay *r1, *r2; - struct streamrelay *rx1, *rx2; - struct callmaster *m; - char addr1[64], addr2[64], addr3[64]; - - m = c->callmaster; - mutex_lock(&c->lock); - - control_stream_printf(s, "session %.*s - - - - %i\n", - STR_FMT(&c->callid), - (int) (poller_now - c->created)); - - for (l = c->callstreams->head; l; l = l->next) { - cs = l->data; - mutex_lock(&cs->lock); - - p = &cs->peers[0]; - r1 = &p->rtps[0]; - r2 = &cs->peers[1].rtps[0]; - rx1 = &p->rtps[1]; - rx2 = &cs->peers[1].rtps[1]; - - if (r1->fd.fd == -1 || r2->fd.fd == -1) - goto next; - - smart_ntop_p(addr1, &r1->peer.ip46, sizeof(addr1)); - smart_ntop_p(addr2, &r2->peer.ip46, sizeof(addr2)); - if (IN6_IS_ADDR_V4MAPPED(&r1->peer.ip46)) - inet_ntop(AF_INET, &m->conf.ipv4, addr3, sizeof(addr3)); - else - smart_ntop_p(addr3, &m->conf.ipv6, sizeof(addr3)); - - control_stream_printf(s, "stream %s:%u %s:%u %s:%u "UINT64F"/"UINT64F"/"UINT64F" %s %s - %i\n", - addr1, r1->peer.port, - addr2, r2->peer.port, - addr3, r1->fd.localport, - r1->stats.bytes + rx1->stats.bytes, r2->stats.bytes + rx2->stats.bytes, - r1->stats.bytes + rx1->stats.bytes + r2->stats.bytes + rx2->stats.bytes, - "active", - p->codec ? : "unknown", - (int) (poller_now - r1->last)); -next: - mutex_unlock(&cs->lock); - } - mutex_unlock(&c->lock); -} - static void callmaster_get_all_calls_interator(void *key, void *val, void *ptr) { GQueue *q = ptr; - g_queue_push_tail(q, obj_get(val)); + g_queue_push_tail(q, obj_get_o(val)); } -void calls_status_tcp(struct callmaster *m, struct control_stream *s) { - struct stats st; - GQueue q = G_QUEUE_INIT; - struct call *c; - - mutex_lock(&m->statslock); - st = m->stats; - mutex_unlock(&m->statslock); - +void callmaster_get_all_calls(struct callmaster *m, GQueue *q) { rwlock_lock_r(&m->hashlock); g_hash_table_foreach(m->callhash, callmaster_get_all_calls_interator, &q); rwlock_unlock_r(&m->hashlock); - control_stream_printf(s, "proxy %u "UINT64F"/"UINT64F"/"UINT64F"\n", - g_queue_get_length(&q), - st.bytes, st.bytes - st.errors, - st.bytes * 2 - st.errors); - - while (q.head) { - c = g_queue_pop_head(&q); - call_status_iterator(c, s); - obj_put(c); - } } - - static void calls_dump_iterator(void *key, void *val, void *ptr) { struct call *c = val; struct callmaster *m = c->callmaster; @@ -2548,350 +2474,26 @@ void calls_dump_redis(struct callmaster *m) { if (!m->conf.redis) return; - mylog(LOG_DEBUG, "Start dumping all call data to Redis...\n"); + ilog(LOG_DEBUG, "Start dumping all call data to Redis...\n"); redis_wipe_mod(m->conf.redis); g_hash_table_foreach(m->callhash, calls_dump_iterator, NULL); - mylog(LOG_DEBUG, "Finished dumping all call data to Redis\n"); + ilog(LOG_DEBUG, "Finished dumping all call data to Redis\n"); } -void callmaster_config(struct callmaster *m, struct callmaster_config *c) { - m->conf = *c; -} - -struct callstream *callstream_new(struct call *ca, int num) { - struct callstream *s; - - s = obj_alloc0("callstream", sizeof(*s), callstream_free); - s->call = obj_get(ca); - s->num = num; - mutex_init(&s->lock); - - return s; -} - - -enum transport_protocol transport_protocol(const str *s) { +const struct transport_protocol *transport_protocol(const str *s) { int i; if (!s || !s->s) goto out; - for (i = PROTO_UNKNOWN + 1; i < __PROTO_LAST; i++) { - if (strlen(transport_protocol_strings[i]) != s->len) + for (i = 0; i < num_transport_protocols; i++) { + if (strlen(transport_protocols[i].name) != s->len) continue; - if (strncasecmp(transport_protocol_strings[i], s->s, s->len)) + if (strncasecmp(transport_protocols[i].name, s->s, s->len)) continue; - return i; + return &transport_protocols[i]; } out: - return PROTO_UNKNOWN; -} - -static void call_ng_process_flags(struct sdp_ng_flags *out, bencode_item_t *input) { - bencode_item_t *list, *it; - int diridx; - str s; - - ZERO(*out); - - if ((list = bencode_dictionary_get_expect(input, "flags", BENCODE_LIST))) { - for (it = list->child; it; it = it->sibling) { - if (!bencode_strcmp(it, "trust address")) - out->trust_address = 1; - else if (!bencode_strcmp(it, "symmetric")) - out->symmetric = 1; - else if (!bencode_strcmp(it, "asymmetric")) - out->asymmetric = 1; - else if (!bencode_strcmp(it, "trust-address")) - out->trust_address = 1; - } - } - - if ((list = bencode_dictionary_get_expect(input, "replace", BENCODE_LIST))) { - for (it = list->child; it; it = it->sibling) { - if (!bencode_strcmp(it, "origin")) - out->replace_origin = 1; - else if (!bencode_strcmp(it, "session connection")) - out->replace_sess_conn = 1; - else if (!bencode_strcmp(it, "session-connection")) - out->replace_sess_conn = 1; - } - } - - /* XXX convert to a "desired-family" kinda thing instead */ - diridx = 0; - if ((list = bencode_dictionary_get_expect(input, "direction", BENCODE_LIST))) { - for (it = list->child; it && diridx < 2; it = it->sibling) { - if (!bencode_strcmp(it, "internal")) - out->directions[diridx++] = DIR_INTERNAL; - else if (!bencode_strcmp(it, "external")) - out->directions[diridx++] = DIR_EXTERNAL; - } - } - - list = bencode_dictionary_get_expect(input, "received from", BENCODE_LIST); - if (!list) - list = bencode_dictionary_get_expect(input, "received-from", BENCODE_LIST); - if (list && (it = list->child)) { - bencode_get_str(it, &out->received_from_family); - bencode_get_str(it->sibling, &out->received_from_address); - } - - if (bencode_dictionary_get_str(input, "ICE", &s)) { - if (!str_cmp(&s, "remove")) - out->ice_remove = 1; - else if (!str_cmp(&s, "force")) - out->ice_force = 1; - } - - bencode_dictionary_get_str(input, "transport protocol", &out->transport_protocol_str); - if (!out->transport_protocol_str.s) - bencode_dictionary_get_str(input, "transport-protocol", &out->transport_protocol_str); - out->transport_protocol = transport_protocol(&out->transport_protocol_str); - bencode_dictionary_get_str(input, "media address", &out->media_address); -} - -static unsigned int stream_hash(struct stream_input *s) { - unsigned int ret, *p; - - ret = s->stream.port; - p = (void *) &s->stream.ip46; - while (((void *) p) < (((void *) &s->stream.ip46) + sizeof(s->stream.ip46))) { - ret ^= *p; - p++; - } - return ret; -} - -static int stream_equal(struct stream_input *a, struct stream_input *b) { - if (a->stream.port != b->stream.port) - return 0; - if (memcmp(&a->stream.ip46, &b->stream.ip46, sizeof(a->stream.ip46))) - return 0; - return 1; -} - -static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output, enum call_opmode opmode, const char *tagname) { - str sdp, fromtag, viabranch, callid; - char *errstr; - GQueue parsed = G_QUEUE_INIT; - GQueue streams = G_QUEUE_INIT; - struct call *call; - int ret, num; - struct sdp_ng_flags flags; - struct sdp_chopper *chopper; - GHashTable *streamhash; - - if (!bencode_dictionary_get_str(input, "sdp", &sdp)) - return "No SDP body in message"; - if (!bencode_dictionary_get_str(input, "call-id", &callid)) - return "No call-id in message"; - if (!bencode_dictionary_get_str(input, tagname, &fromtag)) - return "No from-tag in message"; - bencode_dictionary_get_str(input, "via-branch", &viabranch); - log_info = &viabranch; - - if (sdp_parse(&sdp, &parsed)) - return "Failed to parse SDP"; - - call_ng_process_flags(&flags, input); - - streamhash = g_hash_table_new((GHashFunc) stream_hash, (GEqualFunc) stream_equal); - errstr = "Incomplete SDP specification"; - if (sdp_streams(&parsed, &streams, streamhash, &flags)) - goto out; - - call = call_get_opmode(&callid, &viabranch, m, opmode); - errstr = "Unknown call-id"; - if (!call) - goto out; - log_info = &viabranch; - - chopper = sdp_chopper_new(&sdp); - bencode_buffer_destroy_add(output->buffer, (free_func_t) sdp_chopper_destroy, chopper); - num = call_streams(call, &streams, &fromtag, opmode); - ret = sdp_replace(chopper, &parsed, call, (num >= 0) ? opmode : (opmode ^ 1), &flags, streamhash); - - mutex_unlock(&call->lock); - redis_update(call, m->conf.redis); - obj_put(call); - - errstr = "Error rewriting SDP"; - if (ret) - goto out; - - bencode_dictionary_add_iovec(output, "sdp", &g_array_index(chopper->iov, struct iovec, 0), - chopper->iov_num, chopper->str_len); - bencode_dictionary_add_string(output, "result", "ok"); - - errstr = NULL; -out: - sdp_free(&parsed); - streams_free(&streams); - g_hash_table_destroy(streamhash); - log_info = NULL; - - return errstr; -} - -const char *call_offer_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) { - return call_offer_answer_ng(input, m, output, OP_OFFER, "from-tag"); -} - -const char *call_answer_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) { - return call_offer_answer_ng(input, m, output, OP_ANSWER, "to-tag"); -} - -const char *call_delete_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) { - str fromtag, totag, viabranch, callid; - bencode_item_t *flags, *it; - int fatal = 0; - - if (!bencode_dictionary_get_str(input, "call-id", &callid)) - return "No call-id in message"; - if (!bencode_dictionary_get_str(input, "from-tag", &fromtag)) - return "No from-tag in message"; - bencode_dictionary_get_str(input, "to-tag", &totag); - bencode_dictionary_get_str(input, "via-branch", &viabranch); - - flags = bencode_dictionary_get_expect(input, "flags", BENCODE_LIST); - if (flags) { - for (it = flags->child; it; it = it->sibling) { - if (!bencode_strcmp(it, "fatal")) - fatal = 1; - } - } - - if (call_delete_branch(m, &callid, &viabranch, &fromtag, &totag, output)) { - if (fatal) - return "Call-ID not found or tags didn't match"; - bencode_dictionary_add_string(output, "warning", "Call-ID not found or tags didn't match"); - } - - bencode_dictionary_add_string(output, "result", "ok"); - return NULL; -} - -void callmaster_exclude_port(struct callmaster *m, u_int16_t p) { - mutex_lock(&m->portlock); - bit_array_set(m->ports_used, p); - mutex_unlock(&m->portlock); -} - -static bencode_item_t *peer_address(bencode_buffer_t *b, struct stream *s) { - bencode_item_t *d; - char buf[64]; - - d = bencode_dictionary(b); - if (IN6_IS_ADDR_V4MAPPED(&s->ip46)) { - bencode_dictionary_add_string(d, "family", "IPv4"); - inet_ntop(AF_INET, &(s->ip46.s6_addr32[3]), buf, sizeof(buf)); - } - else { - bencode_dictionary_add_string(d, "family", "IPv6"); - inet_ntop(AF_INET6, &s->ip46, buf, sizeof(buf)); - } - bencode_dictionary_add_string_dup(d, "address", buf); - bencode_dictionary_add_integer(d, "port", s->port); - - return d; -} - -static bencode_item_t *stats_encode(bencode_buffer_t *b, struct stats *s) { - bencode_item_t *d; - - d = bencode_dictionary(b); - bencode_dictionary_add_integer(d, "packets", s->packets); - bencode_dictionary_add_integer(d, "bytes", s->bytes); - bencode_dictionary_add_integer(d, "errors", s->errors); - return d; -} - -static bencode_item_t *streamrelay_stats(bencode_buffer_t *b, struct streamrelay *r) { - bencode_item_t *d; - - d = bencode_dictionary(b); - - bencode_dictionary_add(d, "counters", stats_encode(b, &r->stats)); - bencode_dictionary_add(d, "peer address", peer_address(b, &r->peer)); - bencode_dictionary_add(d, "advertised peer address", peer_address(b, &r->peer_advertised)); - - bencode_dictionary_add_integer(d, "local port", r->fd.localport); - - return d; -} - -static bencode_item_t *rtp_rtcp_stats(bencode_buffer_t *b, struct stats *rtp, struct stats *rtcp) { - bencode_item_t *s; - s = bencode_dictionary(b); - bencode_dictionary_add(s, "rtp", stats_encode(b, rtp)); - bencode_dictionary_add(s, "rtcp", stats_encode(b, rtcp)); - return s; -} - -static bencode_item_t *peer_stats(bencode_buffer_t *b, struct peer *p) { - bencode_item_t *d, *s; - - d = bencode_dictionary(b); - - bencode_dictionary_add_str_dup(d, "tag", &p->tag); - if (p->codec) - bencode_dictionary_add_string(d, "codec", p->codec); - if (p->kernelized) - bencode_dictionary_add_string(d, "status", "in kernel"); - else if (p->confirmed) - bencode_dictionary_add_string(d, "status", "confirmed peer address"); - else if (p->filled) - bencode_dictionary_add_string(d, "status", "known but unconfirmed peer address"); - else - bencode_dictionary_add_string(d, "status", "unknown peer address"); - - s = bencode_dictionary_add_dictionary(d, "stats"); - bencode_dictionary_add(s, "rtp", streamrelay_stats(b, &p->rtps[0])); - bencode_dictionary_add(s, "rtcp", streamrelay_stats(b, &p->rtps[1])); - - return d; -} - -static void ng_stats_cb(struct peer *p, struct peer *px, void *streams) { - bencode_item_t *stream; - - stream = bencode_list_add_list(streams); - bencode_list_add(stream, peer_stats(stream->buffer, p)); - bencode_list_add(stream, peer_stats(stream->buffer, px)); -} - -/* call must be locked */ -static void ng_call_stats(struct call *call, const str *fromtag, const str *totag, bencode_item_t *output) { - bencode_item_t *streams, *dict; - struct call_stats stats; - - bencode_dictionary_add_integer(output, "created", call->created); - - streams = bencode_dictionary_add_list(output, "streams"); - stats_query(call, fromtag, totag, &stats, ng_stats_cb, streams); - - dict = bencode_dictionary_add_dictionary(output, "totals"); - bencode_dictionary_add(dict, "input", rtp_rtcp_stats(output->buffer, &stats.totals[0], &stats.totals[1])); - bencode_dictionary_add(dict, "output", rtp_rtcp_stats(output->buffer, &stats.totals[2], &stats.totals[3])); -} - -const char *call_query_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) { - str callid, fromtag, totag; - struct call *call; - - if (!bencode_dictionary_get_str(input, "call-id", &callid)) - return "No call-id in message"; - call = call_get_opmode(&callid, NULL, m, OP_OTHER); - if (!call) - return "Unknown call-id"; - bencode_dictionary_get_str(input, "from-tag", &fromtag); - bencode_dictionary_get_str(input, "to-tag", &totag); - - bencode_dictionary_add_string(output, "result", "ok"); - ng_call_stats(call, &fromtag, &totag, output); - mutex_unlock(&call->lock); - return NULL; } diff --git a/daemon/call.h b/daemon/call.h index 88e0e2ad6..ba94793c9 100644 --- a/daemon/call.h +++ b/daemon/call.h @@ -8,63 +8,93 @@ #include #include #include +#include + + + + +enum stream_address_format { + SAF_TCP, + SAF_UDP, + SAF_NG, + SAF_ICE, +}; +enum stream_direction { + DIR_UNKNOWN = 0, + DIR_INTERNAL, + DIR_EXTERNAL, +}; +enum call_opmode { + OP_OFFER = 0, + OP_ANSWER = 1, + OP_OTHER, +}; + +enum transport_protocol_index { + PROTO_RTP_AVP = 0, + PROTO_RTP_SAVP, + PROTO_RTP_AVPF, + PROTO_RTP_SAVPF, + PROTO_UDP_TLS_RTP_SAVP, + PROTO_UDP_TLS_RTP_SAVPF, +}; + +struct call_monologue; + + + -#include "control_tcp.h" -#include "control_udp.h" #include "obj.h" #include "aux.h" #include "bencode.h" #include "str.h" #include "crypto.h" +#include "dtls.h" #define MAX_RTP_PACKET_SIZE 8192 #define RTP_BUFFER_HEAD_ROOM 128 -#define RTP_BUFFER_TAIL_ROOM 256 +#define RTP_BUFFER_TAIL_ROOM 512 #define RTP_BUFFER_SIZE (MAX_RTP_PACKET_SIZE + RTP_BUFFER_HEAD_ROOM + RTP_BUFFER_TAIL_ROOM) +#ifdef __DEBUG +#define __C_DBG(x...) ilog(LOG_DEBUG, x) +#else +#define __C_DBG(x...) ((void)0) +#endif + + struct poller; struct control_stream; -struct peer; -struct callstream; struct call; -struct callmaster; struct redis; struct crypto_suite; +struct mediaproxy_srtp; +struct streamhandler; +struct sdp_ng_flags; +typedef bencode_buffer_t call_buffer_t; +#define call_buffer_alloc bencode_buffer_alloc +#define call_buffer_init bencode_buffer_init +#define call_buffer_free bencode_buffer_free -enum stream_address_format { - SAF_TCP, - SAF_UDP, - SAF_NG, - SAF_ICE, -}; -enum stream_direction { - DIR_UNKNOWN = 0, - DIR_INTERNAL, - DIR_EXTERNAL, -}; -enum call_opmode { - OP_OFFER = 0, - OP_ANSWER = 1, - OP_OTHER, -}; -enum transport_protocol { - PROTO_UNKNOWN = 0, - PROTO_RTP_AVP, - PROTO_RTP_SAVP, - PROTO_RTP_AVPF, - PROTO_RTP_SAVPF, - __PROTO_LAST +struct transport_protocol { + enum transport_protocol_index index; + const char *name; + int srtp:1; + int avpf:1; }; -extern const char *transport_protocol_strings[__PROTO_LAST]; +extern const struct transport_protocol transport_protocols[]; + + + struct stats { u_int64_t packets; @@ -72,91 +102,161 @@ struct stats { u_int64_t errors; }; -struct stream { +struct udp_fd { + int fd; + u_int16_t localport; +}; +struct endpoint { struct in6_addr ip46; u_int16_t port; - int num; - enum transport_protocol protocol; }; -struct stream_input { - struct stream stream; +struct stream_params { + unsigned int index; /* starting with 1 */ + str type; + struct endpoint rtp_endpoint; + struct endpoint rtcp_endpoint; + unsigned int consecutive_ports; + const struct transport_protocol *protocol; + struct crypto_params crypto; + unsigned int sdes_tag; enum stream_direction direction[2]; - int consecutive_num; - struct crypto_context crypto; - int has_rtcp:1; - int is_rtcp:1; + int desired_family; + struct dtls_fingerprint fingerprint; + + int no_rtcp:1; + int implicit_rtcp:1; int rtcp_mux:1; + int send:1; + int recv:1; + int asymmetric:1; + int setup_active:1; + int setup_passive:1; }; -struct udp_fd { - int fd; - u_int16_t localport; + +struct stream_fd { + struct obj obj; + struct udp_fd fd; /* RO */ + struct call *call; /* RO */ + struct packet_stream *stream; /* LOCK: call->master_lock */ + struct crypto_context crypto; /* IN direction, LOCK: stream->in_lock */ + struct dtls_connection dtls; /* LOCK: stream->in_lock */ }; -struct streamrelay; -struct mediaproxy_srtp; -struct streamhandler; +struct endpoint_map { + struct endpoint endpoint; + GQueue sfds; + int wildcard:1; +}; -struct streamrelay { - struct udp_fd fd; - struct stream peer; - struct stream peer_advertised; - unsigned char idx; - struct peer *up; - struct streamrelay *other; - struct stats stats; - struct stats kstats; - time_t last; - const struct streamhandler *handler; - struct crypto_context_pair crypto; - int stun:1; - int rtcp:1; - int rtcp_mux:1; +struct packet_stream { + mutex_t in_lock, + out_lock; + /* Both locks valid only with call->master_lock held in R. + * Preempted by call->master_lock held in W. + * If both in/out are to be locked, in_lock must be locked first. */ + + struct call_media *media; /* RO */ + struct call *call; /* RO */ + + struct stream_fd *sfd; /* LOCK: call->master_lock */ + struct packet_stream *rtp_sink; /* LOCK: call->master_lock */ + struct packet_stream *rtcp_sink; /* LOCK: call->master_lock */ + struct packet_stream *rtcp_sibling; /* LOCK: call->master_lock */ + const struct streamhandler *handler; /* LOCK: in_lock */ + struct endpoint endpoint; /* LOCK: out_lock */ + struct endpoint advertised_endpoint; /* RO */ + struct crypto_context crypto; /* OUT direction, LOCK: out_lock */ + + struct stats stats; /* LOCK: in_lock */ + struct stats kernel_stats; /* LOCK: in_lock */ + time_t last_packet; /* LOCK: in_lock */ + + X509 *dtls_cert; /* LOCK: in_lock */ + + /* in_lock must be held for SETTING these: */ + /* (XXX replace with atomic ops where appropriate) */ + int rtp:1; + int rtcp:1; + int implicit_rtcp:1; + int fallback_rtcp:1; + int stun:1; + int filled:1; + int confirmed:1; + int kernelized:1; int no_kernel_support:1; + int has_handler:1; + int fingerprint_verified:1; }; -struct relays_cache { - struct udp_fd relays_A[16]; - struct udp_fd relays_B[16]; - struct udp_fd *array_ptrs[2]; - int relays_open; -}; -struct peer { - struct streamrelay rtps[2]; - str tag; - char *codec; - unsigned char idx; - struct callstream *up; - struct peer *other; + +/* protected by call->master_lock, except the RO elements */ +struct call_media { + struct call_monologue *monologue; /* RO */ + struct call *call; /* RO */ + + unsigned int index; /* RO */ + str type; /* RO */ + const struct transport_protocol *protocol; int desired_family; + str ice_ufrag; str ice_pwd; - int kernelized:1; - int filled:1; - int confirmed:1; + struct { + struct crypto_params params; + unsigned int tag; + } sdes_in, + sdes_out; + + struct dtls_fingerprint fingerprint; /* as received */ + + GQueue streams; /* normally RTP + RTCP */ + GSList *endpoint_maps; + + int initialized:1; + int asymmetric:1; + int send:1; + int recv:1; + int rtcp_mux:1; + int rtcp_mux_override:1; + int dtls:1; + int sdes:1; + int setup_active:1; + int setup_passive:1; }; -struct callstream { - struct obj obj; - mutex_t lock; - struct peer peers[2]; - struct call *call; - int num; + +/* half a dialogue */ +/* protected by call->master_lock, except the RO elements */ +struct call_monologue { + struct call *call; /* RO */ + + str tag; + time_t created; /* RO */ + GHashTable *other_tags; + struct call_monologue *active_dialogue; + + GQueue medias; }; struct call { struct obj obj; - struct callmaster *callmaster; + struct callmaster *callmaster; /* RO */ - mutex_t chunk_lock; - GStringChunk *chunk; + mutex_t buffer_lock; + call_buffer_t buffer; - mutex_t lock; - GQueue *callstreams; - GHashTable *branches; + /* everything below protected by master_lock */ + rwlock_t master_lock; + GSList *monologues; + GHashTable *tags; + //GHashTable *branches; + GSList *streams; + GSList *stream_fds; + struct dtls_cert *dtls_cert; /* for outgoing */ - str callid; + str callid; char redis_uuid[37]; time_t created; - time_t lookup_done; + time_t last_signal; }; struct callmaster_config { @@ -175,15 +275,40 @@ struct callmaster_config { unsigned char tos; }; -struct callmaster; +struct callmaster { + struct obj obj; + + rwlock_t hashlock; + GHashTable *callhash; + + mutex_t portlock; + u_int16_t lastport; + BIT_ARRAY_DECLARE(ports_used, 0x10000); + + mutex_t statspslock; + struct stats statsps; /* per second stats, running timer */ + mutex_t statslock; + struct stats stats; /* copied from statsps once a second */ + + struct poller *poller; + pcre *info_re; + pcre_extra *info_ree; + pcre *streams_re; + pcre_extra *streams_ree; + + struct callmaster_config conf; +}; + +struct call_stats { + time_t newest; + struct stats totals[4]; /* rtp in, rtcp in, rtp out, rtcp out */ +}; struct callmaster *callmaster_new(struct poller *); -void callmaster_config(struct callmaster *m, struct callmaster_config *c); -void callmaster_exclude_port(struct callmaster *m, u_int16_t p); -int callmaster_has_ipv6(struct callmaster *); void callmaster_msg_mh_src(struct callmaster *, struct msghdr *); +void callmaster_get_all_calls(struct callmaster *m, GQueue *q); str *call_request_tcp(char **, struct callmaster *); @@ -204,39 +329,51 @@ const char *call_query_ng(bencode_item_t *, struct callmaster *, bencode_item_t void calls_dump_redis(struct callmaster *); -struct call *call_get_or_create(const str *callid, const str *viabranch, struct callmaster *m); -struct callstream *callstream_new(struct call *ca, int num); -void callstream_init(struct callstream *s, struct relays_cache *); -void kernelize(struct callstream *c); -int call_stream_address(char *o, struct peer *p, enum stream_address_format format, int *len); -int call_stream_address_alt(char *o, struct peer *p, enum stream_address_format format, int *len); +struct call *call_get_or_create(const str *callid, struct callmaster *m); +struct call *call_get_opmode(const str *callid, struct callmaster *m, enum call_opmode opmode); +struct call_monologue *call_get_mono_dialogue(struct call *call, const str *fromtag, const str *totag); +int monologue_offer_answer(struct call_monologue *monologue, GQueue *streams, const struct sdp_ng_flags *flags); +int call_delete_branch(struct callmaster *m, const str *callid, const str *branch, + const str *fromtag, const str *totag, bencode_item_t *output); +void stats_query(struct call *call, const str *fromtag, const str *totag, struct call_stats *stats, + void (*cb)(struct packet_stream *, void *), void *arg); -void relays_cache_init(struct relays_cache *c); -int relays_cache_want_ports(struct relays_cache *c, int portA, int portB, struct call *call); -void relays_cache_cleanup(struct relays_cache *c, struct callmaster *m); +void kernelize(struct packet_stream *); +int call_stream_address_alt(char *, struct packet_stream *, enum stream_address_format, int *); +int call_stream_address(char *, struct packet_stream *, enum stream_address_format, int *); -enum transport_protocol transport_protocol(const str *s); +const struct transport_protocol *transport_protocol(const str *s); -static inline char *call_strdup(struct call *c, const char *s) { +static inline void *call_malloc(struct call *c, size_t l) { + void *ret; + mutex_lock(&c->buffer_lock); + ret = call_buffer_alloc(&c->buffer, l); + mutex_unlock(&c->buffer_lock); + return ret; +} + +static inline char *call_strdup_len(struct call *c, const char *s, unsigned int len) { char *r; + r = call_malloc(c, len + 1); + memcpy(r, s, len); + r[len] = 0; + return r; +} + +static inline char *call_strdup(struct call *c, const char *s) { if (!s) return NULL; - mutex_lock(&c->chunk_lock); - r = g_string_chunk_insert(c->chunk, s); - mutex_unlock(&c->chunk_lock); - return r; + return call_strdup_len(c, s, strlen(s)); } static inline str *call_str_cpy_len(struct call *c, str *out, const char *in, int len) { if (!in) { *out = STR_NULL; return out; } - mutex_lock(&c->chunk_lock); - out->s = g_string_chunk_insert_len(c->chunk, in, len); - mutex_unlock(&c->chunk_lock); + out->s = call_strdup_len(c, in, len); out->len = len; return out; } @@ -248,9 +385,8 @@ static inline str *call_str_cpy_c(struct call *c, str *out, const char *in) { } static inline str *call_str_dup(struct call *c, const str *in) { str *out; - mutex_lock(&c->chunk_lock); - out = str_chunk_insert(c->chunk, in); - mutex_unlock(&c->chunk_lock); + out = call_malloc(c, sizeof(*out)); + call_str_cpy_len(c, out, in->s, in->len); return out; } static inline str *call_str_init_dup(struct call *c, char *s) { @@ -258,7 +394,22 @@ static inline str *call_str_init_dup(struct call *c, char *s) { str_init(&t, s); return call_str_dup(c, &t); } - +static inline int callmaster_has_ipv6(struct callmaster *m) { + return is_addr_unspecified(&m->conf.ipv6) ? 0 : 1; +} +static inline void callmaster_exclude_port(struct callmaster *m, u_int16_t p) { + /* XXX atomic bit field? */ + mutex_lock(&m->portlock); + bit_array_set(m->ports_used, p); + mutex_unlock(&m->portlock); +} +static inline struct packet_stream *packet_stream_sink(struct packet_stream *ps) { + struct packet_stream *ret; + ret = ps->rtp_sink; + if (!ret) + ret = ps->rtcp_sink; + return ret; +} #endif diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c new file mode 100644 index 000000000..7bc96d65f --- /dev/null +++ b/daemon/call_interfaces.c @@ -0,0 +1,722 @@ +#include "call_interfaces.h" + +#include +#include +#include +#include +#include + +#include "call.h" +#include "aux.h" +#include "log.h" +#include "redis.h" +#include "sdp.h" +#include "bencode.h" +#include "str.h" +#include "control_tcp.h" +#include "control_udp.h" + + + + +static int call_stream_address_gstring(GString *o, struct packet_stream *ps, enum stream_address_format format) { + int len, ret; + char buf[64]; /* 64 bytes ought to be enough for anybody */ + + ret = call_stream_address(buf, ps, format, &len); + g_string_append_len(o, buf, len); + return ret; +} + +static str *streams_print(GQueue *s, int start, int end, const char *prefix, enum stream_address_format format) { + GString *o; + int i, af, port; + GList *l; + struct call_media *media; + struct packet_stream *ps; + + o = g_string_new_str(); + if (prefix) + g_string_append_printf(o, "%s ", prefix); + + for (i = start; i < end; i++) { + for (l = s->head; l; l = l->next) { + media = l->data; + if (media->index == i) + goto found; + } + ilog(LOG_WARNING, "Requested media index %i not found", i); + goto out; + +found: + if (!media->streams.head) { + ilog(LOG_WARNING, "Media has no streams"); + goto out; + } + ps = media->streams.head->data; + + if (format == SAF_TCP) + call_stream_address_gstring(o, ps, format); + + port = ps->sfd ? ps->sfd->fd.localport : 0; + g_string_append_printf(o, (format == 1) ? "%i " : " %i", port); + + if (format == SAF_UDP) { + af = call_stream_address_gstring(o, ps, format); + g_string_append_printf(o, " %c", (af == AF_INET) ? '4' : '6'); + } + + } + +out: + g_string_append(o, "\n"); + + return g_string_free_str(o); +} + +static int addr_parse_udp(struct stream_params *sp, char **out) { + u_int32_t ip4; + const char *cp; + char c; + int i; + + ZERO(*sp); + if (out[RE_UDP_UL_ADDR4] && *out[RE_UDP_UL_ADDR4]) { + ip4 = inet_addr(out[RE_UDP_UL_ADDR4]); + if (ip4 == -1) + goto fail; + in4_to_6(&sp->rtp_endpoint.ip46, ip4); + } + else if (out[RE_UDP_UL_ADDR6] && *out[RE_UDP_UL_ADDR6]) { + if (inet_pton(AF_INET6, out[RE_UDP_UL_ADDR6], &sp->rtp_endpoint.ip46) != 1) + goto fail; + } + else + goto fail; + + sp->rtp_endpoint.port = atoi(out[RE_UDP_UL_PORT]); + if (!sp->rtp_endpoint.port && strcmp(out[RE_UDP_UL_PORT], "0")) + goto fail; + + if (out[RE_UDP_UL_FLAGS] && *out[RE_UDP_UL_FLAGS]) { + i = 0; + for (cp =out[RE_UDP_UL_FLAGS]; *cp && i < 2; cp++) { + c = chrtoupper(*cp); + if (c == 'E') + sp->direction[i++] = DIR_EXTERNAL; + else if (c == 'I') + sp->direction[i++] = DIR_INTERNAL; + } + } + + if (out[RE_UDP_UL_NUM] && *out[RE_UDP_UL_NUM]) + sp->index = atoi(out[RE_UDP_UL_NUM]); + if (!sp->index) + sp->index = 1; + sp->consecutive_ports = 1; + + return 0; +fail: + return -1; +} + +static str *call_update_lookup_udp(char **out, struct callmaster *m, enum call_opmode opmode) { + struct call *c; + struct call_monologue *monologue; + GQueue q = G_QUEUE_INIT; + struct stream_params sp; + str *ret, callid, viabranch, fromtag, totag = STR_NULL; + + str_init(&callid, out[RE_UDP_UL_CALLID]); + str_init(&viabranch, out[RE_UDP_UL_VIABRANCH]); + str_init(&fromtag, out[RE_UDP_UL_FROMTAG]); + if (opmode == OP_ANSWER) + str_init(&totag, out[RE_UDP_UL_TOTAG]); + + c = call_get_opmode(&callid, m, opmode); + if (!c) { + ilog(LOG_WARNING, "["STR_FORMAT"] Got UDP LOOKUP for unknown call-id", + STR_FMT(&callid)); + return str_sprintf("%s 0 " IPF "\n", out[RE_UDP_COOKIE], IPP(m->conf.ipv4)); + } + monologue = call_get_mono_dialogue(c, &fromtag, &totag); + + if (addr_parse_udp(&sp, out)) + goto fail; + + g_queue_push_tail(&q, &sp); + /* XXX return value */ + monologue_offer_answer(monologue, &q, NULL); + g_queue_clear(&q); + + ret = streams_print(&monologue->medias, sp.index, sp.index, out[RE_UDP_COOKIE], SAF_UDP); + rwlock_unlock_w(&c->master_lock); + + redis_update(c, m->conf.redis); + + ilog(LOG_INFO, "Returning to SIP proxy: "STR_FORMAT"", STR_FMT(ret)); + goto out; + +fail: + rwlock_unlock_w(&c->master_lock); + ilog(LOG_WARNING, "Failed to parse a media stream: %s/%s:%s", out[RE_UDP_UL_ADDR4], out[RE_UDP_UL_ADDR6], out[RE_UDP_UL_PORT]); + ret = str_sprintf("%s E8\n", out[RE_UDP_COOKIE]); +out: + obj_put(c); + return ret; +} + +str *call_update_udp(char **out, struct callmaster *m) { + return call_update_lookup_udp(out, m, OP_OFFER); +} +str *call_lookup_udp(char **out, struct callmaster *m) { + return call_update_lookup_udp(out, m, OP_ANSWER); +} + + +static int info_parse_func(char **a, void **ret, void *p) { + GHashTable *ih = p; + + g_hash_table_replace(ih, a[0], a[1]); + + return -1; +} + +static void info_parse(const char *s, GHashTable *ih, struct callmaster *m) { + pcre_multi_match(m->info_re, m->info_ree, s, 2, info_parse_func, ih, NULL); +} + + +static int streams_parse_func(char **a, void **ret, void *p) { + struct stream_params *sp; + u_int32_t ip; + int *i; + + i = p; + sp = g_slice_alloc0(sizeof(*sp)); + + ip = inet_addr(a[0]); + if (ip == -1) + goto fail; + + in4_to_6(&sp->rtp_endpoint.ip46, ip); + sp->rtp_endpoint.port = atoi(a[1]); + sp->index = ++(*i); + sp->consecutive_ports = 1; + + sp->rtcp_endpoint = sp->rtp_endpoint; + sp->rtcp_endpoint.port++; + + if (!sp->rtp_endpoint.port && strcmp(a[1], "0")) + goto fail; + + *ret = sp; + return 0; + +fail: + ilog(LOG_WARNING, "Failed to parse a media stream: %s:%s", a[0], a[1]); + g_slice_free1(sizeof(*sp), sp); + return -1; +} + + +static void streams_parse(const char *s, struct callmaster *m, GQueue *q) { + int i; + i = 0; + pcre_multi_match(m->streams_re, m->streams_ree, s, 3, streams_parse_func, &i, q); +} + +static void streams_free(GQueue *q) { + struct stream_params *s; + + while ((s = g_queue_pop_head(q))) { + if (s->crypto.mki) + free(s->crypto.mki); + g_slice_free1(sizeof(*s), s); + } +} + + + +static str *call_request_lookup_tcp(char **out, struct callmaster *m, enum call_opmode opmode) { + struct call *c; + struct call_monologue *monologue; + GQueue s = G_QUEUE_INIT; + str *ret = NULL, callid, fromtag, totag = STR_NULL; + GHashTable *infohash; + + str_init(&callid, out[RE_TCP_RL_CALLID]); + infohash = g_hash_table_new(g_str_hash, g_str_equal); + c = call_get_opmode(&callid, m, opmode); + if (!c) { + ilog(LOG_WARNING, "["STR_FORMAT"] Got LOOKUP for unknown call-id", STR_FMT(&callid)); + goto out; + } + + info_parse(out[RE_TCP_RL_INFO], infohash, m); + streams_parse(out[RE_TCP_RL_STREAMS], m, &s); + str_init(&fromtag, g_hash_table_lookup(infohash, "fromtag")); + if (!fromtag.s) { + ilog(LOG_WARNING, "No from-tag in message"); + goto out2; + } + if (opmode == OP_ANSWER) { + str_init(&totag, g_hash_table_lookup(infohash, "totag")); + if (!totag.s) { + ilog(LOG_WARNING, "No to-tag in message"); + goto out2; + } + } + + monologue = call_get_mono_dialogue(c, &fromtag, &totag); + /* XXX return value */ + monologue_offer_answer(monologue, &s, NULL); + + ret = streams_print(&monologue->medias, 1, s.length, NULL, SAF_TCP); + rwlock_unlock_w(&c->master_lock); + +out2: + streams_free(&s); + + redis_update(c, m->conf.redis); + + ilog(LOG_INFO, "Returning to SIP proxy: "STR_FORMAT"", STR_FMT0(ret)); + obj_put(c); + +out: + g_hash_table_destroy(infohash); + return ret; +} + +str *call_request_tcp(char **out, struct callmaster *m) { + return call_request_lookup_tcp(out, m, OP_OFFER); +} +str *call_lookup_tcp(char **out, struct callmaster *m) { + return call_request_lookup_tcp(out, m, OP_ANSWER); +} + +str *call_delete_udp(char **out, struct callmaster *m) { + str callid, branch, fromtag, totag; + + __C_DBG("got delete for callid '%s' and viabranch '%s'", + out[RE_UDP_DQ_CALLID], out[RE_UDP_DQ_VIABRANCH]); + + str_init(&callid, out[RE_UDP_DQ_CALLID]); + str_init(&branch, out[RE_UDP_DQ_VIABRANCH]); + str_init(&fromtag, out[RE_UDP_DQ_FROMTAG]); + str_init(&totag, out[RE_UDP_DQ_TOTAG]); + + if (call_delete_branch(m, &callid, &branch, &fromtag, &totag, NULL)) + return str_sprintf("%s E8\n", out[RE_UDP_COOKIE]); + + return str_sprintf("%s 0\n", out[RE_UDP_COOKIE]); +} +str *call_query_udp(char **out, struct callmaster *m) { + struct call *c; + str *ret, callid, fromtag, totag; + struct call_stats stats; + + __C_DBG("got query for callid '%s'", out[RE_UDP_DQ_CALLID]); + + str_init(&callid, out[RE_UDP_DQ_CALLID]); + str_init(&fromtag, out[RE_UDP_DQ_FROMTAG]); + str_init(&totag, out[RE_UDP_DQ_TOTAG]); + + c = call_get_opmode(&callid, m, OP_OTHER); + if (!c) { + ilog(LOG_INFO, "["STR_FORMAT"] Call-ID to query not found", STR_FMT(&callid)); + goto err; + } + + stats_query(c, &fromtag, &totag, &stats, NULL, NULL); + + rwlock_unlock_w(&c->master_lock); + + ret = str_sprintf("%s %lld "UINT64F" "UINT64F" "UINT64F" "UINT64F"\n", out[RE_UDP_COOKIE], + (long long int) m->conf.silent_timeout - (poller_now - stats.newest), + stats.totals[0].packets, stats.totals[1].packets, + stats.totals[2].packets, stats.totals[3].packets); + goto out; + +err: + if (c) + rwlock_unlock_w(&c->master_lock); + ret = str_sprintf("%s E8\n", out[RE_UDP_COOKIE]); + goto out; + +out: + if (c) + obj_put(c); + return ret; +} + +void call_delete_tcp(char **out, struct callmaster *m) { + str callid; + + str_init(&callid, out[RE_TCP_D_CALLID]); + call_delete_branch(m, &callid, NULL, NULL, NULL, NULL); +} + +static void call_status_iterator(struct call *c, struct control_stream *s) { +// GList *l; +// struct callstream *cs; +// struct peer *p; +// struct streamrelay *r1, *r2; +// struct streamrelay *rx1, *rx2; +// struct callmaster *m; +// char addr1[64], addr2[64], addr3[64]; + +// m = c->callmaster; +// mutex_lock(&c->master_lock); + + control_stream_printf(s, "session "STR_FORMAT" - - - - %i\n", + STR_FMT(&c->callid), + (int) (poller_now - c->created)); + + /* XXX restore function */ + +// mutex_unlock(&c->master_lock); +} + +void calls_status_tcp(struct callmaster *m, struct control_stream *s) { + struct stats st; + GQueue q = G_QUEUE_INIT; + struct call *c; + + mutex_lock(&m->statslock); + st = m->stats; + mutex_unlock(&m->statslock); + + callmaster_get_all_calls(m, &q); + + control_stream_printf(s, "proxy %u "UINT64F"/"UINT64F"/"UINT64F"\n", + g_queue_get_length(&q), + st.bytes, st.bytes - st.errors, + st.bytes * 2 - st.errors); + + while (q.head) { + c = g_queue_pop_head(&q); + call_status_iterator(c, s); + obj_put(c); + } +} + + + + + + + + +static void call_ng_process_flags(struct sdp_ng_flags *out, bencode_item_t *input) { + bencode_item_t *list, *it; + int diridx; + str s; + + ZERO(*out); + + if ((list = bencode_dictionary_get_expect(input, "flags", BENCODE_LIST))) { + for (it = list->child; it; it = it->sibling) { + if (!bencode_strcmp(it, "trust address")) + out->trust_address = 1; + else if (!bencode_strcmp(it, "symmetric")) + out->symmetric = 1; + else if (!bencode_strcmp(it, "asymmetric")) + out->asymmetric = 1; + else if (!bencode_strcmp(it, "trust-address")) + out->trust_address = 1; + } + } + + if ((list = bencode_dictionary_get_expect(input, "replace", BENCODE_LIST))) { + for (it = list->child; it; it = it->sibling) { + if (!bencode_strcmp(it, "origin")) + out->replace_origin = 1; + else if (!bencode_strcmp(it, "session connection")) + out->replace_sess_conn = 1; + else if (!bencode_strcmp(it, "session-connection")) + out->replace_sess_conn = 1; + } + } + + diridx = 0; + if ((list = bencode_dictionary_get_expect(input, "direction", BENCODE_LIST))) { + for (it = list->child; it && diridx < 2; it = it->sibling) { + if (!bencode_strcmp(it, "internal")) + out->directions[diridx++] = DIR_INTERNAL; + else if (!bencode_strcmp(it, "external")) + out->directions[diridx++] = DIR_EXTERNAL; + } + } + + list = bencode_dictionary_get_expect(input, "received from", BENCODE_LIST); + if (!list) + list = bencode_dictionary_get_expect(input, "received-from", BENCODE_LIST); + if (list && (it = list->child)) { + bencode_get_str(it, &out->received_from_family); + bencode_get_str(it->sibling, &out->received_from_address); + } + + if (bencode_dictionary_get_str(input, "ICE", &s)) { + if (!str_cmp(&s, "remove")) + out->ice_remove = 1; + else if (!str_cmp(&s, "force")) + out->ice_force = 1; + } + + if ((list = bencode_dictionary_get_expect(input, "rtcp-mux", BENCODE_LIST))) { + for (it = list->child; it; it = it->sibling) { + if (!bencode_strcmp(it, "offer")) + out->rtcp_mux_offer = 1; + else if (!bencode_strcmp(it, "demux")) + out->rtcp_mux_demux = 1; + else if (!bencode_strcmp(it, "accept")) + out->rtcp_mux_accept = 1; + else if (!bencode_strcmp(it, "reject")) + out->rtcp_mux_reject = 1; + } + } + + bencode_dictionary_get_str(input, "transport protocol", &out->transport_protocol_str); + if (!out->transport_protocol_str.s) + bencode_dictionary_get_str(input, "transport-protocol", &out->transport_protocol_str); + out->transport_protocol = transport_protocol(&out->transport_protocol_str); + bencode_dictionary_get_str(input, "media address", &out->media_address); + if (bencode_dictionary_get_str(input, "address family", &out->address_family_str)) + out->address_family = address_family(&out->address_family_str); +} + +static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster *m, + bencode_item_t *output, enum call_opmode opmode) +{ + str sdp, fromtag, totag = STR_NULL, callid; + char *errstr; + GQueue parsed = G_QUEUE_INIT; + GQueue streams = G_QUEUE_INIT; + struct call *call; + struct call_monologue *monologue; + int ret; + struct sdp_ng_flags flags; + struct sdp_chopper *chopper; + + if (!bencode_dictionary_get_str(input, "sdp", &sdp)) + return "No SDP body in message"; + if (!bencode_dictionary_get_str(input, "call-id", &callid)) + return "No call-id in message"; + if (!bencode_dictionary_get_str(input, "from-tag", &fromtag)) + return "No from-tag in message"; + if (opmode == OP_ANSWER) { + if (!bencode_dictionary_get_str(input, "to-tag", &totag)) + return "No to-tag in message"; + } + //bencode_dictionary_get_str(input, "via-branch", &viabranch); + + if (sdp_parse(&sdp, &parsed)) + return "Failed to parse SDP"; + + call_ng_process_flags(&flags, input); + flags.opmode = opmode; + + errstr = "Incomplete SDP specification"; + if (sdp_streams(&parsed, &streams, &flags)) + goto out; + + call = call_get_opmode(&callid, m, opmode); + errstr = "Unknown call-id"; + if (!call) + goto out; + + monologue = call_get_mono_dialogue(call, &fromtag, &totag); + + chopper = sdp_chopper_new(&sdp); + bencode_buffer_destroy_add(output->buffer, (free_func_t) sdp_chopper_destroy, chopper); + /* XXX return value */ + monologue_offer_answer(monologue, &streams, &flags); + ret = sdp_replace(chopper, &parsed, monologue, &flags); + + rwlock_unlock_w(&call->master_lock); + redis_update(call, m->conf.redis); + obj_put(call); + + errstr = "Error rewriting SDP"; + if (ret) + goto out; + + bencode_dictionary_add_iovec(output, "sdp", &g_array_index(chopper->iov, struct iovec, 0), + chopper->iov_num, chopper->str_len); + bencode_dictionary_add_string(output, "result", "ok"); + + errstr = NULL; +out: + sdp_free(&parsed); + streams_free(&streams); + + return errstr; +} + +const char *call_offer_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) { + return call_offer_answer_ng(input, m, output, OP_OFFER); +} + +const char *call_answer_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) { + return call_offer_answer_ng(input, m, output, OP_ANSWER); +} + +const char *call_delete_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) { + str fromtag, totag, viabranch, callid; + bencode_item_t *flags, *it; + int fatal = 0; + + if (!bencode_dictionary_get_str(input, "call-id", &callid)) + return "No call-id in message"; + if (!bencode_dictionary_get_str(input, "from-tag", &fromtag)) + return "No from-tag in message"; + bencode_dictionary_get_str(input, "to-tag", &totag); + bencode_dictionary_get_str(input, "via-branch", &viabranch); + + flags = bencode_dictionary_get_expect(input, "flags", BENCODE_LIST); + if (flags) { + for (it = flags->child; it; it = it->sibling) { + if (!bencode_strcmp(it, "fatal")) + fatal = 1; + } + } + + if (call_delete_branch(m, &callid, &viabranch, &fromtag, &totag, output)) { + if (fatal) + return "Call-ID not found or tags didn't match"; + bencode_dictionary_add_string(output, "warning", "Call-ID not found or tags didn't match"); + } + + bencode_dictionary_add_string(output, "result", "ok"); + return NULL; +} + +#if 0 +static bencode_item_t *peer_address(bencode_buffer_t *b, struct stream *s) { + bencode_item_t *d; + char buf[64]; + + d = bencode_dictionary(b); + if (IN6_IS_ADDR_V4MAPPED(&s->ip46)) { + bencode_dictionary_add_string(d, "family", "IPv4"); + inet_ntop(AF_INET, &(s->ip46.s6_addr32[3]), buf, sizeof(buf)); + } + else { + bencode_dictionary_add_string(d, "family", "IPv6"); + inet_ntop(AF_INET6, &s->ip46, buf, sizeof(buf)); + } + bencode_dictionary_add_string_dup(d, "address", buf); + bencode_dictionary_add_integer(d, "port", s->port); + + return d; +} +#endif + +#if 0 +static bencode_item_t *stats_encode(bencode_buffer_t *b, struct stats *s) { + bencode_item_t *d; + + d = bencode_dictionary(b); + bencode_dictionary_add_integer(d, "packets", s->packets); + bencode_dictionary_add_integer(d, "bytes", s->bytes); + bencode_dictionary_add_integer(d, "errors", s->errors); + return d; +} +#endif + +#if 0 +static bencode_item_t *streamrelay_stats(bencode_buffer_t *b, struct packet_stream *ps) { + bencode_item_t *d; + + d = bencode_dictionary(b); + + // XXX + //bencode_dictionary_add(d, "counters", stats_encode(b, &r->stats)); + //bencode_dictionary_add(d, "peer address", peer_address(b, &r->peer)); + //bencode_dictionary_add(d, "advertised peer address", peer_address(b, &r->peer_advertised)); + + bencode_dictionary_add_integer(d, "local port", ps->fd.localport); + + return d; +} +#endif + +#if 0 +static bencode_item_t *rtp_rtcp_stats(bencode_buffer_t *b, struct stats *rtp, struct stats *rtcp) { + bencode_item_t *s; + s = bencode_dictionary(b); + bencode_dictionary_add(s, "rtp", stats_encode(b, rtp)); + bencode_dictionary_add(s, "rtcp", stats_encode(b, rtcp)); + return s; +} +#endif + +#if 0 +XXX +static bencode_item_t *peer_stats(bencode_buffer_t *b, struct peer *p) { + bencode_item_t *d, *s; + + d = bencode_dictionary(b); + + bencode_dictionary_add_str_dup(d, "tag", &p->tag); + if (p->codec) + bencode_dictionary_add_string(d, "codec", p->codec); + if (p->kernelized) + bencode_dictionary_add_string(d, "status", "in kernel"); + else if (p->confirmed) + bencode_dictionary_add_string(d, "status", "confirmed peer address"); + else if (p->filled) + bencode_dictionary_add_string(d, "status", "known but unconfirmed peer address"); + else + bencode_dictionary_add_string(d, "status", "unknown peer address"); + + s = bencode_dictionary_add_dictionary(d, "stats"); + bencode_dictionary_add(s, "rtp", streamrelay_stats(b, &p->rtps[0])); + bencode_dictionary_add(s, "rtcp", streamrelay_stats(b, &p->rtps[1])); + + return d; +} + +static void ng_stats_cb(struct peer *p, struct peer *px, void *streams) { + bencode_item_t *stream; + + stream = bencode_list_add_list(streams); + bencode_list_add(stream, peer_stats(stream->buffer, p)); + bencode_list_add(stream, peer_stats(stream->buffer, px)); +} +#endif + +/* call must be locked */ +void ng_call_stats(struct call *call, const str *fromtag, const str *totag, bencode_item_t *output) { + //bencode_item_t *streams, *dict; +// struct call_stats stats; + +// bencode_dictionary_add_integer(output, "created", call->created); + + //streams = bencode_dictionary_add_list(output, "streams"); + //stats_query(call, fromtag, totag, &stats, ng_stats_cb, streams); XXX + +// dict = bencode_dictionary_add_dictionary(output, "totals"); +// bencode_dictionary_add(dict, "input", rtp_rtcp_stats(output->buffer, &stats.totals[0], &stats.totals[1])); +// bencode_dictionary_add(dict, "output", rtp_rtcp_stats(output->buffer, &stats.totals[2], &stats.totals[3])); +} + +const char *call_query_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) { + str callid, fromtag, totag; + struct call *call; + + if (!bencode_dictionary_get_str(input, "call-id", &callid)) + return "No call-id in message"; + call = call_get_opmode(&callid, m, OP_OTHER); + if (!call) + return "Unknown call-id"; + bencode_dictionary_get_str(input, "from-tag", &fromtag); + bencode_dictionary_get_str(input, "to-tag", &totag); + + bencode_dictionary_add_string(output, "result", "ok"); + ng_call_stats(call, &fromtag, &totag, output); + rwlock_unlock_w(&call->master_lock); + + return NULL; +} diff --git a/daemon/call_interfaces.h b/daemon/call_interfaces.h new file mode 100644 index 000000000..85bba9a47 --- /dev/null +++ b/daemon/call_interfaces.h @@ -0,0 +1,18 @@ +#ifndef _CALL_INTERFACES_H_ +#define _CALL_INTERFACES_H_ + + + +#include "str.h" +#include "bencode.h" + + + +struct call; + + + +void ng_call_stats(struct call *call, const str *fromtag, const str *totag, bencode_item_t *output); + + +#endif diff --git a/daemon/control_ng.c b/daemon/control_ng.c index 86240d038..2c22a0536 100644 --- a/daemon/control_ng.c +++ b/daemon/control_ng.c @@ -67,7 +67,7 @@ static void control_ng_incoming(struct obj *obj, str *buf, struct sockaddr_in6 * str_chr_str(&data, buf, ' '); if (!data.s || data.s == buf->s) { - mylog(LOG_WARNING, "Received invalid data on NG port (no cookie) from %s: %.*s", addr, STR_FMT(buf)); + ilog(LOG_WARNING, "Received invalid data on NG port (no cookie) from %s: "STR_FORMAT, addr, STR_FMT(buf)); return; } @@ -85,7 +85,7 @@ static void control_ng_incoming(struct obj *obj, str *buf, struct sockaddr_in6 * to_send = cookie_cache_lookup(&c->cookie_cache, &cookie); if (to_send) { - mylog(LOG_INFO, "Detected command from %s as a duplicate", addr); + ilog(LOG_INFO, "Detected command from %s as a duplicate", addr); resp = NULL; goto send_only; } @@ -103,7 +103,7 @@ static void control_ng_incoming(struct obj *obj, str *buf, struct sockaddr_in6 * log_str = g_string_sized_new(256); g_string_append_printf(log_str, "Got valid command from %s: %.*s - ", addr, STR_FMT(&cmd)); pretty_print(dict, log_str); - mylog(LOG_INFO, "%.*s", (int) log_str->len, log_str->str); + ilog(LOG_INFO, "%.*s", (int) log_str->len, log_str->str); g_string_free(log_str, TRUE); errstr = NULL; @@ -126,7 +126,7 @@ static void control_ng_incoming(struct obj *obj, str *buf, struct sockaddr_in6 * goto send_resp; err_send: - mylog(LOG_WARNING, "Protocol error in packet from %s: %s [%.*s]", addr, errstr, STR_FMT(&data)); + ilog(LOG_WARNING, "Protocol error in packet from %s: %s ["STR_FORMAT"]", addr, errstr, STR_FMT(&data)); bencode_dictionary_add_string(resp, "result", "error"); bencode_dictionary_add_string(resp, "error-reason", errstr); goto send_resp; @@ -136,7 +136,7 @@ send_resp: to_send = &reply; send_only: - mylog(LOG_INFO, "Returning to SIP proxy: %.*s", STR_FMT(to_send)); + ilog(LOG_INFO, "Returning to SIP proxy: "STR_FORMAT, STR_FMT(to_send)); ZERO(mh); mh.msg_name = sin; @@ -162,6 +162,7 @@ send_only: out: bencode_buffer_free(&bencbuf); + log_info_clear(); } diff --git a/daemon/control_tcp.c b/daemon/control_tcp.c index 0908aebcb..f29c417da 100644 --- a/daemon/control_tcp.c +++ b/daemon/control_tcp.c @@ -56,7 +56,7 @@ static void control_stream_closed(int fd, void *p, uintptr_t u) { struct control_tcp *c; GList *l = NULL; - mylog(LOG_INFO, "Control connection from " DF " closed", DP(s->inaddr)); + ilog(LOG_INFO, "Control connection from " DF " closed", DP(s->inaddr)); c = s->control; @@ -105,11 +105,11 @@ static int control_stream_parse(struct control_stream *s, char *line) { ret = pcre_exec(c->parse_re, c->parse_ree, line, strlen(line), 0, 0, ovec, G_N_ELEMENTS(ovec)); if (ret <= 0) { - mylog(LOG_WARNING, "Unable to parse command line from " DF ": %s", DP(s->inaddr), line); + ilog(LOG_WARNING, "Unable to parse command line from " DF ": %s", DP(s->inaddr), line); return -1; } - mylog(LOG_INFO, "Got valid command from " DF ": %s", DP(s->inaddr), line); + ilog(LOG_INFO, "Got valid command from " DF ": %s", DP(s->inaddr), line); pcre_get_substring_list(line, ovec, ret, (const char ***) &out); @@ -137,6 +137,7 @@ static int control_stream_parse(struct control_stream *s, char *line) { } pcre_free(out); + log_info_clear(); return -1; } @@ -166,7 +167,7 @@ static void control_stream_readable(int fd, void *p, uintptr_t u) { while ((line = streambuf_getline(s->inbuf))) { mutex_unlock(&s->lock); - mylog(LOG_DEBUG, "Got control line from " DF ": %s", DP(s->inaddr), line); + ilog(LOG_DEBUG, "Got control line from " DF ": %s", DP(s->inaddr), line); ret = control_stream_parse(s, line); free(line); if (ret) @@ -175,7 +176,7 @@ static void control_stream_readable(int fd, void *p, uintptr_t u) { } if (streambuf_bufsize(s->inbuf) > 1024) { - mylog(LOG_WARNING, "Buffer length exceeded in control connection from " DF, DP(s->inaddr)); + ilog(LOG_WARNING, "Buffer length exceeded in control connection from " DF, DP(s->inaddr)); goto close; } @@ -226,7 +227,7 @@ next: } nonblock(nfd); - mylog(LOG_INFO, "New control connection from " DF, DP(sin)); + ilog(LOG_INFO, "New control connection from " DF, DP(sin)); s = obj_alloc0("control_stream", sizeof(*s), control_stream_free); diff --git a/daemon/control_udp.c b/daemon/control_udp.c index fd14d0a52..ff8866b43 100644 --- a/daemon/control_udp.c +++ b/daemon/control_udp.c @@ -31,11 +31,11 @@ static void control_udp_incoming(struct obj *obj, str *buf, struct sockaddr_in6 if (ret <= 0) { ret = pcre_exec(u->fallback_re, NULL, buf->s, buf->len, 0, 0, ovec, G_N_ELEMENTS(ovec)); if (ret <= 0) { - mylog(LOG_WARNING, "Unable to parse command line from udp:%s: %.*s", addr, STR_FMT(buf)); + ilog(LOG_WARNING, "Unable to parse command line from udp:%s: %.*s", addr, STR_FMT(buf)); return; } - mylog(LOG_WARNING, "Failed to properly parse UDP command line '%.*s' from %s, using fallback RE", STR_FMT(buf), addr); + ilog(LOG_WARNING, "Failed to properly parse UDP command line '%.*s' from %s, using fallback RE", STR_FMT(buf), addr); pcre_get_substring_list(buf->s, ovec, ret, (const char ***) &out); @@ -68,14 +68,14 @@ static void control_udp_incoming(struct obj *obj, str *buf, struct sockaddr_in6 return; } - mylog(LOG_INFO, "Got valid command from udp:%s: %.*s", addr, STR_FMT(buf)); + ilog(LOG_INFO, "Got valid command from udp:%s: %.*s", addr, STR_FMT(buf)); pcre_get_substring_list(buf->s, ovec, ret, (const char ***) &out); str_init(&cookie, (void *) out[RE_UDP_COOKIE]); reply = cookie_cache_lookup(&u->cookie_cache, &cookie); if (reply) { - mylog(LOG_INFO, "Detected command from udp:%s as a duplicate", addr); + ilog(LOG_INFO, "Detected command from udp:%s as a duplicate", addr); sendto(u->udp_listener.fd, reply->s, reply->len, 0, (struct sockaddr *) sin, sizeof(*sin)); free(reply); goto out; @@ -131,6 +131,7 @@ static void control_udp_incoming(struct obj *obj, str *buf, struct sockaddr_in6 out: pcre_free(out); + log_info_clear(); } struct control_udp *control_udp_new(struct poller *p, struct in6_addr ip, u_int16_t port, struct callmaster *m) { diff --git a/daemon/crypto.c b/daemon/crypto.c index 306fe12ac..9ce329ed1 100644 --- a/daemon/crypto.c +++ b/daemon/crypto.c @@ -29,11 +29,14 @@ static int aes_f8_encrypt_rtcp(struct crypto_context *c, struct rtcp_packet *r, static int aes_cm_session_key_init(struct crypto_context *c); static int aes_f8_session_key_init(struct crypto_context *c); static int evp_session_key_cleanup(struct crypto_context *c); +static int null_crypt_rtp(struct crypto_context *c, struct rtp_header *r, str *s, u_int64_t idx); +static int null_crypt_rtcp(struct crypto_context *c, struct rtcp_packet *r, str *s, u_int64_t idx); /* all lengths are in bytes */ const struct crypto_suite crypto_suites[] = { { .name = "AES_CM_128_HMAC_SHA1_80", + .dtls_name = "SRTP_AES128_CM_SHA1_80", .master_key_len = 16, .master_salt_len = 14, .session_key_len = 16, @@ -57,6 +60,7 @@ const struct crypto_suite crypto_suites[] = { }, { .name = "AES_CM_128_HMAC_SHA1_32", + .dtls_name = "SRTP_AES128_CM_SHA1_32", .master_key_len = 16, .master_salt_len = 14, .session_key_len = 16, @@ -80,6 +84,7 @@ const struct crypto_suite crypto_suites[] = { }, { .name = "F8_128_HMAC_SHA1_80", +// .dtls_name = "SRTP_AES128_F8_SHA1_80", .master_key_len = 16, .master_salt_len = 14, .session_key_len = 16, @@ -101,6 +106,76 @@ const struct crypto_suite crypto_suites[] = { .session_key_init = aes_f8_session_key_init, .session_key_cleanup = evp_session_key_cleanup, }, + { + .name = "F8_128_HMAC_SHA1_32", +// .dtls_name = "SRTP_AES128_F8_SHA1_32", + .master_key_len = 16, + .master_salt_len = 14, + .session_key_len = 16, + .session_salt_len = 14, + .srtp_lifetime = 1ULL << 48, + .srtcp_lifetime = 1ULL << 31, + .kernel_cipher = MPC_AES_F8, + .kernel_hmac = MPH_HMAC_SHA1, + .srtp_auth_tag = 4, + .srtcp_auth_tag = 10, + .srtp_auth_key_len = 20, + .srtcp_auth_key_len = 20, + .encrypt_rtp = aes_f8_encrypt_rtp, + .decrypt_rtp = aes_f8_encrypt_rtp, + .encrypt_rtcp = aes_f8_encrypt_rtcp, + .decrypt_rtcp = aes_f8_encrypt_rtcp, + .hash_rtp = hmac_sha1_rtp, + .hash_rtcp = hmac_sha1_rtcp, + .session_key_init = aes_f8_session_key_init, + .session_key_cleanup = evp_session_key_cleanup, + }, + { + .name = "NULL_HMAC_SHA1_80", +// .dtls_name = "SRTP_NULL_SHA1_80", + .master_key_len = 16, + .master_salt_len = 14, + .session_key_len = 0, + .session_salt_len = 0, + .srtp_lifetime = 1ULL << 48, + .srtcp_lifetime = 1ULL << 31, + .kernel_cipher = MPC_NULL, + .kernel_hmac = MPH_HMAC_SHA1, + .srtp_auth_tag = 10, + .srtcp_auth_tag = 10, + .srtp_auth_key_len = 20, + .srtcp_auth_key_len = 20, + .encrypt_rtp = null_crypt_rtp, + .decrypt_rtp = null_crypt_rtp, + .encrypt_rtcp = null_crypt_rtcp, + .decrypt_rtcp = null_crypt_rtcp, + .hash_rtp = hmac_sha1_rtp, + .hash_rtcp = hmac_sha1_rtcp, + .session_key_cleanup = evp_session_key_cleanup, + }, + { + .name = "NULL_HMAC_SHA1_32", +// .dtls_name = "SRTP_NULL_SHA1_32", + .master_key_len = 16, + .master_salt_len = 14, + .session_key_len = 0, + .session_salt_len = 0, + .srtp_lifetime = 1ULL << 48, + .srtcp_lifetime = 1ULL << 31, + .kernel_cipher = MPC_NULL, + .kernel_hmac = MPH_HMAC_SHA1, + .srtp_auth_tag = 4, + .srtcp_auth_tag = 10, + .srtp_auth_key_len = 20, + .srtcp_auth_key_len = 20, + .encrypt_rtp = null_crypt_rtp, + .decrypt_rtp = null_crypt_rtp, + .encrypt_rtcp = null_crypt_rtcp, + .decrypt_rtcp = null_crypt_rtcp, + .hash_rtp = hmac_sha1_rtp, + .hash_rtcp = hmac_sha1_rtcp, + .session_key_cleanup = evp_session_key_cleanup, + }, }; const int num_crypto_suites = G_N_ELEMENTS(crypto_suites); @@ -134,7 +209,7 @@ const struct crypto_suite *crypto_find_suite(const str *s) { /* rfc 3711 section 4.1 and 4.1.1 * "in" and "out" MAY point to the same buffer */ -static void aes_ctr_128(char *out, str *in, EVP_CIPHER_CTX *ecc, const char *iv) { +static void aes_ctr_128(unsigned char *out, str *in, EVP_CIPHER_CTX *ecc, const unsigned char *iv) { unsigned char ivx[16]; unsigned char key_block[16]; unsigned char *p, *q; @@ -182,13 +257,13 @@ done: ; } -static void aes_ctr_128_no_ctx(char *out, str *in, const char *key, const char *iv) { +static void aes_ctr_128_no_ctx(unsigned char *out, str *in, const unsigned char *key, const unsigned char *iv) { EVP_CIPHER_CTX ctx; unsigned char block[16]; int len; EVP_CIPHER_CTX_init(&ctx); - EVP_EncryptInit_ex(&ctx, EVP_aes_128_ecb(), NULL, (const unsigned char *) key, NULL); + EVP_EncryptInit_ex(&ctx, EVP_aes_128_ecb(), NULL, key, NULL); aes_ctr_128(out, in, &ctx, iv); EVP_EncryptFinal_ex(&ctx, block, &len); EVP_CIPHER_CTX_cleanup(&ctx); @@ -199,10 +274,10 @@ static void aes_ctr_128_no_ctx(char *out, str *in, const char *key, const char * * x: 112 bits * n <= 256 * out->len := n / 8 */ -static void prf_n(str *out, const char *key, const char *x) { - char iv[16]; - char o[32]; - char in[32]; +static void prf_n(str *out, const unsigned char *key, const unsigned char *x) { + unsigned char iv[16]; + unsigned char o[32]; + unsigned char in[32]; str in_s; assert(sizeof(o) >= out->len); @@ -211,7 +286,7 @@ static void prf_n(str *out, const char *key, const char *x) { memcpy(iv, x, 14); /* iv[14] = iv[15] = 0; := x << 16 */ ZERO(in); /* outputs the key stream */ - str_init_len(&in_s, in, out->len > 16 ? 32 : 16); + str_init_len(&in_s, (void *) in, out->len > 16 ? 32 : 16); aes_ctr_128_no_ctx(o, &in_s, key, iv); memcpy(out->s, o, out->len); @@ -230,27 +305,27 @@ int crypto_gen_session_key(struct crypto_context *c, str *out, unsigned char lab * key_derivation_rate == 0 --> r == 0 */ key_id[0] = label; - memcpy(x, c->master_salt, 14); + memcpy(x, c->params.master_salt, 14); for (i = 13 - index_len; i < 14; i++) x[i] = key_id[i - (13 - index_len)] ^ x[i]; - prf_n(out, c->master_key, (char *) x); + prf_n(out, c->params.master_key, x); #if CRYPTO_DEBUG - mylog(LOG_DEBUG, "Generated session key: master key " + ilog(LOG_DEBUG, "Generated session key: master key " "%02x%02x%02x%02x..., " "master salt " "%02x%02x%02x%02x..., " "label %02x, length %i, result " "%02x%02x%02x%02x...", - (unsigned char) c->master_key[0], - (unsigned char) c->master_key[1], - (unsigned char) c->master_key[2], - (unsigned char) c->master_key[3], - (unsigned char) c->master_salt[0], - (unsigned char) c->master_salt[1], - (unsigned char) c->master_salt[2], - (unsigned char) c->master_salt[3], + c->params.master_key[0], + c->params.master_key[1], + c->params.master_key[2], + c->params.master_key[3], + c->params.master_salt[0], + c->params.master_salt[1], + c->params.master_salt[2], + c->params.master_salt[3], label, out->len, (unsigned char) out->s[0], (unsigned char) out->s[1], @@ -278,7 +353,7 @@ static int aes_cm_encrypt(struct crypto_context *c, u_int32_t ssrc, str *s, u_in ivi[2] ^= idxh; ivi[3] ^= idxl; - aes_ctr_128(s->s, s, c->session_key_ctx[0], (char *) iv); + aes_ctr_128((void *) s->s, s, c->session_key_ctx[0], iv); return 0; } @@ -389,15 +464,15 @@ static int hmac_sha1_rtp(struct crypto_context *c, char *out, str *in, u_int64_t HMAC_CTX hc; u_int32_t roc; - HMAC_Init(&hc, c->session_auth_key, c->crypto_suite->srtp_auth_key_len, EVP_sha1()); + HMAC_Init(&hc, c->session_auth_key, c->params.crypto_suite->srtp_auth_key_len, EVP_sha1()); HMAC_Update(&hc, (unsigned char *) in->s, in->len); roc = htonl((index & 0xffffffff0000ULL) >> 16); HMAC_Update(&hc, (unsigned char *) &roc, sizeof(roc)); HMAC_Final(&hc, hmac, NULL); HMAC_CTX_cleanup(&hc); - assert(sizeof(hmac) >= c->crypto_suite->srtp_auth_tag); - memcpy(out, hmac, c->crypto_suite->srtp_auth_tag); + assert(sizeof(hmac) >= c->params.crypto_suite->srtp_auth_tag); + memcpy(out, hmac, c->params.crypto_suite->srtp_auth_tag); return 0; } @@ -406,11 +481,11 @@ static int hmac_sha1_rtp(struct crypto_context *c, char *out, str *in, u_int64_t static int hmac_sha1_rtcp(struct crypto_context *c, char *out, str *in) { unsigned char hmac[20]; - HMAC(EVP_sha1(), c->session_auth_key, c->crypto_suite->srtcp_auth_key_len, + HMAC(EVP_sha1(), c->session_auth_key, c->params.crypto_suite->srtcp_auth_key_len, (unsigned char *) in->s, in->len, hmac, NULL); - assert(sizeof(hmac) >= c->crypto_suite->srtcp_auth_tag); - memcpy(out, hmac, c->crypto_suite->srtcp_auth_tag); + assert(sizeof(hmac) >= c->params.crypto_suite->srtcp_auth_tag); + memcpy(out, hmac, c->params.crypto_suite->srtcp_auth_tag); return 0; } @@ -433,8 +508,8 @@ static int aes_f8_session_key_init(struct crypto_context *c) { aes_cm_session_key_init(c); - k_e_len = c->crypto_suite->session_key_len; - k_s_len = c->crypto_suite->session_salt_len; + k_e_len = c->params.crypto_suite->session_key_len; + k_s_len = c->params.crypto_suite->session_salt_len; key = (unsigned char *) c->session_key; /* m = k_s || 0x555..5 */ @@ -468,3 +543,10 @@ static int evp_session_key_cleanup(struct crypto_context *c) { return 0; } + +static int null_crypt_rtp(struct crypto_context *c, struct rtp_header *r, str *s, u_int64_t idx) { + return 0; +} +static int null_crypt_rtcp(struct crypto_context *c, struct rtcp_packet *r, str *s, u_int64_t idx) { + return 0; +} diff --git a/daemon/crypto.h b/daemon/crypto.h index 280484643..17f909aed 100644 --- a/daemon/crypto.h +++ b/daemon/crypto.h @@ -9,6 +9,14 @@ +#define SRTP_MAX_MASTER_KEY_LEN 16 +#define SRTP_MAX_MASTER_SALT_LEN 14 +#define SRTP_MAX_SESSION_KEY_LEN 16 +#define SRTP_MAX_SESSION_SALT_LEN 14 +#define SRTP_MAX_SESSION_AUTH_LEN 20 + + + struct crypto_context; struct rtp_header; struct rtcp_packet; @@ -22,6 +30,7 @@ typedef int (*session_key_cleanup_func)(struct crypto_context *); struct crypto_suite { const char *name; + const char *dtls_name; unsigned int master_key_len, master_salt_len, @@ -44,35 +53,34 @@ struct crypto_suite { hash_func_rtcp hash_rtcp; session_key_init_func session_key_init; session_key_cleanup_func session_key_cleanup; + const char *dtls_profile_code; }; -struct crypto_context { +struct crypto_params { const struct crypto_suite *crypto_suite; /* we only support one master key for now */ - char master_key[16]; - char master_salt[14]; - u_int64_t mki; + unsigned char master_key[SRTP_MAX_MASTER_KEY_LEN]; + unsigned char master_salt[SRTP_MAX_MASTER_SALT_LEN]; + unsigned char *mki; unsigned int mki_len; - unsigned int tag; +}; + +struct crypto_context { + struct crypto_params params; + + char session_key[SRTP_MAX_SESSION_KEY_LEN]; /* k_e */ + char session_salt[SRTP_MAX_SESSION_SALT_LEN]; /* k_s */ + char session_auth_key[SRTP_MAX_SESSION_AUTH_LEN]; u_int64_t last_index; /* XXX replay list */ /* ? */ - char session_key[16]; /* k_e */ - char session_salt[14]; /* k_s */ - char session_auth_key[20]; - void *session_key_ctx[2]; int have_session_key:1; }; -struct crypto_context_pair { - struct crypto_context in, - out; -}; - @@ -87,41 +95,57 @@ int crypto_gen_session_key(struct crypto_context *, str *, unsigned char, int); static inline int crypto_encrypt_rtp(struct crypto_context *c, struct rtp_header *rtp, str *payload, u_int64_t index) { - return c->crypto_suite->encrypt_rtp(c, rtp, payload, index); + return c->params.crypto_suite->encrypt_rtp(c, rtp, payload, index); } static inline int crypto_decrypt_rtp(struct crypto_context *c, struct rtp_header *rtp, str *payload, u_int64_t index) { - return c->crypto_suite->decrypt_rtp(c, rtp, payload, index); + return c->params.crypto_suite->decrypt_rtp(c, rtp, payload, index); } static inline int crypto_encrypt_rtcp(struct crypto_context *c, struct rtcp_packet *rtcp, str *payload, u_int64_t index) { - return c->crypto_suite->encrypt_rtcp(c, rtcp, payload, index); + return c->params.crypto_suite->encrypt_rtcp(c, rtcp, payload, index); } static inline int crypto_decrypt_rtcp(struct crypto_context *c, struct rtcp_packet *rtcp, str *payload, u_int64_t index) { - return c->crypto_suite->decrypt_rtcp(c, rtcp, payload, index); + return c->params.crypto_suite->decrypt_rtcp(c, rtcp, payload, index); } static inline int crypto_init_session_key(struct crypto_context *c) { - return c->crypto_suite->session_key_init(c); + return c->params.crypto_suite->session_key_init(c); +} + +static inline void crypto_params_cleanup(struct crypto_params *p) { + if (p->mki) + free(p->mki); + p->mki = NULL; } static inline void crypto_cleanup(struct crypto_context *c) { - if (!c->crypto_suite) + if (!c->params.crypto_suite) return; - if (c->crypto_suite->session_key_cleanup) - c->crypto_suite->session_key_cleanup(c); + if (c->params.crypto_suite->session_key_cleanup) + c->params.crypto_suite->session_key_cleanup(c); + c->have_session_key = 0; + crypto_params_cleanup(&c->params); } -static inline void crypto_context_move(struct crypto_context *dst, struct crypto_context *src) { - int i; - - if (src == dst) - return; - crypto_cleanup(dst); - *dst = *src; - for (i = 0; i < G_N_ELEMENTS(src->session_key_ctx); i++) - src->session_key_ctx[i] = NULL; +static inline void crypto_reset(struct crypto_context *c) { + crypto_cleanup(c); + c->last_index = 0; +} +static inline void crypto_params_copy(struct crypto_params *o, const struct crypto_params *i) { + crypto_params_cleanup(o); + *o = *i; + if (o->mki_len > 255) + o->mki_len = 0; + if (o->mki_len) { + o->mki = malloc(i->mki_len); + memcpy(o->mki, i->mki, i->mki_len); + } +} +static inline void crypto_init(struct crypto_context *c, const struct crypto_params *p) { + crypto_cleanup(c); + crypto_params_copy(&c->params, p); } diff --git a/daemon/dtls.c b/daemon/dtls.c new file mode 100644 index 000000000..04efcc2cd --- /dev/null +++ b/daemon/dtls.c @@ -0,0 +1,681 @@ +#include "dtls.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "str.h" +#include "aux.h" +#include "crypto.h" +#include "log.h" +#include "call.h" +#include "poller.h" + + + + + +#define DTLS_DEBUG 0 + +#if DTLS_DEBUG +#define __DBG(x...) ilog(LOG_DEBUG, x) +#else +#define __DBG(x...) ((void)0) +#endif + + + + +#define CERT_EXPIRY_TIME (60*60*24*30) /* 30 days */ + + + + + +static char ciphers_str[1024]; + + + +static unsigned int sha_1_func(unsigned char *, X509 *); +static unsigned int sha_224_func(unsigned char *, X509 *); +static unsigned int sha_256_func(unsigned char *, X509 *); +static unsigned int sha_384_func(unsigned char *, X509 *); +static unsigned int sha_512_func(unsigned char *, X509 *); + + + + +static const struct dtls_hash_func hash_funcs[] = { + { + .name = "sha-1", + .num_bytes = 160 / 8, + .__func = sha_1_func, + }, + { + .name = "sha-224", + .num_bytes = 224 / 8, + .__func = sha_224_func, + }, + { + .name = "sha-256", + .num_bytes = 256 / 8, + .__func = sha_256_func, + }, + { + .name = "sha-384", + .num_bytes = 384 / 8, + .__func = sha_384_func, + }, + { + .name = "sha-512", + .num_bytes = 512 / 8, + .__func = sha_512_func, + }, +}; + +const int num_hash_funcs = G_N_ELEMENTS(hash_funcs); + + + +static struct dtls_cert *__dtls_cert; +static rwlock_t __dtls_cert_lock; + + + +const struct dtls_hash_func *dtls_find_hash_func(const str *s) { + int i; + const struct dtls_hash_func *hf; + + for (i = 0; i < num_hash_funcs; i++) { + hf = &hash_funcs[i]; + if (strlen(hf->name) != s->len) + continue; + if (!strncasecmp(s->s, hf->name, s->len)) + return hf; + } + + return NULL; +} + +static void cert_free(void *p) { + struct dtls_cert *cert = p; + + if (cert->pkey) + EVP_PKEY_free(cert->pkey); + if (cert->x509) + X509_free(cert->x509); +} + +static int cert_init() { + X509 *x509 = NULL; + EVP_PKEY *pkey = NULL; + BIGNUM *exponent = NULL, *serial_number = NULL; + RSA *rsa = NULL; + ASN1_INTEGER *asn1_serial_number; + X509_NAME *name; + struct dtls_cert *new_cert; + + ilog(LOG_INFO, "Generating new DTLS certificate"); + + /* objects */ + + pkey = EVP_PKEY_new(); + exponent = BN_new(); + rsa = RSA_new(); + serial_number = BN_new(); + name = X509_NAME_new(); + x509 = X509_new(); + if (!exponent || !pkey || !rsa || !serial_number || !name || !x509) + goto err; + + /* key */ + + if (!BN_set_word(exponent, 0x10001)) + goto err; + + if (!RSA_generate_key_ex(rsa, 1024, exponent, NULL)) + goto err; + + if (!EVP_PKEY_assign_RSA(pkey, rsa)) + goto err; + + /* x509 cert */ + + if (!X509_set_pubkey(x509, pkey)) + goto err; + + /* serial */ + + if (!BN_pseudo_rand(serial_number, 64, 0, 0)) + goto err; + + asn1_serial_number = X509_get_serialNumber(x509); + if (!asn1_serial_number) + goto err; + + if (!BN_to_ASN1_INTEGER(serial_number, asn1_serial_number)) + goto err; + + /* version 1 */ + + if (!X509_set_version(x509, 0L)) + goto err; + + /* common name */ + + if (!X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_UTF8, + (unsigned char *) "mediaproxy-ng", -1, -1, 0)) + goto err; + + if (!X509_set_subject_name(x509, name)) + goto err; + + if (!X509_set_issuer_name(x509, name)) + goto err; + + /* cert lifetime */ + + if (!X509_gmtime_adj(X509_get_notBefore(x509), -60*60*24)) + goto err; + + if (!X509_gmtime_adj(X509_get_notAfter(x509), CERT_EXPIRY_TIME)) + goto err; + + /* sign it */ + + if (!X509_sign(x509, pkey, EVP_sha1())) + goto err; + + /* digest */ + + new_cert = obj_alloc0("dtls_cert", sizeof(*new_cert), cert_free); + new_cert->fingerprint.hash_func = &hash_funcs[0]; + dtls_fingerprint_hash(&new_cert->fingerprint, x509); + + new_cert->x509 = x509; + new_cert->pkey = pkey; + new_cert->expires = time(NULL) + CERT_EXPIRY_TIME; + + /* swap out certs */ + + rwlock_lock_w(&__dtls_cert_lock); + + if (__dtls_cert) + obj_put(__dtls_cert); + __dtls_cert = new_cert; + + rwlock_unlock_w(&__dtls_cert_lock); + + /* cleanup */ + + BN_free(exponent); + BN_free(serial_number); + X509_NAME_free(name); + + return 0; + +err: + ilog(LOG_ERROR, "Failed to generate DTLS certificate"); + + if (pkey) + EVP_PKEY_free(pkey); + if (exponent) + BN_free(exponent); + if (rsa) + RSA_free(rsa); + if (x509) + X509_free(x509); + if (serial_number) + BN_free(serial_number); + + return -1; +} + +int dtls_init() { + int i; + char *p; + + rwlock_init(&__dtls_cert_lock); + if (cert_init()) + return -1; + + p = ciphers_str; + for (i = 0; i < num_crypto_suites; i++) { + if (!crypto_suites[i].dtls_name) + continue; + + p += sprintf(p, "%s:", crypto_suites[i].dtls_name); + } + + assert(p != ciphers_str); + assert(p - ciphers_str < sizeof(ciphers_str)); + + p[-1] = '\0'; + + return 0; +} + +static void __dtls_timer(void *p) { + struct dtls_cert *c; + long int left; + + c = dtls_cert(); + left = c->expires - poller_now; + if (left > CERT_EXPIRY_TIME/2) + goto out; + + cert_init(); + +out: + obj_put(c); +} + +void dtls_timer(struct poller *p) { + poller_add_timer(p, __dtls_timer, NULL); +} + +static unsigned int generic_func(unsigned char *o, X509 *x, const EVP_MD *md) { + unsigned int n; + assert(md != NULL); + X509_digest(x, md, o, &n); + return n; +} + +static unsigned int sha_1_func(unsigned char *o, X509 *x) { + const EVP_MD *md; + md = EVP_sha1(); + return generic_func(o, x, md); +} +static unsigned int sha_224_func(unsigned char *o, X509 *x) { + const EVP_MD *md; + md = EVP_sha224(); + return generic_func(o, x, md); +} +static unsigned int sha_256_func(unsigned char *o, X509 *x) { + const EVP_MD *md; + md = EVP_sha256(); + return generic_func(o, x, md); +} +static unsigned int sha_384_func(unsigned char *o, X509 *x) { + const EVP_MD *md; + md = EVP_sha384(); + return generic_func(o, x, md); +} +static unsigned int sha_512_func(unsigned char *o, X509 *x) { + const EVP_MD *md; + md = EVP_sha512(); + return generic_func(o, x, md); +} + + +struct dtls_cert *dtls_cert() { + struct dtls_cert *ret; + + rwlock_lock_r(&__dtls_cert_lock); + ret = obj_get(__dtls_cert); + rwlock_unlock_r(&__dtls_cert_lock); + + return ret; +} + +static int verify_callback(int ok, X509_STORE_CTX *store) { + SSL *ssl; + struct stream_fd *sfd; + struct packet_stream *ps; + struct call_media *media; + + ssl = X509_STORE_CTX_get_ex_data(store, SSL_get_ex_data_X509_STORE_CTX_idx()); + sfd = SSL_get_app_data(ssl); + if (sfd->dtls.ssl != ssl) + return 0; + ps = sfd->stream; + if (!ps) + return 0; + if (ps->fingerprint_verified) + return 1; + media = ps->media; + if (!media) + return 0; + + ps->dtls_cert = X509_STORE_CTX_get_current_cert(store); + + if (!media->fingerprint.hash_func) + return 1; /* delay verification */ + + if (dtls_verify_cert(ps)) + return 0; + return 1; +} + +int dtls_verify_cert(struct packet_stream *ps) { + unsigned char fp[DTLS_MAX_DIGEST_LEN]; + struct call_media *media; + + media = ps->media; + if (!media) + return -1; + if (!ps->dtls_cert) + return -1; + + dtls_hash(media->fingerprint.hash_func, ps->dtls_cert, fp); + + if (memcmp(media->fingerprint.digest, fp, media->fingerprint.hash_func->num_bytes)) { + ilog(LOG_WARNING, "DTLS: Peer certificate rejected - fingerprint mismatch"); + __DBG("fingerprint expected: %02x%02x%02x%02x%02x%02x%02x%02x received: %02x%02x%02x%02x%02x%02x%02x%02x", + media->fingerprint.digest[0], media->fingerprint.digest[1], + media->fingerprint.digest[2], media->fingerprint.digest[3], + media->fingerprint.digest[4], media->fingerprint.digest[5], + media->fingerprint.digest[6], media->fingerprint.digest[7], + fp[0], fp[1], fp[2], fp[3], + fp[4], fp[5], fp[6], fp[7]); + return -1; + } + + ps->fingerprint_verified = 1; + ilog(LOG_INFO, "DTLS: Peer certificate accepted"); + + return 0; +} + +static int try_connect(struct dtls_connection *d) { + int ret, code; + + if (d->connected) + return 0; + + __DBG("try_connect(%i)", d->active); + + if (d->active) + ret = SSL_connect(d->ssl); + else + ret = SSL_accept(d->ssl); + + code = SSL_get_error(d->ssl, ret); + + ret = 0; + switch (code) { + case SSL_ERROR_NONE: + ilog(LOG_DEBUG, "DTLS handshake successful"); + d->connected = 1; + ret = 1; + break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + + default: + ret = ERR_peek_last_error(); + ilog(LOG_ERROR, "DTLS error: %i (%s)", code, ERR_reason_error_string(ret)); + ret = -1; + break; + } + + return ret; +} + +int dtls_connection_init(struct packet_stream *ps, int active, struct dtls_cert *cert) { + struct dtls_connection *d = &ps->sfd->dtls; + unsigned long err; + + __DBG("dtls_connection_init(%i)", active); + + if (d->init) { + if (d->active == active) + goto connect; + dtls_connection_cleanup(d); + } + + d->ssl_ctx = SSL_CTX_new(active ? DTLSv1_client_method() : DTLSv1_server_method()); + if (!d->ssl_ctx) + goto error; + + if (SSL_CTX_use_certificate(d->ssl_ctx, cert->x509) != 1) + goto error; + if (SSL_CTX_use_PrivateKey(d->ssl_ctx, cert->pkey) != 1) + goto error; + + SSL_CTX_set_verify(d->ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + verify_callback); + SSL_CTX_set_verify_depth(d->ssl_ctx, 4); + SSL_CTX_set_cipher_list(d->ssl_ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); + + if (SSL_CTX_set_tlsext_use_srtp(d->ssl_ctx, ciphers_str)) + goto error; + + d->ssl = SSL_new(d->ssl_ctx); + if (!d->ssl) + goto error; + + d->r_bio = BIO_new(BIO_s_mem()); + d->w_bio = BIO_new(BIO_s_mem()); + if (!d->r_bio || !d->w_bio) + goto error; + + SSL_set_app_data(d->ssl, ps->sfd); /* XXX obj reference here? */ + SSL_set_bio(d->ssl, d->r_bio, d->w_bio); + SSL_set_mode(d->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + d->init = 1; + d->active = active; + +connect: + dtls(ps, NULL, NULL); + + return 0; + +error: + err = ERR_peek_last_error(); + if (d->r_bio) + BIO_free(d->r_bio); + if (d->w_bio) + BIO_free(d->w_bio); + if (d->ssl) + SSL_free(d->ssl); + if (d->ssl_ctx) + SSL_CTX_free(d->ssl_ctx); + ZERO(*d); + ilog(LOG_ERROR, "Failed to init DTLS connection: %s", ERR_reason_error_string(err)); + return -1; +} + +static int dtls_setup_crypto(struct packet_stream *ps, struct dtls_connection *d) { + const char *err; + SRTP_PROTECTION_PROFILE *spp; + int i; + const struct crypto_suite *cs; + unsigned char keys[2 * (SRTP_MAX_MASTER_KEY_LEN + SRTP_MAX_MASTER_SALT_LEN)]; + struct crypto_params client, server; + + err = "no SRTP protection profile negotiated"; + spp = SSL_get_selected_srtp_profile(d->ssl); + if (!spp) + goto error; + + for (i = 0; i < num_crypto_suites; i++) { + cs = &crypto_suites[i]; + if (!cs->dtls_name) + continue; + if (!strcmp(cs->dtls_name, spp->name)) + goto found; + } + + err = "unknown SRTP protection profile negotiated"; + goto error; + +found: + i = SSL_export_keying_material(d->ssl, keys, sizeof(keys), "EXTRACTOR-dtls_srtp", + strlen("EXTRACTOR-dtls_srtp"), NULL, 0, 0); + err = "failed to export keying material"; + if (i != 1) + goto error; + + /* got everything XXX except MKI */ + ZERO(client); + ZERO(server); + i = 0; + + client.crypto_suite = server.crypto_suite = cs; + + memcpy(client.master_key, &keys[i], cs->master_key_len); + i += cs->master_key_len; + memcpy(server.master_key, &keys[i], cs->master_key_len); + i += cs->master_key_len; + memcpy(client.master_salt, &keys[i], cs->master_salt_len); + i += cs->master_salt_len; + memcpy(server.master_salt, &keys[i], cs->master_salt_len); + + __DBG("SRTP keys negotiated: " + "c-m: %02x%02x%02x%02x%02x%02x%02x%02x " + "c-s: %02x%02x%02x%02x%02x%02x%02x%02x " + "s-m: %02x%02x%02x%02x%02x%02x%02x%02x " + "s-s: %02x%02x%02x%02x%02x%02x%02x%02x", + client.master_key[0], client.master_key[1], client.master_key[2], client.master_key[3], + client.master_key[4], client.master_key[5], client.master_key[6], client.master_key[7], + client.master_salt[0], client.master_salt[1], client.master_salt[2], client.master_salt[3], + client.master_salt[4], client.master_salt[5], client.master_salt[6], client.master_salt[7], + server.master_key[0], server.master_key[1], server.master_key[2], server.master_key[3], + server.master_key[4], server.master_key[5], server.master_key[6], server.master_key[7], + server.master_salt[0], server.master_salt[1], server.master_salt[2], server.master_salt[3], + server.master_salt[4], server.master_salt[5], server.master_salt[6], server.master_salt[7]); + + ilog(LOG_INFO, "DTLS-SRTP successfully negotiated"); + + if (d->active) { + /* we're the client */ + crypto_init(&ps->crypto, &client); + crypto_init(&ps->sfd->crypto, &server); + } + else { + /* we're the server */ + crypto_init(&ps->crypto, &server); + crypto_init(&ps->sfd->crypto, &client); + } + + return 0; + +error: + if (!spp) + ilog(LOG_ERROR, "Failed to set up SRTP after DTLS negotiation: %s", err); + else + ilog(LOG_ERROR, "Failed to set up SRTP after DTLS negotiation: %s (profile \"%s\")", + err, spp->name); + return -1; +} + +int dtls(struct packet_stream *ps, const str *s, struct sockaddr_in6 *fsin) { + struct dtls_connection *d = &ps->sfd->dtls; + int ret; + unsigned char buf[0x10000], ctrl[256]; + struct msghdr mh; + struct iovec iov; + struct sockaddr_in6 sin; + + if (s) + __DBG("dtls packet input: len %u %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + s->len, + (unsigned char) s->s[0], (unsigned char) s->s[1], (unsigned char) s->s[2], (unsigned char) s->s[3], + (unsigned char) s->s[4], (unsigned char) s->s[5], (unsigned char) s->s[6], (unsigned char) s->s[7], + (unsigned char) s->s[8], (unsigned char) s->s[9], (unsigned char) s->s[10], (unsigned char) s->s[11], + (unsigned char) s->s[12], (unsigned char) s->s[13], (unsigned char) s->s[14], (unsigned char) s->s[15]); + + if (d->connected) + return 0; + + if (!d->init || !d->ssl) + return -1; + + if (s) { + BIO_write(d->r_bio, s->s, s->len); + /* we understand this as preference of DTLS over SDES */ + ps->media->sdes = 0; + } + + ret = try_connect(d); + if (ret == -1) { + if (ps->sfd) + ilog(LOG_ERROR, "DTLS error on local port %hu", ps->sfd->fd.localport); + /* fatal error */ + d->init = 0; + /* XXX ?? */ + return 0; + } + else if (ret == 1) { + /* connected! */ + if (dtls_setup_crypto(ps, d)) + /* XXX ?? */ ; + if (ps->rtp && ps->rtcp && ps->rtcp_sibling && ps->media->rtcp_mux) { + if (dtls_setup_crypto(ps->rtcp_sibling, d)) + /* XXX ?? */ ; + } + } + + ret = BIO_ctrl_pending(d->w_bio); + if (ret <= 0) + return 0; + + if (ret > sizeof(buf)) { + ilog(LOG_ERROR, "BIO buffer overflow"); + BIO_reset(d->w_bio); + return 0; + } + + ret = BIO_read(d->w_bio, buf, ret); + if (ret <= 0) + return 0; + + __DBG("dtls packet output: len %u %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + ret, + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7], + buf[8], buf[9], buf[10], buf[11], + buf[12], buf[13], buf[14], buf[15]); + + if (!fsin) { + ZERO(sin); + sin.sin6_family = AF_INET6; + sin.sin6_addr = ps->endpoint.ip46; + sin.sin6_port = htons(ps->endpoint.port); + fsin = &sin; + } + + ZERO(mh); + mh.msg_control = ctrl; + mh.msg_controllen = sizeof(ctrl); + mh.msg_name = fsin; + mh.msg_namelen = sizeof(*fsin); + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + + ZERO(iov); + iov.iov_base = buf; + iov.iov_len = ret; + + callmaster_msg_mh_src(ps->call->callmaster, &mh); + + sendmsg(ps->sfd->fd.fd, &mh, 0); + + return 0; +} + +void dtls_connection_cleanup(struct dtls_connection *c) { + __DBG("dtls_connection_cleanup"); + + if (c->ssl_ctx) + SSL_CTX_free(c->ssl_ctx); + if (c->ssl) + SSL_free(c->ssl); + if (!c->init) { + if (c->r_bio) + BIO_free(c->r_bio); + if (c->w_bio) + BIO_free(c->w_bio); + } + ZERO(*c); +} diff --git a/daemon/dtls.h b/daemon/dtls.h new file mode 100644 index 000000000..4ab302956 --- /dev/null +++ b/daemon/dtls.h @@ -0,0 +1,103 @@ +#ifndef _DTLS_H_ +#define _DTLS_H_ + + + +#include +#include +#include +#include + +#include "str.h" +#include "obj.h" + + + + +#define DTLS_MAX_DIGEST_LEN 64 + + + + +struct packet_stream; +struct sockaddr_in6; +struct poller; + + + +struct dtls_hash_func { + const char *name; + unsigned int num_bytes; + unsigned int (*__func)(unsigned char *, X509 *); +}; + +struct dtls_fingerprint { + unsigned char digest[DTLS_MAX_DIGEST_LEN]; + const struct dtls_hash_func *hash_func; +}; + +struct dtls_cert { + struct obj obj; + struct dtls_fingerprint fingerprint; + EVP_PKEY *pkey; + X509 *x509; + time_t expires; +}; + +struct dtls_connection { + SSL_CTX *ssl_ctx; + SSL *ssl; + BIO *r_bio, *w_bio; + int init:1, + active:1, + connected:1; +}; + + + + +int dtls_init(void); +void dtls_timer(struct poller *); + +int dtls_verify_cert(struct packet_stream *ps); +const struct dtls_hash_func *dtls_find_hash_func(const str *); +struct dtls_cert *dtls_cert(void); + +int dtls_connection_init(struct packet_stream *, int active, struct dtls_cert *cert); +int dtls(struct packet_stream *, const str *s, struct sockaddr_in6 *sin); +void dtls_connection_cleanup(struct dtls_connection *); + + + + +static inline void __dtls_hash(const struct dtls_hash_func *hash_func, X509 *cert, unsigned char *out, + unsigned int bufsize) +{ + unsigned int n; + + assert(bufsize >= hash_func->num_bytes); + n = hash_func->__func(out, cert); + assert(n == hash_func->num_bytes); +} +#define dtls_hash(hash_func, cert, outbuf) __dtls_hash(hash_func, cert, outbuf, sizeof(outbuf)) + +static inline void dtls_fingerprint_hash(struct dtls_fingerprint *fp, X509 *cert) { + __dtls_hash(fp->hash_func, cert, fp->digest, sizeof(fp->digest)); +} + +static inline int is_dtls(const str *s) { + const unsigned char *b = (const void *) s->s; + + if (s->len < 1) + return 0; + /* RFC 5764, 5.1.2 */ + if (b[0] >= 20 && b[0] <= 63) + return 1; + + return 0; +} + + + + +#endif diff --git a/daemon/kernel.c b/daemon/kernel.c index b6f085f72..6d683c63b 100644 --- a/daemon/kernel.c +++ b/daemon/kernel.c @@ -85,7 +85,7 @@ int kernel_add_stream(int fd, struct mediaproxy_target_info *mti, int update) { if (ret > 0) return 0; - mylog(LOG_ERROR, "Failed to push relay stream to kernel: %s", strerror(errno)); + ilog(LOG_ERROR, "Failed to push relay stream to kernel: %s", strerror(errno)); return -1; } @@ -102,7 +102,7 @@ int kernel_del_stream(int fd, u_int16_t p) { if (ret > 0) return 0; - mylog(LOG_ERROR, "Failed to delete relay stream from kernel: %s", strerror(errno)); + ilog(LOG_ERROR, "Failed to delete relay stream from kernel: %s", strerror(errno)); return -1; } diff --git a/daemon/log.c b/daemon/log.c new file mode 100644 index 000000000..39b1f53ee --- /dev/null +++ b/daemon/log.c @@ -0,0 +1,55 @@ +#include "log.h" +#include +#include +#include +#include +#include "str.h" +#include "call.h" + + + +struct log_info __thread log_info; +volatile gint log_level = LOG_INFO; + + + +void ilog(int prio, const char *fmt, ...) { + char prefix[256]; + char *msg; + va_list ap; + int ret; + + if (prio > g_atomic_int_get(&log_level)) + return; + + switch (log_info.e) { + case LOG_INFO_NONE: + prefix[0] = 0; + break; + case LOG_INFO_CALL: + snprintf(prefix, sizeof(prefix), "["STR_FORMAT"] ", + STR_FMT(&log_info.u.call->callid)); + break; + case LOG_INFO_STREAM_FD: + if (log_info.u.stream_fd->call) + snprintf(prefix, sizeof(prefix), "["STR_FORMAT" port %5hu] ", + STR_FMT(&log_info.u.stream_fd->call->callid), + log_info.u.stream_fd->fd.localport); + break; + } + + va_start(ap, fmt); + ret = vasprintf(&msg, fmt, ap); + va_end(ap); + + if (ret < 0) { + syslog(LOG_ERROR, "Failed to print syslog message - message dropped"); + return; + } + + syslog(prio, "%s%s", prefix, msg); + + free(msg); +} + + diff --git a/daemon/log.h b/daemon/log.h index 5ffdd3668..b02fa3603 100644 --- a/daemon/log.h +++ b/daemon/log.h @@ -3,9 +3,74 @@ #include +#include + + + +struct log_info { + union { + struct call *call; + struct stream_fd *stream_fd; + } u; + enum { + LOG_INFO_NONE = 0, + LOG_INFO_CALL, + LOG_INFO_STREAM_FD, + } e; +}; + + + + +extern struct log_info __thread log_info; +extern volatile gint log_level; + + + + +void ilog(int prio, const char *fmt, ...)__attribute__ ((format (printf, 2, 3))); + + + + +#include "obj.h" + + + + +static inline void log_info_clear() { + switch (log_info.e) { + case LOG_INFO_NONE: + return; + case LOG_INFO_CALL: + __obj_put((void *) log_info.u.call); + break; + case LOG_INFO_STREAM_FD: + __obj_put((void *) log_info.u.stream_fd); + break; + } + log_info.e = LOG_INFO_NONE; +} +static inline void log_info_call(struct call *c) { + log_info_clear(); + if (!c) + return; + log_info.e = LOG_INFO_CALL; + log_info.u.call = __obj_get((void *) c); +} +static inline void log_info_stream_fd(struct stream_fd *sfd) { + log_info_clear(); + if (!sfd) + return; + log_info.e = LOG_INFO_STREAM_FD; + log_info.u.stream_fd = __obj_get((void *) sfd); +} + + + + -#define mylog(x,y...) syslog(x,y) #define LOG_ERROR LOG_ERR #define LOG_WARN LOG_WARNING diff --git a/daemon/main.c b/daemon/main.c index ae6dd2bd7..f387ca11e 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "poller.h" #include "control_tcp.h" @@ -22,6 +23,7 @@ #include "kernel.h" #include "redis.h" #include "sdp.h" +#include "dtls.h" @@ -85,7 +87,6 @@ static u_int32_t redis_ip; static u_int16_t redis_port; static int redis_db = -1; static char *b2b_url; -static int log_level = LOG_INFO; @@ -114,17 +115,19 @@ static void sighandler(gpointer x) { if (ret == SIGINT || ret == SIGTERM) global_shutdown = 1; else if (ret == SIGUSR1) { - if (log_level > 0) { - log_level--; - setlogmask(LOG_UPTO(log_level)); - mylog(log_level, "Set log level to %d\n", log_level); + if (g_atomic_int_get(&log_level) > 0) { + g_atomic_int_add(&log_level, -1); + setlogmask(LOG_UPTO(g_atomic_int_get(&log_level))); + ilog(g_atomic_int_get(&log_level), "Set log level to %d\n", + g_atomic_int_get(&log_level)); } } else if (ret == SIGUSR2) { - if (log_level < 7) { - log_level++; - setlogmask(LOG_UPTO(log_level)); - mylog(log_level, "Set log level to %d\n", log_level); + if (g_atomic_int_get(&log_level) < 7) { + g_atomic_int_add(&log_level, 1); + setlogmask(LOG_UPTO(g_atomic_int_get(&log_level))); + ilog(g_atomic_int_get(&log_level), "Set log level to %d\n", + g_atomic_int_get(&log_level)); } } else @@ -252,7 +255,7 @@ static void options(int *argc, char ***argv) { { "redis", 'r', 0, G_OPTION_ARG_STRING, &redisps, "Connect to Redis database", "IP:PORT" }, { "redis-db", 'R', 0, G_OPTION_ARG_INT, &redis_db, "Which Redis DB to use", "INT" }, { "b2b-url", 'b', 0, G_OPTION_ARG_STRING, &b2b_url, "XMLRPC URL of B2B UA" , "STRING" }, - { "log-level", 'L', 0, G_OPTION_ARG_INT, &log_level, "Mask log priorities above this level", "INT" }, + { "log-level", 'L', 0, G_OPTION_ARG_INT, (void *)&log_level, "Mask log priorities above this level", "INT" }, { NULL, } }; @@ -353,6 +356,8 @@ static void init_everything() { clock_gettime(CLOCK_REALTIME, &ts); srandom(ts.tv_sec ^ ts.tv_nsec); + SSL_library_init(); + SSL_load_error_strings(); #if !GLIB_CHECK_VERSION(2,32,0) g_thread_init(NULL); @@ -361,6 +366,7 @@ static void init_everything() { signals(); resources(); sdp_init(); + dtls_init(); } void redis_mod_verify(void *dlh) { @@ -370,6 +376,7 @@ void redis_mod_verify(void *dlh) { dlresolve(redis_delete); dlresolve(redis_wipe); + /* check_struct_size(call); check_struct_size(callstream); check_struct_size(crypto_suite); @@ -398,6 +405,7 @@ void redis_mod_verify(void *dlh) { check_struct_offset(stream, ip46); check_struct_offset(stream, num); check_struct_offset(stream, protocol); + */ } void create_everything(struct main_context *ctx) { @@ -411,7 +419,7 @@ void create_everything(struct main_context *ctx) { if (table >= 0 && kernel_create_table(table)) { fprintf(stderr, "FAILED TO CREATE KERNEL TABLE %i, KERNEL FORWARDING DISABLED\n", table); - mylog(LOG_CRIT, "FAILED TO CREATE KERNEL TABLE %i, KERNEL FORWARDING DISABLED\n", table); + ilog(LOG_CRIT, "FAILED TO CREATE KERNEL TABLE %i, KERNEL FORWARDING DISABLED\n", table); table = -1; if (no_fallback) exit(-1); @@ -420,7 +428,7 @@ void create_everything(struct main_context *ctx) { kfd = kernel_open_table(table); if (kfd == -1) { fprintf(stderr, "FAILED TO OPEN KERNEL TABLE %i, KERNEL FORWARDING DISABLED\n", table); - mylog(LOG_CRIT, "FAILED TO OPEN KERNEL TABLE %i, KERNEL FORWARDING DISABLED\n", table); + ilog(LOG_CRIT, "FAILED TO OPEN KERNEL TABLE %i, KERNEL FORWARDING DISABLED\n", table); table = -1; if (no_fallback) exit(-1); @@ -435,6 +443,8 @@ void create_everything(struct main_context *ctx) { if (!ctx->m) die("callmaster creation failed\n"); + dtls_timer(ctx->p); + ZERO(mc); mc.kernelfd = kfd; mc.kernelid = table; @@ -488,7 +498,7 @@ void create_everything(struct main_context *ctx) { die("Cannot start up without Redis database\n"); } - callmaster_config(ctx->m, &mc); + ctx->m->conf = mc; if (!foreground) daemonize(); @@ -519,7 +529,7 @@ int main(int argc, char **argv) { options(&argc, &argv); create_everything(&ctx); - mylog(LOG_INFO, "Startup complete, version %s", MEDIAPROXY_VERSION); + ilog(LOG_INFO, "Startup complete, version %s", MEDIAPROXY_VERSION); thread_create_detach(sighandler, NULL); thread_create_detach(timer_loop, ctx.p); @@ -535,7 +545,7 @@ int main(int argc, char **argv) { threads_join_all(1); - mylog(LOG_INFO, "Version %s shutting down", MEDIAPROXY_VERSION); + ilog(LOG_INFO, "Version %s shutting down", MEDIAPROXY_VERSION); return 0; } diff --git a/daemon/obj.h b/daemon/obj.h index dc9990914..86c1ff3bd 100644 --- a/daemon/obj.h +++ b/daemon/obj.h @@ -9,7 +9,6 @@ #include #include -#include "log.h" @@ -37,26 +36,60 @@ struct obj { + + #if OBJ_DEBUG #define OBJ_MAGIC 0xf1eef1ee #define obj_alloc(t,a,b) __obj_alloc(a,b,t,__FILE__,__LINE__) #define obj_alloc0(t,a,b) __obj_alloc0(a,b,t,__FILE__,__LINE__) -#define obj_hold(a) __obj_hold(a,__FILE__,__LINE__) -#define obj_get(a) __obj_get(a,__FILE__,__LINE__) -#define obj_put(a) __obj_put(a,__FILE__,__LINE__) +#define obj_hold(a) __obj_hold(&(a)->obj,__FILE__,__LINE__) +#define obj_get(a) __obj_get(&(a)->obj,__FILE__,__LINE__) +#define obj_put(a) __obj_put(&(a)->obj,__FILE__,__LINE__) +#define obj_hold_o(a) __obj_hold(a,__FILE__,__LINE__) +#define obj_get_o(a) __obj_get(a,__FILE__,__LINE__) +#define obj_put_o(a) __obj_put(a,__FILE__,__LINE__) + +static inline void __obj_init(struct obj *o, unsigned int size, void (*free_func)(void *), + const char *type, const char *file, unsigned int line); +static inline void *__obj_alloc(unsigned int size, void (*free_func)(void *), + const char *type, const char *file, unsigned int line); +static inline void *__obj_alloc0(unsigned int size, void (*free_func)(void *), + const char *type, const char *file, unsigned int line); +static inline struct obj *__obj_hold(struct obj *o, + const char *type, const char *file, unsigned int line); +static inline void *__obj_get(struct obj *o, + const char *type, const char *file, unsigned int line); +static inline void __obj_put(struct obj *o,, + const char *type, const char *file, unsigned int line); #else #define obj_alloc(t,a,b) __obj_alloc(a,b) #define obj_alloc0(t,a,b) __obj_alloc0(a,b) -#define obj_hold(a) __obj_hold(a) -#define obj_get(a) __obj_get(a) -#define obj_put(a) __obj_put(a) +#define obj_hold(a) __obj_hold(&(a)->obj) +#define obj_get(a) __obj_get(&(a)->obj) +#define obj_put(a) __obj_put(&(a)->obj) +#define obj_hold_o(a) __obj_hold(a) +#define obj_get_o(a) __obj_get(a) +#define obj_put_o(a) __obj_put(a) + +static inline void __obj_init(struct obj *o, unsigned int size, void (*free_func)(void *)); +static inline void *__obj_alloc(unsigned int size, void (*free_func)(void *)); +static inline void *__obj_alloc0(unsigned int size, void (*free_func)(void *)); +static inline struct obj *__obj_hold(struct obj *o); +static inline void *__obj_get(struct obj *o); +static inline void __obj_put(struct obj *o); #endif + + +#include "log.h" + + + static inline void __obj_init(struct obj *o, unsigned int size, void (*free_func)(void *) #if OBJ_DEBUG , const char *type, const char *file, unsigned int line @@ -104,12 +137,11 @@ static inline void *__obj_alloc0(unsigned int size, void (*free_func)(void *) return r; } -static inline struct obj *__obj_hold(void *p +static inline struct obj *__obj_hold(struct obj *o #if OBJ_DEBUG , const char *file, unsigned int line #endif ) { - struct obj *o = p; #if OBJ_DEBUG assert(o->magic == OBJ_MAGIC); mylog(LOG_DEBUG, "obj_hold(%p, \"%s\"), refcnt before %u [%s:%u]", @@ -123,24 +155,23 @@ static inline struct obj *__obj_hold(void *p return o; } -static inline void *__obj_get(void *p +static inline void *__obj_get(struct obj *o #if OBJ_DEBUG , const char *file, unsigned int line #endif ) { - return __obj_hold(p + return __obj_hold(o #if OBJ_DEBUG , file, line #endif ); } -static inline void __obj_put(void *p +static inline void __obj_put(struct obj *o #if OBJ_DEBUG , const char *file, unsigned int line #endif ) { - struct obj *o = p; #if OBJ_DEBUG assert(o->magic == OBJ_MAGIC); mylog(LOG_DEBUG, "obj_put(%p, \"%s\"), refcnt before %u [%s:%u]", diff --git a/daemon/poller.c b/daemon/poller.c index 5c33473d8..96e83e1d9 100644 --- a/daemon/poller.c +++ b/daemon/poller.c @@ -92,7 +92,7 @@ static void poller_fd_timer(void *p) { static void poller_item_free(void *p) { struct poller_item_int *i = p; - obj_put(i->item.obj); + obj_put_o(i->item.obj); } @@ -132,7 +132,7 @@ static int __poller_add_item(struct poller *p, struct poller_item *i, int has_lo ip = obj_alloc0("poller_item_int", sizeof(*ip), poller_item_free); memcpy(&ip->item, i, sizeof(*i)); - obj_hold(ip->item.obj); /* new ref in *ip */ + obj_hold_o(ip->item.obj); /* new ref in *ip */ p->items[i->fd] = obj_get(ip); mutex_unlock(&p->lock); @@ -209,8 +209,8 @@ int poller_update_item(struct poller *p, struct poller_item *i) { if (i->fd >= p->items_size || !(np = p->items[i->fd])) return __poller_add_item(p, i, 1); - obj_hold(i->obj); - obj_put(np->item.obj); + obj_hold_o(i->obj); + obj_put_o(np->item.obj); np->item.obj = i->obj; np->item.uintp = i->uintp; np->item.readable = i->readable; @@ -257,12 +257,12 @@ next: found: l = *ll; *ll = (*ll)->next; - obj_put(l->data); + obj_put_o(l->data); g_slist_free_1(l); l = *kk; *kk = (*kk)->next; - obj_put(l->data); + obj_put_o(l->data); g_slist_free_1(l); } } @@ -443,19 +443,20 @@ out: static void timer_item_free(void *p) { struct timer_item *i = p; - obj_put(i->obj_ptr); + if (i->obj_ptr) + obj_put_o(i->obj_ptr); } static int poller_timer_link(struct poller *p, GSList **lp, void (*f)(void *), struct obj *o) { struct timer_item *i; - if (!o || !f) + if (!f) return -1; i = obj_alloc0("timer_item", sizeof(*i), timer_item_free); i->func = f; - i->obj_ptr = obj_hold(o); + i->obj_ptr = o ? obj_hold_o(o) : NULL; mutex_lock(&p->timers_add_del_lock); *lp = g_slist_prepend(*lp, i); diff --git a/daemon/poller.h b/daemon/poller.h index 906262ca9..6c0003248 100644 --- a/daemon/poller.h +++ b/daemon/poller.h @@ -7,7 +7,10 @@ #include #include #include -#include "obj.h" + + + +struct obj; diff --git a/daemon/rtcp.c b/daemon/rtcp.c index d9dd99dcf..848538ba4 100644 --- a/daemon/rtcp.c +++ b/daemon/rtcp.c @@ -332,19 +332,22 @@ int rtcp_avpf2avp(str *s) { static inline int check_session_keys(struct crypto_context *c) { str s; + const char *err; if (c->have_session_key) return 0; - if (!c->crypto_suite) + err = "SRTCP output wanted, but no crypto suite was negotiated"; + if (!c->params.crypto_suite) goto error; - str_init_len(&s, c->session_key, c->crypto_suite->session_key_len); + err = "Failed to generate SRTCP session keys"; + str_init_len_assert(&s, c->session_key, c->params.crypto_suite->session_key_len); if (crypto_gen_session_key(c, &s, 0x03, SRTCP_R_LENGTH)) goto error; - str_init_len(&s, c->session_auth_key, c->crypto_suite->srtcp_auth_key_len); + str_init_len_assert(&s, c->session_auth_key, c->params.crypto_suite->srtcp_auth_key_len); if (crypto_gen_session_key(c, &s, 0x04, SRTCP_R_LENGTH)) goto error; - str_init_len(&s, c->session_salt, c->crypto_suite->session_salt_len); + str_init_len_assert(&s, c->session_salt, c->params.crypto_suite->session_salt_len); if (crypto_gen_session_key(c, &s, 0x05, SRTCP_R_LENGTH)) goto error; @@ -354,7 +357,7 @@ static inline int check_session_keys(struct crypto_context *c) { return 0; error: - mylog(LOG_ERROR, "Error generating SRTCP session keys"); + ilog(LOG_ERROR, "%s", err); return -1; } @@ -382,7 +385,7 @@ static int rtcp_payload(struct rtcp_packet **out, str *p, const str *s) { return 0; error: - mylog(LOG_WARNING, "Error parsing RTCP header: %s", err); + ilog(LOG_WARNING, "Error parsing RTCP header: %s", err); return -1; } @@ -408,8 +411,8 @@ int rtcp_avp2savp(str *s, struct crypto_context *c) { rtp_append_mki(s, c); - c->crypto_suite->hash_rtcp(c, s->s + s->len, &to_auth); - s->len += c->crypto_suite->srtcp_auth_tag; + c->params.crypto_suite->hash_rtcp(c, s->s + s->len, &to_auth); + s->len += c->params.crypto_suite->srtcp_auth_tag; return 1; } @@ -429,7 +432,7 @@ int rtcp_savp2avp(str *s, struct crypto_context *c) { return -1; if (srtp_payloads(&to_auth, &to_decrypt, &auth_tag, NULL, - c->crypto_suite->srtcp_auth_tag, c->mki_len, + c->params.crypto_suite->srtcp_auth_tag, c->params.mki_len, s, &payload)) return -1; @@ -441,7 +444,7 @@ int rtcp_savp2avp(str *s, struct crypto_context *c) { idx = ntohl(*idx_p); assert(sizeof(hmac) >= auth_tag.len); - c->crypto_suite->hash_rtcp(c, hmac, &to_auth); + c->params.crypto_suite->hash_rtcp(c, hmac, &to_auth); err = "authentication failed"; if (str_memcmp(&auth_tag, hmac)) goto error; @@ -457,7 +460,7 @@ int rtcp_savp2avp(str *s, struct crypto_context *c) { return 0; error: - mylog(LOG_WARNING, "Discarded invalid SRTCP packet: %s", err); + ilog(LOG_WARNING, "Discarded invalid SRTCP packet: %s", err); return -1; } diff --git a/daemon/rtp.c b/daemon/rtp.c index 9fb088f36..08b8bc194 100644 --- a/daemon/rtp.c +++ b/daemon/rtp.c @@ -21,19 +21,22 @@ struct rtp_extension { static inline int check_session_keys(struct crypto_context *c) { str s; + const char *err; if (c->have_session_key) return 0; - if (!c->crypto_suite) + err = "SRTP output wanted, but no crypto suite was negotiated"; + if (!c->params.crypto_suite) goto error; - str_init_len(&s, c->session_key, c->crypto_suite->session_key_len); + err = "Failed to generate SRTP session keys"; + str_init_len_assert(&s, c->session_key, c->params.crypto_suite->session_key_len); if (crypto_gen_session_key(c, &s, 0x00, 6)) goto error; - str_init_len(&s, c->session_auth_key, c->crypto_suite->srtp_auth_key_len); + str_init_len_assert(&s, c->session_auth_key, c->params.crypto_suite->srtp_auth_key_len); if (crypto_gen_session_key(c, &s, 0x01, 6)) goto error; - str_init_len(&s, c->session_salt, c->crypto_suite->session_salt_len); + str_init_len_assert(&s, c->session_salt, c->params.crypto_suite->session_salt_len); if (crypto_gen_session_key(c, &s, 0x02, 6)) goto error; @@ -43,7 +46,7 @@ static inline int check_session_keys(struct crypto_context *c) { return 0; error: - mylog(LOG_ERROR, "Error generating SRTP session keys"); + ilog(LOG_ERROR, "%s", err); return -1; } @@ -85,7 +88,7 @@ static int rtp_payload(struct rtp_header **out, str *p, const str *s) { return 0; error: - mylog(LOG_WARNING, "Error parsing RTP header: %s", err); + ilog(LOG_WARNING, "Error parsing RTP header: %s", err); return -1; } @@ -121,31 +124,15 @@ static u_int64_t packet_index(struct crypto_context *c, struct rtp_header *rtp) } void rtp_append_mki(str *s, struct crypto_context *c) { - u_int32_t mki_part; char *p; - if (!c->mki_len) + if (!c->params.mki_len) return; /* RTP_BUFFER_TAIL_ROOM guarantees enough room */ p = s->s + s->len; - memset(p, 0, c->mki_len); - if (c->mki_len > 4) { - mki_part = (c->mki & 0xffffffff00000000ULL) >> 32; - mki_part = htonl(mki_part); - if (c->mki_len < 8) - memcpy(p, ((char *) &mki_part) + (8 - c->mki_len), c->mki_len - 4); - else - memcpy(p + (c->mki_len - 8), &mki_part, 4); - } - mki_part = (c->mki & 0xffffffffULL); - mki_part = htonl(mki_part); - if (c->mki_len < 4) - memcpy(p, ((char *) &mki_part) + (4 - c->mki_len), c->mki_len); - else - memcpy(p + (c->mki_len - 4), &mki_part, 4); - - s->len += c->mki_len; + memcpy(p, c->params.mki, c->params.mki_len); + s->len += c->params.mki_len; } /* rfc 3711, section 3.3 */ @@ -170,9 +157,9 @@ int rtp_avp2savp(str *s, struct crypto_context *c) { rtp_append_mki(s, c); - if (c->crypto_suite->srtp_auth_tag) { - c->crypto_suite->hash_rtp(c, s->s + s->len, &to_auth, index); - s->len += c->crypto_suite->srtp_auth_tag; + if (c->params.crypto_suite->srtp_auth_tag) { + c->params.crypto_suite->hash_rtp(c, s->s + s->len, &to_auth, index); + s->len += c->params.crypto_suite->srtp_auth_tag; } return 0; @@ -192,13 +179,13 @@ int rtp_savp2avp(str *s, struct crypto_context *c) { index = packet_index(c, rtp); if (srtp_payloads(&to_auth, &to_decrypt, &auth_tag, NULL, - c->crypto_suite->srtp_auth_tag, c->mki_len, + c->params.crypto_suite->srtp_auth_tag, c->params.mki_len, s, &payload)) return -1; if (auth_tag.len) { assert(sizeof(hmac) >= auth_tag.len); - c->crypto_suite->hash_rtp(c, hmac, &to_auth, index); + c->params.crypto_suite->hash_rtp(c, hmac, &to_auth, index); if (str_memcmp(&auth_tag, hmac)) goto error; } @@ -211,7 +198,7 @@ int rtp_savp2avp(str *s, struct crypto_context *c) { return 0; error: - mylog(LOG_WARNING, "Discarded invalid SRTP packet: authentication failed"); + ilog(LOG_WARNING, "Discarded invalid SRTP packet: authentication failed"); return -1; } @@ -251,6 +238,6 @@ int srtp_payloads(str *to_auth, str *to_decrypt, str *auth_tag, str *mki, return 0; error: - mylog(LOG_WARNING, "Invalid SRTP/SRTCP packet received (short packet)"); + ilog(LOG_WARNING, "Invalid SRTP/SRTCP packet received (short packet)"); return -1; } diff --git a/daemon/sdp.c b/daemon/sdp.c index 50b198b32..2b29261d7 100644 --- a/daemon/sdp.c +++ b/daemon/sdp.c @@ -11,6 +11,7 @@ #include "str.h" #include "call.h" #include "crypto.h" +#include "dtls.h" struct network_address { str network_type; @@ -97,10 +98,10 @@ struct attribute_crypto { const struct crypto_suite *crypto_suite; str master_key; str salt; - char key_salt_buf[30]; + char key_salt_buf[SRTP_MAX_MASTER_KEY_LEN + SRTP_MAX_MASTER_SALT_LEN]; u_int64_t lifetime; - unsigned int mki, - mki_len; + unsigned char mki[256]; + unsigned int mki_len; }; struct attribute_ssrc { @@ -112,6 +113,32 @@ struct attribute_ssrc { str value; }; +struct attribute_group { + enum { + GROUP_OTHER = 0, + GROUP_BUNDLE, + } semantics; +}; + +struct attribute_fingerprint { + str hash_func_str; + str fingerprint_str; + + const struct dtls_hash_func *hash_func; + unsigned char fingerprint[DTLS_MAX_DIGEST_LEN]; +}; + +struct attribute_setup { + str s; + enum { + SETUP_UNKNOWN = 0, + SETUP_ACTPASS, + SETUP_ACTIVE, + SETUP_PASSIVE, + SETUP_HOLDCONN, + } value; +}; + struct sdp_attribute { str full_line, /* including a= and \r\n */ line_value, /* without a= and without \r\n */ @@ -134,6 +161,10 @@ struct sdp_attribute { ATTR_RECVONLY, ATTR_RTCP_MUX, ATTR_EXTMAP, + ATTR_GROUP, + ATTR_MID, + ATTR_FINGERPRINT, + ATTR_SETUP, } attr; union { @@ -141,6 +172,9 @@ struct sdp_attribute { struct attribute_candidate candidate; struct attribute_crypto crypto; struct attribute_ssrc ssrc; + struct attribute_group group; + struct attribute_fingerprint fingerprint; + struct attribute_setup setup; } u; }; @@ -156,7 +190,7 @@ static str ice_foundation_str_alt; -static int has_rtcp(struct sdp_media *media); +//static int has_rtcp(struct sdp_media *media); @@ -164,6 +198,15 @@ static inline struct sdp_attribute *attr_get_by_id(struct sdp_attributes *a, int return g_hash_table_lookup(a->id_hash, &id); } +static struct sdp_attribute *attr_get_by_id_m_s(struct sdp_media *m, int id) { + struct sdp_attribute *a; + + a = attr_get_by_id(&m->attributes, id); + if (a) + return a; + return attr_get_by_id(&m->session->attributes, id); +} + /* hack hack */ static inline int inet_pton_str(int af, str *src, void *dst) { @@ -177,8 +220,24 @@ static inline int inet_pton_str(int af, str *src, void *dst) { return ret; } +int address_family(const str *s) { + if (s->len != 3) + return 0; + + if (!memcmp(s->s, "IP4", 3) + || !memcmp(s->s, "ip4", 3)) + return AF_INET; + + if (!memcmp(s->s, "IP6", 3) + || !memcmp(s->s, "ip6", 3)) + return AF_INET6; + + return 0; +} + static int __parse_address(struct in6_addr *out, str *network_type, str *address_type, str *address) { struct in_addr in4; + int af; if (network_type) { if (network_type->len != 2) @@ -196,17 +255,15 @@ static int __parse_address(struct in6_addr *out, str *network_type, str *address return -1; } - if (address_type->len != 3) - return -1; - if (!memcmp(address_type->s, "IP4", 3) - || !memcmp(address_type->s, "ip4", 3)) { + af = address_family(address_type); + + if (af == AF_INET) { if (inet_pton_str(AF_INET, address, &in4) != 1) return -1; ip4: in4_to_6(out, in4.s_addr); } - else if (!memcmp(address_type->s, "IP6", 3) - || !memcmp(address_type->s, "ip6", 3)) { + else if (af == AF_INET6) { if (inet_pton_str(AF_INET6, address, out) != 1) return -1; } @@ -306,6 +363,16 @@ static void attrs_init(struct sdp_attributes *a) { NULL, (GDestroyNotify) g_queue_free); } +static int parse_attribute_group(struct sdp_attribute *output) { + output->attr = ATTR_GROUP; + + output->u.group.semantics = GROUP_OTHER; + if (output->value.len >= 7 && !strncmp(output->value.s, "BUNDLE ", 7)) + output->u.group.semantics = GROUP_BUNDLE; + + return 0; +} + static int parse_attribute_ssrc(struct sdp_attribute *output) { char *start, *end; struct attribute_ssrc *s; @@ -334,7 +401,6 @@ static int parse_attribute_ssrc(struct sdp_attribute *output) { return 0; } -/* XXX error handling/logging */ static int parse_attribute_crypto(struct sdp_attribute *output) { char *start, *end, *endp; struct attribute_crypto *c; @@ -343,6 +409,8 @@ static int parse_attribute_crypto(struct sdp_attribute *output) { unsigned int b64_save = 0; gsize ret; str s; + u_int32_t u32; + const char *err; output->attr = ATTR_CRYPTO; @@ -356,27 +424,31 @@ static int parse_attribute_crypto(struct sdp_attribute *output) { c = &output->u.crypto; c->tag = strtoul(c->tag_str.s, &endp, 10); + err = "invalid 'tag'"; if (endp == c->tag_str.s) - return -1; + goto error; c->crypto_suite = crypto_find_suite(&c->crypto_suite_str); + err = "unknown crypto suite"; if (!c->crypto_suite) - return -1; + goto error; salt_key_len = c->crypto_suite->master_key_len + c->crypto_suite->master_salt_len; - assert(sizeof(c->key_salt_buf) >= salt_key_len); enc_salt_key_len = ceil((double) salt_key_len * 4.0/3.0); + err = "invalid key parameter length"; if (c->key_params_str.len < 7 + enc_salt_key_len) - return -1; + goto error; + err = "unknown key method"; if (strncasecmp(c->key_params_str.s, "inline:", 7)) - return -1; + goto error; c->key_base64_str = c->key_params_str; str_shift(&c->key_base64_str, 7); ret = g_base64_decode_step(c->key_base64_str.s, enc_salt_key_len, (guchar *) c->key_salt_buf, &b64_state, &b64_save); + err = "invalid base64 encoding"; if (ret != salt_key_len) - return -1; + goto error; c->master_key.s = c->key_salt_buf; c->master_key.len = c->crypto_suite->master_key_len; @@ -386,8 +458,9 @@ static int parse_attribute_crypto(struct sdp_attribute *output) { c->lifetime_str = c->key_params_str; str_shift(&c->lifetime_str, 7 + enc_salt_key_len); if (c->lifetime_str.len >= 2) { + err = "invalid key parameter syntax"; if (c->lifetime_str.s[0] != '|') - return -1; + goto error; str_shift(&c->lifetime_str, 1); str_chr_str(&c->mki_str, &c->lifetime_str, '|'); if (!c->mki_str.s) { @@ -407,29 +480,42 @@ static int parse_attribute_crypto(struct sdp_attribute *output) { if (c->lifetime_str.s) { if (c->lifetime_str.len >= 3 && !memcmp(c->lifetime_str.s, "2^", 2)) { c->lifetime = strtoull(c->lifetime_str.s + 2, NULL, 10); + err = "invalid key lifetime"; if (!c->lifetime || c->lifetime > 64) - return -1; + goto error; c->lifetime = 1 << c->lifetime; } else c->lifetime = strtoull(c->lifetime_str.s, NULL, 10); + err = "invalid key lifetime"; if (!c->lifetime || c->lifetime > c->crypto_suite->srtp_lifetime || c->lifetime > c->crypto_suite->srtcp_lifetime) - return -1; + goto error; } if (c->mki_str.s) { str_chr_str(&s, &c->mki_str, ':'); + err = "invalid MKI specification"; if (!s.s) - return -1; - c->mki = strtoul(c->mki_str.s, NULL, 10); + goto error; + u32 = htonl(strtoul(c->mki_str.s, NULL, 10)); c->mki_len = strtoul(s.s + 1, NULL, 10); - if (!c->mki || !c->mki_len || c->mki_len > 128) - return -1; + err = "MKI too long"; + if (c->mki_len > sizeof(c->mki)) + goto error; + memset(c->mki, 0, c->mki_len); + if (sizeof(u32) >= c->mki_len) + memcpy(c->mki, ((void *) &u32) + (sizeof(u32) - c->mki_len), c->mki_len); + else + memcpy(c->mki + (c->mki_len - sizeof(u32)), &u32, sizeof(u32)); } return 0; + +error: + ilog(LOG_ERROR, "Failed to parse a=crypto attribute: %s", err); + return -1; } static int parse_attribute_rtcp(struct sdp_attribute *output) { @@ -479,6 +565,76 @@ static int parse_attribute_candidate(struct sdp_attribute *output) { return 0; } +static int parse_attribute_fingerprint(struct sdp_attribute *output) { + char *end, *start; + unsigned char *c; + int i; + + start = output->value.s; + end = start + output->value.len; + output->attr = ATTR_FINGERPRINT; + + EXTRACT_TOKEN(u.fingerprint.hash_func_str); + EXTRACT_TOKEN(u.fingerprint.fingerprint_str); + + output->u.fingerprint.hash_func = dtls_find_hash_func(&output->u.fingerprint.hash_func_str); + if (!output->u.fingerprint.hash_func) + return -1; + + assert(sizeof(output->u.fingerprint.fingerprint) >= output->u.fingerprint.hash_func->num_bytes); + + c = (unsigned char *) output->u.fingerprint.fingerprint_str.s; + for (i = 0; i < output->u.fingerprint.hash_func->num_bytes; i++) { + if (c[0] >= '0' && c[0] <= '9') + output->u.fingerprint.fingerprint[i] = c[0] - '0'; + else if (c[0] >= 'a' && c[0] <= 'f') + output->u.fingerprint.fingerprint[i] = c[0] - 'a' + 10; + else if (c[0] >= 'A' && c[0] <= 'F') + output->u.fingerprint.fingerprint[i] = c[0] - 'A' + 10; + else + return -1; + + output->u.fingerprint.fingerprint[i] <<= 4; + + if (c[1] >= '0' && c[1] <= '9') + output->u.fingerprint.fingerprint[i] |= c[1] - '0'; + else if (c[1] >= 'a' && c[1] <= 'f') + output->u.fingerprint.fingerprint[i] |= c[1] - 'a' + 10; + else if (c[1] >= 'A' && c[1] <= 'F') + output->u.fingerprint.fingerprint[i] |= c[1] - 'A' + 10; + else + return -1; + + if (c[2] != ':') + goto done; + + c += 3; + } + + return -1; + +done: + if (++i != output->u.fingerprint.hash_func->num_bytes) + return -1; + + return 0; +} + +static int parse_attribute_setup(struct sdp_attribute *output) { + output->attr = ATTR_SETUP; + + if (!str_cmp(&output->value, "actpass")) + output->u.setup.value = SETUP_ACTPASS; + else if (!str_cmp(&output->value, "active")) + output->u.setup.value = SETUP_ACTIVE; + else if (!str_cmp(&output->value, "passive")) + output->u.setup.value = SETUP_PASSIVE; + else if (!str_cmp(&output->value, "holdconn")) + output->u.setup.value = SETUP_HOLDCONN; + + return 0; +} + static int parse_attribute(struct sdp_attribute *a) { int ret; @@ -507,16 +663,26 @@ static int parse_attribute(struct sdp_attribute *a) { ret = 0; switch (a->name.len) { + case 3: + if (!str_cmp(&a->name, "mid")) + a->attr = ATTR_MID; + break; case 4: if (!str_cmp(&a->name, "rtcp")) ret = parse_attribute_rtcp(a); else if (!str_cmp(&a->name, "ssrc")) ret = parse_attribute_ssrc(a); break; + case 5: + if (!str_cmp(&a->name, "group")) + ret = parse_attribute_group(a); + else if (!str_cmp(&a->name, "setup")) + ret = parse_attribute_setup(a); + break; case 6: if (!str_cmp(&a->name, "crypto")) ret = parse_attribute_crypto(a); - if (!str_cmp(&a->name, "extmap")) + else if (!str_cmp(&a->name, "extmap")) a->attr = ATTR_EXTMAP; break; case 7: @@ -554,6 +720,8 @@ static int parse_attribute(struct sdp_attribute *a) { case 11: if (!str_cmp(&a->name, "ice-options")) a->attr = ATTR_ICE; + else if (!str_cmp(&a->name, "fingerprint")) + ret = parse_attribute_fingerprint(a); break; case 12: if (!str_cmp(&a->name, "ice-mismatch")) @@ -724,7 +892,7 @@ int sdp_parse(str *body, GQueue *sessions) { return 0; error: - mylog(LOG_WARNING, "Error parsing SDP at offset %li: %s", b - body->s, errstr); + ilog(LOG_WARNING, "Error parsing SDP at offset %li: %s", b - body->s, errstr); sdp_free(sessions); return -1; } @@ -755,7 +923,8 @@ void sdp_free(GQueue *sessions) { } } -static int fill_stream_address(struct stream_input *si, struct sdp_media *media, struct sdp_ng_flags *flags) { +static int fill_endpoint(struct endpoint *ep, const struct sdp_media *media, struct sdp_ng_flags *flags, + struct network_address *address, long int port) { struct sdp_session *session = media->session; if (!flags->trust_address) { @@ -764,44 +933,31 @@ static int fill_stream_address(struct stream_input *si, struct sdp_media *media, &flags->received_from_address)) return -1; } - si->stream.ip46 = flags->parsed_received_from; + ep->ip46 = flags->parsed_received_from; } + else if (address && !is_addr_unspecified(&address->parsed)) + ep->ip46 = address->parsed; else if (media->connection.parsed) - si->stream.ip46 = media->connection.address.parsed; + ep->ip46 = media->connection.address.parsed; else if (session->connection.parsed) - si->stream.ip46 = session->connection.address.parsed; + ep->ip46 = session->connection.address.parsed; else return -1; - return 0; -} -static int fill_stream(struct stream_input *si, struct sdp_media *media, int offset, struct sdp_ng_flags *flags) { - if (fill_stream_address(si, media, flags)) - return -1; - - /* we ignore the media type */ - si->stream.port = (media->port_num + (offset * 2)) & 0xffff; + ep->port = port; return 0; } -static int fill_stream_rtcp(struct stream_input *si, struct sdp_media *media, int port, struct sdp_ng_flags *flags) { - if (fill_stream_address(si, media, flags)) - return -1; - si->stream.port = port; - return 0; -} -int sdp_streams(const GQueue *sessions, GQueue *streams, GHashTable *streamhash, struct sdp_ng_flags *flags) { +int sdp_streams(const GQueue *sessions, GQueue *streams, struct sdp_ng_flags *flags) { struct sdp_session *session; struct sdp_media *media; - struct stream_input *si; + struct stream_params *sp; GList *l, *k; const char *errstr; - int i, num; + int num; struct sdp_attribute *attr; - enum transport_protocol tp; - struct crypto_context cctx; num = 0; for (l = sessions->head; l; l = l->next) { @@ -809,87 +965,106 @@ int sdp_streams(const GQueue *sessions, GQueue *streams, GHashTable *streamhash, for (k = session->media_streams.head; k; k = k->next) { media = k->data; - tp = transport_protocol(&media->transport); - ZERO(cctx); - attr = attr_get_by_id(&media->attributes, ATTR_CRYPTO); - if (attr) { - cctx.crypto_suite = attr->u.crypto.crypto_suite; - cctx.mki = attr->u.crypto.mki; - cctx.mki_len = attr->u.crypto.mki_len; - cctx.tag = attr->u.crypto.tag; - assert(sizeof(cctx.master_key) >= attr->u.crypto.master_key.len); - assert(sizeof(cctx.master_salt) >= attr->u.crypto.salt.len); - memcpy(cctx.master_key, attr->u.crypto.master_key.s, attr->u.crypto.master_key.len); - memcpy(cctx.master_salt, attr->u.crypto.salt.s, attr->u.crypto.salt.len); - assert(sizeof(cctx.session_key) >= cctx.crypto_suite->session_key_len); - assert(sizeof(cctx.session_salt) >= cctx.crypto_suite->session_salt_len); - } + sp = g_slice_alloc0(sizeof(*sp)); + sp->index = ++num; - si = NULL; - for (i = 0; i < media->port_count; i++) { - si = g_slice_alloc0(sizeof(*si)); + errstr = "No address info found for stream"; + if (fill_endpoint(&sp->rtp_endpoint, media, flags, NULL, media->port_num)) + goto error; - errstr = "No address info found for stream"; - if (fill_stream(si, media, i, flags)) - goto error; + sp->consecutive_ports = media->port_count; + sp->protocol = transport_protocol(&media->transport); + sp->type = media->media_type; + memcpy(sp->direction, flags->directions, sizeof(sp->direction)); + sp->desired_family = flags->address_family; + sp->asymmetric = flags->asymmetric; - if (i == 0 && g_hash_table_contains(streamhash, si)) { - g_slice_free1(sizeof(*si), si); - continue; + /* a=crypto */ + attr = attr_get_by_id(&media->attributes, ATTR_CRYPTO); + if (attr) { + sp->crypto.crypto_suite = attr->u.crypto.crypto_suite; + sp->crypto.mki_len = attr->u.crypto.mki_len; + if (sp->crypto.mki_len) { + sp->crypto.mki = malloc(sp->crypto.mki_len); + memcpy(sp->crypto.mki, attr->u.crypto.mki, sp->crypto.mki_len); } + sp->sdes_tag = attr->u.crypto.tag; + assert(sizeof(sp->crypto.master_key) >= attr->u.crypto.master_key.len); + assert(sizeof(sp->crypto.master_salt) >= attr->u.crypto.salt.len); + memcpy(sp->crypto.master_key, attr->u.crypto.master_key.s, + attr->u.crypto.master_key.len); + memcpy(sp->crypto.master_salt, attr->u.crypto.salt.s, + attr->u.crypto.salt.len); + } + + /* a=sendrecv/sendonly/recvonly/inactive */ + sp->send = 1; + sp->recv = 1; + if (attr_get_by_id_m_s(media, ATTR_RECVONLY)) + sp->send = 0; + else if (attr_get_by_id_m_s(media, ATTR_SENDONLY)) + sp->recv = 0; + else if (attr_get_by_id_m_s(media, ATTR_INACTIVE)) + { + sp->recv = 0; + sp->send = 0; + } - si->stream.num = ++num; - si->consecutive_num = (i == 0) ? media->port_count : 1; - si->stream.protocol = tp; - si->crypto = cctx; - memcpy(&si->direction, &flags->directions, sizeof(si->direction)); + /* a=setup */ + attr = attr_get_by_id_m_s(media, ATTR_SETUP); + if (attr) { + if (attr->u.setup.value == SETUP_ACTPASS + || attr->u.setup.value == SETUP_ACTIVE) + sp->setup_active = 1; + if (attr->u.setup.value == SETUP_ACTPASS + || attr->u.setup.value == SETUP_PASSIVE) + sp->setup_passive = 1; + } - g_hash_table_insert(streamhash, si, si); - g_queue_push_tail(streams, si); + /* a=fingerprint */ + attr = attr_get_by_id_m_s(media, ATTR_FINGERPRINT); + if (attr && attr->u.fingerprint.hash_func) { + sp->fingerprint.hash_func = attr->u.fingerprint.hash_func; + memcpy(sp->fingerprint.digest, attr->u.fingerprint.fingerprint, + sp->fingerprint.hash_func->num_bytes); } - if (!si || media->port_count != 1) - continue; + /* determine RTCP endpoint */ if (attr_get_by_id(&media->attributes, ATTR_RTCP_MUX)) { - si->rtcp_mux = 1; - continue; + sp->rtcp_mux = 1; + goto next; } + if (media->port_count != 1) + goto next; + attr = attr_get_by_id(&media->attributes, ATTR_RTCP); - if (!attr || !attr->u.rtcp.port_num) - continue; - if (attr->u.rtcp.port_num == si->stream.port) { - si->rtcp_mux = 1; - continue; + if (!attr) { + sp->implicit_rtcp = 1; + goto next; } - if (attr->u.rtcp.port_num == si->stream.port + 1) - continue; - - si->has_rtcp = 1; - - si = g_slice_alloc0(sizeof(*si)); - if (fill_stream_rtcp(si, media, attr->u.rtcp.port_num, flags)) + if (attr->u.rtcp.port_num == sp->rtp_endpoint.port) { + sp->rtcp_mux = 1; + goto next; + } + errstr = "Invalid RTCP attribute"; + if (fill_endpoint(&sp->rtcp_endpoint, media, flags, &attr->u.rtcp.address, + attr->u.rtcp.port_num)) goto error; - si->stream.num = ++num; - si->consecutive_num = 1; - si->is_rtcp = 1; - si->stream.protocol = tp; - si->crypto = cctx; - memcpy(&si->direction, &flags->directions, sizeof(si->direction)); - - g_hash_table_insert(streamhash, si, si); - g_queue_push_tail(streams, si); + +next: + g_queue_push_tail(streams, sp); } } return 0; error: - mylog(LOG_WARNING, "Failed to extract streams from SDP: %s", errstr); - if (si) - g_slice_free1(sizeof(*si), si); + ilog(LOG_WARNING, "Failed to extract streams from SDP: %s", errstr); + if (sp) + g_slice_free1(sizeof(*sp), sp); return -1; } @@ -924,7 +1099,7 @@ static void chopper_append_dup(struct sdp_chopper *c, const char *s, int len) { static void chopper_append_printf(struct sdp_chopper *c, const char *fmt, ...) __attribute__((format(printf,2,3))); static void chopper_append_printf(struct sdp_chopper *c, const char *fmt, ...) { - char buf[32]; + char buf[512]; int l; va_list va; @@ -943,7 +1118,7 @@ static int copy_up_to_ptr(struct sdp_chopper *chop, const char *b) { len = offset - chop->position; if (len < 0) { - mylog(LOG_WARNING, "Malformed SDP, cannot rewrite"); + ilog(LOG_WARNING, "Malformed SDP, cannot rewrite"); return -1; } chopper_append(chop, chop->input->s + chop->position, len); @@ -972,53 +1147,33 @@ static int skip_over(struct sdp_chopper *chop, str *where) { len = offset - chop->position; if (len < 0) { - mylog(LOG_WARNING, "Malformed SDP, cannot rewrite"); + ilog(LOG_WARNING, "Malformed SDP, cannot rewrite"); return -1; } chop->position += len; return 0; } -static int fill_relays(struct streamrelay **rtp, struct streamrelay **rtcp, GList *m, - int off, struct stream_input *sip, struct sdp_media *media) -{ - *rtp = &((struct callstream *) m->data)->peers[off].rtps[0]; - - if (!rtcp) - return 1; - - *rtcp = &((struct callstream *) m->data)->peers[off].rtps[1]; - if (sip && sip->has_rtcp && m->next) - *rtcp = &((struct callstream *) m->next->data)->peers[off].rtps[0]; - - if ((*rtp)->rtcp_mux) - return 2; - if (!has_rtcp(media)) - return 3; - - return 0; -} - static int replace_transport_protocol(struct sdp_chopper *chop, - struct sdp_media *media, struct streamrelay *sr) + struct sdp_media *media, struct call_media *cm) { str *tp = &media->transport; - const char *new_tp = transport_protocol_strings[sr->peer.protocol]; - if (!new_tp) + if (!cm->protocol) return 0; /* XXX correct? or give warning? */ if (copy_up_to(chop, tp)) return -1; - chopper_append_c(chop, new_tp); + chopper_append_c(chop, cm->protocol->name); if (skip_over(chop, tp)) return -1; return 0; } -static int replace_media_port(struct sdp_chopper *chop, struct sdp_media *media, struct streamrelay *sr) { +static int replace_media_port(struct sdp_chopper *chop, struct sdp_media *media, struct packet_stream *ps) { str *port = &media->port; + unsigned int p; if (!media->port_num) return 0; @@ -1026,7 +1181,8 @@ static int replace_media_port(struct sdp_chopper *chop, struct sdp_media *media, if (copy_up_to(chop, port)) return -1; - chopper_append_printf(chop, "%hu", sr->fd.localport); + p = ps->sfd ? ps->sfd->fd.localport : 0; + chopper_append_printf(chop, "%u", p); if (skip_over(chop, port)) return -1; @@ -1035,22 +1191,22 @@ static int replace_media_port(struct sdp_chopper *chop, struct sdp_media *media, } static int replace_consecutive_port_count(struct sdp_chopper *chop, struct sdp_media *media, - struct streamrelay *rtp, GList *m, int off) + struct packet_stream *ps, GList *j) { int cons; - struct streamrelay *sr; + struct packet_stream *ps_n; - if (media->port_count == 1) + if (media->port_count == 1 || !ps->sfd) return 0; for (cons = 1; cons < media->port_count; cons++) { - m = m->next; - if (!m) + j = j->next; + if (!j) goto warn; - fill_relays(&sr, NULL, m, off, NULL, media); - if (sr->fd.localport != rtp->fd.localport + cons * 2) { + ps_n = j->data; + if (ps_n->sfd->fd.localport != ps->sfd->fd.localport + cons * 2) { warn: - mylog(LOG_WARN, "Failed to handle consecutive ports"); + ilog(LOG_WARN, "Failed to handle consecutive ports"); break; } } @@ -1060,34 +1216,30 @@ warn: return 0; } -static int insert_ice_address(struct sdp_chopper *chop, struct streamrelay *sr) { +static int insert_ice_address(struct sdp_chopper *chop, struct packet_stream *ps) { char buf[64]; int len; - mutex_lock(&sr->up->up->lock); - call_stream_address(buf, sr->up, SAF_ICE, &len); - mutex_unlock(&sr->up->up->lock); + call_stream_address(buf, ps, SAF_ICE, &len); chopper_append_dup(chop, buf, len); - chopper_append_printf(chop, " %hu", sr->fd.localport); + chopper_append_printf(chop, " %hu", ps->sfd->fd.localport); return 0; } -static int insert_ice_address_alt(struct sdp_chopper *chop, struct streamrelay *sr) { +static int insert_ice_address_alt(struct sdp_chopper *chop, struct packet_stream *ps) { char buf[64]; int len; - mutex_lock(&sr->up->up->lock); - call_stream_address_alt(buf, sr->up, SAF_ICE, &len); - mutex_unlock(&sr->up->up->lock); + call_stream_address_alt(buf, ps, SAF_ICE, &len); chopper_append_dup(chop, buf, len); - chopper_append_printf(chop, " %hu", sr->fd.localport); + chopper_append_printf(chop, " %hu", ps->sfd->fd.localport); return 0; } static int replace_network_address(struct sdp_chopper *chop, struct network_address *address, - struct streamrelay *sr, struct sdp_ng_flags *flags) + struct packet_stream *ps, struct sdp_ng_flags *flags) { char buf[64]; int len; @@ -1098,6 +1250,9 @@ static int replace_network_address(struct sdp_chopper *chop, struct network_addr if (copy_up_to(chop, &address->address_type)) return -1; + if (flags->media_address.s && is_addr_unspecified(&flags->parsed_media_address)) + __parse_address(&flags->parsed_media_address, NULL, NULL, &flags->media_address); + if (!is_addr_unspecified(&flags->parsed_media_address)) { if (IN6_IS_ADDR_V4MAPPED(&flags->parsed_media_address)) len = sprintf(buf, "IP4 " IPF, IPP(flags->parsed_media_address.s6_addr32[3])); @@ -1107,11 +1262,8 @@ static int replace_network_address(struct sdp_chopper *chop, struct network_addr len = strlen(buf); } } - else { - mutex_lock(&sr->up->up->lock); - call_stream_address(buf, sr->up, SAF_NG, &len); - mutex_unlock(&sr->up->up->lock); - } + else + call_stream_address(buf, ps, SAF_NG, &len); chopper_append_dup(chop, buf, len); if (skip_over(chop, &address->address)) @@ -1126,12 +1278,6 @@ void sdp_chopper_destroy(struct sdp_chopper *chop) { g_slice_free1(sizeof(*chop), chop); } -/* XXX replace with better source of randomness */ -static void random_string(unsigned char *buf, int len) { - while (len--) - *buf++ = random() % 0x100; -} - static void random_ice_string(char *buf, int len) { while (len--) *buf++ = ice_chars[random() % strlen(ice_chars)]; @@ -1166,8 +1312,19 @@ static int process_session_attributes(struct sdp_chopper *chop, struct sdp_attri goto strip; case ATTR_EXTMAP: + case ATTR_INACTIVE: + case ATTR_SENDONLY: + case ATTR_RECVONLY: + case ATTR_SENDRECV: + case ATTR_FINGERPRINT: + case ATTR_SETUP: goto strip; + case ATTR_GROUP: + if (attr->u.group.semantics == GROUP_BUNDLE) + goto strip; + break; + default: break; } @@ -1184,11 +1341,12 @@ strip: return 0; } -static int process_media_attributes(struct sdp_chopper *chop, struct sdp_attributes *attrs, - struct sdp_ng_flags *flags) +static int process_media_attributes(struct sdp_chopper *chop, struct sdp_media *sdp, + struct sdp_ng_flags *flags, struct call_media *media) { GList *l; - struct sdp_attribute *attr; + struct sdp_attributes *attrs = &sdp->attributes; + struct sdp_attribute *attr, *a; for (l = attrs->list.head; l; l = l->next) { attr = l->data; @@ -1204,16 +1362,19 @@ static int process_media_attributes(struct sdp_chopper *chop, struct sdp_attribu case ATTR_RTCP: case ATTR_RTCP_MUX: case ATTR_EXTMAP: + case ATTR_CRYPTO: + case ATTR_INACTIVE: + case ATTR_SENDONLY: + case ATTR_RECVONLY: + case ATTR_SENDRECV: + case ATTR_FINGERPRINT: + case ATTR_SETUP: goto strip; - case ATTR_CRYPTO: - switch (flags->transport_protocol) { - case PROTO_RTP_AVP: - case PROTO_RTP_AVPF: - goto strip; - default: - break; - } + case ATTR_MID: + a = attr_get_by_id(&sdp->session->attributes, ATTR_GROUP); + if (a && a->u.group.semantics == GROUP_BUNDLE) + goto strip; break; default: @@ -1232,29 +1393,6 @@ strip: return 0; } -static GList *find_stream_num(GList *m, int num) { - /* XXX use a hash instead? must link input streams to output streams */ - while (m && ((struct callstream *) m->data)->num < num) - m = m->next; - while (m && ((struct callstream *) m->data)->num > num) - m = m->prev; - return m; -} - -static int has_rtcp(struct sdp_media *media) { - struct sdp_session *session; - - if (!media) - return 0; - - session = media->session; - - if ((media->rr == -1 ? session->rr : media->rr) != 0 - && (media->rs == -1 ? session->rs : media->rs) != 0) - return 1; - return 0; -} - static unsigned long prio_calc(unsigned int pref) { return (1 << 24) * 126 + (1 << 8) * pref + 256 * 1; } @@ -1290,7 +1428,7 @@ out: return prio; } -static void insert_candidates(struct sdp_chopper *chop, struct streamrelay *rtp, struct streamrelay *rtcp, +static void insert_candidates(struct sdp_chopper *chop, struct packet_stream *rtp, struct packet_stream *rtcp, unsigned long priority, struct sdp_media *media) { chopper_append_c(chop, "a=candidate:"); @@ -1310,7 +1448,7 @@ static void insert_candidates(struct sdp_chopper *chop, struct streamrelay *rtp, } -static void insert_candidates_alt(struct sdp_chopper *chop, struct streamrelay *rtp, struct streamrelay *rtcp, +static void insert_candidates_alt(struct sdp_chopper *chop, struct packet_stream *rtp, struct packet_stream *rtcp, unsigned long priority, struct sdp_media *media) { chopper_append_c(chop, "a=candidate:"); @@ -1351,144 +1489,129 @@ static int has_ice(GQueue *sessions) { return 0; } -static int generate_crypto(struct sdp_media *media, struct sdp_ng_flags *flags, - struct streamrelay *rtp, struct streamrelay *rtcp, - struct sdp_chopper *chop) -{ - struct crypto_context *c, *src = NULL; - char b64_buf[64]; - char *p; - int state = 0, save = 0; +static void insert_dtls(struct call_media *media, struct sdp_chopper *chop) { + char hexbuf[DTLS_MAX_DIGEST_LEN * 3 + 2]; + unsigned char *p; + char *o; + int i; + const struct dtls_hash_func *hf; + const char *actpass; + struct call *call = media->call; - if (flags->transport_protocol != PROTO_RTP_SAVP - && flags->transport_protocol != PROTO_RTP_SAVPF) - return 0; + if (!call->dtls_cert || !media->dtls) + return; - if (attr_get_by_id(&media->attributes, ATTR_CRYPTO)) { - /* SRTP <> SRTP case, copy from other stream - * and leave SDP untouched */ - src = &rtp->other->crypto.in; - - mutex_lock(&rtp->up->up->lock); - c = &rtp->crypto.out; - if (!c->crypto_suite) - *c = *src; - mutex_unlock(&rtp->up->up->lock); - - if (rtcp) { - mutex_lock(&rtcp->up->up->lock); - c = &rtcp->crypto.out; - if (!c->crypto_suite) - *c = *src; - mutex_unlock(&rtcp->up->up->lock); - } + hf = call->dtls_cert->fingerprint.hash_func; - return 0; - } + assert(hf->num_bytes > 0); + + p = call->dtls_cert->fingerprint.digest; + o = hexbuf; + for (i = 0; i < hf->num_bytes; i++) + o += sprintf(o, "%02X:", *p++); + *(--o) = '\0'; - mutex_lock(&rtp->up->up->lock); - - /* write-once, read-only */ - c = &rtp->crypto.out; - if (!c->crypto_suite) { - c->crypto_suite = rtp->crypto.in.crypto_suite; - if (!c->crypto_suite) - c->crypto_suite = &crypto_suites[0]; - random_string((unsigned char *) c->master_key, - c->crypto_suite->master_key_len); - random_string((unsigned char *) c->master_salt, - c->crypto_suite->master_salt_len); - /* mki = mki_len = 0 */ - c->tag = rtp->crypto.in.tag; + actpass = "holdconn"; + if (media->setup_passive) { + if (media->setup_active) + actpass = "actpass"; + else + actpass = "passive"; } + else if (media->setup_active) + actpass = "active"; + + chopper_append_c(chop, "a=setup:"); + chopper_append_c(chop, actpass); + chopper_append_c(chop, "\r\na=fingerprint:"); + chopper_append_c(chop, hf->name); + chopper_append_c(chop, " "); + chopper_append_dup(chop, hexbuf, o - hexbuf); + chopper_append_c(chop, "\r\n"); +} - mutex_unlock(&rtp->up->up->lock); +static void insert_crypto(struct call_media *media, struct sdp_chopper *chop) { + char b64_buf[((SRTP_MAX_MASTER_KEY_LEN + SRTP_MAX_MASTER_SALT_LEN) / 3 + 1) * 4 + 4]; + char *p; + int state = 0, save = 0, i; + struct crypto_params *cp = &media->sdes_out.params; + unsigned long long ull; - assert(sizeof(b64_buf) >= (((c->crypto_suite->master_key_len - + c->crypto_suite->master_salt_len)) / 3 + 1) * 4 + 4); + if (!cp->crypto_suite || !media->sdes) + return; p = b64_buf; - p += g_base64_encode_step((unsigned char *) c->master_key, - c->crypto_suite->master_key_len, 0, + p += g_base64_encode_step((unsigned char *) cp->master_key, + cp->crypto_suite->master_key_len, 0, p, &state, &save); - p += g_base64_encode_step((unsigned char *) c->master_salt, - c->crypto_suite->master_salt_len, 0, + p += g_base64_encode_step((unsigned char *) cp->master_salt, + cp->crypto_suite->master_salt_len, 0, p, &state, &save); p += g_base64_encode_close(0, p, &state, &save); - if (rtcp) { - mutex_lock(&rtcp->up->up->lock); - - src = c; - c = &rtcp->crypto.out; - - c->crypto_suite = src->crypto_suite; - c->tag = src->tag; - memcpy(c->master_key, src->master_key, - c->crypto_suite->master_key_len); - memcpy(c->master_salt, src->master_salt, - c->crypto_suite->master_salt_len); - - mutex_unlock(&rtcp->up->up->lock); - } - chopper_append_c(chop, "a=crypto:"); - chopper_append_printf(chop, "%u ", c->tag); - chopper_append_c(chop, c->crypto_suite->name); + chopper_append_printf(chop, "%u ", media->sdes_out.tag); + chopper_append_c(chop, cp->crypto_suite->name); chopper_append_c(chop, " inline:"); chopper_append_dup(chop, b64_buf, p - b64_buf); + if (cp->mki_len) { + ull = 0; + for (i = 0; i < cp->mki_len && i < sizeof(ull); i++) + ull |= cp->mki[cp->mki_len - i - 1] << (i * 8); + chopper_append_printf(chop, "|%llu:%u", ull, cp->mki_len); + } chopper_append_c(chop, "\r\n"); - - return 0; } -int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call *call, - enum call_opmode opmode, struct sdp_ng_flags *flags, GHashTable *streamhash) +/* called with call->master_lock held in W */ +int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call_monologue *monologue, + struct sdp_ng_flags *flags) { struct sdp_session *session; - struct sdp_media *media; - GList *l, *k, *m; - int off, do_ice, r_flags, sess_conn; - struct stream_input si, *sip; - struct streamrelay *rtp, *rtcp; + struct sdp_media *sdp_media; + GList *l, *k, *m, *j; + int do_ice, media_index, sess_conn; unsigned long priority; + struct call_media *call_media; + struct packet_stream *ps, *ps_rtcp; + struct call *call; - off = opmode; - m = call->callstreams->head; + m = monologue->medias.head; do_ice = (flags->ice_force || (!has_ice(sessions) && !flags->ice_remove)) ? 1 : 0; - if (flags->media_address.s) { - if (is_addr_unspecified(&flags->parsed_media_address)) { - if (__parse_address(&flags->parsed_media_address, NULL, NULL, - &flags->media_address)) - return -1; - } - } + call = monologue->call; for (l = sessions->head; l; l = l->next) { session = l->data; + if (!m) + goto error; + call_media = m->data; + if (call_media->index != 1) + goto error; + j = call_media->streams.head; + if (!j) + goto error; + ps = j->data; sess_conn = 0; if (flags->replace_sess_conn) sess_conn = 1; else { for (k = session->media_streams.head; k; k = k->next) { - media = k->data; - if (!media->connection.parsed) { + sdp_media = k->data; + if (!sdp_media->connection.parsed) { sess_conn = 1; break; } } } - fill_relays(&rtp, &rtcp, m, off, NULL, NULL); - if (session->origin.parsed && flags->replace_origin) { - if (replace_network_address(chop, &session->origin.address, rtp, flags)) + if (replace_network_address(chop, &session->origin.address, ps, flags)) goto error; } if (session->connection.parsed && sess_conn) { - if (replace_network_address(chop, &session->connection.address, rtp, flags)) + if (replace_network_address(chop, &session->connection.address, ps, flags)) goto error; } @@ -1500,96 +1623,110 @@ int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call *call, chopper_append_c(chop, "a=ice-lite\r\n"); } - for (k = session->media_streams.head; k; k = k->next) { - media = k->data; + media_index = 1; - if (fill_stream(&si, media, 0, flags)) + for (k = session->media_streams.head; k; k = k->next) { + sdp_media = k->data; + if (!m) goto error; - - sip = g_hash_table_lookup(streamhash, &si); - if (!sip) + call_media = m->data; + if (call_media->index != media_index) goto error; - m = find_stream_num(m, sip->stream.num); - if (!m) + j = call_media->streams.head; + if (!j) goto error; - r_flags = fill_relays(&rtp, &rtcp, m, off, sip, media); - - rtp->peer.protocol = flags->transport_protocol; - rtcp->peer.protocol = rtp->peer.protocol; + ps = j->data; - if (replace_media_port(chop, media, rtp)) + if (replace_media_port(chop, sdp_media, ps)) goto error; - if (replace_consecutive_port_count(chop, media, rtp, m, off)) + if (replace_consecutive_port_count(chop, sdp_media, ps, j)) goto error; - if (replace_transport_protocol(chop, media, rtp)) + if (replace_transport_protocol(chop, sdp_media, call_media)) goto error; - if (media->connection.parsed) { - if (replace_network_address(chop, &media->connection.address, rtp, flags)) + if (sdp_media->connection.parsed) { + if (replace_network_address(chop, &sdp_media->connection.address, ps, flags)) goto error; } - if (process_media_attributes(chop, &media->attributes, flags)) + if (process_media_attributes(chop, sdp_media, flags, call_media)) goto error; - copy_up_to_end_of(chop, &media->s); + copy_up_to_end_of(chop, &sdp_media->s); - if (!media->port_num) { - if (!attr_get_by_id(&media->attributes, ATTR_INACTIVE)) - chopper_append_c(chop, "a=inactive\r\n"); - continue; + ps_rtcp = NULL; + if (ps->rtcp_sibling) { + ps_rtcp = ps->rtcp_sibling; + j = j->next; + if (!j) + goto error; + assert(j->data == ps_rtcp); } - if (r_flags == 0) { - chopper_append_c(chop, "a=rtcp:"); - chopper_append_printf(chop, "%hu", rtcp->fd.localport); - chopper_append_c(chop, "\r\n"); + if (!sdp_media->port_num || !ps->sfd) { + chopper_append_c(chop, "a=inactive\r\n"); + goto next; } - else if (r_flags == 2) { + + if (call_media->send && call_media->recv) + chopper_append_c(chop, "a=sendrecv\r\n"); + else if (call_media->send && !call_media->recv) + chopper_append_c(chop, "a=sendonly\r\n"); + else if (!call_media->send && call_media->recv) + chopper_append_c(chop, "a=recvonly\r\n"); + else + chopper_append_c(chop, "a=inactive\r\n"); + + if (call_media->rtcp_mux && flags->opmode == OP_ANSWER) { chopper_append_c(chop, "a=rtcp:"); - chopper_append_printf(chop, "%hu", rtp->fd.localport); + chopper_append_printf(chop, "%hu", ps->sfd->fd.localport); chopper_append_c(chop, "\r\na=rtcp-mux\r\n"); + ps_rtcp = NULL; + } + else if (ps_rtcp) { + chopper_append_c(chop, "a=rtcp:"); + chopper_append_printf(chop, "%hu", ps_rtcp->sfd->fd.localport); + if (!call_media->rtcp_mux) + chopper_append_c(chop, "\r\n"); + else + chopper_append_c(chop, "\r\na=rtcp-mux\r\n"); } - generate_crypto(media, flags, rtp, rtcp, chop); + insert_crypto(call_media, chop); + insert_dtls(call_media, chop); if (do_ice) { - mutex_lock(&rtp->up->up->lock); - if (!rtp->up->ice_ufrag.s) { - create_random_ice_string(call, &rtp->up->ice_ufrag, 8); - create_random_ice_string(call, &rtp->up->ice_pwd, 28); + if (!call_media->ice_ufrag.s) { + create_random_ice_string(call, &call_media->ice_ufrag, 8); + create_random_ice_string(call, &call_media->ice_pwd, 28); } - rtp->stun = 1; - mutex_unlock(&rtp->up->up->lock); - - mutex_lock(&rtcp->up->up->lock); - if (rtp->up != rtcp->up && !rtcp->up->ice_ufrag.s) { - /* safe to read */ - rtcp->up->ice_ufrag = rtp->up->ice_ufrag; - rtcp->up->ice_pwd = rtp->up->ice_pwd; - } - rtcp->stun = 1; - mutex_unlock(&rtcp->up->up->lock); + ps->stun = 1; + if (ps_rtcp) + ps_rtcp->stun = 1; chopper_append_c(chop, "a=ice-ufrag:"); - chopper_append_str(chop, &rtp->up->ice_ufrag); + chopper_append_str(chop, &call_media->ice_ufrag); chopper_append_c(chop, "\r\na=ice-pwd:"); - chopper_append_str(chop, &rtp->up->ice_pwd); + chopper_append_str(chop, &call_media->ice_pwd); chopper_append_c(chop, "\r\n"); } if (!flags->ice_remove) { - priority = new_priority(flags->ice_force ? NULL : media); + priority = new_priority(flags->ice_force ? NULL : sdp_media); - insert_candidates(chop, rtp, (r_flags == 2) ? NULL : rtcp, - priority, media); + insert_candidates(chop, ps, ps_rtcp, + priority, sdp_media); - if (callmaster_has_ipv6(rtp->up->up->call->callmaster)) { + if (callmaster_has_ipv6(call->callmaster)) { priority -= 256; - insert_candidates_alt(chop, rtp, (r_flags == 2) ? NULL : rtcp, - priority, media); + insert_candidates_alt(chop, ps, ps_rtcp, + priority, sdp_media); } } + +next: + media_index++; + m = m->next; } } @@ -1597,7 +1734,7 @@ int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call *call, return 0; error: - mylog(LOG_ERROR, "Error rewriting SDP"); + ilog(LOG_ERROR, "Error rewriting SDP"); return -1; } diff --git a/daemon/sdp.h b/daemon/sdp.h index cf51eab7b..fbf5c6e38 100644 --- a/daemon/sdp.h +++ b/daemon/sdp.h @@ -7,22 +7,28 @@ struct sdp_ng_flags { - int desired_family[2]; + enum call_opmode opmode; str received_from_family; str received_from_address; str media_address; str transport_protocol_str; - enum transport_protocol transport_protocol; + str address_family_str; + const struct transport_protocol *transport_protocol; struct in6_addr parsed_received_from; struct in6_addr parsed_media_address; enum stream_direction directions[2]; + int address_family; int asymmetric:1, symmetric:1, trust_address:1, replace_origin:1, replace_sess_conn:1, ice_remove:1, - ice_force:1; + ice_force:1, + rtcp_mux_offer:1, + rtcp_mux_demux:1, + rtcp_mux_accept:1, + rtcp_mux_reject:1; }; struct sdp_chopper { @@ -37,11 +43,13 @@ struct sdp_chopper { void sdp_init(void); int sdp_parse(str *body, GQueue *sessions); -int sdp_streams(const GQueue *sessions, GQueue *streams, GHashTable *, struct sdp_ng_flags *); +int sdp_streams(const GQueue *sessions, GQueue *streams, struct sdp_ng_flags *); void sdp_free(GQueue *sessions); -int sdp_replace(struct sdp_chopper *, GQueue *, struct call *, enum call_opmode, struct sdp_ng_flags *, GHashTable *); +int sdp_replace(struct sdp_chopper *, GQueue *, struct call_monologue *, struct sdp_ng_flags *); struct sdp_chopper *sdp_chopper_new(str *input); void sdp_chopper_destroy(struct sdp_chopper *chop); +int address_family(const str *s); + #endif diff --git a/daemon/str.h b/daemon/str.h index 76e8d5cda..6880aa676 100644 --- a/daemon/str.h +++ b/daemon/str.h @@ -45,6 +45,8 @@ static inline int str_cmp_str0(const str *a, const str *b); static inline str *str_init(str *out, char *s); /* inits a str object from any binary string. returns out */ static inline str *str_init_len(str *out, char *s, int len); +static inline str *str_init_len_assert_len(str *out, char *s, int buflen, int len); +#define str_init_len_assert(out, s, len) str_init_len_assert_len(out, s, sizeof(s), len) /* returns new str object allocated with malloc, including buffer */ static inline str *str_dup(const str *s); /* returns new str object allocated from chunk, including buffer */ @@ -141,6 +143,10 @@ static inline str *str_init_len(str *out, char *s, int len) { out->len = len; return out; } +static inline str *str_init_len_assert_len(str *out, char *s, int buflen, int len) { + assert(buflen >= len); + return str_init_len(out, s, len); +} static inline str *str_dup(const str *s) { str *r; r = malloc(sizeof(*r) + s->len + 1); diff --git a/daemon/stun.c b/daemon/stun.c index 1b4636242..64e742683 100644 --- a/daemon/stun.c +++ b/daemon/stun.c @@ -158,7 +158,7 @@ static int stun_attributes(struct stun_attrs *out, str *s, u_int16_t *unknowns) break; default: - mylog(LOG_INFO, "Unknown STUN attribute: 0x%04x", type); + ilog(LOG_INFO, "Unknown STUN attribute: 0x%04x", type); if ((type & 0x8000)) break; unknowns[uc] = tlv->type; @@ -330,15 +330,15 @@ static int check_fingerprint(str *msg, struct stun_attrs *attrs) { return 0; } -static int check_auth(str *msg, struct stun_attrs *attrs, struct peer *peer) { +static int check_auth(str *msg, struct stun_attrs *attrs, struct call_media *media) { u_int16_t lenX; char digest[20]; str ufrag[2]; struct iovec iov[3]; - if (!peer->ice_ufrag.s || !peer->ice_ufrag.len) + if (!media->ice_ufrag.s || !media->ice_ufrag.len) return -1; - if (!peer->ice_pwd.s || !peer->ice_pwd.len) + if (!media->ice_pwd.s || !media->ice_pwd.len) return -1; ufrag[0] = attrs->username; @@ -350,7 +350,7 @@ static int check_auth(str *msg, struct stun_attrs *attrs, struct peer *peer) { if (!ufrag[0].len || !ufrag[1].len) return -1; - if (str_cmp_str(&ufrag[0], &peer->ice_ufrag)) + if (str_cmp_str(&ufrag[0], &media->ice_ufrag)) return -1; lenX = htons((attrs->msg_integrity_attr - msg->s) - 20 + 24); @@ -361,13 +361,13 @@ static int check_auth(str *msg, struct stun_attrs *attrs, struct peer *peer) { iov[2].iov_base = msg->s + OFFSET_OF(struct header, cookie); iov[2].iov_len = ntohs(lenX) + - 24 + 20 - OFFSET_OF(struct header, cookie); - __integrity(iov, G_N_ELEMENTS(iov), &peer->ice_pwd, digest); + __integrity(iov, G_N_ELEMENTS(iov), &media->ice_pwd, digest); return memcmp(digest, attrs->msg_integrity.s, 20) ? -1 : 0; } static int stun_binding_success(int fd, struct header *req, struct stun_attrs *attrs, - struct sockaddr_in6 *sin, struct peer *peer) + struct sockaddr_in6 *sin, struct call_media *media) { struct header hdr; struct xor_mapped_address xma; @@ -376,7 +376,7 @@ static int stun_binding_success(int fd, struct header *req, struct stun_attrs *a struct msghdr mh; struct iovec iov[4]; /* hdr, xma, mi, fp */ unsigned char buf[256]; - struct callmaster *cm = peer->up->call->callmaster; + struct callmaster *cm = media->call->callmaster; output_init(&mh, iov, sin, &hdr, STUN_BINDING_SUCCESS_RESPONSE, req->transaction, buf, sizeof(buf)); @@ -395,7 +395,7 @@ static int stun_binding_success(int fd, struct header *req, struct stun_attrs *a output_add(&mh, &xma, STUN_XOR_MAPPED_ADDRESS); } - integrity(&mh, &mi, &peer->ice_pwd); + integrity(&mh, &mi, &media->ice_pwd); fingerprint(&mh, &fp); output_finish(&mh, cm); @@ -412,14 +412,14 @@ static inline int u_int16_t_arr_len(u_int16_t *arr) { } -#define SLF " on port %hu from %s" -#define SLP sr->fd.localport, addr +#define SLF " from %s" +#define SLP addr /* return values: * 0 = stun packet processed successfully * -1 = stun packet not processed, processing should continue as non-stun packet * 1 = stun packet processed and "use candidate" was set */ -int stun(str *b, struct streamrelay *sr, struct sockaddr_in6 *sin) { +int stun(str *b, struct packet_stream *ps, struct sockaddr_in6 *sin) { struct header *req = (void *) b->s; int msglen, method, class; str attr_str; @@ -427,7 +427,7 @@ int stun(str *b, struct streamrelay *sr, struct sockaddr_in6 *sin) { u_int16_t unknowns[UNKNOWNS_COUNT]; const char *err; char addr[64]; - struct callmaster *cm = sr->up->up->call->callmaster; + struct callmaster *cm = ps->call->callmaster; smart_ntop_port(addr, sin, sizeof(addr)); @@ -451,9 +451,9 @@ int stun(str *b, struct streamrelay *sr, struct sockaddr_in6 *sin) { err = "failed to parse attributes"; if (unknowns[0] == 0xffff) goto ignore; - mylog(LOG_WARNING, "STUN packet contained unknown " + ilog(LOG_WARNING, "STUN packet contained unknown " "\"comprehension required\" attribute(s)" SLF, SLP); - stun_error_attrs(cm, sr->fd.fd, sin, req, 420, "Unknown attribute", + stun_error_attrs(cm, ps->sfd->fd.fd, sin, req, 420, "Unknown attribute", STUN_UNKNOWN_ATTRIBUTES, unknowns, u_int16_t_arr_len(unknowns) * 2); return 0; @@ -475,23 +475,23 @@ int stun(str *b, struct streamrelay *sr, struct sockaddr_in6 *sin) { err = "FINGERPRINT mismatch"; if (check_fingerprint(b, &attrs)) goto ignore; - if (check_auth(b, &attrs, sr->up)) + if (check_auth(b, &attrs, ps->media)) goto unauth; - mylog(LOG_NOTICE, "Successful STUN binding request" SLF, SLP); - stun_binding_success(sr->fd.fd, req, &attrs, sin, sr->up); + ilog(LOG_NOTICE, "Successful STUN binding request" SLF, SLP); + stun_binding_success(ps->sfd->fd.fd, req, &attrs, sin, ps->media); return attrs.use ? 1 : 0; bad_req: - mylog(LOG_INFO, "Received invalid STUN packet" SLF ": %s", SLP, err); - stun_error(cm, sr->fd.fd, sin, req, 400, "Bad request"); + ilog(LOG_INFO, "Received invalid STUN packet" SLF ": %s", SLP, err); + stun_error(cm, ps->sfd->fd.fd, sin, req, 400, "Bad request"); return 0; unauth: - mylog(LOG_INFO, "STUN authentication mismatch" SLF, SLP); - stun_error(cm, sr->fd.fd, sin, req, 401, "Unauthorized"); + ilog(LOG_INFO, "STUN authentication mismatch" SLF, SLP); + stun_error(cm, ps->sfd->fd.fd, sin, req, 401, "Unauthorized"); return 0; ignore: - mylog(LOG_INFO, "Not handling potential STUN packet" SLF ": %s", SLP, err); + ilog(LOG_INFO, "Not handling potential STUN packet" SLF ": %s", SLP, err); return -1; } diff --git a/daemon/stun.h b/daemon/stun.h index c199adb77..7804c4d81 100644 --- a/daemon/stun.h +++ b/daemon/stun.h @@ -11,7 +11,7 @@ #define STUN_COOKIE 0x2112A442UL -static inline int is_stun(str *s) { +static inline int is_stun(const str *s) { const unsigned char *b = (const void *) s->s; const u_int32_t *u; @@ -29,7 +29,7 @@ static inline int is_stun(str *s) { } -int stun(str *, struct streamrelay *, struct sockaddr_in6 *); +int stun(str *, struct packet_stream *, struct sockaddr_in6 *); #endif diff --git a/daemon/udp_listener.c b/daemon/udp_listener.c index fbcca56c3..ec7a66d40 100644 --- a/daemon/udp_listener.c +++ b/daemon/udp_listener.c @@ -10,6 +10,8 @@ #include "poller.h" #include "aux.h" #include "str.h" +#include "log.h" +#include "obj.h" struct udp_listener_callback { struct obj obj; @@ -26,7 +28,7 @@ static void udp_listener_incoming(int fd, void *p, uintptr_t x) { struct sockaddr_in6 sin; socklen_t sin_len; int len; - char buf[8192]; + char buf[0x10000]; char addr[64]; str str; @@ -39,7 +41,7 @@ static void udp_listener_incoming(int fd, void *p, uintptr_t x) { if (errno == EINTR) continue; if (errno != EWOULDBLOCK && errno != EAGAIN) - mylog(LOG_WARNING, "Error reading from UDP socket"); + ilog(LOG_WARNING, "Error reading from UDP socket"); return; } @@ -58,7 +60,7 @@ int udp_listener_init(struct udp_listener *u, struct poller *p, struct in6_addr cb = obj_alloc("udp_listener_callback", sizeof(*cb), NULL); cb->func = func; - cb->p = obj_get(obj); + cb->p = obj_get_o(obj); u->fd = socket(AF_INET6, SOCK_DGRAM, 0); if (u->fd == -1) @@ -88,7 +90,7 @@ int udp_listener_init(struct udp_listener *u, struct poller *p, struct in6_addr fail: if (u->fd != -1) close(u->fd); - obj_put(obj); - obj_put(&cb->obj); + obj_put_o(obj); + obj_put(cb); return -1; } diff --git a/debian/changelog b/debian/changelog index 729180b79..cca1f24d8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +ngcp-mediaproxy-ng (2.9.9) unstable; urgency=low + + * Complete rewrite of internal media streams handling + * Support for DTLS-SRTP + * Support for unBUNDLE + * Improved support for rtcp-mux + * WIP + + -- Richard Fuchs Tue, 04 Mar 2014 11:06:23 -0500 + ngcp-mediaproxy-ng (2.4.0.0+0~mr3.3.0.0) unstable; urgency=low [ Richard Fuchs ] diff --git a/debian/control b/debian/control index d1597873c..dd229707d 100644 --- a/debian/control +++ b/debian/control @@ -8,7 +8,7 @@ Build-Depends: debhelper (>= 5), libcurl3-openssl-dev | libcurl3-gnutls-dev, libglib2.0-dev, libpcre3-dev, - libssl-dev, + libssl-dev (>= 1.0.1), libxmlrpc-c3-dev (>= 1.16.07) | libxmlrpc-core-c3-dev (>= 1.16.07), markdown, zlib1g-dev diff --git a/debian/ngcp-mediaproxy-ng-kernel-dkms.postinst b/debian/ngcp-mediaproxy-ng-kernel-dkms.postinst index 66de7230c..e3500de2c 100644 --- a/debian/ngcp-mediaproxy-ng-kernel-dkms.postinst +++ b/debian/ngcp-mediaproxy-ng-kernel-dkms.postinst @@ -19,7 +19,7 @@ if [ "$1" = 'configure' ] ; then # try to start the daemon if [ -x /etc/init.d/ngcp-mediaproxy-ng-daemon ] ; then - invoke-rc.d ngcp-mediaproxy-ng-daemon start || true + invoke-rc.d ngcp-mediaproxy-ng-daemon restart || true fi fi diff --git a/kernel-module/xt_MEDIAPROXY.c b/kernel-module/xt_MEDIAPROXY.c index 213337d7c..305aa142b 100644 --- a/kernel-module/xt_MEDIAPROXY.c +++ b/kernel-module/xt_MEDIAPROXY.c @@ -33,7 +33,7 @@ MODULE_LICENSE("GPL"); #define MAX_ID 64 /* - 1 */ -#define MAX_SKB_TAIL_ROOM (128 + 20) +#define MAX_SKB_TAIL_ROOM (sizeof(((struct mediaproxy_srtp *) 0)->mki) + 20) #define MIPF "%i:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x:%u" #define MIPP(x) (x).family, \ @@ -945,8 +945,11 @@ static void proc_list_crypto_print(struct seq_file *f, struct mp_crypto_context if (!hdr++) seq_printf(f, " SRTP %s parameters:\n", label); seq_printf(f, " cipher: %s\n", c->cipher->name ? : ""); - if (s->mki || s->mki_len) - seq_printf(f, " MKI: %llu length %u\n", (unsigned long long) s->mki, s->mki_len); + if (s->mki_len) + seq_printf(f, " MKI: length %u, %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x...\n", + s->mki_len, + s->mki[0], s->mki[1], s->mki[2], s->mki[3], + s->mki[4], s->mki[5], s->mki[6], s->mki[7]); } if (c->hmac && c->hmac->id != MPH_NULL) { if (!hdr++) @@ -1064,7 +1067,7 @@ static int validate_srtp(struct mediaproxy_srtp *s) { return -1; if (s->auth_tag_len > 20) return -1; - if (s->mki_len > 128) + if (s->mki_len > sizeof(s->mki)) return -1; return 0; } @@ -1930,29 +1933,13 @@ error: /* XXX shared code */ static void rtp_append_mki(struct rtp_parsed *r, struct mediaproxy_srtp *c) { - u_int32_t mki_part; unsigned char *p; if (!c->mki_len) return; p = r->payload + r->payload_len; - memset(p, 0, c->mki_len); - if (c->mki_len > 4) { - mki_part = (c->mki & 0xffffffff00000000ULL) >> 32; - mki_part = htonl(mki_part); - if (c->mki_len < 8) - memcpy(p, ((char *) &mki_part) + (8 - c->mki_len), c->mki_len - 4); - else - memcpy(p + (c->mki_len - 8), &mki_part, 4); - } - mki_part = (c->mki & 0xffffffffULL); - mki_part = htonl(mki_part); - if (c->mki_len < 4) - memcpy(p, ((char *) &mki_part) + (4 - c->mki_len), c->mki_len); - else - memcpy(p + (c->mki_len - 4), &mki_part, 4); - + memcpy(p, c->mki, c->mki_len); r->payload_len += c->mki_len; } @@ -2096,6 +2083,14 @@ static inline int is_muxed_rtcp(struct rtp_parsed *r) { return 1; } +static inline int is_dtls(struct rtp_parsed *r) { + if (r->header->m_pt < 20) + return 0; + if (r->header->m_pt > 63) + return 0; + return 1; +} + static unsigned int mediaproxy46(struct sk_buff *skb, struct mediaproxy_table *t) { struct udphdr *uh; struct mediaproxy_target *g; @@ -2147,6 +2142,8 @@ not_stun: goto skip1; if (g->target.rtcp_mux && is_muxed_rtcp(&rtp)) goto skip1; + if (g->target.dtls && is_dtls(&rtp)) + goto skip1; 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; diff --git a/kernel-module/xt_MEDIAPROXY.h b/kernel-module/xt_MEDIAPROXY.h index daeede08f..3c03ceacd 100644 --- a/kernel-module/xt_MEDIAPROXY.h +++ b/kernel-module/xt_MEDIAPROXY.h @@ -46,7 +46,7 @@ struct mediaproxy_srtp { enum mediaproxy_hmac hmac; unsigned char master_key[16]; unsigned char master_salt[14]; - u_int64_t mki; + unsigned char mki[256]; /* XXX uses too much memory? */ u_int64_t last_index; unsigned int auth_tag_len; /* in bytes */ unsigned int mki_len; @@ -65,7 +65,8 @@ struct mediaproxy_target_info { struct mediaproxy_srtp encrypt; unsigned char tos; - int rtcp_mux:1; + int rtcp_mux:1, + dtls:1; }; struct mediaproxy_message { diff --git a/tests/simulator-ng.pl b/tests/simulator-ng.pl index 705232689..38c8100e6 100755 --- a/tests/simulator-ng.pl +++ b/tests/simulator-ng.pl @@ -12,10 +12,11 @@ use Time::HiRes; use Crypt::Rijndael; use Digest::SHA qw(hmac_sha1); use MIME::Base64; +use Data::Dumper; my ($NUM, $RUNTIME, $STREAMS, $PAYLOAD, $INTERVAL, $RTCP_INTERVAL, $STATS_INTERVAL) = (1000, 30, 1, 160, 20, 5, 5); -my ($NODEL, $IP, $IPV6, $KEEPGOING, $REINVITES, $BRANCHES, $PROTOS, $DEST, $SUITES, $NOENC, $RTCPMUX); +my ($NODEL, $IP, $IPV6, $KEEPGOING, $REINVITES, $PROTOS, $DEST, $SUITES, $NOENC, $RTCPMUX, $BUNDLE, $LAZY); GetOptions( 'no-delete' => \$NODEL, 'num-calls=i' => \$NUM, @@ -24,7 +25,6 @@ GetOptions( 'runtime=i' => \$RUNTIME, 'keep-going' => \$KEEPGOING, # don't stop sending rtp if a packet doesn't go through 'reinvites' => \$REINVITES, - 'branches' => \$BRANCHES, 'max-streams=i' => \$STREAMS, 'protocols=s' => \$PROTOS, # "RTP/AVP,RTP/SAVP" 'destination=s' => \$DEST, @@ -35,6 +35,8 @@ GetOptions( 'suites=s' => \$SUITES, 'no-encrypt' => \$NOENC, 'rtcp-mux' => \$RTCPMUX, + 'bundle' => \$BUNDLE, + 'lazy-params' => \$LAZY, ) or die; ($IP || $IPV6) or die("at least one of --local-ip or --local-ipv6 must be given"); @@ -81,7 +83,7 @@ connect($fd, sockaddr_in($$DEST[1], inet_aton($$DEST[0]))) or die $!; msg({command => 'ping'})->{result} eq 'pong' or die; -my (@calls, %branches); +my (@calls, %calls); my %NOENC; sub send_receive { @@ -267,6 +269,21 @@ sub aes_f8_iv_rtcp { return $iv; } +sub append_mki { + my ($ctx_dir, $pack_r) = @_; + + $$ctx_dir{rtp_mki_len} or return; + + my $mki = pack('N', $$ctx_dir{rtp_mki}); + while (length($mki) < $$ctx_dir{rtp_mki_len}) { + $mki = "\x00" . $mki; + } + if (length($mki) > $$ctx_dir{rtp_mki_len}) { + $mki = substr($mki, -$$ctx_dir{rtp_mki_len}); + } + $$pack_r .= $mki; +} + sub rtcp_encrypt { my ($r, $ctx, $dir) = @_; @@ -286,6 +303,8 @@ sub rtcp_encrypt { my $hmac = hmac_sha1($pkt, $$ctx{$dir}{rtcp_session_auth_key}); + append_mki($$ctx{$dir}, \$pkt); + #$pkt .= pack("N", 1); # mki $pkt .= substr($hmac, 0, 10); @@ -319,6 +338,8 @@ sub rtp_encrypt { my $hmac = hmac_sha1($pkt . pack("N", $$ctx{$dir}{rtp_roc}), $$ctx{$dir}{rtp_session_auth_key}); # print("HMAC for packet " . unpack("H*", $pkt) . " ROC $roc is " . unpack("H*", $hmac) . "\n"); + append_mki($$ctx{$dir}, \$pkt); + #$pkt .= pack("N", 1); # mki $pkt .= substr($hmac, 0, $$ctx{$dir}{crypto_suite}{auth_tag}); @@ -359,6 +380,15 @@ sub savp_sdp { if (!$$ctx{out}{crypto_suite}) { $$ctx{out}{crypto_suite} = $$ctx_o{in}{crypto_suite} ? $$ctx_o{in}{crypto_suite} : $crypto_suites[rand(@crypto_suites)]; + + $$ctx{out}{rtp_mki_len} = 0; + if (rand() > .5) { + $$ctx{out}{rtp_mki_len} = int(rand(120)) + 1; + $$ctx{out}{rtp_mki} = int(rand(2**30)) | 1; + if ($$ctx{out}{rtp_mki_len} < 32) { + $$ctx{out}{rtp_mki} &= (0xffffffff >> (32 - ($$ctx{out}{rtp_mki_len}))); + } + } } if (!$$ctx{out}{rtp_master_key}) { @@ -371,7 +401,14 @@ sub savp_sdp { $NOENC{rtp_master_key} = $$ctx{out}{rtp_master_key}; $NOENC{rtp_master_salt} = $$ctx{out}{rtp_master_salt}; } - return "a=crypto:0 $$ctx{out}{crypto_suite}{str} inline:" . encode_base64($$ctx{out}{rtp_master_key} . $$ctx{out}{rtp_master_salt}, '') . "\n"; + + my $ret = "a=crypto:0 $$ctx{out}{crypto_suite}{str} inline:" . encode_base64($$ctx{out}{rtp_master_key} . $$ctx{out}{rtp_master_salt}, ''); + if ($$ctx{out}{rtp_mki_len}) { + $ret .= "|$$ctx{out}{rtp_mki}:$$ctx{out}{rtp_mki_len}"; + } + + $ret .= "\n"; + return $ret; } sub rtcp_sr { @@ -462,18 +499,22 @@ sub rtp_savp { sub savp_crypto { my ($sdp, $ctx, $ctx_o) = @_; - my $cs = $$ctx_o{out}{crypto_suite}{str}; - my $re = $cs ? qr/\Q$cs\E/ : qr/\w+/; - my @a = $sdp =~ /[\r\n]a=crypto:\d+ ($re) inline:([\w\/+]{40})(?:\|(?:2\^(\d+)|(\d+)))?(?:\|(\d+):(\d+))?[\r\n]/si; + my @a = $sdp =~ /[\r\n]a=crypto:\d+ (\w+) inline:([\w\/+]{40})(?:\|(?:2\^(\d+)|(\d+)))?(?:\|(\d+):(\d+))?[\r\n]/sig; @a or die; - $$ctx{in}{crypto_suite} = $crypto_suites{$a[0]} or die; - my $ks = decode_base64($a[1]); - length($ks) == 30 or die; - ($$ctx{in}{rtp_master_key}, $$ctx{in}{rtp_master_salt}) = unpack('a16a14', $ks); - $$ctx{in}{rtp_mki} = $a[4]; - $$ctx{in}{rtp_mki_len} = $a[5]; - undef($$ctx{in}{rtp_session_key}); - undef($$ctx{in}{rtcp_session_key}); + my $i = 0; + while (@a >= 6) { + $$ctx[$i]{in}{crypto_suite} = $crypto_suites{$a[0]} or die; + my $ks = decode_base64($a[1]); + length($ks) == 30 or die; + ($$ctx[$i]{in}{rtp_master_key}, $$ctx[$i]{in}{rtp_master_salt}) = unpack('a16a14', $ks); + $$ctx[$i]{in}{rtp_mki} = $a[4]; + $$ctx[$i]{in}{rtp_mki_len} = $a[5]; + undef($$ctx[$i]{in}{rtp_session_key}); + undef($$ctx[$i]{in}{rtcp_session_key}); + + $i++; + @a = @a[6 .. $#a]; + } } sub hexdump { @@ -490,46 +531,60 @@ sub do_rtp { my ($rtcp) = @_; for my $c (@calls) { $c or next; - my ($fds,$outputs,$protos,$cfds,$trans,$tctxs) - = @$c{qw(fds outputs protos rtcp_fds transports trans_contexts)}; - for my $j (0 .. $#{$$fds[0]}) { - for my $i ([0,1],[1,0]) { - my ($a, $b) = @$i; - my $pr = $$protos[$a]; - my $tcx = $$tctxs[$a]; - my $tcx_o = $$tctxs[$b]; - my $addr = inet_pton($$pr{family}, $$outputs[$b][$j][1]); - my ($payload, $expect) = $$trans[$a]{rtp_func}($$trans[$b], $tcx, $tcx_o); - my $dst = $$pr{sockaddr}($$outputs[$b][$j][0], $addr); - my $repl = send_receive($$fds[$a][$j], $$fds[$b][$j], $payload, $dst); + for my $i ([0,1],[1,0]) { + my ($a, $b) = @$i; + my $A = $$c{sides}[$a]; + my $B = $$c{sides}[$b]; + + my $rtp_fds = $$A{rtp_fds}; + my $rtcp_fds = $$A{rtcp_fds}; + my $rtp_fds_o = $$B{rtp_fds}; + my $rtcp_fds_o = $$B{rtcp_fds}; + + my $pr = $$A{proto};; + my $trans = $$A{transport}; + my $trans_o = $$B{transport}; + my $tcx = $$A{trans_contexts}; + my $tcx_o = $$B{trans_contexts}; + my $outputs = $$A{outputs}; + + for my $j (0 .. ($$A{streams_active} - 1)) { + my ($bj_a, $bj_b) = ($j, $j); + $$A{bundle} + and $bj_a = 0; + $$B{bundle} + and $bj_b = 0; + + my $addr = inet_pton($$pr{family}, $$outputs[$j][1]); + my ($payload, $expect) = $$trans{rtp_func}($trans_o, $$tcx[$j], $$tcx_o[$j]); + my $dst = $$pr{sockaddr}($$outputs[$j][0], $addr); + my $repl = send_receive($$rtp_fds[$bj_a], $$rtp_fds_o[$bj_b], $payload, $dst); $RTP_COUNT++; if ($repl eq '') { - warn("no rtp reply received, ports $$outputs[$b][$j][0] and $$outputs[$a][$j][0]"); + warn("no rtp reply received, port $$outputs[$j][0]"); $KEEPGOING or undef($c); } $NOENC and $repl = $expect; !$repl && $KEEPGOING and next; - $repl eq $expect or die hexdump($repl, $expect) . " $$trans[$a]{name} > $$trans[$b]{name}, ports $$outputs[$b][$j][0] and $$outputs[$a][$j][0]"; + $repl eq $expect or die hexdump($repl, $expect) . " $$trans{name} > $$trans_o{name}, $$c{callid}, RTP port $$outputs[$j][0]"; $rtcp or next; - ($payload, $expect) = $$trans[$a]{rtcp_func}($$trans[$b], $tcx, $tcx_o); - my $dstport = $$outputs[$b][$j][0] + 1; - my $sendfd = $$cfds[$a][$j]; - my $expfd = $$cfds[$b][$j]; - if ($RTCPMUX) { - if (!$a) { - $dstport--; - $sendfd = $$fds[$a][$j]; - } - else { - $expfd = $$fds[$b][$j]; - } + ($payload, $expect) = $$trans{rtcp_func}($trans_o, $$tcx[$j], $$tcx_o[$j]); + my $dstport = $$outputs[$j][0] + 1; + my $sendfd = $$rtcp_fds[$bj_a]; + my $expfd = $$rtcp_fds_o[$bj_b]; + if ($$A{rtcpmux}) { + $dstport--; + $sendfd = $$rtp_fds[$bj_a]; + } + if ($$B{rtcpmux}) { + $expfd = $$rtp_fds_o[$bj_b]; } $dst = $$pr{sockaddr}($dstport, $addr); $repl = send_receive($sendfd, $expfd, $payload, $dst); $NOENC and $repl = $expect; !$repl && $KEEPGOING and next; - $repl eq $expect or die hexdump($repl, $expect) . " $$trans[$a]{name} > $$trans[$b]{name}"; + $repl eq $expect or die hexdump($repl, $expect) . " $$trans{name} > $$trans_o{name}, $$c{callid}, RTCP"; } } } @@ -590,82 +645,102 @@ my %transports = map {$$_{name} => $_} @transports; sub callid { my $i = rand_str(50); - $BRANCHES or return [$i]; - rand() < .5 and return [$i]; - if (rand() < .5) { - my @k = keys(%branches); - @k and $i = $k[rand(@k)]; - } - my $b = rand_str(20); - push(@{$branches{$i}}, $b); - return [$i, $b]; + return $i; } my $NUM_STREAMS = 0; -sub update_lookup { - my ($c, $i) = @_; - my $j = $i ^ 1; - - my $c_v = $$c{callid_viabranch} || ($$c{callid_viabranch} = callid()); - my ($callid, $viabranch) = @$c_v; - - my $protos = $$c{protos} || ($$c{protos} = []); - my $trans = $$c{transports} || ($$c{transports} = []); - my $tctxs = $$c{trans_contexts} || ($$c{trans_contexts} = []); - my $fds_a = $$c{fds} || ($$c{fds} = []); - my $cfds_a = $$c{rtcp_fds} || ($$c{rtcp_fds} = []); - for my $x (0,1) { - $$protos[$x] and next; - $$protos[$x] = $protos_avail[rand(@protos_avail)]; - undef($$fds_a[$x]); + +sub port_setup { + my ($r, $j) = @_; + + my $pr = $$r{proto}; + my $rtp_fds = $$r{rtp_fds}; + my $rtcp_fds = $$r{rtcp_fds}; + my $ports = $$r{ports}; + my $ips = $$r{ips}; + my $tcx = $$r{trans_contexts}; + $$tcx[$j] or $$tcx[$j] = {}; + + while (1) { + socket(my $rtp, $$pr{family}, SOCK_DGRAM, 0) or die $!; + socket(my $rtcp, $$pr{family}, SOCK_DGRAM, 0) or die $!; + my $port = rand(0x7000) << 1 + 1024; + bind($rtp, $$pr{sockaddr}($port, + inet_pton($$pr{family}, $$pr{address}))) or next; + bind($rtcp, $$pr{sockaddr}($port + 1, + inet_pton($$pr{family}, $$pr{address}))) or next; + + $$rtp_fds[$j] = $rtp; + $$rtcp_fds[$j] = $rtcp; + + my $addr = getsockname($rtp); + my $ip; + ($$ports[$j], $ip) = $$pr{sockaddr}($addr); + $$ips[$j] = inet_ntop($$pr{family}, $ip); + + last; } - for my $x (0,1) { - $$trans[$x] and next; - $$trans[$x] = ($PROTOS && $$PROTOS[$x] && $transports{$$PROTOS[$x]}) - ? $transports{$$PROTOS[$x]} +} + +sub side_setup { + my ($i) = @_; + my $r = {}; + + my $pr = $$r{proto} = $protos_avail[rand(@protos_avail)]; + $$r{transport} = ($PROTOS && $$PROTOS[$i] && $transports{$$PROTOS[$i]}) + ? $transports{$$PROTOS[$i]} : $transports[rand(@transports)]; - } - my ($pr, $pr_o) = @$protos[$i, $j]; - my ($tr, $tr_o) = @$trans[$i, $j]; - my $tcx = $$tctxs[$i] || ($$tctxs[$i] = {}); - my $tcx_o = $$tctxs[$j] || ($$tctxs[$j] = {}); - my @commands = qw(offer answer); - - my $ports_a = $$c{ports} || ($$c{ports} = []); - my $ports_t = $$ports_a[$i] || ($$ports_a[$i] = []); - my $ips_a = $$c{ips} || ($$c{ips} = []); - my $ips_t = $$ips_a[$i] || ($$ips_a[$i] = []); - my $fds_t = $$fds_a[$i] || ($$fds_a[$i] = []); - my $fds_o = $$fds_a[$j]; - my $cfds_t = $$cfds_a[$i] || ($$cfds_a[$i] = []); - my $cfds_o = $$cfds_a[$j]; - my $num_streams = int(rand($STREAMS)); - ($fds_o && @$fds_o) and $num_streams = $#$fds_o; - for my $j (0 .. $num_streams) { - if (!$$fds_t[$j]) { - $NUM_STREAMS++; - undef($$tcx_o{in}); - while (1) { - undef($$fds_t[$j]); - undef($$cfds_t[$j]); - socket($$fds_t[$j], $$pr{family}, SOCK_DGRAM, 0) or die $!; - socket($$cfds_t[$j], $$pr{family}, SOCK_DGRAM, 0) or die $!; - my $port = rand(0x7000) << 1 + 1024; - bind($$fds_t[$j], $$pr{sockaddr}($port, - inet_pton($$pr{family}, $$pr{address}))) or next; - bind($$cfds_t[$j], $$pr{sockaddr}($port + 1, - inet_pton($$pr{family}, $$pr{address}))) or next; - last; - } - my $addr = getsockname($$fds_t[$j]); - my $ip; - ($$ports_t[$j], $ip) = $$pr{sockaddr}($addr); - $$ips_t[$j] = inet_ntop($$pr{family}, $ip); - } + $$r{trans_contexts} = []; + $$r{outputs} = []; + + $$r{num_streams} = int(rand($STREAMS)); + $$r{streams_seen} = 0; + $$r{streams_active} = 0; + $$r{rtp_fds} = []; + $$r{rtcp_fds} = []; + $$r{ports} = []; + $$r{ips} = []; + + for my $j (0 .. $$r{num_streams}) { + port_setup($r, $j); } - my $tags = $$c{tags} || ($$c{tags} = []); - $$tags[$i] or $$tags[$i] = rand_str(15); + $$r{tag} = rand_str(15); + $RTCPMUX and $$r{want_rtcpmux} = rand() >= .3; + $BUNDLE and $$r{want_bundle} = rand() >= .3; + $$r{want_bundle} and $$r{want_rtcpmux} = 1; + + return $r; +} + +sub call_setup { + my ($c) = @_; + + $$c{setup} = 1; + $$c{callid} = callid(); + + $$c{sides}[0] = side_setup(0); + $$c{sides}[1] = side_setup(1); +} + +sub offer_answer { + my ($c, $a, $b, $op) = @_; + + $$c{setup} or call_setup($c); + + my $callid = $$c{callid} || ($$c{callid} = callid()); + + my $A = $$c{sides}[$a]; + my $B = $$c{sides}[$b]; + + my $pr = $$A{proto}; + my $pr_o = $$B{proto}; + my $ips_t = $$A{ips}; + my $ports_t = $$A{ports}; + my $tr = $$A{transport}; + my $tr_o = $$B{transport}; + my $tcx = $$A{trans_contexts}; + my $tcx_o = $$B{trans_contexts}; my $sdp = <<"!"; v=0 @@ -674,67 +749,148 @@ s=session c=IN $$pr{family_str} $$ips_t[0] t=0 0 ! - for my $p (@$ports_t) { + my $ul = $$A{num_streams}; + $op eq 'answer' && $$A{streams_seen} < $$A{num_streams} + and $ul = $$A{streams_seen}; + + $$A{want_bundle} && $op eq 'offer' and + $$A{bundle} = 1, + $sdp .= "a=group:BUNDLE " . join(' ', (0 .. $ul)) . "\n"; + + for my $i (0 .. $ul) { + my $bi = $i; + $$A{bundle} + and $bi = 0; + + my $p = $$ports_t[$bi]; my $cp = $p + 1; + $$A{bundle} && $$A{want_rtcpmux} && $op eq 'offer' + and $cp = $p; + $sdp .= <<"!"; m=audio $p $$tr{name} 8 a=rtpmap:8 PCMA/8000 ! - if ($RTCPMUX && !$i) { + if ($$A{want_rtcpmux} && $op eq 'offer') { $sdp .= "a=rtcp-mux\n"; - rand() >= .5 and $sdp .= "a=rtcp:$p\n"; + $sdp .= "a=rtcp:$cp\n"; + $$A{rtcpmux} = 1; } else { - $sdp .= "a=rtcp:$cp\n"; + rand() >= .5 and $sdp .= "a=rtcp:$cp\n"; } - $$tr{sdp_media_params} and $sdp .= $$tr{sdp_media_params}($tcx, $tcx_o); + $$tr{sdp_media_params} and $sdp .= $$tr{sdp_media_params}($$tcx[$i], $$tcx_o[$i]); + + $$A{bundle} and + $sdp .= "a=mid:$i\n"; + } + + for my $x (($ul + 1) .. $$A{streams_seen}) { + $sdp .= "m=audio 0 $$tr{name} 0\n"; } - $i or print("transport is $$tr{name} -> $$tr_o{name}\n"); - - my $dict = {sdp => $sdp, command => $commands[$i], 'call-id' => $callid, - 'from-tag' => $$tags[0], - flags => [ qw( trust-address ) ], - replace => [ qw( origin session-connection ) ], - direction => [ $$pr{direction}, $$pr_o{direction} ], - 'received-from' => [ qw(IP4 127.0.0.1) ], - 'transport-protocol' => $$tr_o{name}, + + $op eq 'offer' and print("transport is $$tr{name} -> $$tr_o{name}\n"); + + #print(Dumper($op, $A, $B, $sdp) . "\n\n\n\n"); + #print("sdp $op in:\n$sdp\n\n"); + + my $dict = {sdp => $sdp, command => $op, 'call-id' => $$c{callid}, + flags => [ 'trust address' ], + replace => [ 'origin', 'session connection' ], + #direction => [ $$pr{direction}, $$pr_o{direction} ], + 'received from' => [ qw(IP4 127.0.0.1) ], + 'rtcp-mux' => ['demux'], }; - $viabranch and $dict->{'via-branch'} = $viabranch; - $i == 1 and $dict->{'to-tag'} = $$tags[1]; + #$viabranch and $dict->{'via-branch'} = $viabranch; + if ($op eq 'offer') { + $dict->{'from-tag'} = $$A{tag}; + rand() > .5 and $$dict{'to-tag'} = $$B{tag}; + } + elsif ($op eq 'answer') { + $dict->{'from-tag'} = $$B{tag}, + $dict->{'to-tag'} = $$A{tag}; + } + if (!$LAZY + || ($op eq 'offer' && !$$c{established}) + || (rand() > .5)) + { + $$dict{'address family'} = $$pr_o{family_str}; + $$dict{'transport protocol'} = $$tr_o{name}; + } + #print(Dumper($dict) . "\n\n"); my $o = msg($dict); $$o{result} eq 'ok' or die; + #print("sdp $op out:\n$$o{sdp}\n\n\n\n"); my ($rp_af, $rp_add) = $$o{sdp} =~ /c=IN IP([46]) (\S+)/s or die; - $RTCPMUX && $i and ($$o{sdp} =~ /a=rtcp-mux/s or die); + $$B{rtcpmux} and ($$o{sdp} =~ /a=rtcp-mux/s or die); my @rp_ports = $$o{sdp} =~ /m=audio (\d+) \Q$$tr_o{name}\E /gs or die; + $$B{streams_seen} = $#rp_ports; $rp_af ne $$pr_o{reply} and die "incorrect address family reply code"; - my $rpl_a = $$c{outputs} || ($$c{outputs} = []); - my $rpl_t = $$rpl_a[$i] = []; - for my $rpl (@rp_ports) { - $rpl == 0 and die "mediaproxy ran out of ports"; + $NUM_STREAMS -= $$B{streams_active}; + $$B{streams_active} = 0; + my $old_outputs = $$B{outputs}; + my $rpl_t = $$B{outputs} = []; + for my $i (0 .. $#rp_ports) { + my $rpl = $rp_ports[$i]; + + if ($rpl == 0) { + $op eq 'offer' and $$B{streams_seen}--; + if ($$A{rtp_fds}[$i]) { + undef($$A{rtp_fds}[$i]); + } + next; + } + + $$B{ports}[$i] or next; + + $$B{streams_active}++; + $NUM_STREAMS++; push(@$rpl_t, [$rpl,$rp_add]); + my $oa = shift(@$old_outputs); + if (defined($oa) && $$oa[0] != $rpl) { + print("port change: $$oa[0] -> $rpl\n"); + #print(Dumper($i, $c) . "\n"); + undef($$tcx_o[$i]{out}{rtcp_index}); + undef($$tcx_o[$i]{out}{rtp_roc}); + } } $$tr_o{sdp_parse_func} and $$tr_o{sdp_parse_func}($$o{sdp}, $tcx_o, $tcx); + #print(Dumper($op, $A, $B) . "\n\n\n\n"); + + $op eq 'answer' and $$c{established} = 1; +} + +sub offer { + my ($c, $a, $b) = @_; + return offer_answer($c, $a, $b, 'offer'); +} +sub answer { + my ($c, $a, $b) = @_; + return offer_answer($c, $a, $b, 'answer'); } for my $iter (1 .. $NUM) { ($iter % 10 == 0) and print("$iter calls established\n"), do_rtp(); my $c = {}; - update_lookup($c, 0); - update_lookup($c, 1); + offer($c, 0, 1); + answer($c, 1, 0); push(@calls, $c); + $calls{$$c{callid}} = $c; } print("all calls established\n"); +#print(Dumper(\@calls) . "\n"); + my $end = time() + $RUNTIME; my $rtptime = Time::HiRes::gettimeofday(); my $rtcptime = $rtptime; my $countstart = $rtptime; my $countstop = $countstart + $STATS_INTERVAL; -my $last_reinv = 0; +my $last_reinv = $rtptime; while (time() < $end) { my $now = Time::HiRes::gettimeofday(); $now <= $rtptime and Time::HiRes::sleep($rtptime - $now); @@ -761,23 +917,27 @@ while (time() < $end) { @calls = sort {rand() < .5} grep(defined, @calls); - if ($REINVITES && $now >= $last_reinv + 5) { + if ($REINVITES && $now >= $last_reinv + 15) { $last_reinv = $now; my $c = $calls[rand(@calls)]; - print("simulating re-invite on $$c{callid_viabranch}[0]"); + print("simulating re-invite on $$c{callid}\n"); for my $i (0,1) { - if (rand() < .5) { - print(", side $sides[$i]: new port"); - undef($$c{fds}[$i]); - $NUM_STREAMS--; - } - else { - print(", side $sides[$i]: same port"); + my $s = $$c{sides}[$i]; + for my $j (0 .. $$s{num_streams}) { + if (rand() < .5) { + print("\tside $sides[$i] stream #$j: new port\n"); + port_setup($s, $j); + #print("\n" . Dumper($i, $c) . "\n"); + undef($$s{trans_contexts}[$j]{in}{rtcp_index}); + undef($$s{trans_contexts}[$j]{in}{rtp_roc}); + } + else { + print("\tside $sides[$i] stream #$j: same port\n"); + } } } - print("\n"); - update_lookup($c, 0); - update_lookup($c, 1); + offer($c, 0, 1); + answer($c, 1, 0); } } @@ -785,12 +945,12 @@ if (!$NODEL) { print("deleting\n"); for my $c (@calls) { $c or next; - my ($tags, $c_v) = @$c{qw(tags callid_viabranch)}; - my ($callid, $viabranch) = @$c_v; - my $dict = { command => 'delete', 'call-id' => $callid, 'from-tag' => $$tags[0], - 'to-tag' => $$tags[1], + my $callid = $$c{callid}; + my $fromtag = $$c{sides}[0]{tag}; + my $totag = $$c{sides}[1]{tag}; + my $dict = { command => 'delete', 'call-id' => $callid, 'from-tag' => $fromtag, + 'to-tag' => $totag, }; - $BRANCHES && rand() < .7 and $$dict{'via-branch'} = $viabranch; msg($dict); } } diff --git a/utils/ng-client b/utils/ng-client index ade86e2cf..95c1cb444 100755 --- a/utils/ng-client +++ b/utils/ng-client @@ -26,6 +26,11 @@ GetOptions( 'sdp=s' => \$options{'sdp'}, 'sdp-file=s' => \$options{'sdp-file'}, 'ICE=s' => \$options{'ICE'}, + 'rtcp-mux-offer' => \$options{'rtcp-mux-offer'}, + 'rtcp-mux-demux' => \$options{'rtcp-mux-demux'}, + 'rtcp-mux-accept' => \$options{'rtcp-mux-accept'}, + 'rtcp-mux-reject' => \$options{'rtcp-mux-reject'}, + 'address-family=s' => \$options{'address family'}, 'force' => \$options{'force'}, 'v|verbose' => \$options{'verbose'}, ) or die; @@ -34,7 +39,7 @@ my $cmd = shift(@ARGV) or die; my %packet = (command => $cmd); -for my $x (split(',', 'from-tag,to-tag,call-id,transport protocol,media address,ICE')) { +for my $x (split(',', 'from-tag,to-tag,call-id,transport protocol,media address,ICE,address family')) { defined($options{$x}) and $packet{$x} = $options{$x}; } for my $x (split(',', 'trust address,symmetric,asymmetric,force')) { @@ -43,6 +48,9 @@ for my $x (split(',', 'trust address,symmetric,asymmetric,force')) { for my $x (split(',', 'origin,session connection')) { defined($options{'replace-' . $x}) and push(@{$packet{replace}}, $x); } +for my $x (split(',', 'offer,demux,accept,reject')) { + defined($options{'rtcp-mux-' . $x}) and push(@{$packet{'rtcp-mux'}}, $x); +} if (defined($options{sdp})) { $packet{sdp} = $options{sdp};