From f99d6d4f4259a873d9125fe552ee358a31d5eed8 Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Fri, 27 Feb 2015 09:47:06 -0500 Subject: [PATCH] implement full ICE support Squashed commit of the following: commit 00213e66c74d402d2c3045eabeb5a10fa68f10c7 Author: Richard Fuchs Date: Fri Feb 27 09:40:04 2015 -0500 perform ICE restart if we change ports commit 27fbcbd6cbebdd071c1fa960c6e55f016534d171 Author: Richard Fuchs Date: Thu Feb 26 15:58:12 2015 -0500 locking fixes commit 60c1c5ae13b2c11720ee099daea1e3cdebea6317 Author: Richard Fuchs Date: Thu Feb 26 15:58:06 2015 -0500 unkernelize when ICE completes commit 1d816f9864ce31ea9a55a0bdf243a9900e597cdf Author: Richard Fuchs Date: Thu Feb 26 15:54:44 2015 -0500 relaxed locking where not needed commit 75b58a9093d97daa5b2f4f8c1790f3b0f64117f8 Author: Richard Fuchs Date: Thu Feb 26 11:46:08 2015 -0500 use atomic bitfield instead of bitfield+mutex commit 03552eeed92aad419c1ad3cb9cfe5a7bded7f601 Author: Richard Fuchs Date: Thu Feb 26 11:24:59 2015 -0500 shuffle around aux.h a bit more commit b9b8a3aa5ef4d1a9026977465716fdbadf96917a Author: Richard Fuchs Date: Thu Feb 26 11:16:12 2015 -0500 remove some code redundancy through a "state machine" commit 0b4bfef1b18b75e22611f18e510209c743708cd6 Author: Richard Fuchs Date: Thu Feb 26 10:10:41 2015 -0500 reorder to match struct commit a2a51d81a8f02dbb0fd8afe103ae0b0fa2a28dcf Author: Richard Fuchs Date: Thu Feb 26 10:10:03 2015 -0500 clear more states for ICE restart commit d554a2b858dc3655d72d037a16c12e7eea243e1b Author: Richard Fuchs Date: Thu Feb 26 09:58:29 2015 -0500 dont duplicate candidates on re-invite commit 4c804652b7985046d0214e4a90e67da9cce8fd03 Author: Richard Fuchs Date: Thu Feb 26 09:58:20 2015 -0500 retain ICE role across re-invites commit 4a586dd72d9f31ba87fb82e1e0e3602b467c252a Author: Richard Fuchs Date: Thu Feb 26 09:42:09 2015 -0500 eliminate duplicate log messages commit ef0be2e308e1e3c3ce063afd2854e3fb749d813a Author: Richard Fuchs Date: Thu Feb 26 09:32:36 2015 -0500 fix incorrect log message commit 2544b60f00b3b03fd64ac6e4b4e8ec20423b7745 Author: Richard Fuchs Date: Wed Feb 25 15:26:48 2015 -0500 better logging for ICE agents commit c42807384efa167afa08e10296b5177526efb9ba Author: Richard Fuchs Date: Wed Feb 25 15:18:27 2015 -0500 dont run ICE checks if we dont have a password commit 1b56cb75b5912fd5aa6e3c66bf792e56f706d249 Author: Richard Fuchs Date: Wed Feb 25 15:16:19 2015 -0500 ICE pairs should go in triggered queue only once commit d10c56f3ae6172240c2a786fcacb14e5df177f13 Author: Richard Fuchs Date: Wed Feb 25 14:04:00 2015 -0500 obsolete the ICE agent running flag commit 52237e33995d8b6ff25e898daee7d46af37992ed Author: Richard Fuchs Date: Wed Feb 25 14:02:06 2015 -0500 cease checks once ICE is completed commit 5332d18612d5c8e068590a6b4e708b969d385844 Author: Richard Fuchs Date: Wed Feb 25 13:53:57 2015 -0500 fix ICE completion logging commit 85f5fd63aa24998d206657cabeaf3fecbe4d187a Author: Richard Fuchs Date: Wed Feb 25 13:53:33 2015 -0500 make better use of bit flags commit 796b48bb78601e98270c182e2ad60f1c5278091a Author: Richard Fuchs Date: Wed Feb 25 12:09:46 2015 -0500 improved learned candidate pairing and completion logic commit d15561072e24a842e4fc7f074ec6ecadcf96cd8e Author: Richard Fuchs Date: Wed Feb 25 11:21:45 2015 -0500 support upper case transport strings commit 557da7b1c39807bad119f23e57c4d31d77962103 Author: Richard Fuchs Date: Wed Feb 25 10:43:57 2015 -0500 use struct endpoint in ice candidates commit 951040bfd62eb52bccb091d99b7b8289ccef2cc9 Author: Richard Fuchs Date: Wed Feb 25 10:31:13 2015 -0500 more meaningful ICE log messages commit 8ec2426bd3101c4f381e95cd0686b6b1a6fec658 Author: Richard Fuchs Date: Wed Feb 25 09:54:49 2015 -0500 shut down agent if no components - limit number of candidates commit 149260f3a6eaa5f58a6f9071e0b05a18914b987d Author: Richard Fuchs Date: Wed Feb 25 09:44:13 2015 -0500 handle ICE restarts commit 6a18c31f81ab94df1f5d5a701339b14be0e10ee5 Author: Richard Fuchs Date: Tue Feb 24 16:29:01 2015 -0500 dont clear succeeded flag when nominating commit 93e0861d0250f9190b16b14c3f4fe33a359d5d47 Author: Richard Fuchs Date: Tue Feb 24 16:21:40 2015 -0500 use correct pwd in stun binding response commit 32ba3ea406e30d168834f93d3e454d812820cf55 Author: Richard Fuchs Date: Tue Feb 24 16:03:09 2015 -0500 use deterministic foundation for prflx cands commit 2f2dc9151566d0bf2eebbaf02845718229be0e3d Author: Richard Fuchs Date: Tue Feb 24 15:21:13 2015 -0500 handle initial ICE role commit a6b8ad25e6c326f2a0edda12db5dff910bc3d771 Author: Richard Fuchs Date: Tue Feb 24 14:43:47 2015 -0500 another ICE scheduling fix commit c572b04e55b695af28efed4fc4dc8c798989f02d Author: Richard Fuchs Date: Tue Feb 24 14:14:29 2015 -0500 make ICE aware of rtcp-mux commit 93cd2d2560809f82e8ab9ffb2f5e2725fb439f4c Author: Richard Fuchs Date: Tue Feb 24 14:13:27 2015 -0500 print timestamp when logging to stderr commit 22a52ffda2f21dba37c44c9e148a4cbe830be52e Author: Richard Fuchs Date: Tue Feb 24 14:13:13 2015 -0500 ICE scheduling fixes commit 5d2d1a7739ef7d41514352fbf84deddcbd4500af Author: Richard Fuchs Date: Tue Feb 24 13:27:59 2015 -0500 increase ICE pwd length to make chrome happy commit ceff6698db33fa7a4500cb94a4fa377a7629b8aa Author: Richard Fuchs Date: Tue Feb 24 13:09:54 2015 -0500 dont discard RTP if ICE hasnt finished yet commit e809877d0ee0e7d60f3826be091c59145a2a9e19 Author: Richard Fuchs Date: Tue Feb 24 13:06:31 2015 -0500 implement remote-candidates (untested) commit 41670eadbbbe99a35a70158cf054b20a84c9c51b Author: Richard Fuchs Date: Tue Feb 24 11:58:13 2015 -0500 shut down ICE agent when everything fails commit 1ca26c4a815c3f78b0c97cc6b5a5d794a64926f4 Author: Richard Fuchs Date: Tue Feb 24 11:10:46 2015 -0500 fix up SDP output for ICE candidates commit 0287d68f3330d9b2b7e61ea77ce9c77dec83d217 Author: Richard Fuchs Date: Tue Feb 24 10:09:49 2015 -0500 process ICE completion and fire up DTLS if desired commit 5b6386036b05f43ccbdbeb63ea49d8585498cfd0 Author: Richard Fuchs Date: Tue Feb 24 09:37:52 2015 -0500 use a btree to schedule ice checks commit 2bc25f1e0f5acee9262186a866fcc851d2e911ba Author: Richard Fuchs Date: Tue Feb 24 09:37:02 2015 -0500 convert shutdown condition into global var commit ecf0c5587c62fcb39c8a67e510bcc7e6e3c10162 Author: Richard Fuchs Date: Mon Feb 23 14:50:46 2015 -0500 replace poller_now with timeval g_now commit 164ecdd7ac5d37641de32c98bba7db2e23446b91 Author: Richard Fuchs Date: Mon Feb 23 13:00:15 2015 -0500 handle nominations if we're controlling commit d013659365c33a3e802e1ec734dc45361531071d Author: Richard Fuchs Date: Mon Feb 23 12:11:40 2015 -0500 copy controlling/ed role into agent commit 09f1cae14a2dd1aaf06b1c81ebe30ad198b96ec6 Author: Richard Fuchs Date: Mon Feb 23 12:02:54 2015 -0500 separate nominated pairs from valid pairs commit f75f338cded413617318288ceb6a664771e9434c Author: Richard Fuchs Date: Mon Feb 23 11:55:30 2015 -0500 organize aux.h into sections commit d6acee1392a9cb34bd51524d11fc3d1e40f788cd Author: Richard Fuchs Date: Mon Feb 23 11:55:17 2015 -0500 use b-tree for various ICE lists commit af9804d139cf50c6908ac760c9179685b3a872f0 Author: Richard Fuchs Date: Fri Feb 20 16:21:09 2015 -0500 prepare to finalize ice processing commit 18df118375cb30015f703cfbd9ed5c5020ff122e Author: Richard Fuchs Date: Fri Feb 20 16:19:50 2015 -0500 prettier logging commit 280755c61a5e014422acfd45a30e98b2ec6b9efa Author: Richard Fuchs Date: Fri Feb 20 15:02:14 2015 -0500 implement unfreezing of other components on success commit 5d13657d5b7a962ebbbd96677909ba49f59492dc Author: Richard Fuchs Date: Fri Feb 20 15:00:24 2015 -0500 bitfield access macros commit 71746ad6a1171234adbb274ad67159a890c59c3c Author: Richard Fuchs Date: Fri Feb 20 14:15:05 2015 -0500 handle ice updates and duplicate candidates commit 02309d1b5b39333778e04ef61b61be3ba81cb9d3 Author: Richard Fuchs Date: Fri Feb 20 12:22:29 2015 -0500 handle role conflicts commit 52acf54ba5b2fe694f647e828ab92b0bf82bdce1 Author: Richard Fuchs Date: Fri Feb 20 10:24:01 2015 -0500 proper pair priorities calculation commit 307af79e8d347d703b065c5c7b7b0321f1ca82fb Author: Richard Fuchs Date: Fri Feb 20 09:54:18 2015 -0500 fix address family mixups commit 7cbfd4d36a21ad917974259bcb37c5cea340bf4e Author: Richard Fuchs Date: Fri Feb 20 09:14:49 2015 -0500 delay dtls startup and timeout checks while ice is running commit 2a8ab752280ebf9961137414c88c6afbf03b0df2 Author: Richard Fuchs Date: Thu Feb 19 16:47:56 2015 -0500 process ice/stun responses commit 92da323dcff4f725e29ab378ede1aab68d9900a1 Author: Richard Fuchs Date: Thu Feb 19 15:14:30 2015 -0500 adding stun server script for testing commit c5cfeb122cc8503a8f4c901e279eac2932feb3f7 Author: Richard Fuchs Date: Thu Feb 19 13:42:40 2015 -0500 act on stun requests, learn prflx candidates commit 1cafd35e7a5105214aa4af5b03d14054f02c668e Author: Richard Fuchs Date: Thu Feb 19 13:30:14 2015 -0500 fix pktinfo for ipv4 packets commit 8e338b842606a910a34a7ced09516a0cb097b449 Author: Richard Fuchs Date: Thu Feb 19 11:48:55 2015 -0500 rework interface handling to prepare to learn ICE candidate commit 09e365c1429d2a1d81126661df4749567f1e109d Author: Richard Fuchs Date: Wed Feb 18 16:24:42 2015 -0500 add some locking commit 8fc7b75095d09f118a4febf1f3f7b75ed333751d Author: Richard Fuchs Date: Wed Feb 18 16:05:20 2015 -0500 extend logic in response to stun req and implement triggered checks commit 35eeb04376c5a590f8fc42fcb1083a2fa7126e13 Author: Richard Fuchs Date: Wed Feb 18 12:46:42 2015 -0500 handle ice/stun retransmits and timeouts commit b5637565b659350fefb63034e4146ace8c6019b0 Author: Richard Fuchs Date: Wed Feb 18 11:19:19 2015 -0500 first implementation of sending ICE checks commit f0c1928c05a2c87fe23494367670207805c0b096 Author: Richard Fuchs Date: Tue Feb 17 14:39:11 2015 -0500 preliminary list and loop for ICE checks commit c38d6e22c18285a7fb87e5d7a00ac61cb5b7df02 Author: Richard Fuchs Date: Tue Feb 17 12:00:24 2015 -0500 pair up candidates and prepare to run checks commit d9559b4c5935ec602fdb801ecb0bd2158b39db65 Author: Richard Fuchs Date: Fri Feb 13 15:36:29 2015 -0500 parse and remeber basic ICE attributes --- daemon/Makefile | 2 +- daemon/aux.c | 21 + daemon/aux.h | 339 +++++++--- daemon/call.c | 444 ++++++++----- daemon/call.h | 70 +- daemon/call_interfaces.c | 2 + daemon/dtls.c | 10 +- daemon/graphite.c | 30 +- daemon/ice.c | 1357 ++++++++++++++++++++++++++++++++++++++ daemon/ice.h | 208 ++++++ daemon/log.c | 10 +- daemon/log.h | 14 +- daemon/main.c | 19 +- daemon/poller.c | 12 +- daemon/poller.h | 6 +- daemon/sdp.c | 368 ++++++----- daemon/sdp.h | 1 - daemon/stun.c | 285 ++++++-- daemon/stun.h | 24 +- tests/stun-client | 91 +++ tests/stun-server | 204 ++++++ 21 files changed, 2960 insertions(+), 557 deletions(-) create mode 100644 daemon/ice.c create mode 100644 daemon/ice.h create mode 100755 tests/stun-client create mode 100755 tests/stun-server diff --git a/daemon/Makefile b/daemon/Makefile index d1488770b..235526e14 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -63,7 +63,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 call_interfaces.c dtls.c log.c cli.c graphite.c + crypto.c rtp.c call_interfaces.c dtls.c log.c cli.c graphite.c ice.c OBJS= $(SRCS:.c=.o) diff --git a/daemon/aux.c b/daemon/aux.c index 840f75e4a..cbb645024 100644 --- a/daemon/aux.c +++ b/daemon/aux.c @@ -34,6 +34,9 @@ static cond_t threads_cond = COND_STATIC_INIT; static struct thread_buf __thread t_bufs[NUM_THREAD_BUFS]; static int __thread t_buf_idx; +__thread struct timeval g_now; +volatile int g_shutdown; + #ifdef NEED_ATOMIC64_MUTEX mutex_t __atomic64_mutex = MUTEX_STATIC_INIT; #endif @@ -191,3 +194,21 @@ char *get_thread_buf(void) { t_buf_idx = 0; return ret; } + +int g_tree_find_first_cmp(void *k, void *v, void *d) { + void **p = d; + GEqualFunc f = p[1]; + if (!f || f(v, p[0])) { + p[2] = v; + return TRUE; + } + return FALSE; +} +int g_tree_find_all_cmp(void *k, void *v, void *d) { + void **p = d; + GEqualFunc f = p[1]; + GQueue *q = p[2]; + if (!f || f(v, p[0])) + g_queue_push_tail(q, v); + return FALSE; +} diff --git a/daemon/aux.h b/daemon/aux.h index 05c7671c1..4b0643349 100644 --- a/daemon/aux.h +++ b/daemon/aux.h @@ -27,27 +27,11 @@ +/*** HELPER MACROS ***/ + #define OFFSET_OF(t,e) ((unsigned int) (unsigned long) &(((t *) 0)->e)) #define ZERO(x) memset(&(x), 0, sizeof(x)) -#define IPF "%u.%u.%u.%u" -#define IPP(x) ((unsigned char *) (&(x)))[0], ((unsigned char *) (&(x)))[1], ((unsigned char *) (&(x)))[2], ((unsigned char *) (&(x)))[3] -#define IP6F "%x:%x:%x:%x:%x:%x:%x:%x" -#define IP6P(x) ntohs(((u_int16_t *) (x))[0]), \ - ntohs(((u_int16_t *) (x))[1]), \ - ntohs(((u_int16_t *) (x))[2]), \ - ntohs(((u_int16_t *) (x))[3]), \ - ntohs(((u_int16_t *) (x))[4]), \ - ntohs(((u_int16_t *) (x))[5]), \ - ntohs(((u_int16_t *) (x))[6]), \ - ntohs(((u_int16_t *) (x))[7]) -#define D6F "["IP6F"]:%u" -#define D6P(x) IP6P((x).sin6_addr.s6_addr), ntohs((x).sin6_port) -#define DF IPF ":%u" -#define DP(x) IPP((x).sin_addr.s_addr), ntohs((x).sin_port) - -#define BIT_ARRAY_DECLARE(name, size) unsigned long name[((size) + sizeof(long) * 8 - 1) / (sizeof(long) * 8)] - #define UINT64F "%" G_GUINT64_FORMAT #define THREAD_BUF_SIZE 64 @@ -56,21 +40,50 @@ +/*** TYPES ***/ + +struct endpoint { + struct in6_addr ip46; + u_int16_t port; +}; + + + +/*** GLOBALS ***/ + +extern __thread struct timeval g_now; +extern volatile int g_shutdown; + + + + +/*** PROTOTYPES ***/ + typedef int (*parse_func)(char **, void **, void *); -GList *g_list_link(GList *, GList *); int pcre_multi_match(pcre *, pcre_extra *, const char *, unsigned int, parse_func, void *, GQueue *); INLINE void strmove(char **, char **); INLINE void strdupfree(char **, const char *); char *get_thread_buf(void); +unsigned int in6_addr_hash(const void *p); +int in6_addr_eq(const void *a, const void *b); + +/*** GLIB HELPERS ***/ + +GList *g_list_link(GList *, GList *); + #if !GLIB_CHECK_VERSION(2,32,0) INLINE int g_hash_table_contains(GHashTable *h, const void *k) { return g_hash_table_lookup(h, k) ? 1 : 0; } #endif + + +/* GQUEUE */ + INLINE void g_queue_move(GQueue *dst, GQueue *src) { GList *l; while ((l = g_queue_pop_head_link(src))) @@ -85,8 +98,55 @@ INLINE void g_queue_clear_full(GQueue *q, GDestroyNotify free_func) { while ((p = g_queue_pop_head(q))) free_func(p); } +INLINE void g_queue_append(GQueue *dst, const GQueue *src) { + GList *l; + if (!src || !dst) + return; + for (l = src->head; l; l = l->next) + g_queue_push_tail(dst, l->data); +} + + +/* GTREE */ + +int g_tree_find_first_cmp(void *, void *, void *); +int g_tree_find_all_cmp(void *, void *, void *); +INLINE void *g_tree_find_first(GTree *t, GEqualFunc f, void *data) { + void *p[3]; + p[0] = data; + p[1] = f; + p[2] = NULL; + g_tree_foreach(t, g_tree_find_first_cmp, p); + return p[2]; +} +INLINE void g_tree_find_all(GQueue *out, GTree *t, GEqualFunc f, void *data) { + void *p[3]; + p[0] = data; + p[1] = f; + p[2] = out; + g_tree_foreach(t, g_tree_find_all_cmp, p); +} +INLINE void g_tree_get_values(GQueue *out, GTree *t) { + g_tree_find_all(out, t, NULL, NULL); +} +INLINE void g_tree_remove_all(GQueue *out, GTree *t) { + GList *l; + g_queue_init(out); + g_tree_find_all(out, t, NULL, NULL); + for (l = out->head; l; l = l->next) + g_tree_remove(t, l->data); +} +INLINE void g_tree_add_all(GTree *t, GQueue *q) { + GList *l; + for (l = q->head; l; l = l->next) + g_tree_insert(t, l->data, l->data); + g_queue_clear(q); +} + +/*** STRING HELPERS ***/ + INLINE void strmove(char **d, char **s) { if (*d) free(*d); @@ -100,6 +160,36 @@ INLINE void strdupfree(char **d, const char *s) { *d = strdup(s); } +INLINE int strmemcmp(const void *mem, int len, const char *str) { + int l = strlen(str); + if (l < len) + return -1; + if (l > len) + return 1; + return memcmp(mem, str, len); +} + +INLINE void random_string(unsigned char *buf, int len) { + RAND_bytes(buf, len); +} + +INLINE const char *__get_enum_array_text(const char * const *array, unsigned int idx, + unsigned int len, const char *deflt) +{ + const char *ret; + if (idx >= len) + return deflt; + ret = array[idx]; + return ret ? : deflt; +} +#define get_enum_array_text(array, idx, deflt) \ + __get_enum_array_text(array, idx, G_N_ELEMENTS(array), deflt) + + + + + +/*** SOCKET/FD HELPERS ***/ INLINE void nonblock(int fd) { fcntl(fd, F_SETFL, O_NONBLOCK); @@ -112,17 +202,10 @@ INLINE void ipv6only(int fd, int yn) { setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yn, sizeof(yn)); } -INLINE unsigned long bit_array_isset(unsigned long *name, unsigned int bit) { - return name[(bit) / (sizeof(long) * 8)] & (1UL << ((bit) % (sizeof(long) * 8))); -} -INLINE void bit_array_set(unsigned long *name, unsigned int bit) { - name[(bit) / (sizeof(long) * 8)] |= 1UL << ((bit) % (sizeof(long) * 8)); -} -INLINE void bit_array_clear(unsigned long *name, unsigned int bit) { - name[(bit) / (sizeof(long) * 8)] &= ~(1UL << ((bit) % (sizeof(long) * 8))); -} + +/*** GENERIC HELPERS ***/ INLINE char chrtoupper(char x) { return x & 0xdf; @@ -137,6 +220,34 @@ INLINE void swap_ptrs(void *a, void *b) { *bb = t; } +INLINE int rlim(int res, rlim_t val) { + struct rlimit rlim; + + ZERO(rlim); + rlim.rlim_cur = rlim.rlim_max = val; + return setrlimit(res, &rlim); +} + + + +/*** INET ADDRESS HELPERS ***/ + +#define IPF "%u.%u.%u.%u" +#define IPP(x) ((unsigned char *) (&(x)))[0], ((unsigned char *) (&(x)))[1], ((unsigned char *) (&(x)))[2], ((unsigned char *) (&(x)))[3] +#define IP6F "%x:%x:%x:%x:%x:%x:%x:%x" +#define IP6P(x) ntohs(((u_int16_t *) (x))[0]), \ + ntohs(((u_int16_t *) (x))[1]), \ + ntohs(((u_int16_t *) (x))[2]), \ + ntohs(((u_int16_t *) (x))[3]), \ + ntohs(((u_int16_t *) (x))[4]), \ + ntohs(((u_int16_t *) (x))[5]), \ + ntohs(((u_int16_t *) (x))[6]), \ + ntohs(((u_int16_t *) (x))[7]) +#define D6F "["IP6F"]:%u" +#define D6P(x) IP6P((x).sin6_addr.s6_addr), ntohs((x).sin6_port) +#define DF IPF ":%u" +#define DP(x) IPP((x).sin_addr.s_addr), ntohs((x).sin_port) + INLINE void in4_to_6(struct in6_addr *o, u_int32_t ip) { o->s6_addr32[0] = 0; o->s6_addr32[1] = 0; @@ -193,15 +304,19 @@ INLINE char *smart_ntop_p_buf(const struct in6_addr *a) { return buf; } -INLINE void smart_ntop_port(char *o, const struct sockaddr_in6 *a, size_t len) { +INLINE void smart_ntop_ap(char *o, const struct in6_addr *a, unsigned int port, size_t len) { char *e; - e = smart_ntop_p(o, &a->sin6_addr, len); + e = smart_ntop_p(o, a, len); if (!e) return; if (len - (e - o) < 7) return; - sprintf(e, ":%hu", ntohs(a->sin6_port)); + sprintf(e, ":%u", port); +} + +INLINE void smart_ntop_port(char *o, const struct sockaddr_in6 *a, size_t len) { + return smart_ntop_ap(o, &a->sin6_addr, ntohs(a->sin6_port), len); } INLINE char *smart_ntop_port_buf(const struct sockaddr_in6 *a) { @@ -210,6 +325,18 @@ INLINE char *smart_ntop_port_buf(const struct sockaddr_in6 *a) { return buf; } +INLINE char *smart_ntop_ap_buf(const struct in6_addr *a, unsigned int port) { + char *buf = get_thread_buf(); + smart_ntop_ap(buf, a, port, THREAD_BUF_SIZE); + return buf; +} + +INLINE char *smart_ntop_ep_buf(const struct endpoint *ep) { + char *buf = get_thread_buf(); + smart_ntop_ap(buf, &ep->ip46, ep->port, THREAD_BUF_SIZE); + return buf; +} + INLINE int smart_pton(int af, char *src, void *dst) { char *p; int ret; @@ -302,22 +429,62 @@ fail: return -1; } - -INLINE int strmemcmp(const void *mem, int len, const char *str) { - int l = strlen(str); - if (l < len) - return -1; - if (l > len) +INLINE int is_addr_unspecified(const struct in6_addr *a) { + if (a->s6_addr32[0]) + return 0; + if (a->s6_addr32[1]) + return 0; + if (a->s6_addr32[3]) + return 0; + if (a->s6_addr32[2] == 0 || a->s6_addr32[2] == htonl(0xffff)) return 1; - return memcmp(mem, str, len); + return 0; } -INLINE void random_string(unsigned char *buf, int len) { - RAND_bytes(buf, len); +INLINE int family_from_address(const struct in6_addr *a) { + if (IN6_IS_ADDR_V4MAPPED(a)) + return AF_INET; + return AF_INET6; +} + +INLINE void msg_mh_src(const struct in6_addr *src, struct msghdr *mh) { + struct cmsghdr *ch; + struct in_pktinfo *pi; + struct in6_pktinfo *pi6; + struct sockaddr_in6 *sin6; + + sin6 = mh->msg_name; + ch = CMSG_FIRSTHDR(mh); + ZERO(*ch); + + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { + ch->cmsg_len = CMSG_LEN(sizeof(*pi)); + ch->cmsg_level = IPPROTO_IP; + ch->cmsg_type = IP_PKTINFO; + + pi = (void *) CMSG_DATA(ch); + ZERO(*pi); + pi->ipi_spec_dst.s_addr = in6_to_4(src); + + mh->msg_controllen = CMSG_SPACE(sizeof(*pi)); + } + else { + ch->cmsg_len = CMSG_LEN(sizeof(*pi6)); + ch->cmsg_level = IPPROTO_IPV6; + ch->cmsg_type = IPV6_PKTINFO; + + pi6 = (void *) CMSG_DATA(ch); + ZERO(*pi6); + pi6->ipi6_addr = *src; + + mh->msg_controllen = CMSG_SPACE(sizeof(*pi6)); + } } +/*** MUTEX ABSTRACTION ***/ + typedef pthread_mutex_t mutex_t; typedef pthread_rwlock_t rwlock_t; typedef pthread_cond_t cond_t; @@ -338,10 +505,18 @@ typedef pthread_cond_t cond_t; #define cond_init(c) __debug_cond_init(c, __FILE__, __LINE__) #define cond_wait(c,m) __debug_cond_wait(c,m, __FILE__, __LINE__) +#define cond_timedwait(c,m,t) __debug_cond_timedwait(c,m,t, __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 +INLINE int __cond_timedwait_tv(cond_t *c, mutex_t *m, const struct timeval *tv) { + struct timespec ts; + ts.tv_sec = tv->tv_sec; + ts.tv_nsec = tv->tv_usec * 1000; + return pthread_cond_timedwait(c, m, &ts); +} + #ifndef __THREAD_DEBUG #define __debug_mutex_init(m, F, L) pthread_mutex_init(m, NULL) @@ -359,6 +534,7 @@ typedef pthread_cond_t cond_t; #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_timedwait(c, m, t, F, L) __cond_timedwait_tv(c,m,t) #define __debug_cond_signal(c, F, L) pthread_cond_signal(c) #define __debug_cond_broadcast(c, F, L) pthread_cond_broadcast(c) @@ -429,6 +605,7 @@ INLINE int __debug_rwlock_unlock_w(rwlock_t *m, const char *file, unsigned int 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_timedwait(c, m, t, F, L) __cond_timedwait_tv(c,m,t) #define __debug_cond_signal(c, F, L) pthread_cond_signal(c) #define __debug_cond_broadcast(c, F, L) pthread_cond_broadcast(c) @@ -436,36 +613,16 @@ INLINE int __debug_rwlock_unlock_w(rwlock_t *m, const char *file, unsigned int l -void threads_join_all(int); -void thread_create_detach(void (*)(void *), void *); +/*** THREAD HELPERS ***/ +void threads_join_all(int); +void thread_create_detach(void (*)(void *), void *); -INLINE int rlim(int res, rlim_t val) { - struct rlimit rlim; - ZERO(rlim); - rlim.rlim_cur = rlim.rlim_max = val; - return setrlimit(res, &rlim); -} -INLINE int is_addr_unspecified(const struct in6_addr *a) { - if (a->s6_addr32[0]) - return 0; - if (a->s6_addr32[1]) - return 0; - if (a->s6_addr32[3]) - return 0; - if (a->s6_addr32[2] == 0 || a->s6_addr32[2] == htonl(0xffff)) - return 1; - return 0; -} -INLINE int family_from_address(const struct in6_addr *a) { - if (IN6_IS_ADDR_V4MAPPED(a)) - return AF_INET; - return AF_INET6; -} +/*** ATOMIC BITFIELD OPERATIONS ***/ /* checks if at least one of the flags is set */ INLINE int bf_isset(const volatile unsigned int *u, unsigned int f) { @@ -473,11 +630,19 @@ INLINE int bf_isset(const volatile unsigned int *u, unsigned int f) { return -1; return 0; } -INLINE void bf_set(volatile unsigned int *u, unsigned int f) { - g_atomic_int_or(u, f); +/* checks if all of the flags are set */ +INLINE int bf_areset(const volatile unsigned int *u, unsigned int f) { + if ((g_atomic_int_get(u) & f) == f) + return -1; + return 0; +} +/* returns true if at least one of the flags was set already */ +INLINE int bf_set(volatile unsigned int *u, unsigned int f) { + return (g_atomic_int_or(u, f) & f) ? -1 : 0; } -INLINE void bf_clear(volatile unsigned int *u, unsigned int f) { - g_atomic_int_and(u, ~f); +/* returns true if at least one of the flags was set */ +INLINE int bf_clear(volatile unsigned int *u, unsigned int f) { + return (g_atomic_int_and(u, ~f) & f) ? -1 : 0; } INLINE void bf_set_clear(volatile unsigned int *u, unsigned int f, int cond) { if (cond) @@ -502,21 +667,27 @@ INLINE void bf_copy_same(volatile unsigned int *u, const volatile unsigned int * } -INLINE void g_queue_append(GQueue *dst, const GQueue *src) { - GList *l; - if (!src || !dst) - return; - for (l = src->head; l; l = l->next) - g_queue_push_tail(dst, l->data); -} +/*** BIT ARRAY FUNCTIONS ***/ +#define BIT_ARRAY_DECLARE(name, size) \ + volatile unsigned int name[((size) + sizeof(int) * 8 - 1) / (sizeof(int) * 8)] -unsigned int in6_addr_hash(const void *p); -int in6_addr_eq(const void *a, const void *b); +INLINE int bit_array_isset(const volatile unsigned int *name, unsigned int bit) { + return bf_isset(&name[bit / (sizeof(int) * 8)], 1U << (bit % (sizeof(int) * 8))); +} +INLINE int bit_array_set(volatile unsigned int *name, unsigned int bit) { + return bf_set(&name[bit / (sizeof(int) * 8)], 1U << (bit % (sizeof(int) * 8))); +} +INLINE int bit_array_clear(volatile unsigned int *name, unsigned int bit) { + return bf_clear(&name[bit / (sizeof(int) * 8)], 1U << (bit % (sizeof(int) * 8))); +} + +/*** ATOMIC64 ***/ + #if GLIB_SIZEOF_VOID_P >= 8 typedef struct { @@ -611,18 +782,4 @@ INLINE void atomic64_local_copy_zero(atomic64 *dst, atomic64 *src) { -INLINE const char *__get_enum_array_text(const char * const *array, unsigned int idx, - unsigned int len, const char *deflt) -{ - const char *ret; - if (idx >= len) - return deflt; - ret = array[idx]; - return ret ? : deflt; -} -#define get_enum_array_text(array, idx, deflt) \ - __get_enum_array_text(array, idx, G_N_ELEMENTS(array), deflt) - - - #endif diff --git a/daemon/call.c b/daemon/call.c index d0b8ab84f..0f90b4c89 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -32,6 +32,7 @@ #include "rtcp.h" #include "rtp.h" #include "call_interfaces.h" +#include "ice.h" @@ -320,12 +321,12 @@ static const struct rtpengine_srtp __res_null = { -static void unkernelize(struct packet_stream *); -static void __stream_unkernelize(struct packet_stream *ps); -static void stream_unkernelize(struct packet_stream *ps); +static void __unkernelize(struct packet_stream *); +static void __stream_unconfirm(struct packet_stream *ps); +static void stream_unconfirm(struct packet_stream *ps); static void __monologue_destroy(struct call_monologue *monologue); static struct interface_address *get_interface_address(struct local_interface *lif, int family); -static const GQueue *get_interface_addresses(struct local_interface *lif, int family); +static struct interface_address *get_any_interface_address(struct local_interface *lif, int family); @@ -412,7 +413,7 @@ void kernelize(struct packet_stream *stream) { ZERO(reti); - if (PS_ISSET(stream, STRICT_SOURCE) || PS_ISSET(stream, MEDIA_HANDOVER)) { + if (PS_ISSET2(stream, STRICT_SOURCE, MEDIA_HANDOVER)) { mutex_lock(&stream->out_lock); __re_address_translate(&reti.expected_src, &stream->endpoint); mutex_unlock(&stream->out_lock); @@ -428,7 +429,7 @@ void kernelize(struct packet_stream *stream) { reti.tos = call->tos; reti.rtcp_mux = MEDIA_ISSET(stream->media, RTCP_MUX); reti.dtls = MEDIA_ISSET(stream->media, DTLS); - reti.stun = PS_ISSET(stream, STUN); + reti.stun = stream->media->ice_agent ? 1 : 0; __re_address_translate(&reti.dst_addr, &sink->endpoint); @@ -583,41 +584,10 @@ noop: } void stream_msg_mh_src(struct packet_stream *ps, struct msghdr *mh) { - struct cmsghdr *ch; - struct in_pktinfo *pi; - struct in6_pktinfo *pi6; - struct sockaddr_in6 *sin6; struct interface_address *ifa; - - sin6 = mh->msg_name; ifa = g_atomic_pointer_get(&ps->media->local_address); - - ch = CMSG_FIRSTHDR(mh); - ZERO(*ch); - - if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { - ch->cmsg_len = CMSG_LEN(sizeof(*pi)); - ch->cmsg_level = IPPROTO_IP; - ch->cmsg_type = IP_PKTINFO; - - pi = (void *) CMSG_DATA(ch); - ZERO(*pi); - pi->ipi_spec_dst.s_addr = in6_to_4(&ifa->addr); - - mh->msg_controllen = CMSG_SPACE(sizeof(*pi)); - } - else { - ch->cmsg_len = CMSG_LEN(sizeof(*pi6)); - ch->cmsg_level = IPPROTO_IPV6; - ch->cmsg_type = IPV6_PKTINFO; - - pi6 = (void *) CMSG_DATA(ch); - ZERO(*pi6); - pi6->ipi6_addr = ifa->addr; - - mh->msg_controllen = CMSG_SPACE(sizeof(*pi6)); - } + msg_mh_src(&ifa->addr, mh); } /* XXX split this function into pieces */ @@ -654,35 +624,39 @@ static int stream_packet(struct stream_fd *sfd, str *s, struct sockaddr_in6 *fsi if (!stream) goto unlock_out; - mutex_lock(&stream->in_lock); media = stream->media; if (!stream->sfd) - goto done; + goto unlock_out; /* demux other protocols running on this port */ if (MEDIA_ISSET(media, DTLS) && is_dtls(s)) { + mutex_lock(&stream->in_lock); ret = dtls(stream, s, fsin); + mutex_unlock(&stream->in_lock); if (!ret) - goto done; + goto unlock_out; } - if (PS_ISSET(stream, STUN) && is_stun(s)) { - stun_ret = stun(s, stream, fsin); + if (media->ice_agent && is_stun(s)) { + stun_ret = stun(s, stream, fsin, dst); if (!stun_ret) - goto done; + goto unlock_out; if (stun_ret == 1) { - ilog(LOG_INFO, "STUN: using this candidate"); - goto use_cand; + call_stream_state_machine(stream); + mutex_lock(&stream->in_lock); /* for the jump */ + goto kernel_check; } else /* not an stun packet */ stun_ret = 0; } #if RTP_LOOP_PROTECT + mutex_lock(&stream->in_lock); + for (i = 0; i < RTP_LOOP_PACKETS; i++) { if (stream->lp_buf[i].len != s->len) continue; @@ -706,9 +680,8 @@ static int stream_packet(struct stream_fd *sfd, str *s, struct sockaddr_in6 *fsi memcpy(stream->lp_buf[stream->lp_idx].buf, s->s, MIN(s->len, RTP_LOOP_PROTECT)); stream->lp_idx = (stream->lp_idx + 1) % RTP_LOOP_PACKETS; loop_ok: -#endif - mutex_unlock(&stream->in_lock); +#endif /* demux RTCP */ @@ -792,9 +765,11 @@ loop_ok: mutex_unlock(&out_srtp->out_lock); mutex_unlock(&in_srtp->in_lock); + + /* endpoint address handling */ + mutex_lock(&stream->in_lock); -use_cand: /* we're OK to (potentially) use the source address of this packet as destination * in the other direction. */ /* if the other side hasn't been signalled yet, just forward the packet */ @@ -808,7 +783,7 @@ use_cand: /* if we have already updated the endpoint in the past ... */ if (PS_ISSET(stream, CONFIRMED)) { /* see if we need to compare the source address with the known endpoint */ - if (PS_ISSET(stream, STRICT_SOURCE) || PS_ISSET(stream, MEDIA_HANDOVER)) { + if (PS_ISSET2(stream, STRICT_SOURCE, MEDIA_HANDOVER)) { endpoint.ip46 = fsin->sin6_addr; endpoint.port = ntohs(fsin->sin6_port); mutex_lock(&stream->out_lock); @@ -874,7 +849,7 @@ kernel_check: if (PS_ISSET(stream, NO_KERNEL_SUPPORT)) goto forward; - if (PS_ISSET(stream, CONFIRMED) && sink && PS_ISSET(sink, CONFIRMED) && PS_ISSET(sink, FILLED)) + if (PS_ISSET(stream, CONFIRMED) && sink && PS_ARESET2(sink, CONFIRMED, FILLED)) kernelize(stream); forward: @@ -936,11 +911,11 @@ out: done: if (unk) - __stream_unkernelize(stream); + __stream_unconfirm(stream); mutex_unlock(&stream->in_lock); if (unk) { - stream_unkernelize(stream->rtp_sink); - stream_unkernelize(stream->rtcp_sink); + stream_unconfirm(stream->rtp_sink); + stream_unconfirm(stream->rtcp_sink); } unlock_out: rwlock_unlock_r(&call->master_lock); @@ -964,7 +939,8 @@ static void stream_fd_readable(int fd, void *p, uintptr_t u) { char control[128]; struct cmsghdr *cmh; struct in6_pktinfo *pi6; - struct in6_addr *dst; + struct in6_addr dst_buf, *dst; + struct in_pktinfo *pi; if (sfd->fd.fd != fd) goto out; @@ -1003,20 +979,30 @@ static void stream_fd_readable(int fd, void *p, uintptr_t u) { if (ret >= MAX_RTP_PACKET_SIZE) ilog(LOG_WARNING, "UDP packet possibly truncated"); - dst = NULL; for (cmh = CMSG_FIRSTHDR(&mh); cmh; cmh = CMSG_NXTHDR(&mh, cmh)) { if (cmh->cmsg_level == IPPROTO_IPV6 && cmh->cmsg_type == IPV6_PKTINFO) { pi6 = (void *) CMSG_DATA(cmh); dst = &pi6->ipi6_addr; + goto got_dst; + } + if (cmh->cmsg_level == IPPROTO_IP && cmh->cmsg_type == IP_PKTINFO) { + pi = (void *) CMSG_DATA(cmh); + in4_to_6(&dst_buf, pi->ipi_addr.s_addr); + dst = &dst_buf; + goto got_dst; } } + ilog(LOG_WARNING, "No pkt_info present in received UDP packet, cannot handle packet"); + goto done; + +got_dst: str_init_len(&s, buf + RTP_BUFFER_HEAD_ROOM, ret); ret = stream_packet(sfd, &s, &sin6_src, dst); if (ret < 0) { ilog(LOG_WARNING, "Write error on RTP socket: %s", strerror(-ret)); call_destroy(sfd->call); - return; + goto done; } if (ret == 1) update = 1; @@ -1096,6 +1082,7 @@ static void call_timer_iterator(void *key, void *val, void *ptr) { int tmp_t_reason=0; struct call_monologue *ml; GSList *i; + enum call_stream_state css; rwlock_lock_r(&c->master_lock); log_info_call(c); @@ -1116,7 +1103,6 @@ static void call_timer_iterator(void *key, void *val, void *ptr) { for (it = c->streams; it; it = it->next) { ps = it->data; - mutex_lock(&ps->in_lock); if (!ps->media) goto next; @@ -1124,8 +1110,14 @@ static void call_timer_iterator(void *key, void *val, void *ptr) { if (!sfd) goto no_sfd; - if (MEDIA_ISSET(ps->media, DTLS) && sfd->dtls.init && !sfd->dtls.connected) - dtls(ps, NULL, NULL); + /* valid stream */ + + css = call_stream_state_machine(ps); + + if (css == CSS_ICE) { + good = 1; + goto next; + } if (hlp->ports[sfd->fd.localport]) goto next; @@ -1147,7 +1139,7 @@ no_sfd: good = 1; next: - mutex_unlock(&ps->in_lock); + ; } if (good) @@ -1532,6 +1524,7 @@ static void __get_pktinfo(int fd) { int x; x = 1; setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &x, sizeof(x)); + setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &x, sizeof(x)); } static int get_port6(struct udp_fd *r, u_int16_t p, const struct call *c) { @@ -1571,23 +1564,17 @@ static int get_port(struct udp_fd *r, u_int16_t p, const struct call *c) { __C_DBG("attempting to open port %u", p); - mutex_lock(&m->portlock); - if (bit_array_isset(m->ports_used, p)) { - mutex_unlock(&m->portlock); + if (bit_array_set(m->ports_used, p)) { __C_DBG("port in use"); return -1; } - bit_array_set(m->ports_used, p); - mutex_unlock(&m->portlock); __C_DBG("port locked"); ret = get_port6(r, p, c); if (ret) { __C_DBG("couldn't open port"); - mutex_lock(&m->portlock); bit_array_clear(m->ports_used, p); - mutex_unlock(&m->portlock); return ret; } @@ -1600,9 +1587,7 @@ static void release_port(struct udp_fd *r, struct callmaster *m) { if (r->fd == -1 || !r->localport) return; __C_DBG("releasing port %u", r->localport); - mutex_lock(&m->portlock); bit_array_clear(m->ports_used, r->localport); - mutex_unlock(&m->portlock); close(r->fd); r->fd = -1; r->localport = 0; @@ -1619,9 +1604,7 @@ int __get_consecutive_ports(struct udp_fd *array, int array_len, int wanted_star if (wanted_start_port > 0) port = wanted_start_port; else { - mutex_lock(&m->portlock); - port = m->lastport; - mutex_unlock(&m->portlock); + port = g_atomic_int_get(&m->lastport); #if PORT_RANDOM_MIN && PORT_RANDOM_MAX port += PORT_RANDOM_MIN + (random() % (PORT_RANDOM_MAX - PORT_RANDOM_MIN)); #endif @@ -1658,9 +1641,7 @@ release_restart: } /* success */ - mutex_lock(&m->portlock); - m->lastport = port; - mutex_unlock(&m->portlock); + g_atomic_int_set(&m->lastport, port); ilog(LOG_DEBUG, "Opened ports %u..%u for media relay", array[0].localport, array[array_len - 1].localport); @@ -1810,22 +1791,27 @@ static void __assign_stream_fds(struct call_media *media, GList *sfds) { GList *l; struct packet_stream *ps; struct stream_fd *sfd; + int reset = 0; for (l = media->streams.head; l; l = l->next) { assert(sfds != NULL); ps = l->data; sfd = sfds->data; - /* if we switch local ports, we reset crypto params */ + /* if we switch local ports, we reset crypto params and ICE */ if (ps->sfd && ps->sfd != sfd) { dtls_shutdown(ps); crypto_reset(&ps->sfd->crypto); + reset = 1; } ps->sfd = sfd; sfd->stream = ps; sfds = sfds->next; } + + if (reset && media->ice_agent) + ice_restart(media->ice_agent); } static int __wildcard_endpoint_map(struct call_media *media, unsigned int num_ports) { @@ -1869,6 +1855,7 @@ static int __num_media_streams(struct call_media *media, unsigned int num_ports) stream = __packet_stream_new(call); stream->media = media; g_queue_push_tail(&media->streams, stream); + stream->component = media->streams.length; ret++; } @@ -1889,6 +1876,29 @@ static void __fill_stream(struct packet_stream *ps, const struct endpoint *ep, u PS_SET(ps, FILLED); } +/* called with call locked in R or W, but ps not locked */ +enum call_stream_state call_stream_state_machine(struct packet_stream *ps) { + struct call_media *media = ps->media; + + if (!ps->sfd) + return CSS_SHUTDOWN; + + if (MEDIA_ISSET(media, ICE) && !ice_has_finished(media)) + return CSS_ICE; /* handled by ICE timer */ + + if (MEDIA_ISSET(media, DTLS)) { + mutex_lock(&ps->in_lock); + if (ps->sfd->dtls.init && !ps->sfd->dtls.connected) { + dtls(ps, NULL, NULL); + mutex_unlock(&ps->in_lock); + return CSS_DTLS; + } + mutex_unlock(&ps->in_lock); + } + + return CSS_RUNNING; +} + static int __init_stream(struct packet_stream *ps) { struct call_media *media = ps->media; struct call *call = ps->call; @@ -1908,6 +1918,8 @@ static int __init_stream(struct packet_stream *ps) { if (dtls_verify_cert(ps)) return -1; } + + call_stream_state_machine(ps); } } @@ -1967,6 +1979,8 @@ static int __init_streams(struct call_media *A, struct call_media *B, const stru bf_copy_same(&a->ps_flags, &sp->sp_flags, SHARED_FLAG_STRICT_SOURCE | SHARED_FLAG_MEDIA_HANDOVER); } + bf_copy_same(&a->ps_flags, &A->media_flags, SHARED_FLAG_ICE); + if (__init_stream(a)) return -1; @@ -2016,6 +2030,8 @@ static int __init_streams(struct call_media *A, struct call_media *B, const stru bf_copy_same(&a->ps_flags, &sp->sp_flags, SHARED_FLAG_STRICT_SOURCE | SHARED_FLAG_MEDIA_HANDOVER); } + bf_copy_same(&a->ps_flags, &A->media_flags, SHARED_FLAG_ICE); + if (__init_stream(a)) return -1; @@ -2046,6 +2062,29 @@ static void __ice_offer(const struct sdp_ng_flags *flags, struct call_media *thi ilog(LOG_DEBUG, "enabling passthrough mode"); MEDIA_SET(this, PASSTHRU); MEDIA_SET(other, PASSTHRU); + return; + } + + /* determine roles (even if we don't actually do ICE) */ + /* this = receiver, other = sender */ + /* ICE_CONTROLLING is from our POV, the other ICE flags are from peer's POV */ + if (MEDIA_ISSET(this, ICE_LITE)) + MEDIA_SET(this, ICE_CONTROLLING); + else if (!MEDIA_ISSET(this, INITIALIZED)) { + if (flags->opmode == OP_OFFER) + MEDIA_SET(this, ICE_CONTROLLING); + else + MEDIA_CLEAR(this, ICE_CONTROLLING); + } + + /* roles are reversed for the other side */ + if (MEDIA_ISSET(other, ICE_LITE)) + MEDIA_SET(other, ICE_CONTROLLING); + else if (!MEDIA_ISSET(other, INITIALIZED)) { + if (flags->opmode == OP_OFFER) + MEDIA_CLEAR(other, ICE_CONTROLLING); + else + MEDIA_SET(other, ICE_CONTROLLING); } } @@ -2265,9 +2304,8 @@ static void __init_interface(struct call_media *media, const str *ifname) { if (!str_cmp_str(&media->interface->name, ifname)) return; get: - media->interface = get_local_interface(media->call->callmaster, ifname); + media->interface = get_local_interface(media->call->callmaster, ifname, media->desired_family); if (!media->interface) { - media->interface = get_local_interface(media->call->callmaster, NULL); /* legacy support */ if (!str_cmp(ifname, "internal")) media->desired_family = AF_INET; @@ -2275,6 +2313,7 @@ get: media->desired_family = AF_INET6; else ilog(LOG_WARNING, "Interface '"STR_FORMAT"' not found, using default", STR_FMT(ifname)); + media->interface = get_local_interface(media->call->callmaster, NULL, media->desired_family); } media->local_address = ifa = get_interface_address(media->interface, media->desired_family); if (!ifa) { @@ -2317,7 +2356,7 @@ static void __dtls_logic(const struct sdp_ng_flags *flags, struct call_media *me other_media->fingerprint = sp->fingerprint; } MEDIA_CLEAR(other_media, DTLS); - if ((MEDIA_ISSET(other_media, SETUP_PASSIVE) || MEDIA_ISSET(other_media, SETUP_ACTIVE)) + if (MEDIA_ISSET2(other_media, SETUP_PASSIVE, SETUP_ACTIVE) && other_media->fingerprint.hash_func) MEDIA_SET(other_media, DTLS); } @@ -2335,6 +2374,15 @@ static void __rtp_payload_types(struct call_media *media, GQueue *types) { } } +static void __ice_start(struct call_media *media) { + if (!MEDIA_ISSET(media, ICE) || MEDIA_ISSET(media, PASSTHRU)) { + ice_shutdown(&media->ice_agent); + return; + } + + ice_agent_init(&media->ice_agent, media); +} + /* called with call->master_lock held in W */ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams, const struct sdp_ng_flags *flags) @@ -2396,7 +2444,7 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams, /* copy parameters advertised by the sender of this message */ bf_copy_same(&other_media->media_flags, &sp->sp_flags, SHARED_FLAG_RTCP_MUX | SHARED_FLAG_ASYMMETRIC | SHARED_FLAG_ICE - | SHARED_FLAG_TRICKLE_ICE); + | SHARED_FLAG_TRICKLE_ICE | SHARED_FLAG_ICE_LITE); crypto_params_copy(&other_media->sdes_in.params, &sp->crypto); other_media->sdes_in.tag = sp->sdes_tag; @@ -2414,11 +2462,9 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams, /* DTLS stuff */ __dtls_logic(flags, media, other_media, sp); - /* ICE negotiation */ - __ice_offer(flags, media, other_media); - /* control rtcp-mux */ __rtcp_mux_logic(flags, media, other_media); + /* XXX update ICE if rtcp-mux changes */ /* SDES and DTLS */ __generate_crypto(flags, media, other_media); @@ -2431,11 +2477,16 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams, if (sp->desired_family) media->desired_family = sp->desired_family; - /* local interface selection */ __init_interface(media, &sp->direction[1]); __init_interface(other_media, &sp->direction[0]); + /* ICE stuff - must come after interface and address family selection */ + __ice_offer(flags, media, other_media); + __ice_start(other_media); + __ice_start(media); + + /* we now know what's being advertised by the other side */ MEDIA_SET(other_media, INITIALIZED); @@ -2486,6 +2537,9 @@ init: return -1; if (__init_streams(other_media, media, sp)) return -1; + + /* we are now ready to fire up ICE if so desired and requested */ + ice_update(other_media->ice_agent, sp); } return 0; @@ -2496,7 +2550,7 @@ error: } /* must be called with in_lock held or call->master_lock held in W */ -static void unkernelize(struct packet_stream *p) { +static void __unkernelize(struct packet_stream *p) { if (!PS_ISSET(p, KERNELIZED)) return; if (PS_ISSET(p, NO_KERNEL_SUPPORT)) @@ -2508,33 +2562,57 @@ static void unkernelize(struct packet_stream *p) { PS_CLEAR(p, KERNELIZED); } -void timeval_subtract (struct timeval *result, const struct timeval *a, const struct timeval *b) { - u_int64_t microseconds=0; +/* XXX move these somewhere else */ +u_int64_t timeval_diff(const struct timeval *a, const struct timeval *b) { + u_int64_t microseconds; microseconds = ((u_int64_t)a->tv_sec - (u_int64_t)b->tv_sec) * 1000000LLU + (a->tv_usec - b->tv_usec); + return microseconds; +} +void timeval_subtract (struct timeval *result, const struct timeval *a, const struct timeval *b) { + u_int64_t microseconds; + microseconds = timeval_diff(a, b); result->tv_sec = microseconds/1000000LLU; result->tv_usec = microseconds%1000000LLU; } void timeval_multiply(struct timeval *result, const struct timeval *a, const long multiplier) { - u_int64_t microseconds=0; + u_int64_t microseconds; microseconds = (((u_int64_t)a->tv_sec * 1000000LLU) + a->tv_usec) * multiplier; result->tv_sec = microseconds/1000000LLU; result->tv_usec = microseconds%1000000LLU; } void timeval_divide(struct timeval *result, const struct timeval *a, const long divisor) { - u_int64_t microseconds=0; + u_int64_t microseconds; microseconds = (((u_int64_t)a->tv_sec * 1000000LLU) + a->tv_usec) / divisor; result->tv_sec = microseconds/1000000LLU; result->tv_usec = microseconds%1000000LLU; } void timeval_add(struct timeval *result, const struct timeval *a, const struct timeval *b) { - u_int64_t microseconds=0; + u_int64_t microseconds; microseconds = ((u_int64_t)a->tv_sec + (u_int64_t)b->tv_sec) * 1000000LLU + (a->tv_usec + b->tv_usec); result->tv_sec = microseconds/1000000LLU; result->tv_usec = microseconds%1000000LLU; } +void timeval_add_usec(struct timeval *tv, long usec) { + struct timeval a; + a.tv_sec = usec / 1000000LLU; + a.tv_usec = usec % 1000000LLU; + timeval_add(tv, tv, &a); +} + +int timeval_cmp(const struct timeval *a, const struct timeval *b) { + if (a->tv_sec < b->tv_sec) + return -1; + if (a->tv_sec > b->tv_sec) + return 1; + if (a->tv_usec < b->tv_usec) + return -1; + if (a->tv_usec > b->tv_usec) + return 1; + return 0; +} static void timeval_totalstats_average_add(struct totalstats *s, const struct timeval *add) { struct timeval dp, oa; @@ -2746,6 +2824,8 @@ void call_destroy(struct call *c) { atomic64_add(&m->totalstats_interval.total_relayed_errors, atomic64_get(&ps->stats.errors)); } + + ice_shutdown(&md->ice_agent); } if (_log_facility_cdr) ++cdrlinecnt; @@ -2833,7 +2913,7 @@ void call_destroy(struct call *c) { for (l = c->streams; l; l = l->next) { ps = l->data; - unkernelize(ps); + __unkernelize(ps); dtls_shutdown(ps); ps->sfd = NULL; crypto_cleanup(&ps->crypto); @@ -3084,16 +3164,23 @@ void __monologue_tag(struct call_monologue *ml, const str *tag) { g_hash_table_insert(call->tags, &ml->tag, ml); } -static void __stream_unkernelize(struct packet_stream *ps) { - unkernelize(ps); +static void __stream_unconfirm(struct packet_stream *ps) { + __unkernelize(ps); PS_CLEAR(ps, CONFIRMED); ps->handler = NULL; } -static void stream_unkernelize(struct packet_stream *ps) { +static void stream_unconfirm(struct packet_stream *ps) { if (!ps) return; mutex_lock(&ps->in_lock); - __stream_unkernelize(ps); + __stream_unconfirm(ps); + mutex_unlock(&ps->in_lock); +} +static void unkernelize(struct packet_stream *ps) { + if (!ps) + return; + mutex_lock(&ps->in_lock); + __unkernelize(ps); mutex_unlock(&ps->in_lock); } @@ -3114,15 +3201,28 @@ static void __monologue_unkernelize(struct call_monologue *monologue) { for (m = media->streams.head; m; m = m->next) { stream = m->data; - __stream_unkernelize(stream); + __stream_unconfirm(stream); if (stream->rtp_sink) - __stream_unkernelize(stream->rtp_sink); + __stream_unconfirm(stream->rtp_sink); if (stream->rtcp_sink) - __stream_unkernelize(stream->rtcp_sink); + __stream_unconfirm(stream->rtcp_sink); } } } +/* call locked in R */ +void call_media_unkernelize(struct call_media *media) { + GList *m; + struct packet_stream *stream; + + for (m = media->streams.head; m; m = m->next) { + stream = m->data; + unkernelize(stream); + unkernelize(stream->rtp_sink); + unkernelize(stream->rtcp_sink); + } +} + /* must be called with call->master_lock held in W */ static void __monologue_destroy(struct call_monologue *monologue) { struct call *call; @@ -3357,102 +3457,106 @@ out: return NULL; } +static unsigned int __local_interface_hash(const void *p) { + const struct local_interface *lif = p; + return str_hash(&lif->name) ^ lif->preferred_family; +} +static int __local_interface_eq(const void *a, const void *b) { + const struct local_interface *A = a, *B = b; + return str_equal(&A->name, &B->name) && A->preferred_family == B->preferred_family; +} +static GQueue *__interface_list_for_family(struct callmaster *m, int family) { + return (family == AF_INET6) ? &m->interface_list_v6 : &m->interface_list_v4; +} +static void __interface_append(struct callmaster *m, struct interface_address *ifa, int family) { + struct local_interface *lif; + GQueue *q; + struct interface_address *ifc; + + lif = get_local_interface(m, &ifa->interface_name, family); + + if (!lif) { + lif = g_slice_alloc0(sizeof(*lif)); + lif->name = ifa->interface_name; + lif->preferred_family = family; + lif->addr_hash = g_hash_table_new(in6_addr_hash, in6_addr_eq); + g_hash_table_insert(m->interfaces, lif, lif); + if (ifa->family == family) { + q = __interface_list_for_family(m, family); + g_queue_push_tail(q, lif); + } + } + + if (!ifa->ice_foundation.s) + ice_foundation(ifa); + + ifc = g_slice_alloc(sizeof(*ifc)); + *ifc = *ifa; + ifc->preference = lif->list.length; + + g_queue_push_tail(&lif->list, ifc); + g_hash_table_insert(lif->addr_hash, &ifc->addr, ifc); +} + +/* XXX interface handling should go somewhere else */ void callmaster_config_init(struct callmaster *m) { GList *l; struct interface_address *ifa; - struct local_interface *lif; - m->interfaces = g_hash_table_new(str_hash, str_equal); + m->interfaces = g_hash_table_new(__local_interface_hash, __local_interface_eq); + /* build primary lists first */ for (l = m->conf.interfaces->head; l; l = l->next) { ifa = l->data; + __interface_append(m, ifa, ifa->family); + } - lif = g_hash_table_lookup(m->interfaces, &ifa->interface_name); - if (!lif) { - lif = g_slice_alloc0(sizeof(*lif)); - lif->name = ifa->interface_name; - g_hash_table_insert(m->interfaces, &lif->name, lif); - g_queue_push_tail(&m->interface_list, lif); - } - - if (IN6_IS_ADDR_V4MAPPED(&ifa->addr)) - g_queue_push_tail(&lif->ipv4, ifa); + /* then append to each other as lower-preference alternatives */ + for (l = m->conf.interfaces->head; l; l = l->next) { + ifa = l->data; + if (ifa->family == AF_INET) + __interface_append(m, ifa, AF_INET6); + else if (ifa->family == AF_INET6) + __interface_append(m, ifa, AF_INET); else - g_queue_push_tail(&lif->ipv6, ifa); - - sdp_ice_foundation(ifa); + abort(); } } -struct local_interface *get_local_interface(struct callmaster *m, const str *name) { - struct local_interface *lif; - - if (!name || !name->s) - return m->interface_list.head->data; +struct local_interface *get_local_interface(struct callmaster *m, const str *name, int family) { + struct local_interface d, *lif; - lif = g_hash_table_lookup(m->interfaces, name); - return lif; -} + if (!name || !name->s) { + GQueue *q; + q = __interface_list_for_family(m, family); + return q->head ? q->head->data : NULL; + } -static const GQueue *get_interface_addresses(struct local_interface *lif, int family) { - if (!lif) - return NULL; + d.name = *name; + d.preferred_family = family; - switch (family) { - case AF_INET: - return &lif->ipv4; - break; - case AF_INET6: - return &lif->ipv6; - break; - default: - return NULL; - } + lif = g_hash_table_lookup(m->interfaces, &d); + return lif; } static struct interface_address *get_interface_address(struct local_interface *lif, int family) { const GQueue *q; - q = get_interface_addresses(lif, family); - if (!q || !q->head) + q = &lif->list; + if (!q->head) return NULL; return q->head->data; } /* safety fallback */ -struct interface_address *get_any_interface_address(struct local_interface *lif, int family) { +static struct interface_address *get_any_interface_address(struct local_interface *lif, int family) { struct interface_address *ifa; - GQueue q = G_QUEUE_INIT; - get_all_interface_addresses(&q, lif, family); - ifa = q.head->data; - g_queue_clear(&q); - return ifa; -} - -void get_all_interface_addresses(GQueue *q, struct local_interface *lif, int family) { - g_queue_append(q, get_interface_addresses(lif, family)); - if (family == AF_INET) - g_queue_append(q, get_interface_addresses(lif, AF_INET6)); - else - g_queue_append(q, get_interface_addresses(lif, AF_INET)); -} - -struct interface_address *get_interface_from_address(struct local_interface *lif, const struct in6_addr *addr) { - GQueue *q; - GList *l; - struct interface_address *ifa; - - if (IN6_IS_ADDR_V4MAPPED(addr)) - q = &lif->ipv4; - else - q = &lif->ipv6; - - for (l = q->head; l; l = l->next) { - ifa = l->data; - if (!memcmp(&ifa->addr, addr, sizeof(*addr))) - return ifa; - } - - return NULL; + ifa = get_interface_address(lif, family); + if (ifa) + return ifa; + ifa = get_interface_address(lif, AF_INET); + if (ifa) + return ifa; + return get_interface_address(lif, AF_INET6); } diff --git a/daemon/call.h b/daemon/call.h index 8be6893eb..3ca148c14 100644 --- a/daemon/call.h +++ b/daemon/call.h @@ -58,7 +58,14 @@ enum xmlrpc_format { XF_CALLID, }; -struct call_monologue; +enum call_stream_state { + CSS_UNKNOWN = 0, + CSS_SHUTDOWN, + CSS_ICE, + CSS_DTLS, + CSS_RUNNING, +}; + @@ -105,6 +112,7 @@ struct call_monologue; #define SHARED_FLAG_STRICT_SOURCE 0x00000100 #define SHARED_FLAG_MEDIA_HANDOVER 0x00000200 #define SHARED_FLAG_TRICKLE_ICE 0x00000400 +#define SHARED_FLAG_ICE_LITE 0x00000800 /* struct stream_params */ #define SP_FLAG_NO_RTCP 0x00010000 @@ -119,13 +127,14 @@ struct call_monologue; #define SP_FLAG_STRICT_SOURCE SHARED_FLAG_STRICT_SOURCE #define SP_FLAG_MEDIA_HANDOVER SHARED_FLAG_MEDIA_HANDOVER #define SP_FLAG_TRICKLE_ICE SHARED_FLAG_TRICKLE_ICE +#define SP_FLAG_ICE_LITE SHARED_FLAG_ICE_LITE /* struct packet_stream */ #define PS_FLAG_RTP 0x00010000 #define PS_FLAG_RTCP 0x00020000 #define PS_FLAG_IMPLICIT_RTCP SHARED_FLAG_IMPLICIT_RTCP #define PS_FLAG_FALLBACK_RTCP 0x00040000 -#define PS_FLAG_STUN 0x00080000 +#define PS_FLAG_UNUSED2 0x00080000 #define PS_FLAG_FILLED 0x00100000 #define PS_FLAG_CONFIRMED 0x00200000 #define PS_FLAG_KERNELIZED 0x00400000 @@ -134,6 +143,7 @@ struct call_monologue; #define PS_FLAG_FINGERPRINT_VERIFIED 0x02000000 #define PS_FLAG_STRICT_SOURCE SHARED_FLAG_STRICT_SOURCE #define PS_FLAG_MEDIA_HANDOVER SHARED_FLAG_MEDIA_HANDOVER +#define PS_FLAG_ICE SHARED_FLAG_ICE /* struct call_media */ #define MEDIA_FLAG_INITIALIZED 0x00010000 @@ -149,15 +159,21 @@ struct call_monologue; #define MEDIA_FLAG_PASSTHRU 0x00100000 #define MEDIA_FLAG_ICE SHARED_FLAG_ICE #define MEDIA_FLAG_TRICKLE_ICE SHARED_FLAG_TRICKLE_ICE +#define MEDIA_FLAG_ICE_LITE SHARED_FLAG_ICE_LITE +#define MEDIA_FLAG_ICE_CONTROLLING 0x00200000 /* access macros */ #define SP_ISSET(p, f) bf_isset(&(p)->sp_flags, SP_FLAG_ ## f) #define SP_SET(p, f) bf_set(&(p)->sp_flags, SP_FLAG_ ## f) #define SP_CLEAR(p, f) bf_clear(&(p)->sp_flags, SP_FLAG_ ## f) #define PS_ISSET(p, f) bf_isset(&(p)->ps_flags, PS_FLAG_ ## f) +#define PS_ISSET2(p, f, g) bf_isset(&(p)->ps_flags, PS_FLAG_ ## f | PS_FLAG_ ## g) +#define PS_ARESET2(p, f, g) bf_areset(&(p)->ps_flags, PS_FLAG_ ## f | PS_FLAG_ ## g) #define PS_SET(p, f) bf_set(&(p)->ps_flags, PS_FLAG_ ## f) #define PS_CLEAR(p, f) bf_clear(&(p)->ps_flags, PS_FLAG_ ## f) #define MEDIA_ISSET(p, f) bf_isset(&(p)->media_flags, MEDIA_FLAG_ ## f) +#define MEDIA_ISSET2(p, f, g) bf_isset(&(p)->media_flags, MEDIA_FLAG_ ## f | MEDIA_FLAG_ ## g) +#define MEDIA_ARESET2(p, f, g) bf_areset(&(p)->media_flags, MEDIA_FLAG_ ## f | MEDIA_FLAG_ ## g) #define MEDIA_SET(p, f) bf_set(&(p)->media_flags, MEDIA_FLAG_ ## f) #define MEDIA_CLEAR(p, f) bf_clear(&(p)->media_flags, MEDIA_FLAG_ ## f) @@ -173,6 +189,8 @@ struct rtpengine_srtp; struct streamhandler; struct sdp_ng_flags; struct local_interface; +struct call_monologue; +struct ice_agent; typedef bencode_buffer_t call_buffer_t; @@ -221,10 +239,6 @@ struct udp_fd { int fd; u_int16_t localport; }; -struct endpoint { - struct in6_addr ip46; - u_int16_t port; -}; struct stream_params { unsigned int index; /* starting with 1 */ str type; @@ -239,6 +253,9 @@ struct stream_params { struct dtls_fingerprint fingerprint; unsigned int sp_flags; GQueue rtp_payload_types; /* slice-alloc'd */ + GQueue ice_candidates; /* slice-alloc'd */ + str ice_ufrag; + str ice_pwd; }; struct stream_fd { @@ -278,6 +295,7 @@ struct packet_stream { struct call_media *media; /* RO */ struct call *call; /* RO */ + unsigned int component; /* RO, starts with 1 */ struct stream_fd *sfd; /* LOCK: call->master_lock */ struct packet_stream *rtp_sink; /* LOCK: call->master_lock */ @@ -322,8 +340,8 @@ struct call_media { * atomic ops to access it when holding an R lock. */ volatile struct interface_address *local_address; - str ice_ufrag; - str ice_pwd; + struct ice_agent *ice_agent; + struct { struct crypto_params params; unsigned int tag; @@ -386,8 +404,9 @@ struct call { struct local_interface { str name; - GQueue ipv4; /* struct interface_address */ - GQueue ipv6; /* struct interface_address */ + int preferred_family; + GQueue list; /* struct interface_address */ + GHashTable *addr_hash; }; struct interface_address { str interface_name; @@ -396,6 +415,7 @@ struct interface_address { struct in6_addr advertised; str ice_foundation; char foundation_buf[16]; + unsigned int preference; /* starting with 0 */ }; struct callmaster_config { @@ -420,10 +440,10 @@ struct callmaster { GHashTable *callhash; GHashTable *interfaces; /* struct local_interface */ - GQueue interface_list; /* ditto */ + GQueue interface_list_v4; /* ditto */ + GQueue interface_list_v6; /* ditto */ - mutex_t portlock; - u_int16_t lastport; + volatile unsigned int lastport; BIT_ARRAY_DECLARE(ports_used, 0x10000); /* XXX rework these */ @@ -473,15 +493,19 @@ int monologue_offer_answer(struct call_monologue *monologue, GQueue *streams, co int call_delete_branch(struct callmaster *m, const str *callid, const str *branch, const str *fromtag, const str *totag, bencode_item_t *output); void call_destroy(struct call *); +enum call_stream_state call_stream_state_machine(struct packet_stream *); +void call_media_unkernelize(struct call_media *media); void kernelize(struct packet_stream *); int call_stream_address(char *, struct packet_stream *, enum stream_address_format, int *); int call_stream_address46(char *o, struct packet_stream *ps, enum stream_address_format format, int *len, struct interface_address *ifa); -void get_all_interface_addresses(GQueue *q, struct local_interface *lif, int family); -struct local_interface *get_local_interface(struct callmaster *m, const str *name); -struct interface_address *get_any_interface_address(struct local_interface *lif, int family); -struct interface_address *get_interface_from_address(struct local_interface *lif, const struct in6_addr *addr); +struct local_interface *get_local_interface(struct callmaster *m, const str *name, int familiy); +INLINE struct interface_address *get_interface_from_address(struct local_interface *lif, + const struct in6_addr *addr) +{ + return g_hash_table_lookup(lif->addr_hash, addr); +} const struct transport_protocol *transport_protocol(const str *s); @@ -489,6 +513,15 @@ void timeval_subtract (struct timeval *result, const struct timeval *a, const st void timeval_multiply(struct timeval *result, const struct timeval *a, const long multiplier); void timeval_divide(struct timeval *result, const struct timeval *a, const long divisor); void timeval_add(struct timeval *result, const struct timeval *a, const struct timeval *b); +int timeval_cmp(const struct timeval *a, const struct timeval *b); +void timeval_add_usec(struct timeval *tv, long usec); +u_int64_t timeval_diff(const struct timeval *a, const struct timeval *b); +INLINE void timeval_lowest(struct timeval *l, const struct timeval *n) { + if (!n->tv_sec) + return; + if (!l->tv_sec || timeval_cmp(l, n) == 1) + *l = *n; +} INLINE void *call_malloc(struct call *c, size_t l) { @@ -541,10 +574,7 @@ INLINE str *call_str_init_dup(struct call *c, char *s) { return call_str_dup(c, &t); } 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); } INLINE struct packet_stream *packet_stream_sink(struct packet_stream *ps) { struct packet_stream *ret; diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 4fabe1b8d..f9f495803 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -17,6 +17,7 @@ #include "control_tcp.h" #include "control_udp.h" #include "rtp.h" +#include "ice.h" @@ -285,6 +286,7 @@ static void sp_free(void *p) { if (s->crypto.mki) free(s->crypto.mki); g_queue_clear_full(&s->rtp_payload_types, rtp_pt_free); + ice_candidates_free(&s->ice_candidates); g_slice_free1(sizeof(*s), s); } static void streams_free(GQueue *q) { diff --git a/daemon/dtls.c b/daemon/dtls.c index e4b987ca1..3e25bd09b 100644 --- a/daemon/dtls.c +++ b/daemon/dtls.c @@ -16,6 +16,7 @@ #include "log.h" #include "call.h" #include "poller.h" +#include "ice.h" @@ -483,7 +484,7 @@ int dtls_connection_init(struct packet_stream *ps, int active, struct dtls_cert if (d->init) { if ((d->active && active) || (!d->active && !active)) - goto connect; + goto done; dtls_connection_cleanup(d); } @@ -522,9 +523,7 @@ int dtls_connection_init(struct packet_stream *ps, int active, struct dtls_cert d->init = 1; d->active = active ? -1 : 0; -connect: - dtls(ps, NULL, NULL); - +done: return 0; error: @@ -628,6 +627,7 @@ error: return -1; } +/* called with call locked in W or R with ps->in_lock held */ int dtls(struct packet_stream *ps, const str *s, struct sockaddr_in6 *fsin) { struct dtls_connection *d; int ret; @@ -638,6 +638,8 @@ int dtls(struct packet_stream *ps, const str *s, struct sockaddr_in6 *fsin) { if (!ps || !ps->sfd) return 0; + if (!MEDIA_ISSET(ps->media, DTLS)) + return 0; d = &ps->sfd->dtls; diff --git a/daemon/graphite.c b/daemon/graphite.c index 8e5f8fca4..c571bcf8d 100644 --- a/daemon/graphite.c +++ b/daemon/graphite.c @@ -18,7 +18,7 @@ static u_int32_t graphite_ipaddress; static int graphite_port=0; static struct callmaster* cm=0; //struct totalstats totalstats_prev; -static time_t g_now, next_run; +static time_t next_run; int connect_to_graphite_server(u_int32_t ipaddress, int port) { @@ -106,17 +106,17 @@ int send_graphite_data() { ZERO(ts.total_managed_sess); mutex_unlock(&cm->totalstats_interval.total_average_lock); - rc = sprintf(ptr,"%s.totals.average_call_dur.tv_sec %llu %llu\n",hostname, (unsigned long long) ts.total_average_call_dur.tv_sec,(unsigned long long)g_now); ptr += rc; - rc = sprintf(ptr,"%s.totals.average_call_dur.tv_usec %lu %llu\n",hostname, ts.total_average_call_dur.tv_usec,(unsigned long long)g_now); ptr += rc; - rc = sprintf(ptr,"%s.totals.forced_term_sess "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_forced_term_sess),(unsigned long long)g_now); ptr += rc; - rc = sprintf(ptr,"%s.totals.managed_sess "UINT64F" %llu\n",hostname, ts.total_managed_sess,(unsigned long long)g_now); ptr += rc; - rc = sprintf(ptr,"%s.totals.nopacket_relayed_sess "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_nopacket_relayed_sess),(unsigned long long)g_now); ptr += rc; - rc = sprintf(ptr,"%s.totals.oneway_stream_sess "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_oneway_stream_sess),(unsigned long long)g_now); ptr += rc; - rc = sprintf(ptr,"%s.totals.regular_term_sess "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_regular_term_sess),(unsigned long long)g_now); ptr += rc; - rc = sprintf(ptr,"%s.totals.relayed_errors "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_relayed_errors),(unsigned long long)g_now); ptr += rc; - rc = sprintf(ptr,"%s.totals.relayed_packets "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_relayed_packets),(unsigned long long)g_now); ptr += rc; - rc = sprintf(ptr,"%s.totals.silent_timeout_sess "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_silent_timeout_sess),(unsigned long long)g_now); ptr += rc; - rc = sprintf(ptr,"%s.totals.timeout_sess "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_timeout_sess),(unsigned long long)g_now); ptr += rc; + rc = sprintf(ptr,"%s.totals.average_call_dur.tv_sec %llu %llu\n",hostname, (unsigned long long) ts.total_average_call_dur.tv_sec,(unsigned long long)g_now.tv_sec); ptr += rc; + rc = sprintf(ptr,"%s.totals.average_call_dur.tv_usec %lu %llu\n",hostname, ts.total_average_call_dur.tv_usec,(unsigned long long)g_now.tv_sec); ptr += rc; + rc = sprintf(ptr,"%s.totals.forced_term_sess "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_forced_term_sess),(unsigned long long)g_now.tv_sec); ptr += rc; + rc = sprintf(ptr,"%s.totals.managed_sess "UINT64F" %llu\n",hostname, ts.total_managed_sess,(unsigned long long)g_now.tv_sec); ptr += rc; + rc = sprintf(ptr,"%s.totals.nopacket_relayed_sess "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_nopacket_relayed_sess),(unsigned long long)g_now.tv_sec); ptr += rc; + rc = sprintf(ptr,"%s.totals.oneway_stream_sess "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_oneway_stream_sess),(unsigned long long)g_now.tv_sec); ptr += rc; + rc = sprintf(ptr,"%s.totals.regular_term_sess "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_regular_term_sess),(unsigned long long)g_now.tv_sec); ptr += rc; + rc = sprintf(ptr,"%s.totals.relayed_errors "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_relayed_errors),(unsigned long long)g_now.tv_sec); ptr += rc; + rc = sprintf(ptr,"%s.totals.relayed_packets "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_relayed_packets),(unsigned long long)g_now.tv_sec); ptr += rc; + rc = sprintf(ptr,"%s.totals.silent_timeout_sess "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_silent_timeout_sess),(unsigned long long)g_now.tv_sec); ptr += rc; + rc = sprintf(ptr,"%s.totals.timeout_sess "UINT64F" %llu\n",hostname, atomic64_get_na(&ts.total_timeout_sess),(unsigned long long)g_now.tv_sec); ptr += rc; rc = write(graphite_sock, data_to_send, ptr - data_to_send); if (rc<0) { @@ -134,11 +134,11 @@ void graphite_loop_run(struct callmaster* callmaster, int seconds) { int rc=0; - g_now = time(NULL); - if (g_now < next_run) + gettimeofday(&g_now, NULL); + if (g_now.tv_sec < next_run) goto sleep; - next_run = g_now + seconds; + next_run = g_now.tv_sec + seconds; if (!cm) cm = callmaster; diff --git a/daemon/ice.c b/daemon/ice.c new file mode 100644 index 000000000..9af547a33 --- /dev/null +++ b/daemon/ice.c @@ -0,0 +1,1357 @@ +#include "ice.h" +#include +#include +#include +#include "str.h" +#include "call.h" +#include "aux.h" +#include "log.h" +#include "obj.h" +#include "stun.h" + + + +#if __DEBUG +#define ICE_DEBUG 1 +#else +#define ICE_DEBUG 0 +#endif + +#if ICE_DEBUG +#define __DBG(x...) ilog(LOG_DEBUG, x) +#else +#define __DBG(x...) ((void)0) +#endif + + + + +#define PAIR_FORMAT STR_FORMAT":"STR_FORMAT":%lu" +#define PAIR_FMT(p) \ + STR_FMT(&(p)->local_address->ice_foundation), \ + STR_FMT(&(p)->remote_candidate->foundation), \ + (p)->remote_candidate->component_id + + + + +static void __ice_agent_free(void *p); +static void create_random_ice_string(struct call *call, str *s, int len); +static void __do_ice_checks(struct ice_agent *ag); +static struct ice_candidate_pair *__pair_lookup(struct ice_agent *, struct ice_candidate *cand, + struct interface_address *ifa); +static void __recalc_pair_prios(struct ice_agent *ag); +static void __role_change(struct ice_agent *ag, int new_controlling); +static void __get_complete_components(GQueue *out, struct ice_agent *ag, GTree *t, unsigned int); +static void __agent_schedule(struct ice_agent *ag, unsigned long); +static void __agent_schedule_abs(struct ice_agent *ag, const struct timeval *tv); +static void __agent_deschedule(struct ice_agent *ag); +static void __ice_agent_free_components(struct ice_agent *ag); +static void __agent_shutdown(struct ice_agent *ag); + + + +static u_int64_t tie_breaker; + +static mutex_t ice_agents_timers_lock = MUTEX_STATIC_INIT; +static cond_t ice_agents_timers_cond = COND_STATIC_INIT; +static GTree *ice_agents_timers; + +static const char ice_chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +const unsigned int ice_type_preferences[] = { + [ICT_UNKNOWN] = 0, + [ICT_HOST] = 126, + [ICT_SRFLX] = 100, + [ICT_PRFLX] = 110, + [ICT_RELAY] = 0, +}; + +const char * const ice_type_strings[] = { + [ICT_UNKNOWN] = "unknown", + [ICT_HOST] = "host", + [ICT_SRFLX] = "srflx", + [ICT_PRFLX] = "prflx", + [ICT_RELAY] = "relay", +}; + + + + + +enum ice_candidate_type ice_candidate_type(const str *s) { + int i; + for (i = 1; i < G_N_ELEMENTS(ice_type_strings); i++) { + if (!str_cmp(s, ice_type_strings[i])) + return i; + } + return ICT_UNKNOWN; +} + +enum ice_transport ice_transport(const str *s) { + if (!str_cmp(s, "udp")) + return ITP_UDP; +// if (!str_cmp(s, "tcp")) +// return ITP_TCP; + if (!str_cmp(s, "UDP")) + return ITP_UDP; +// if (!str_cmp(s, "TCP")) +// return ITP_TCP; + return ITP_UNKNOWN; +} + +int ice_has_related(enum ice_candidate_type t) { + if (t == ICT_HOST) + return 0; + /* ignoring ICT_UNKNOWN */ + return 1; +} + + + +static u_int64_t __ice_pair_priority(struct interface_address *ifa, struct ice_candidate *cand, + int controlling) +{ + u_int64_t g, d; + + g = ice_priority(ICT_HOST, ifa->preference, cand->component_id); + d = cand->priority; + + if (!controlling) { + u_int64_t t = g; + g = d; + d = t; + } + + return (MIN(g,d) << 32) + (MAX(g,d) << 1) + (g > d ? 1 : 0); +} +static void __do_ice_pair_priority(struct ice_candidate_pair *pair) { + pair->pair_priority = __ice_pair_priority(pair->local_address, pair->remote_candidate, + AGENT_ISSET(pair->agent, CONTROLLING)); +} +static void __new_stun_transaction(struct ice_candidate_pair *pair) { + struct ice_agent *ag = pair->agent; + + g_hash_table_remove(ag->transaction_hash, pair->stun_transaction); + random_string((void *) pair->stun_transaction, sizeof(pair->stun_transaction)); + g_hash_table_insert(ag->transaction_hash, pair->stun_transaction, pair); +} + +/* agent must be locked */ +static void __all_pairs_list(struct ice_agent *ag) { + g_queue_clear(&ag->all_pairs_list); + g_tree_get_values(&ag->all_pairs_list, ag->all_pairs); +} + +/* agent must be locked */ +static struct ice_candidate_pair *__pair_candidate(struct interface_address *addr, struct ice_agent *ag, + struct ice_candidate *cand, struct packet_stream *ps) +{ + struct ice_candidate_pair *pair; + + if (addr->family != family_from_address(&cand->endpoint.ip46)) + return NULL; + + pair = g_slice_alloc0(sizeof(*pair)); + + pair->agent = ag; + pair->remote_candidate = cand; + pair->local_address = addr; + pair->packet_stream = ps; + if (cand->component_id != 1) + PAIR_SET(pair, FROZEN); + __do_ice_pair_priority(pair); + __new_stun_transaction(pair); + + g_queue_push_tail(&ag->candidate_pairs, pair); + g_hash_table_insert(ag->pair_hash, pair, pair); + g_tree_insert(ag->all_pairs, pair, pair); + + ilog(LOG_DEBUG, "Created candidate pair "PAIR_FORMAT" between %s and %s, type %s", PAIR_FMT(pair), + smart_ntop_buf(&addr->addr), smart_ntop_ep_buf(&cand->endpoint), + ice_candidate_type_str(cand->type)); + + return pair; +} + +static unsigned int __pair_hash(const void *p) { + const struct ice_candidate_pair *pair = p; + return g_direct_hash(pair->local_address) ^ g_direct_hash(pair->remote_candidate); +} +static int __pair_equal(const void *a, const void *b) { + const struct ice_candidate_pair *A = a, *B = b; + return A->local_address == B->local_address + && A->remote_candidate == B->remote_candidate; +} +static unsigned int __cand_hash(const void *p) { + const struct ice_candidate *cand = p; + return in6_addr_hash(&cand->endpoint.ip46) ^ cand->endpoint.port ^ cand->component_id; +} +static int __cand_equal(const void *a, const void *b) { + const struct ice_candidate *A = a, *B = b; + return A->endpoint.port == B->endpoint.port + && A->component_id == B->component_id + && in6_addr_eq(&A->endpoint.ip46, &B->endpoint.ip46); +} +static unsigned int __found_hash(const void *p) { + const struct ice_candidate *cand = p; + return str_hash(&cand->foundation) ^ cand->component_id; +} +static int __found_equal(const void *a, const void *b) { + const struct ice_candidate *A = a, *B = b; + return str_equal(&A->foundation, &B->foundation) + && A->component_id == B->component_id; +} +static unsigned int __trans_hash(const void *p) { + const u_int32_t *tp = p; + return tp[0] ^ tp[1] ^ tp[2]; +} +static int __trans_equal(const void *a, const void *b) { + const u_int32_t *A = a, *B = b; + return A[0] == B[0] && A[1] == B[1] && A[2] == B[2]; +} +static int __pair_prio_cmp(const void *a, const void *b) { + const struct ice_candidate_pair *A = a, *B = b; + /* highest priority first */ + if (A->pair_priority < B->pair_priority) + return 1; + if (A->pair_priority > B->pair_priority) + return -1; + /* lowest component first */ + if (A->remote_candidate->component_id < B->remote_candidate->component_id) + return -1; + if (A->remote_candidate->component_id > B->remote_candidate->component_id) + return 1; + /* highest local preference first, which is lowest number first */ + if (A->local_address->preference < B->local_address->preference) + return -1; + if (A->local_address->preference > B->local_address->preference) + return 1; + return 0; +} + +static void __ice_agent_initialize(struct ice_agent *ag) { + struct call_media *media = ag->media; + struct call *call = ag->call; + + ag->candidate_hash = g_hash_table_new(__cand_hash, __cand_equal); + ag->pair_hash = g_hash_table_new(__pair_hash, __pair_equal); + ag->transaction_hash = g_hash_table_new(__trans_hash, __trans_equal); + ag->foundation_hash = g_hash_table_new(__found_hash, __found_equal); + ag->agent_flags = 0; + bf_copy(&ag->agent_flags, ICE_AGENT_CONTROLLING, &media->media_flags, MEDIA_FLAG_ICE_CONTROLLING); + ag->local_interface = media->interface; + ag->desired_family = media->desired_family; + ag->nominated_pairs = g_tree_new(__pair_prio_cmp); + ag->valid_pairs = g_tree_new(__pair_prio_cmp); + ag->succeeded_pairs = g_tree_new(__pair_prio_cmp); + ag->all_pairs = g_tree_new(__pair_prio_cmp); + + create_random_ice_string(call, &ag->ufrag[1], 8); + create_random_ice_string(call, &ag->pwd[1], 26); +} + +static struct ice_agent *__ice_agent_new(struct call_media *media) { + struct ice_agent *ag; + struct call *call = media->call; + + ag = obj_alloc0("ice_agent", sizeof(*ag), __ice_agent_free); + ag->call = obj_get(call); + ag->media = media; + mutex_init(&ag->lock); + + __ice_agent_initialize(ag); + + return ag; +} + +/* called with the call lock held in W */ +void ice_agent_init(struct ice_agent **agp, struct call_media *media) { + struct ice_agent *ag; + + if (*agp) + ag = *agp; + else + *agp = ag = __ice_agent_new(media); +} + +static int __copy_cand(struct call *call, struct ice_candidate *dst, const struct ice_candidate *src) { + int eq = (dst->priority == src->priority); + *dst = *src; + call_str_cpy(call, &dst->foundation, &src->foundation); + return eq ? 0 : 1; +} + +static void __ice_reset(struct ice_agent *ag) { + AGENT_CLEAR2(ag, COMPLETED, NOMINATING); + __ice_agent_free_components(ag); + ZERO(ag->active_components); + ZERO(ag->start_nominating); + ZERO(ag->next_check); + ZERO(ag->last_run); + __ice_agent_initialize(ag); +} + +/* if the other side did a restart */ +static void __ice_restart(struct ice_agent *ag) { + ilog(LOG_DEBUG, "ICE restart, resetting ICE agent"); + + ag->ufrag[0] = STR_NULL; + ag->pwd[0] = STR_NULL; + __ice_reset(ag); +} + +/* if we're doing a restart */ +void ice_restart(struct ice_agent *ag) { + ag->ufrag[1] = STR_NULL; + ag->pwd[1] = STR_NULL; + __ice_reset(ag); +} + +/* called with the call lock held in W, hence agent doesn't need to be locked */ +void ice_update(struct ice_agent *ag, struct stream_params *sp) { + GList *l, *k; + struct ice_candidate *cand, *dup; + struct call_media *media; + struct call *call; + int recalc = 0; + unsigned int comps; + struct packet_stream *components[MAX_COMPONENTS], *ps; + + if (!ag || !sp) + return; + + media = ag->media; + call = media->call; + + __role_change(ag, MEDIA_ISSET(media, ICE_CONTROLLING)); + + /* check for ICE restarts */ + if (ag->ufrag[0].s && sp->ice_ufrag.s && str_cmp_str(&ag->ufrag[0], &sp->ice_ufrag)) + __ice_restart(ag); + else if (ag->pwd[0].s && sp->ice_pwd.s && str_cmp_str(&ag->pwd[0], &sp->ice_pwd)) + __ice_restart(ag); + else if (ag->local_interface != media->interface) + __ice_restart(ag); + + /* update remote info */ + if (sp->ice_ufrag.s) + call_str_cpy(call, &ag->ufrag[0], &sp->ice_ufrag); + if (sp->ice_pwd.s) + call_str_cpy(call, &ag->pwd[0], &sp->ice_pwd); + + /* get our component streams */ + ZERO(components); + comps = 0; + for (l = media->streams.head; l; l = l->next) + components[comps++] = l->data; + if (comps == 2 && MEDIA_ISSET(media, RTCP_MUX)) + components[1] = NULL; + + comps = 0; + for (l = sp->ice_candidates.head; l; l = l->next) { + if (ag->remote_candidates.length >= MAX_ICE_CANDIDATES) { + ilog(LOG_WARNING, "Maxmimum number of ICE candidates exceeded"); + break; + } + + cand = l->data; + + /* skip invalid */ + if (!cand->component_id || cand->component_id > G_N_ELEMENTS(components)) + continue; + /* skip if we don't have a candidate of our own */ + ps = components[cand->component_id - 1]; + if (!ps) + continue; + + comps = MAX(comps, cand->component_id); + + /* check for duplicates */ + dup = g_hash_table_lookup(ag->candidate_hash, cand); + if (dup) { + /* if this is peer reflexive, we've learned it through STUN. + * otherwise it's simply one we've seen before. */ + if (dup->type == ICT_PRFLX) { + ilog(LOG_DEBUG, "Replacing previously learned prflx ICE candidate with " + STR_FORMAT":%lu", STR_FMT(&cand->foundation), + cand->component_id); + } + else { + /* if the new one has higher priority then the old one, then we + * update it, otherwise we just drop it */ + if (cand->priority <= dup->priority) { + ilog(LOG_DEBUG, "Dropping new ICE candidate "STR_FORMAT" in favour of " + STR_FORMAT":%lu", + STR_FMT(&cand->foundation), + STR_FMT(&dup->foundation), cand->component_id); + continue; + } + + ilog(LOG_DEBUG, "Replacing known ICE candidate "STR_FORMAT" with higher " + "priority " + STR_FORMAT":%lu", + STR_FMT(&dup->foundation), + STR_FMT(&cand->foundation), cand->component_id); + } + + /* priority and foundation may change */ + g_hash_table_remove(ag->foundation_hash, dup); + recalc += __copy_cand(call, dup, cand); + } + else { + ilog(LOG_DEBUG, "Learning new ICE candidate "STR_FORMAT":%lu", + STR_FMT(&cand->foundation), cand->component_id); + dup = g_slice_alloc(sizeof(*dup)); + __copy_cand(call, dup, cand); + g_hash_table_insert(ag->candidate_hash, dup, dup); + g_queue_push_tail(&ag->remote_candidates, dup); + } + + g_hash_table_insert(ag->foundation_hash, dup, dup); + + for (k = ag->local_interface->list.head; k; k = k->next) { + /* skip duplicates here also */ + if (__pair_lookup(ag, dup, k->data)) + continue; + __pair_candidate(k->data, ag, dup, ps); + } + } + + ag->active_components = comps; + + /* if we're here, we can start our ICE checks */ + if (recalc) + __recalc_pair_prios(ag); + else + __all_pairs_list(ag); + + if (comps) + __do_ice_checks(ag); + else + __agent_shutdown(ag); +} + + +static void ice_candidate_free(void *p) { + g_slice_free1(sizeof(struct ice_candidate), p); +} +void ice_candidates_free(GQueue *q) { + g_queue_clear_full(q, ice_candidate_free); +} +static void ice_candidate_pair_free(void *p) { + g_slice_free1(sizeof(struct ice_candidate_pair), p); +} +static void ice_candidate_pairs_free(GQueue *q) { + g_queue_clear_full(q, ice_candidate_pair_free); +} + + +/* call must be locked */ +void ice_shutdown(struct ice_agent **agp) { + struct ice_agent *ag = *agp; + if (!ag) + return; + + __agent_deschedule(ag); + + *agp = NULL; + obj_put(ag); +} +static void __ice_agent_free_components(struct ice_agent *ag) { + ice_candidates_free(&ag->remote_candidates); + ice_candidate_pairs_free(&ag->candidate_pairs); + g_queue_clear(&ag->triggered); + g_hash_table_destroy(ag->candidate_hash); + g_hash_table_destroy(ag->pair_hash); + g_hash_table_destroy(ag->transaction_hash); + g_hash_table_destroy(ag->foundation_hash); + g_tree_destroy(ag->all_pairs); + g_queue_clear(&ag->all_pairs_list); + g_tree_destroy(ag->nominated_pairs); + g_tree_destroy(ag->succeeded_pairs); + g_tree_destroy(ag->valid_pairs); +} +static void __ice_agent_free(void *p) { + struct ice_agent *ag = p; + + __DBG("freeing ice_agent"); + + __ice_agent_free_components(ag); + mutex_destroy(&ag->lock); + + obj_put(ag->call); +} + + +static void __agent_schedule(struct ice_agent *ag, unsigned long usec) { + struct timeval nxt; + + nxt = g_now; + timeval_add_usec(&nxt, usec); + __agent_schedule_abs(ag, &nxt); +} +static void __agent_schedule_abs(struct ice_agent *ag, const struct timeval *tv) { + struct timeval nxt; + unsigned long diff; + + nxt = *tv; + + mutex_lock(&ice_agents_timers_lock); + if (ag->last_run.tv_sec) { + /* make sure we don't run more often than we should */ + diff = timeval_diff(&nxt, &ag->last_run); + if (diff < TIMER_RUN_INTERVAL * 1000) + timeval_add_usec(&nxt, TIMER_RUN_INTERVAL * 1000 - diff); + } + if (ag->next_check.tv_sec && timeval_cmp(&ag->next_check, &nxt) <= 0) + goto nope; /* already scheduled sooner */ + if (!g_tree_remove(ice_agents_timers, ag)) + obj_hold(ag); /* if it wasn't removed (should never happen), we make a new reference */ + ag->next_check = nxt; + g_tree_insert(ice_agents_timers, ag, ag); + cond_broadcast(&ice_agents_timers_cond); +nope: + mutex_unlock(&ice_agents_timers_lock); +} +static void __agent_deschedule(struct ice_agent *ag) { + int ret; + mutex_lock(&ice_agents_timers_lock); + if (!ag->next_check.tv_sec) + goto nope; /* already descheduled */ + ret = g_tree_remove(ice_agents_timers, ag); + ZERO(ag->next_check); + if (ret) /* should always be true */ + obj_put(ag); +nope: + mutex_unlock(&ice_agents_timers_lock); +} + +static int __ice_agent_timer_cmp(const void *a, const void *b) { + const struct ice_agent *A = a, *B = b; + int ret; + /* zero timevals go last */ + if (A->next_check.tv_sec == 0 && B->next_check.tv_sec != 0) + return 1; + if (B->next_check.tv_sec == 0 && A->next_check.tv_sec == 0) + return -1; + if (A->next_check.tv_sec == 0 && B->next_check.tv_sec == 0) + goto ptr; + /* earlier timevals go first */ + ret = timeval_cmp(&A->next_check, &B->next_check); + if (ret) + return ret; + /* equal timeval, so use pointer as tie breaker */ +ptr: + if (A < B) + return -1; + if (A > B) + return 1; + return 0; +} +void ice_init(void) { + random_string((void *) &tie_breaker, sizeof(tie_breaker)); + ice_agents_timers = g_tree_new(__ice_agent_timer_cmp); +} + + + +static void __fail_pair(struct ice_candidate_pair *pair) { + ilog(LOG_DEBUG, "Setting ICE candidate pair "PAIR_FORMAT" as failed", PAIR_FMT(pair)); + PAIR_SET(pair, FAILED); +} + +/* agent must NOT be locked, but call must be locked in R */ +static void __do_ice_check(struct ice_candidate_pair *pair) { + struct sockaddr_in6 dst; + struct packet_stream *ps = pair->packet_stream; + struct ice_agent *ag = pair->agent; + u_int32_t prio, transact[3]; + + if (PAIR_ISSET(pair, SUCCEEDED) && !PAIR_ISSET(pair, TO_USE)) + return; + + if (!ag->pwd[0].s) + return; + + ZERO(dst); + dst.sin6_port = htons(pair->remote_candidate->endpoint.port); + dst.sin6_addr = pair->remote_candidate->endpoint.ip46; + dst.sin6_family = AF_INET6; + + prio = ice_priority(ICT_PRFLX, pair->local_address->preference, + pair->remote_candidate->component_id); + + mutex_lock(&ag->lock); + + pair->retransmit = g_now; + if (!PAIR_SET(pair, IN_PROGRESS)) { + PAIR_CLEAR2(pair, FROZEN, FAILED); + pair->retransmit_ms = STUN_RETRANSMIT_INTERVAL; + pair->retransmits = 0; + } + else if (pair->retransmits > STUN_MAX_RETRANSMITS) { + __fail_pair(pair); + mutex_unlock(&ag->lock); + return; + } + else { + pair->retransmit_ms *= 2; + pair->retransmits++; + } + timeval_add_usec(&pair->retransmit, pair->retransmit_ms * 1000); + __agent_schedule_abs(pair->agent, &pair->retransmit); + memcpy(transact, pair->stun_transaction, sizeof(transact)); + + pair->was_controlling = AGENT_ISSET(ag, CONTROLLING); + pair->was_nominated = PAIR_ISSET(pair, TO_USE); + + mutex_unlock(&ag->lock); + + ilog(LOG_DEBUG, "Sending %sICE/STUN request for candidate pair "PAIR_FORMAT" from %s to %s", + PAIR_ISSET(pair, TO_USE) ? "nominating " : "", + PAIR_FMT(pair), smart_ntop_buf(&pair->local_address->addr), + smart_ntop_ep_buf(&pair->remote_candidate->endpoint)); + + stun_binding_request(&dst, transact, &ag->pwd[0], ag->ufrag, + AGENT_ISSET(ag, CONTROLLING), tie_breaker, + prio, &pair->local_address->addr, ps->sfd->fd.fd, + PAIR_ISSET(pair, TO_USE)); + +} + +static int __component_find(const void *a, const void *b) { + const struct ice_candidate_pair *A = a; + unsigned int comp = GPOINTER_TO_UINT(b); + if (A->remote_candidate->component_id == comp) + return TRUE; + return FALSE; +} +static struct ice_candidate_pair *__get_pair_by_component(GTree *t, unsigned int component) { + return g_tree_find_first(t, __component_find, GUINT_TO_POINTER(component)); +} +static void __get_pairs_by_component(GQueue *out, GTree *t, unsigned int component) { + g_tree_find_all(out, t, __component_find, GUINT_TO_POINTER(component)); +} + +static void __get_complete_succeeded_pairs(GQueue *out, struct ice_agent *ag) { + __get_complete_components(out, ag, ag->succeeded_pairs, ICE_PAIR_SUCCEEDED); +} +static void __get_complete_valid_pairs(GQueue *out, struct ice_agent *ag) { + __get_complete_components(out, ag, ag->valid_pairs, ICE_PAIR_VALID); +} + +static void __nominate_pairs(struct ice_agent *ag) { + GQueue complete; + GList *l; + struct ice_candidate_pair *pair; + + ilog(LOG_DEBUG, "Start nominating ICE pairs"); + + AGENT_SET(ag, NOMINATING); + ZERO(ag->start_nominating); + + __get_complete_succeeded_pairs(&complete, ag); + + for (l = complete.head; l; l = l->next) { + pair = l->data; + ilog(LOG_DEBUG, "Nominating ICE pair "PAIR_FORMAT, PAIR_FMT(pair)); + PAIR_CLEAR(pair, IN_PROGRESS); + PAIR_SET2(pair, NOMINATED, TO_USE); + pair->retransmits = 0; + __new_stun_transaction(pair); + g_queue_push_tail(&ag->triggered, pair); + } + + g_queue_clear(&complete); +} + +/* call must be locked R or W, agent must not be locked */ +static void __do_ice_checks(struct ice_agent *ag) { + GList *l; + struct ice_candidate_pair *pair, *highest = NULL, *frozen = NULL, *valid; + struct packet_stream *ps; + GQueue retransmits = G_QUEUE_INIT; + struct timeval next_run = {0,0}; + int have_more = 0; + + if (!ag->pwd[0].s) + return; + + __DBG("running checks, call "STR_FORMAT" tag "STR_FORMAT"", STR_FMT(&ag->call->callid), + STR_FMT(&ag->media->monologue->tag)); + + mutex_lock(&ag->lock); + + /* check if we're done and should start nominating pairs */ + if (AGENT_ISSET(ag, CONTROLLING) && !AGENT_ISSET(ag, NOMINATING) && ag->start_nominating.tv_sec) { + if (timeval_cmp(&g_now, &ag->start_nominating) >= 0) + __nominate_pairs(ag); + timeval_lowest(&next_run, &ag->start_nominating); + } + + /* triggered checks are preferred */ + pair = g_queue_pop_head(&ag->triggered); + if (pair) { + PAIR_CLEAR(pair, TRIGGERED); + next_run = g_now; + goto check; + } + + /* find the highest-priority non-frozen non-in-progress pair */ + for (l = ag->all_pairs_list.head; l; l = l->next) { + pair = l->data; + + /* skip dead streams */ + ps = pair->packet_stream; + if (!ps || !ps->sfd) + continue; + if (PAIR_ISSET(pair, FAILED)) + continue; + if (PAIR_ISSET(pair, SUCCEEDED) && !PAIR_ISSET(pair, TO_USE)) + continue; + + valid = __get_pair_by_component(ag->valid_pairs, pair->remote_candidate->component_id); + + if (PAIR_ISSET(pair, IN_PROGRESS)) { + /* handle retransmits */ + /* but only if our priority is lower than any valid pair */ + if (valid && valid->pair_priority > pair->pair_priority) + continue; + + if (timeval_cmp(&pair->retransmit, &g_now) <= 0) + g_queue_push_tail(&retransmits, pair); /* can't run check directly + due to locks */ + else + timeval_lowest(&next_run, &pair->retransmit); + continue; + } + + /* don't do anything else if we already have a valid pair */ + if (valid) + continue; + /* or if we're in or past the final phase */ + if (AGENT_ISSET2(ag, NOMINATING, COMPLETED)) + continue; + + have_more = 1; + + /* remember the first frozen pair in case we find nothing else */ + if (PAIR_ISSET(pair, FROZEN)) { + if (!frozen) + frozen = pair; + continue; + } + + if (!highest) + highest = pair; + } + + if (highest) + pair = highest; + else if (frozen) + pair = frozen; + else + pair = NULL; + +check: + mutex_unlock(&ag->lock); + + if (pair) + __do_ice_check(pair); + + while ((pair = g_queue_pop_head(&retransmits))) + __do_ice_check(pair); + + + /* determine when to run next */ + if (have_more) + __agent_schedule(ag, 0); + else if (next_run.tv_sec) + __agent_schedule_abs(ag, &next_run); /* for retransmits */ +} + +static void __agent_shutdown(struct ice_agent *ag) { + ilog(LOG_DEBUG, "Shutting down ICE agent (nothing to do)"); + __agent_deschedule(ag); +} + +/* agent must be locked for these */ +static struct ice_candidate *__cand_lookup(struct ice_agent *ag, const struct sockaddr_in6 *sin, + unsigned int component) +{ + struct ice_candidate d; + + d.endpoint.port = ntohs(sin->sin6_port); + d.endpoint.ip46 = sin->sin6_addr; + d.component_id = component; + return g_hash_table_lookup(ag->candidate_hash, &d); +} +static struct ice_candidate *__foundation_lookup(struct ice_agent *ag, const str *foundation, + unsigned int component) +{ + struct ice_candidate d; + + d.foundation = *foundation; + d.component_id = component; + return g_hash_table_lookup(ag->foundation_hash, &d); +} +static struct ice_candidate_pair *__pair_lookup(struct ice_agent *ag, struct ice_candidate *cand, + struct interface_address *ifa) +{ + struct ice_candidate_pair p; + + p.local_address = ifa; + p.remote_candidate = cand; + return g_hash_table_lookup(ag->pair_hash, &p); +} + +static void __cand_ice_foundation(struct call *call, struct ice_candidate *cand) { + char buf[64]; + int len; + + len = sprintf(buf, "%lx%lx%lx%lx%x%x", + (long unsigned) cand->endpoint.ip46.s6_addr32[0], + (long unsigned) cand->endpoint.ip46.s6_addr32[1], + (long unsigned) cand->endpoint.ip46.s6_addr32[2], + (long unsigned) cand->endpoint.ip46.s6_addr32[3], + cand->type, cand->transport); + call_str_cpy_len(call, &cand->foundation, buf, len); +} + +/* agent must be locked */ +static struct ice_candidate_pair *__learned_candidate(struct ice_agent *ag, struct packet_stream *ps, + struct sockaddr_in6 *src, struct interface_address *ifa, unsigned long priority) +{ + struct ice_candidate *cand, *old_cand; + struct ice_candidate_pair *pair; + struct call *call = ag->call; + + cand = g_slice_alloc0(sizeof(*cand)); + cand->component_id = ps->component; + cand->transport = ITP_UDP; + cand->priority = priority; + cand->endpoint.ip46 = src->sin6_addr; + cand->endpoint.port = ntohs(src->sin6_port); + cand->type = ICT_PRFLX; + __cand_ice_foundation(call, cand); + + old_cand = __foundation_lookup(ag, &cand->foundation, ps->component); + if (old_cand && old_cand->priority > priority) { + /* this is possible if two distinct requests are received from the same NAT IP + * address, but from different ports. we cannot distinguish such candidates and + * will drop the one with the lower priority */ + g_slice_free1(sizeof(*cand), cand); + pair = __pair_lookup(ag, old_cand, ifa); + if (pair) + goto out; /* nothing to do */ + cand = old_cand; + goto pair; + } + + g_queue_push_tail(&ag->remote_candidates, cand); + g_hash_table_insert(ag->candidate_hash, cand, cand); + g_hash_table_insert(ag->foundation_hash, cand, cand); + +pair: + pair = __pair_candidate(ifa, ag, cand, ps); + PAIR_SET(pair, LEARNED); + __all_pairs_list(ag); + +out: + return pair; +} + +/* agent must NOT be locked */ +static void __trigger_check(struct ice_candidate_pair *pair) { + struct ice_agent *ag = pair->agent; + + ilog(LOG_DEBUG, "Triggering check for "PAIR_FORMAT, PAIR_FMT(pair)); + + mutex_lock(&ag->lock); + PAIR_CLEAR(pair, FAILED); + if (ag->triggered.length < 4 * MAX_ICE_CANDIDATES && !PAIR_SET(pair, TRIGGERED)) + g_queue_push_tail(&ag->triggered, pair); + mutex_unlock(&ag->lock); + + __agent_schedule(ag, 0); +} + +/* agent must be locked */ +/* also regenerates all_pairs_list */ +static void __recalc_pair_prios(struct ice_agent *ag) { + struct ice_candidate_pair *pair; + GList *l; + GQueue nominated, valid, succ, all; + + ilog(LOG_DEBUG, "Recalculating all ICE pair priorities"); + + g_tree_remove_all(&nominated, ag->nominated_pairs); + g_tree_remove_all(&succ, ag->succeeded_pairs); + g_tree_remove_all(&valid, ag->valid_pairs); + g_tree_remove_all(&all, ag->all_pairs); + + for (l = ag->candidate_pairs.head; l; l = l->next) { + pair = l->data; + __do_ice_pair_priority(pair); + /* this changes the packets, so we must keep these from being seen as retransmits */ + __new_stun_transaction(pair); + } + + g_tree_add_all(ag->nominated_pairs, &nominated); + g_tree_add_all(ag->succeeded_pairs, &succ); + g_tree_add_all(ag->valid_pairs, &valid); + g_tree_add_all(ag->all_pairs, &all); + __all_pairs_list(ag); +} + +/* agent must NOT be locked */ +static void __role_change(struct ice_agent *ag, int new_controlling) { + if (new_controlling && !AGENT_SET(ag, CONTROLLING)) + ; + else if (!new_controlling && AGENT_CLEAR(ag, CONTROLLING)) + ; + else + return; + + ilog(LOG_DEBUG, "ICE role change, now %s", new_controlling ? "controlling" : "controlled"); + + /* recalc priorities and resort list */ + + mutex_lock(&ag->lock); + __recalc_pair_prios(ag); + mutex_unlock(&ag->lock); +} + +/* initializes "out" */ +static void __get_complete_components(GQueue *out, struct ice_agent *ag, GTree *t, unsigned int flag) { + GQueue compo1 = G_QUEUE_INIT; + GList *l; + struct ice_candidate_pair *pair1, *pairX; + struct ice_candidate *cand; + unsigned int i; + + __get_pairs_by_component(&compo1, t, 1); + + g_queue_init(out); + + for (l = compo1.head; l; l = l->next) { + pair1 = l->data; + + g_queue_clear(out); + g_queue_push_tail(out, pair1); + + for (i = 2; i <= ag->active_components; i++) { + cand = __foundation_lookup(ag, &pair1->remote_candidate->foundation, i); + if (!cand) + goto next_foundation; + pairX = __pair_lookup(ag, cand, pair1->local_address); + if (!pairX) + goto next_foundation; + if (!bf_isset(&pairX->pair_flags, flag)) + goto next_foundation; + g_queue_push_tail(out, pairX); + } + goto found; + +next_foundation: + ; + } + + /* nothing found */ + g_queue_clear(out); + +found: + g_queue_clear(&compo1); +} + +/* call(W) or call(R)+agent must be locked - no in_lock or out_lock must be held */ +static int __check_valid(struct ice_agent *ag) { + struct call_media *media = ag->media; + struct packet_stream *ps; + GList *l, *k; + GQueue all_compos; + struct ice_candidate_pair *pair; + struct interface_address *ifa; + + __get_complete_valid_pairs(&all_compos, ag); + + if (!all_compos.length) { + ilog(LOG_DEBUG, "ICE not completed yet"); + return 0; + } + + pair = all_compos.head->data; + ilog(LOG_DEBUG, "ICE completed, using pair "PAIR_FORMAT, PAIR_FMT(pair)); + AGENT_SET(ag, COMPLETED); + + ifa = g_atomic_pointer_get(&media->local_address); + if (ifa != pair->local_address + && g_atomic_pointer_compare_and_exchange(&media->local_address, ifa, + pair->local_address)) + ilog(LOG_INFO, "ICE negotiated: local interface %s", smart_ntop_buf(&pair->local_address->addr)); + + for (l = media->streams.head, k = all_compos.head; l && k; l = l->next, k = k->next) { + ps = l->data; + pair = k->data; + + mutex_lock(&ps->out_lock); + if (memcmp(&ps->endpoint, &pair->remote_candidate->endpoint, sizeof(ps->endpoint))) { + ilog(LOG_INFO, "ICE negotiated: peer for component %u is %s", ps->component, + smart_ntop_ep_buf(&pair->remote_candidate->endpoint)); + ps->endpoint = pair->remote_candidate->endpoint; + } + mutex_unlock(&ps->out_lock); + } + + call_media_unkernelize(media); + + g_queue_clear(&all_compos); + return 1; +} + + +/* call is locked in R */ +/* return values: + * 1 = ICE completed, interfaces selected + * 0 = packet processed + * -1 = generic error, process packet as normal + * -2 = role conflict + */ +int ice_request(struct packet_stream *ps, struct sockaddr_in6 *src, struct in6_addr *dst, + struct stun_attrs *attrs) +{ + struct call_media *media = ps->media; + struct ice_agent *ag; + struct interface_address *ifa; + const char *err; + struct ice_candidate *cand; + struct ice_candidate_pair *pair; + int ret; + + __DBG("received ICE request from %s on %s", smart_ntop_port_buf(src), smart_ntop_buf(dst)); + + ag = media->ice_agent; + if (!ag) + return -1; + + ifa = get_interface_from_address(ag->local_interface, dst); + err = "ICE/STUN binding request received on unknown local interface address"; + if (!ifa) + goto err; + + /* determine candidate pair */ + mutex_lock(&ag->lock); + + cand = __cand_lookup(ag, src, ps->component); + + if (!cand) + pair = __learned_candidate(ag, ps, src, ifa, attrs->priority); + else + pair = __pair_lookup(ag, cand, ifa); + + err = "Failed to determine ICE candidate from STUN request"; + if (!pair) + goto err_unlock; + + mutex_unlock(&ag->lock); + + /* determine role conflict */ + if (attrs->controlling && AGENT_ISSET(ag, CONTROLLING)) { + if (tie_breaker >= attrs->tiebreaker) + return -2; + else + __role_change(ag, 0); + } + else if (attrs->controlled && !AGENT_ISSET(ag, CONTROLLING)) { + if (tie_breaker >= attrs->tiebreaker) + __role_change(ag, 1); + else + return -2; + } + + + if (PAIR_ISSET(pair, SUCCEEDED)) + ; + else + __trigger_check(pair); + + ret = 0; + + if (attrs->use) { + ilog(LOG_DEBUG, "ICE pair "PAIR_FORMAT" has been nominated by peer", PAIR_FMT(pair)); + + PAIR_SET(pair, NOMINATED); + + mutex_lock(&ag->lock); + + g_tree_insert(ag->nominated_pairs, pair, pair); + + if (PAIR_ISSET(pair, SUCCEEDED)) + g_tree_insert(ag->valid_pairs, pair, pair); + + if (!AGENT_ISSET(ag, CONTROLLING)) + ret = __check_valid(ag); + + mutex_unlock(&ag->lock); + } + + return ret; + +err_unlock: + mutex_unlock(&ag->lock); +err: + ilog(LOG_NOTICE, "%s (from %s on interface %s)", err, smart_ntop_port_buf(src), smart_ntop_buf(dst)); + return 0; +} + + +static int __check_succeeded_complete(struct ice_agent *ag) { + GQueue complete; + int ret; + + __get_complete_succeeded_pairs(&complete, ag); + if (complete.length) { + struct ice_candidate_pair *pair = complete.head->data; + ilog(LOG_DEBUG, "Best succeeded ICE pair with all components is "PAIR_FORMAT, PAIR_FMT(pair)); + ret = 1; + } + else { + ilog(LOG_DEBUG, "No succeeded ICE pairs with all components yet"); + ret = 0; + } + g_queue_clear(&complete); + return ret; +} + +/* call is locked in R */ +int ice_response(struct packet_stream *ps, struct sockaddr_in6 *src, struct in6_addr *dst, + struct stun_attrs *attrs, u_int32_t transaction[3]) +{ + struct ice_candidate_pair *pair; + struct ice_agent *ag; + struct call_media *media = ps->media; + const char *err; + unsigned int component; + struct ice_candidate *cand; + struct interface_address *ifa; + int ret, was_ctl; + + __DBG("received ICE response from %s on %s", smart_ntop_port_buf(src), smart_ntop_buf(dst)); + + ag = media->ice_agent; + if (!ag) + return -1; + + mutex_lock(&ag->lock); + + pair = g_hash_table_lookup(ag->transaction_hash, transaction); + err = "ICE/STUN response with unknown transaction received"; + if (!pair) + goto err_unlock; + was_ctl = pair->was_controlling; + + mutex_unlock(&ag->lock); + + ifa = pair->local_address; + + ilog(LOG_DEBUG, "Received ICE/STUN response code %u for candidate pair "PAIR_FORMAT" from %s to %s", + attrs->error_code, PAIR_FMT(pair), + smart_ntop_ep_buf(&pair->remote_candidate->endpoint), + smart_ntop_buf(&ifa->addr)); + + /* verify endpoints */ + err = "ICE/STUN response received, but source address didn't match remote candidate address"; + if (memcmp(&src->sin6_addr, &pair->remote_candidate->endpoint.ip46, sizeof(src->sin6_addr))) + goto err; + if (ntohs(src->sin6_port) != pair->remote_candidate->endpoint.port) + goto err; + + err = "ICE/STUN response received, but destination address didn't match local interface address"; + if (memcmp(dst, &ifa->addr, sizeof(*dst))) + goto err; + if (pair->packet_stream != ps) + goto err; + + PAIR_CLEAR(pair, IN_PROGRESS); + ret = 0; + + /* handle all errors */ + if (attrs->error_code) { + err = "ICE/STUN error received"; + if (attrs->error_code != 487) + goto err; + __role_change(ag, !was_ctl); + __trigger_check(pair); + goto out; + } + + /* we don't discover peer reflexive here (RFC 5245 7.1.3.2.1) as we don't expect to be behind NAT */ + /* we also skip parts of 7.1.3.2.2 as we don't do server reflexive */ + + mutex_lock(&ag->lock); + + /* check if we're in the final (controlling) phase */ + if (pair->was_nominated && PAIR_CLEAR(pair, TO_USE)) { + ilog(LOG_DEBUG, "Setting nominated ICE candidate pair "PAIR_FORMAT" as valid", PAIR_FMT(pair)); + PAIR_SET(pair, VALID); + g_tree_insert(ag->valid_pairs, pair, pair); + ret = __check_valid(ag); + goto out_unlock; + } + + if (PAIR_SET(pair, SUCCEEDED)) + goto out_unlock; + + ilog(LOG_DEBUG, "Setting ICE candidate pair "PAIR_FORMAT" as succeeded", PAIR_FMT(pair)); + g_tree_insert(ag->succeeded_pairs, pair, pair); + + if (!ag->start_nominating.tv_sec) { + if (__check_succeeded_complete(ag)) { + ag->start_nominating = g_now; + timeval_add_usec(&ag->start_nominating, 100000); + __agent_schedule_abs(ag, &ag->start_nominating); + } + } + + /* now unfreeze all other pairs from the same foundation */ + for (component = 1; component <= MAX_COMPONENTS; component++) { + if (component == ps->component) + continue; + cand = __foundation_lookup(ag, &pair->remote_candidate->foundation, component); + if (!cand) + continue; + pair = __pair_lookup(ag, cand, ifa); + if (!pair) + continue; + + if (PAIR_ISSET(pair, FAILED)) + continue; + if (!PAIR_CLEAR(pair, FROZEN)) + continue; + + ilog(LOG_DEBUG, "Unfreezing related ICE pair "PAIR_FORMAT, PAIR_FMT(pair)); + } + + /* if this was previously nominated by the peer, it's now valid */ + if (PAIR_ISSET(pair, NOMINATED)) { + g_tree_insert(ag->valid_pairs, pair, pair); + + if (!AGENT_ISSET(ag, CONTROLLING)) + ret = __check_valid(ag); + } + +out_unlock: + mutex_unlock(&ag->lock); +out: + return ret; + +err_unlock: + mutex_unlock(&ag->lock); +err: + if (err) + ilog(LOG_NOTICE, "%s (from %s on interface %s)", + err, smart_ntop_port_buf(src), smart_ntop_buf(dst)); + + if (pair && attrs->error_code) + __fail_pair(pair); + + return 0; +} + + + +void ice_thread_run(void *p) { + struct ice_agent *ag; + struct call *call; + unsigned long sleeptime; + struct timeval tv; + + mutex_lock(&ice_agents_timers_lock); + + while (!g_shutdown) { + gettimeofday(&g_now, NULL); + + /* lock our list and get the first element */ + ag = g_tree_find_first(ice_agents_timers, NULL, NULL); + /* scheduled to run? if not, we just go to sleep, otherwise we remove it from the tree, + * steal the reference and run it */ + if (!ag) + goto sleep; + if (timeval_cmp(&g_now, &ag->next_check) < 0) + goto sleep; + + g_tree_remove(ice_agents_timers, ag); + ZERO(ag->next_check); + ag->last_run = g_now; + mutex_unlock(&ice_agents_timers_lock); + + /* this agent is scheduled to run right now */ + + /* lock the call */ + call = ag->call; + log_info_ice_agent(ag); + rwlock_lock_r(&call->master_lock); + + /* and run our checks */ + __do_ice_checks(ag); + + /* finally, release our reference and start over */ + log_info_clear(); + rwlock_unlock_r(&call->master_lock); + obj_put(ag); + mutex_lock(&ice_agents_timers_lock); + continue; + +sleep: + /* figure out how long we should sleep */ + sleeptime = ag ? timeval_diff(&ag->next_check, &g_now) : 100000; + sleeptime = MIN(100000, sleeptime); /* 100 ms at the most */ + tv = g_now; + timeval_add_usec(&tv, sleeptime); + cond_timedwait(&ice_agents_timers_cond, &ice_agents_timers_lock, &tv); + continue; + } + + mutex_unlock(&ice_agents_timers_lock); +} + +static void random_ice_string(char *buf, int len) { + while (len--) + *buf++ = ice_chars[random() % strlen(ice_chars)]; +} + +static void create_random_ice_string(struct call *call, str *s, int len) { + char buf[30]; + + assert(len < sizeof(buf)); + if (s->s) + return; + + random_ice_string(buf, len); + call_str_cpy_len(call, s, buf, len); +} + +void ice_foundation(struct interface_address *ifa) { + random_ice_string(ifa->foundation_buf, sizeof(ifa->foundation_buf)); + str_init_len(&ifa->ice_foundation, ifa->foundation_buf, sizeof(ifa->foundation_buf)); +} + +void ice_remote_candidates(GQueue *out, struct ice_agent *ag) { + GQueue all_compos; + GList *l; + struct ice_candidate_pair *pair; + + g_queue_init(out); + + mutex_lock(&ag->lock); + __get_complete_valid_pairs(&all_compos, ag); + mutex_unlock(&ag->lock); + + for (l = all_compos.head; l; l = l->next) { + pair = l->data; + g_queue_push_tail(out, pair->remote_candidate); + } + + g_queue_clear(&all_compos); +} diff --git a/daemon/ice.h b/daemon/ice.h new file mode 100644 index 000000000..44fe15adc --- /dev/null +++ b/daemon/ice.h @@ -0,0 +1,208 @@ +#ifndef __ICE_H__ +#define __ICE_H__ + + + +#include +#include +#include +#include +#include "str.h" +#include "obj.h" +#include "aux.h" +#include "call.h" + + + + +#define MAX_COMPONENTS 2 +#define TIMER_RUN_INTERVAL 20 /* ms */ +#define STUN_RETRANSMIT_INTERVAL 100 /* ms, with exponential backoff */ +#define STUN_MAX_RETRANSMITS 7 +#define MAX_ICE_CANDIDATES 100 + + + +#define ICE_AGENT_COMPLETED 0x0002 +#define ICE_AGENT_CONTROLLING 0x0004 +#define ICE_AGENT_NOMINATING 0x0008 + +#define ICE_PAIR_FROZEN 0x0001 +#define ICE_PAIR_IN_PROGRESS 0x0002 +#define ICE_PAIR_FAILED 0x0004 +#define ICE_PAIR_SUCCEEDED 0x0008 +#define ICE_PAIR_NOMINATED 0x0010 +#define ICE_PAIR_LEARNED 0x0020 +#define ICE_PAIR_VALID 0x0040 +#define ICE_PAIR_TO_USE 0x0080 +#define ICE_PAIR_TRIGGERED 0x0100 + +#define PAIR_ISSET(p, f) bf_isset(&(p)->pair_flags, ICE_PAIR_ ## f) +#define PAIR_SET(p, f) bf_set(&(p)->pair_flags, ICE_PAIR_ ## f) +#define PAIR_SET2(p, f, g) bf_set(&(p)->pair_flags, ICE_PAIR_ ## f | ICE_PAIR_ ## g) +#define PAIR_CLEAR(p, f) bf_clear(&(p)->pair_flags, ICE_PAIR_ ## f) +#define PAIR_CLEAR2(p, f, g) bf_clear(&(p)->pair_flags, ICE_PAIR_ ## f | ICE_PAIR_ ## g) + +#define AGENT_ISSET(p, f) bf_isset(&(p)->agent_flags, ICE_AGENT_ ## f) +#define AGENT_ISSET2(p, f, g) bf_isset(&(p)->agent_flags, ICE_AGENT_ ## f | ICE_AGENT_ ## g) +#define AGENT_SET(p, f) bf_set(&(p)->agent_flags, ICE_AGENT_ ## f) +#define AGENT_SET2(p, f, g) bf_set(&(p)->agent_flags, ICE_AGENT_ ## f | ICE_AGENT_ ## g) +#define AGENT_CLEAR(p, f) bf_clear(&(p)->agent_flags, ICE_AGENT_ ## f) +#define AGENT_CLEAR2(p, f, g) bf_clear(&(p)->agent_flags, ICE_AGENT_ ## f | ICE_AGENT_ ## g) + + + +struct local_interface; +struct interface_address; +struct packet_stream; +struct call_media; +struct call; +struct stream_params; +struct stun_attrs; + + + + +enum ice_candidate_type { + ICT_UNKNOWN = 0, + ICT_HOST, + ICT_SRFLX, + ICT_PRFLX, + ICT_RELAY, + __ICT_LAST, +}; + +enum ice_transport { + ITP_UNKNOWN = 0, + ITP_UDP, +// ITP_TCP, +}; + +struct ice_candidate { + str foundation; + unsigned long component_id; + enum ice_transport transport; + unsigned long priority; + struct endpoint endpoint; + enum ice_candidate_type type; + struct in6_addr related_address; + unsigned int related_port; +}; + +struct ice_candidate_pair { + struct ice_candidate *remote_candidate; + struct interface_address *local_address; + struct packet_stream *packet_stream; + volatile unsigned int pair_flags; + u_int32_t stun_transaction[3]; /* belongs to transaction_hash, thus agent->lock */ + unsigned int retransmit_ms; + struct timeval retransmit; + unsigned int retransmits; + struct ice_agent *agent; + u_int64_t pair_priority; + int was_controlling:1, + was_nominated:1; +}; + +/* these are protected by the call's master_lock */ +struct ice_agent { + struct obj obj; + struct call *call; /* main reference */ + struct call_media *media; + struct local_interface *local_interface; + int desired_family; + + mutex_t lock; /* for elements below. and call must be locked in R */ + /* lock order: in_lock first, then agent->lock */ + GQueue remote_candidates; + GQueue candidate_pairs; /* for storage */ + GQueue triggered; + GHashTable *candidate_hash; + GHashTable *pair_hash; + GHashTable *transaction_hash; + GHashTable *foundation_hash; + GTree *all_pairs; + GQueue all_pairs_list; /* sorted through gtree */ + GTree *nominated_pairs; /* nominated by peer */ + GTree *succeeded_pairs; /* checked by us */ + GTree *valid_pairs; /* succeeded and nominated */ + unsigned int active_components; + struct timeval start_nominating; + + str ufrag[2]; /* 0 = remote, 1 = local */ + str pwd[2]; /* ditto */ + volatile unsigned int agent_flags; + + struct timeval next_check; /* protected by ice_agents_timers_lock */ + struct timeval last_run; /* ditto */ +}; + + + + +extern const unsigned int ice_type_preferences[]; +extern const char * const ice_type_strings[]; + + + + +void ice_init(void); + +enum ice_candidate_type ice_candidate_type(const str *s); +enum ice_transport ice_transport(const str *s); +int ice_has_related(enum ice_candidate_type); +void ice_foundation(struct interface_address *ifa); + +void ice_agent_init(struct ice_agent **agp, struct call_media *media); +void ice_update(struct ice_agent *, struct stream_params *); +void ice_shutdown(struct ice_agent **); +void ice_restart(struct ice_agent *); + +void ice_candidates_free(GQueue *); +void ice_remote_candidates(GQueue *, struct ice_agent *); + +void ice_thread_run(void *); + +int ice_request(struct packet_stream *, struct sockaddr_in6 *, struct in6_addr *, struct stun_attrs *); +int ice_response(struct packet_stream *ps, struct sockaddr_in6 *src, struct in6_addr *dst, + struct stun_attrs *attrs, u_int32_t transaction[3]); + +/* returns 0 if ICE still has work to do, 1 otherwise */ +INLINE int ice_has_finished(struct call_media *media) { + if (!media) + return 1; + if (!MEDIA_ISSET(media, ICE)) + return 1; + if (!media->ice_agent) + return 1; + if (AGENT_ISSET(media->ice_agent, COMPLETED)) + return 1; + return 0; +} +INLINE unsigned int ice_type_preference(enum ice_candidate_type type) { + if (type >= __ICT_LAST) + return 0; + return ice_type_preferences[type]; +} +/* local_pref starts with 0 */ +INLINE u_int32_t ice_priority_pref(unsigned int type_pref, unsigned int local_pref, unsigned int component) { + return type_pref << 24 | (65535 - local_pref) << 8 | (256 - component); +} +INLINE u_int32_t ice_priority(enum ice_candidate_type type, unsigned int local_pref, unsigned int component) { + return ice_priority_pref(ice_type_preference(type), local_pref, component); +} +INLINE unsigned int ice_type_pref_from_prio(u_int32_t prio) { + return (prio & 0xff000000) >> 24; +} +INLINE unsigned int ice_local_pref_from_prio(u_int32_t prio) { + return 65535 - ((prio & 0xffff00) >> 8); +} +INLINE const char *ice_candidate_type_str(enum ice_candidate_type type) { + if (type >= __ICT_LAST) + return 0; + return ice_type_strings[type]; +} + + + +#endif diff --git a/daemon/log.c b/daemon/log.c index adfdcf750..7bd4c2e84 100644 --- a/daemon/log.c +++ b/daemon/log.c @@ -6,6 +6,7 @@ #include "str.h" #include "call.h" #include "poller.h" +#include "ice.h" @@ -88,7 +89,8 @@ void log_to_stderr(int facility_priority, char *format, ...) { return; } - fprintf(stderr, "%s: %s\n", prio_str[facility_priority & LOG_PRIMASK], msg); + fprintf(stderr, "[%lu.%06lu] %s: %s\n", (unsigned long) g_now.tv_sec, (unsigned long) g_now.tv_usec, + prio_str[facility_priority & LOG_PRIMASK], msg); free(msg); } @@ -123,6 +125,12 @@ void __ilog(int prio, const char *fmt, ...) { case LOG_INFO_C_STRING: snprintf(prefix, sizeof(prefix), "[%s] ", log_info.u.cstr); break; + case LOG_INFO_ICE_AGENT: + snprintf(prefix, sizeof(prefix), "["STR_FORMAT"/"STR_FORMAT"/%u] ", + STR_FMT(&log_info.u.ice_agent->call->callid), + STR_FMT(&log_info.u.ice_agent->media->monologue->tag), + log_info.u.ice_agent->media->index); + break; } va_start(ap, fmt); diff --git a/daemon/log.h b/daemon/log.h index 8ee9e9081..ffe3f941e 100644 --- a/daemon/log.h +++ b/daemon/log.h @@ -15,6 +15,7 @@ struct log_info { const struct stream_fd *stream_fd; const str *str; const char *cstr; + const struct ice_agent *ice_agent; const void *ptr; } u; enum { @@ -23,6 +24,7 @@ struct log_info { LOG_INFO_STREAM_FD, LOG_INFO_STR, LOG_INFO_C_STRING, + LOG_INFO_ICE_AGENT, } e; }; @@ -76,10 +78,9 @@ INLINE void log_info_clear() { 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); + case LOG_INFO_ICE_AGENT: + __obj_put((void *) log_info.u.ptr); break; case LOG_INFO_STR: case LOG_INFO_C_STRING: @@ -116,6 +117,13 @@ INLINE void log_info_c_string(const char *s) { log_info.e = LOG_INFO_C_STRING; log_info.u.cstr = s; } +INLINE void log_info_ice_agent(const struct ice_agent *ag) { + log_info_clear(); + if (!ag) + return; + log_info.e = LOG_INFO_ICE_AGENT; + log_info.u.ice_agent = __obj_get((void *) ag); +} INLINE int get_log_level(void) { return g_atomic_int_get(&log_level); } diff --git a/daemon/main.c b/daemon/main.c index b7e1aa575..97b835c33 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -27,6 +27,7 @@ #include "call_interfaces.h" #include "cli.h" #include "graphite.h" +#include "ice.h" @@ -80,7 +81,6 @@ struct main_context { -static int global_shutdown; static mutex_t *openssl_locks; static char *pidfile; @@ -126,7 +126,7 @@ static void sighandler(gpointer x) { ts.tv_sec = 0; ts.tv_nsec = 100000000; /* 0.1 sec */ - while (!global_shutdown) { + while (!g_shutdown) { ret = sigtimedwait(&ss, NULL, &ts); if (ret == -1) { if (errno == EAGAIN || errno == EINTR) @@ -135,7 +135,7 @@ static void sighandler(gpointer x) { } if (ret == SIGINT || ret == SIGTERM) - global_shutdown = 1; + g_shutdown = 1; else if (ret == SIGUSR1) { if (get_log_level() > 0) { g_atomic_int_add(&log_level, -1); @@ -237,7 +237,7 @@ static struct interface_address *if_addr_parse(char *s) { return NULL; } - ifa = g_slice_alloc(sizeof(*ifa)); + ifa = g_slice_alloc0(sizeof(*ifa)); ifa->interface_name = name; ifa->addr = addr; ifa->advertised = adv; @@ -458,6 +458,7 @@ static void init_everything() { resources(); sdp_init(); dtls_init(); + ice_init(); } void redis_mod_verify(void *dlh) { @@ -622,10 +623,11 @@ no_kernel: die("Refusing to continue without working Redis database"); } +/* XXX move loop functions */ static void timer_loop(void *d) { struct poller *p = d; - while (!global_shutdown) + while (!g_shutdown) poller_timers_wait_run(p, 100); } @@ -639,14 +641,14 @@ static void graphite_loop(void *d) { connect_to_graphite_server(graphite_ip,graphite_port); - while (!global_shutdown) + while (!g_shutdown) graphite_loop_run(cm,graphite_interval); // time in seconds } static void poller_loop(void *d) { struct poller *p = d; - while (!global_shutdown) + while (!g_shutdown) poller_poll(p, 100); } @@ -664,6 +666,7 @@ int main(int argc, char **argv) { thread_create_detach(timer_loop, ctx.p); if (graphite_ip) thread_create_detach(graphite_loop, ctx.m); + thread_create_detach(ice_thread_run, NULL); if (num_threads < 1) { #ifdef _SC_NPROCESSORS_ONLN @@ -677,7 +680,7 @@ int main(int argc, char **argv) { thread_create_detach(poller_loop, ctx.p); } - while (!global_shutdown) { + while (!g_shutdown) { usleep(100000); threads_join_all(0); } diff --git a/daemon/poller.c b/daemon/poller.c index 96e83e1d9..5d0830a6d 100644 --- a/daemon/poller.c +++ b/daemon/poller.c @@ -50,18 +50,12 @@ struct poller { -__thread time_t poller_now; - - - - - struct poller *poller_new(void) { struct poller *p; p = malloc(sizeof(*p)); memset(p, 0, sizeof(*p)); - poller_now = time(NULL); + gettimeofday(&g_now, NULL); p->fd = epoll_create1(0); if (p->fd == -1) abort(); @@ -315,7 +309,7 @@ int poller_poll(struct poller *p, int timeout) { if (ret <= 0) goto out; - poller_now = time(NULL); + gettimeofday(&g_now, NULL); for (i = 0; i < ret; i++) { ev = &evs[i]; @@ -502,6 +496,6 @@ retry: goto retry; now: - poller_now = tv.tv_sec; + gettimeofday(&g_now, NULL); poller_timers_run(p); } diff --git a/daemon/poller.h b/daemon/poller.h index 6c0003248..0bc6d9485 100644 --- a/daemon/poller.h +++ b/daemon/poller.h @@ -30,10 +30,8 @@ struct poller_item { struct poller; - -extern __thread time_t poller_now; - - +/* XXX replace all occurrences with g_now */ +#define poller_now g_now.tv_sec struct poller *poller_new(void); diff --git a/daemon/sdp.c b/daemon/sdp.c index e869d621b..84e71241e 100644 --- a/daemon/sdp.c +++ b/daemon/sdp.c @@ -14,6 +14,7 @@ #include "crypto.h" #include "dtls.h" #include "rtp.h" +#include "ice.h" struct network_address { str network_type; @@ -76,17 +77,17 @@ struct attribute_rtcp { }; struct attribute_candidate { - str foundation; str component_str; - str transport; + str transport_str; str priority_str; - str ip_str; + str address_str; str port_str; str typ_str; str type_str; + str related_address_str; + str related_port_str; - unsigned long component; - unsigned long priority; + struct ice_candidate cand_parsed; int parsed:1; }; @@ -166,8 +167,10 @@ struct sdp_attribute { ATTR_RTCP, ATTR_CANDIDATE, ATTR_ICE, + ATTR_ICE_LITE, ATTR_ICE_OPTIONS, ATTR_ICE_UFRAG, + ATTR_ICE_PWD, ATTR_CRYPTO, ATTR_SSRC, ATTR_INACTIVE, @@ -198,11 +201,6 @@ struct sdp_attribute { -static const char ice_chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - - - INLINE struct sdp_attribute *attr_get_by_id(struct sdp_attributes *a, int id) { return g_hash_table_lookup(a->id_hash, &id); } @@ -576,27 +574,59 @@ static int parse_attribute_rtcp(struct sdp_attribute *output) { static int parse_attribute_candidate(struct sdp_attribute *output) { PARSE_DECL; char *ep; + struct attribute_candidate *c; output->attr = ATTR_CANDIDATE; + c = &output->u.candidate; PARSE_INIT; - EXTRACT_TOKEN(u.candidate.foundation); + EXTRACT_TOKEN(u.candidate.cand_parsed.foundation); EXTRACT_TOKEN(u.candidate.component_str); - EXTRACT_TOKEN(u.candidate.transport); + EXTRACT_TOKEN(u.candidate.transport_str); EXTRACT_TOKEN(u.candidate.priority_str); - EXTRACT_TOKEN(u.candidate.ip_str); + EXTRACT_TOKEN(u.candidate.address_str); EXTRACT_TOKEN(u.candidate.port_str); EXTRACT_TOKEN(u.candidate.typ_str); EXTRACT_TOKEN(u.candidate.type_str); - output->u.candidate.component = strtoul(output->u.candidate.component_str.s, &ep, 10); - if (ep == output->u.candidate.component_str.s) + c->cand_parsed.component_id = strtoul(c->component_str.s, &ep, 10); + if (ep == c->component_str.s) + return -1; + + c->cand_parsed.transport = ice_transport(&c->transport_str); + if (!c->cand_parsed.transport) + return 0; + + c->cand_parsed.priority = strtoul(c->priority_str.s, &ep, 10); + if (ep == c->priority_str.s) + return -1; + + if (__parse_address(&c->cand_parsed.endpoint.ip46, NULL, NULL, &c->address_str)) + return 0; + + c->cand_parsed.endpoint.port = strtoul(c->port_str.s, &ep, 10); + if (ep == c->port_str.s) return -1; - output->u.candidate.priority = strtoul(output->u.candidate.priority_str.s, &ep, 10); - if (ep == output->u.candidate.priority_str.s) + + if (str_cmp(&c->typ_str, "typ")) + return -1; + + c->cand_parsed.type = ice_candidate_type(&c->type_str); + if (!c->cand_parsed.type) + return 0; + + if (!ice_has_related(c->cand_parsed.type)) + goto done; + + if (__parse_address(&c->cand_parsed.related_address, NULL, NULL, &c->related_address_str)) + return 0; + + c->cand_parsed.related_port = strtoul(c->related_port_str.s, &ep, 10); + if (ep == c->related_port_str.s) return -1; - output->u.candidate.parsed = 1; +done: + c->parsed = 1; return 0; } @@ -766,13 +796,13 @@ static int parse_attribute(struct sdp_attribute *a) { break; case 7: if (!str_cmp(&a->name, "ice-pwd")) - a->attr = ATTR_ICE; + a->attr = ATTR_ICE_PWD; break; case 8: switch (a->name.s[0]) { case 'i': if (!str_cmp(&a->name, "ice-lite")) - a->attr = ATTR_ICE; + a->attr = ATTR_ICE_LITE; else if (!str_cmp(&a->name, "inactive")) a->attr = ATTR_INACTIVE; break; @@ -1093,6 +1123,48 @@ out: return ret; } +static void __sdp_ice(struct stream_params *sp, struct sdp_media *media) { + struct sdp_attribute *attr; + struct attribute_candidate *ac; + struct ice_candidate *cand; + GQueue *q; + GList *ql; + + q = attr_list_get_by_id(&media->attributes, ATTR_CANDIDATE); + if (!q) + return; + + SP_SET(sp, ICE); + + for (ql = q->head; ql; ql = ql->next) { + attr = ql->data; + ac = &attr->u.candidate; + if (!ac->parsed) + continue; + cand = g_slice_alloc(sizeof(*cand)); + *cand = ac->cand_parsed; + g_queue_push_tail(&sp->ice_candidates, cand); + } + + if ((attr = attr_get_by_id(&media->attributes, ATTR_ICE_OPTIONS))) { + if (str_str(&attr->value, "trickle") >= 0) + SP_SET(sp, TRICKLE_ICE); + } + else if (is_trickle_ice_address(&sp->rtp_endpoint)) + SP_SET(sp, TRICKLE_ICE); + + if (attr_get_by_id(&media->attributes, ATTR_ICE_LITE)) + SP_SET(sp, ICE_LITE); + + attr = attr_get_by_id_m_s(media, ATTR_ICE_UFRAG); + if (attr) + sp->ice_ufrag = attr->value; + + attr = attr_get_by_id_m_s(media, ATTR_ICE_PWD); + if (attr) + sp->ice_pwd = attr->value; +} + /* XXX split this function up */ int sdp_streams(const GQueue *sessions, GQueue *streams, struct sdp_ng_flags *flags) { @@ -1181,15 +1253,7 @@ int sdp_streams(const GQueue *sessions, GQueue *streams, struct sdp_ng_flags *fl sp->fingerprint.hash_func->num_bytes); } - /* ICE stuff */ - if (attr_get_by_id(&media->attributes, ATTR_CANDIDATE)) - SP_SET(sp, ICE); - if ((attr = attr_get_by_id(&media->attributes, ATTR_ICE_OPTIONS))) { - if (str_str(&attr->value, "trickle") >= 0) - SP_SET(sp, TRICKLE_ICE); - } - else if (is_trickle_ice_address(&sp->rtp_endpoint)) - SP_SET(sp, TRICKLE_ICE); + __sdp_ice(sp, media); /* determine RTCP endpoint */ @@ -1430,22 +1494,6 @@ void sdp_chopper_destroy(struct sdp_chopper *chop) { g_slice_free1(sizeof(*chop), chop); } -static void random_ice_string(char *buf, int len) { - while (len--) - *buf++ = ice_chars[random() % strlen(ice_chars)]; -} - -static void create_random_ice_string(struct call *call, str *s, int len) { - char buf[30]; - - assert(len < sizeof(buf)); - if (s->s) - return; - - random_ice_string(buf, len); - call_str_cpy_len(call, s, buf, len); -} - static int process_session_attributes(struct sdp_chopper *chop, struct sdp_attributes *attrs, struct sdp_ng_flags *flags) { @@ -1458,6 +1506,9 @@ static int process_session_attributes(struct sdp_chopper *chop, struct sdp_attri switch (attr->attr) { case ATTR_ICE: case ATTR_ICE_UFRAG: + case ATTR_ICE_PWD: + case ATTR_ICE_OPTIONS: + case ATTR_ICE_LITE: if (!flags->ice_remove && !flags->ice_force) break; goto strip; @@ -1517,6 +1568,9 @@ static int process_media_attributes(struct sdp_chopper *chop, struct sdp_media * switch (attr->attr) { case ATTR_ICE: case ATTR_ICE_UFRAG: + case ATTR_ICE_PWD: + case ATTR_ICE_OPTIONS: + case ATTR_ICE_LITE: if (MEDIA_ISSET(media, PASSTHRU)) break; if (!flags->ice_remove && !flags->ice_force) @@ -1579,32 +1633,19 @@ strip: return 0; } -INLINE unsigned long prio_calc(unsigned int pref, unsigned int tpref) { - return (1 << 24) * tpref + (1 << 8) * pref + (256 - 1); -} -INLINE unsigned long pref_from_prio(unsigned int prio) { - return (prio & 0xffff00) >> 8; -} -INLINE unsigned long type_from_prio(unsigned int prio) { - return (prio & 0xff000000) >> 24; -} - -static unsigned long new_priority(struct sdp_media *media, int relay) { +static void new_priority(struct sdp_media *media, enum ice_candidate_type type, unsigned int *tprefp, + unsigned int *lprefp) +{ GQueue *cands; - int pref; - unsigned long prio, tpref; + unsigned int lpref, tpref; + u_int32_t prio; GList *l; struct sdp_attribute *a; struct attribute_candidate *c; - tpref = 126; - if (relay) - tpref = 0; - pref = 65535; - prio = prio_calc(pref, tpref); - - if (!media) - goto out; + lpref = 0; + tpref = ice_type_preference(type); + prio = ice_priority_pref(tpref, lpref, 1); cands = attr_list_get_by_id(&media->attributes, ATTR_CANDIDATE); if (!cands) @@ -1613,88 +1654,111 @@ static unsigned long new_priority(struct sdp_media *media, int relay) { for (l = cands->head; l; l = l->next) { a = l->data; c = &a->u.candidate; - if (c->priority <= prio && !str_cmp(&c->type_str, "host")) { - /* tpref should come out as 126 here, unless the client isn't following + if (c->cand_parsed.priority <= prio && c->cand_parsed.type == type + && c->cand_parsed.component_id == 1) + { + /* tpref should come out as 126 (if host) here, unless the client isn't following * the RFC, in which case we must adapt */ - tpref = type_from_prio(c->priority); + tpref = ice_type_pref_from_prio(c->cand_parsed.priority); - pref = pref_from_prio(c->priority); - if (pref) - pref--; + lpref = ice_local_pref_from_prio(c->cand_parsed.priority); + if (lpref) + lpref--; else { /* we must deviate from the RFC recommended values */ if (tpref) tpref--; - pref = 65535; + lpref = 65535; } - prio = prio_calc(pref, tpref); + prio = ice_priority_pref(tpref, lpref, 1); } } out: - return prio; + *tprefp = tpref; + *lprefp = lpref; +} + +static void insert_candidate(struct sdp_chopper *chop, struct packet_stream *ps, unsigned int component, + unsigned int type_pref, unsigned int local_pref, enum ice_candidate_type type, + struct interface_address *ifa) +{ + unsigned long priority; + + priority = ice_priority_pref(type_pref, local_pref, component); + chopper_append_c(chop, "a=candidate:"); + chopper_append_str(chop, &ifa->ice_foundation); + chopper_append_printf(chop, " %u UDP %lu ", component, priority); + insert_ice_address(chop, ps, ifa); + chopper_append_c(chop, " typ "); + chopper_append_c(chop, ice_candidate_type_str(type)); + chopper_append_c(chop, "\r\n"); } static void insert_candidates(struct sdp_chopper *chop, struct packet_stream *rtp, struct packet_stream *rtcp, - unsigned long priority, struct sdp_media *media, - unsigned int relay) + struct sdp_ng_flags *flags, struct sdp_media *sdp_media) { - GQueue addrs = G_QUEUE_INIT; GList *l; struct interface_address *ifa; + unsigned int pref; + struct call_media *media; + struct local_interface *lif; + struct ice_agent *ag; + unsigned int type_pref, local_pref; + enum ice_candidate_type cand_type; + struct ice_candidate *cand; + + media = rtp->media; + + cand_type = ICT_HOST; + if (flags->ice_force_relay) + cand_type = ICT_RELAY; + if (MEDIA_ISSET(media, PASSTHRU)) + new_priority(sdp_media, cand_type, &type_pref, &local_pref); + else { + type_pref = ice_type_preference(cand_type); + local_pref = -1; + } - get_all_interface_addresses(&addrs, rtp->media->interface, rtp->media->desired_family); - - for (l = addrs.head; l; l = l->next) { - ifa = l->data; - - chopper_append_c(chop, "a=candidate:"); - chopper_append_str(chop, &ifa->ice_foundation); - chopper_append_printf(chop, " 1 UDP %lu ", priority); - insert_ice_address(chop, rtp, ifa); - if (relay) - chopper_append_c(chop, " typ relay\r\n"); - else - chopper_append_c(chop, " typ host\r\n"); - - if (rtcp) { - /* rtcp-mux only possible in answer */ - chopper_append_c(chop, "a=candidate:"); - chopper_append_str(chop, &ifa->ice_foundation); - chopper_append_printf(chop, " 2 UDP %lu ", priority - 1); - insert_ice_address(chop, rtcp, ifa); - if (relay) - chopper_append_c(chop, " typ relay\r\n"); - else - chopper_append_c(chop, " typ host\r\n"); + ag = media->ice_agent; + lif = ag ? ag->local_interface : media->interface; + + if (ag && AGENT_ISSET(ag, COMPLETED)) { + ifa = g_atomic_pointer_get(&media->local_address); + insert_candidate(chop, rtp, 1, type_pref, ifa->preference, cand_type, ifa); + if (rtcp) /* rtcp-mux only possible in answer */ + insert_candidate(chop, rtcp, 2, type_pref, ifa->preference, cand_type, ifa); + + if (flags->opmode == OP_OFFER && AGENT_ISSET(ag, CONTROLLING)) { + GQueue rc; + GList *l; + chopper_append_c(chop, "a=remote-candidates:"); + ice_remote_candidates(&rc, ag); + for (l = rc.head; l; l = l->next) { + if (l != rc.head) + chopper_append_c(chop, " "); + cand = l->data; + chopper_append_printf(chop, "%lu %s %u", cand->component_id, + smart_ntop_buf(&cand->endpoint.ip46), cand->endpoint.port); + } + chopper_append_c(chop, "\r\n"); + g_queue_clear(&rc); } - - priority -= 256; + return; } - g_queue_clear(&addrs); -} - -static int has_ice(GQueue *sessions) { - GList *l, *m; - struct sdp_session *session; - struct sdp_media *media; - - for (l = sessions->head; l; l = l->next) { - session = l->data; + for (l = lif->list.head; l; l = l->next) { + ifa = l->data; + pref = (local_pref == -1) ? ifa->preference : local_pref; - if (attr_get_by_id(&session->attributes, ATTR_ICE_UFRAG)) - return 1; + insert_candidate(chop, rtp, 1, type_pref, pref, cand_type, ifa); - for (m = session->media_streams.head; m; m = m->next) { - media = m->data; + if (rtcp) /* rtcp-mux only possible in answer */ + insert_candidate(chop, rtcp, 2, type_pref, pref, cand_type, ifa); - if (attr_get_by_id(&media->attributes, ATTR_ICE_UFRAG)) - return 1; - } + if (local_pref != -1) + local_pref++; } - - return 0; } static void insert_dtls(struct call_media *media, struct sdp_chopper *chop) { @@ -1720,12 +1784,10 @@ static void insert_dtls(struct call_media *media, struct sdp_chopper *chop) { *(--o) = '\0'; actpass = "holdconn"; - if (MEDIA_ISSET(media, SETUP_PASSIVE)) { - if (MEDIA_ISSET(media, SETUP_ACTIVE)) - actpass = "actpass"; - else - actpass = "passive"; - } + if (MEDIA_ARESET2(media, SETUP_PASSIVE, SETUP_ACTIVE)) + actpass = "actpass"; + else if (MEDIA_ISSET(media, SETUP_PASSIVE)) + actpass = "passive"; else if (MEDIA_ISSET(media, SETUP_ACTIVE)) actpass = "active"; @@ -1779,16 +1841,11 @@ int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call_monologu struct sdp_session *session; struct sdp_media *sdp_media; GList *l, *k, *m, *j; - int do_ice, media_index, sess_conn; - unsigned long priority; + int media_index, sess_conn; struct call_media *call_media; struct packet_stream *ps, *ps_rtcp; - struct call *call; m = monologue->medias.head; - do_ice = (flags->ice_force || flags->ice_force_relay || - (!has_ice(sessions) && !flags->ice_remove)) ? 1 : 0; - call = monologue->call; for (l = sessions->head; l; l = l->next) { session = l->data; @@ -1829,11 +1886,6 @@ int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call_monologu if (!MEDIA_ISSET(call_media, PASSTHRU)) { if (process_session_attributes(chop, &session->attributes, flags)) goto error; - - if (do_ice) { - copy_up_to_end_of(chop, &session->s); - chopper_append_c(chop, "a=ice-lite\r\n"); - } } media_index = 1; @@ -1881,11 +1933,11 @@ int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call_monologu if (!sdp_media->port_num || !ps->sfd) goto next; - if (MEDIA_ISSET(call_media, SEND) && MEDIA_ISSET(call_media, RECV)) + if (MEDIA_ARESET2(call_media, SEND, RECV)) chopper_append_c(chop, "a=sendrecv\r\n"); - else if (MEDIA_ISSET(call_media, SEND) && !MEDIA_ISSET(call_media, RECV)) + else if (MEDIA_ISSET(call_media, SEND)) chopper_append_c(chop, "a=sendonly\r\n"); - else if (!MEDIA_ISSET(call_media, SEND) && MEDIA_ISSET(call_media, RECV)) + else if (MEDIA_ISSET(call_media, RECV)) chopper_append_c(chop, "a=recvonly\r\n"); else chopper_append_c(chop, "a=inactive\r\n"); @@ -1908,31 +1960,16 @@ int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call_monologu insert_crypto(call_media, chop); insert_dtls(call_media, chop); - if (do_ice && !MEDIA_ISSET(call_media, PASSTHRU)) { - 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); - } - PS_SET(ps, STUN); - if (ps_rtcp) - PS_SET(ps_rtcp, STUN); - + if (call_media->ice_agent) { chopper_append_c(chop, "a=ice-ufrag:"); - chopper_append_str(chop, &call_media->ice_ufrag); + chopper_append_str(chop, &call_media->ice_agent->ufrag[1]); chopper_append_c(chop, "\r\na=ice-pwd:"); - chopper_append_str(chop, &call_media->ice_pwd); + chopper_append_str(chop, &call_media->ice_agent->pwd[1]); chopper_append_c(chop, "\r\n"); } - if (!flags->ice_remove) { - priority = new_priority( - (flags->ice_force || flags->ice_force_relay) ? NULL : sdp_media, - flags->ice_force_relay - ); - - insert_candidates(chop, ps, ps_rtcp, - priority, sdp_media, flags->ice_force_relay); - } + if (!flags->ice_remove) + insert_candidates(chop, ps, ps_rtcp, flags, sdp_media); next: media_index++; @@ -1950,8 +1987,3 @@ error: void sdp_init() { } - -void sdp_ice_foundation(struct interface_address *ifa) { - random_ice_string(ifa->foundation_buf, sizeof(ifa->foundation_buf)); - str_init_len(&ifa->ice_foundation, ifa->foundation_buf, sizeof(ifa->foundation_buf)); -} diff --git a/daemon/sdp.h b/daemon/sdp.h index ab659f17b..5d20f7ec6 100644 --- a/daemon/sdp.h +++ b/daemon/sdp.h @@ -45,7 +45,6 @@ struct sdp_chopper { }; void sdp_init(void); -void sdp_ice_foundation(struct interface_address *ifa); int sdp_parse(str *body, GQueue *sessions); int sdp_streams(const GQueue *sessions, GQueue *streams, struct sdp_ng_flags *); diff --git a/daemon/stun.c b/daemon/stun.c index d0826f95c..85ac584ea 100644 --- a/daemon/stun.c +++ b/daemon/stun.c @@ -6,11 +6,13 @@ #include #include #include +#include #include "compat.h" #include "str.h" #include "aux.h" #include "log.h" +#include "ice.h" @@ -21,7 +23,11 @@ #define STUN_ERROR_CODE 0x0009 #define STUN_UNKNOWN_ATTRIBUTES 0x000a #define STUN_XOR_MAPPED_ADDRESS 0x0020 +#define STUN_PRIORITY 0x0024 +#define STUN_USE_CANDIDATE 0x0025 #define STUN_FINGERPRINT 0x8028 +#define STUN_ICE_CONTROLLED 0x8029 +#define STUN_ICE_CONTROLLING 0x802a #define STUN_CLASS_REQUEST 0x00 #define STUN_CLASS_INDICATION 0x01 @@ -35,6 +41,8 @@ | (((method) & 0x0f80) << 2) | (((class) & 0x1) << 4) \ | (((class) & 0x2) << 7)) +#define STUN_BINDING_REQUEST \ + STUN_MESSAGE_TYPE(STUN_METHOD_BINDING, STUN_CLASS_REQUEST) #define STUN_BINDING_SUCCESS_RESPONSE \ STUN_MESSAGE_TYPE(STUN_METHOD_BINDING, STUN_CLASS_SUCCESS) #define STUN_BINDING_ERROR_RESPONSE \ @@ -44,18 +52,6 @@ -struct stun_attrs { - str username; - char *msg_integrity_attr; - str msg_integrity; - u_int32_t priority; - char *fingerprint_attr; - u_int32_t fingerprint; - int use:1, - controlled:1, - controlling:1; -}; - struct header { u_int16_t msg_type; u_int16_t msg_len; @@ -94,9 +90,23 @@ struct xor_mapped_address { u_int32_t address[4]; } __attribute__ ((packed)); +struct controlled_ing { + struct tlv tlv; + u_int64_t tiebreaker; +} __attribute__ ((packed)); + +struct priority { + struct tlv tlv; + u_int32_t priority; +} __attribute__ ((packed)); + + + +/* XXX add const in functions */ -static int stun_attributes(struct stun_attrs *out, str *s, u_int16_t *unknowns) { + +static int stun_attributes(struct stun_attrs *out, str *s, u_int16_t *unknowns, struct header *req) { struct tlv *tlv; int len, type, uc; str attr; @@ -129,12 +139,14 @@ static int stun_attributes(struct stun_attrs *out, str *s, u_int16_t *unknowns) case STUN_USERNAME: out->username = attr; break; + case STUN_MESSAGE_INTEGRITY: if (attr.len != 20) return -1; out->msg_integrity_attr = (void *) tlv; out->msg_integrity = attr; break; + case STUN_FINGERPRINT: if (attr.len != 4) return -1; @@ -142,17 +154,29 @@ static int stun_attributes(struct stun_attrs *out, str *s, u_int16_t *unknowns) out->fingerprint = ntohl(*(u_int32_t *) attr.s); goto out; - case 0x0025: /* use-candidate */ + case STUN_USE_CANDIDATE: out->use = 1; break; - case 0x8029: /* ice-controlled */ + + case STUN_ICE_CONTROLLED: + if (out->controlling) + return -1; + if (attr.len != 8) + return -1; + out->tiebreaker = be64toh(*((u_int64_t *) attr.s)); out->controlled = 1; break; - case 0x802a: /* ice-controlling */ + + case STUN_ICE_CONTROLLING: + if (out->controlled) + return -1; + if (attr.len != 8) + return -1; + out->tiebreaker = be64toh(*((u_int64_t *) attr.s)); out->controlling = 1; break; - case 0x0024: /* priority */ + case STUN_PRIORITY: if (attr.len != 4) return -1; out->priority = ntohl(*((u_int32_t *) attr.s)); @@ -161,6 +185,33 @@ static int stun_attributes(struct stun_attrs *out, str *s, u_int16_t *unknowns) case 0x8022: /* software */ break; /* ignore but suppress warning message */ + case STUN_XOR_MAPPED_ADDRESS: + if (attr.len < 8) + return -1; + out->mapped_port = ntohs(*((u_int16_t *) (&attr.s[2]))) ^ (STUN_COOKIE >> 16); + if (attr.len == 8 && ntohs(*((u_int16_t *) attr.s)) == 1) + in4_to_6(&out->mapped_address, + ntohl(*((u_int32_t *) (&attr.s[4]))) ^ STUN_COOKIE); + else if (attr.len == 20 && ntohs(*((u_int16_t *) attr.s)) == 1) { + out->mapped_address.s6_addr32[0] + = *((u_int32_t *) (&attr.s[4])) ^ htonl(STUN_COOKIE); + out->mapped_address.s6_addr32[1] + = *((u_int32_t *) (&attr.s[8])) ^ req->transaction[0]; + out->mapped_address.s6_addr32[2] + = *((u_int32_t *) (&attr.s[12])) ^ req->transaction[1]; + out->mapped_address.s6_addr32[3] + = *((u_int32_t *) (&attr.s[16])) ^ req->transaction[2]; + } + break; + + case STUN_ERROR_CODE: + if (attr.len < 4) + return -1; + out->error_code = ntohl(*((u_int32_t *) attr.s)); + out->error_code = ((out->error_code & 0x700) >> 8) * 100 + + (out->error_code & 0x0ff); + break; + default: ilog(LOG_NOTICE, "Unknown STUN attribute: 0x%04x", type); if ((type & 0x8000)) @@ -231,13 +282,19 @@ INLINE void __output_add(struct msghdr *mh, struct tlv *tlv, unsigned int len, u __output_add(mh, &(attr)->tlv, sizeof(*(attr)), code, data, len) -static void output_finish(struct msghdr *mh, struct packet_stream *ps) { +static void __output_finish(struct msghdr *mh) { struct header *hdr; hdr = mh->msg_iov->iov_base; hdr->msg_len = htons(hdr->msg_len); - - stream_msg_mh_src(ps, mh); +} +//static void output_finish_ps(struct msghdr *mh, struct packet_stream *ps) { +// __output_finish(mh); +// stream_msg_mh_src(ps, mh); +//} +static void output_finish_src(struct msghdr *mh, const struct in6_addr *src) { + __output_finish(mh); + msg_mh_src(src, mh); } static void fingerprint(struct msghdr *mh, struct fingerprint *fp) { @@ -277,6 +334,9 @@ static void integrity(struct msghdr *mh, struct msg_integrity *mi, str *pwd) { struct iovec *iov; struct header *hdr; + if (!pwd || !pwd->s) + return; + output_add(mh, mi, STUN_MESSAGE_INTEGRITY); iov = mh->msg_iov; hdr = iov->iov_base; @@ -287,16 +347,18 @@ static void integrity(struct msghdr *mh, struct msg_integrity *mi, str *pwd) { hdr->msg_len = ntohs(hdr->msg_len); } -static void stun_error_len(struct packet_stream *ps, struct sockaddr_in6 *sin, struct header *req, +static void stun_error_len(struct packet_stream *ps, struct sockaddr_in6 *sin, struct in6_addr *dst, + struct header *req, int code, char *reason, int len, u_int16_t add_attr, void *attr_cont, int attr_len) { struct header hdr; struct error_code ec; + struct msg_integrity mi; struct fingerprint fp; struct generic aa; struct msghdr mh; - struct iovec iov[6]; /* hdr, ec, reason, aa, attr_cont, fp */ + struct iovec iov[7]; /* hdr, ec, reason, aa, attr_cont, mi, fp */ unsigned char buf[256]; output_init(&mh, iov, sin, &hdr, STUN_BINDING_ERROR_RESPONSE, req->transaction, buf, sizeof(buf)); @@ -306,17 +368,18 @@ static void stun_error_len(struct packet_stream *ps, struct sockaddr_in6 *sin, s if (attr_cont) output_add_data(&mh, &aa, add_attr, attr_cont, attr_len); + integrity(&mh, &mi, &ps->media->ice_agent->pwd[0]); fingerprint(&mh, &fp); - output_finish(&mh, ps); + output_finish_src(&mh, dst); sendmsg(ps->sfd->fd.fd, &mh, 0); } -#define stun_error(ps, sin, str, code, reason) \ - stun_error_len(ps, sin, str, code, reason "\0\0\0", strlen(reason), \ +#define stun_error(ps, sin, dst, req, code, reason) \ + stun_error_len(ps, sin, dst, req, code, reason "\0\0\0", strlen(reason), \ 0, NULL, 0) -#define stun_error_attrs(ps, sin, str, code, reason, type, content, len) \ - stun_error_len(ps, sin, str, code, reason "\0\0\0", strlen(reason), \ +#define stun_error_attrs(ps, sin, dst, req, code, reason, type, content, len) \ + stun_error_len(ps, sin, dst, req, code, reason "\0\0\0", strlen(reason), \ type, content, len) @@ -334,28 +397,35 @@ static int check_fingerprint(str *msg, struct stun_attrs *attrs) { return 0; } -static int check_auth(str *msg, struct stun_attrs *attrs, struct call_media *media) { +static int check_auth(str *msg, struct stun_attrs *attrs, struct call_media *media, int dst, int src) { u_int16_t lenX; char digest[20]; str ufrag[2]; struct iovec iov[3]; + struct ice_agent *ag; - if (!media->ice_ufrag.s || !media->ice_ufrag.len) + ag = media->ice_agent; + if (!ag) return -1; - if (!media->ice_pwd.s || !media->ice_pwd.len) + if (!ag->ufrag[dst].s || !ag->ufrag[dst].len) return -1; - - ufrag[0] = attrs->username; - str_chr_str(&ufrag[1], &ufrag[0], ':'); - if (!ufrag[1].s) + if (!ag->pwd[dst].s || !ag->pwd[dst].len) return -1; - ufrag[0].len -= ufrag[1].len; - str_shift(&ufrag[1], 1); - if (!ufrag[0].len || !ufrag[1].len) - return -1; - if (str_cmp_str(&ufrag[0], &media->ice_ufrag)) - return -1; + if (attrs->username.s) { + /* request */ + ufrag[dst] = attrs->username; + str_chr_str(&ufrag[src], &ufrag[dst], ':'); + if (!ufrag[src].s) + return -1; + ufrag[dst].len -= ufrag[src].len; + str_shift(&ufrag[src], 1); + + if (!ufrag[src].len || !ufrag[dst].len) + return -1; + if (str_cmp_str(&ufrag[dst], &ag->ufrag[dst])) + return -1; + } lenX = htons((attrs->msg_integrity_attr - msg->s) - 20 + 24); iov[0].iov_base = msg->s; @@ -365,13 +435,14 @@ static int check_auth(str *msg, struct stun_attrs *attrs, struct call_media *med 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), &media->ice_pwd, digest); + __integrity(iov, G_N_ELEMENTS(iov), &ag->pwd[dst], digest); return memcmp(digest, attrs->msg_integrity.s, 20) ? -1 : 0; } +/* XXX way too many parameters being passed around here, unify into a struct */ static int stun_binding_success(struct packet_stream *ps, struct header *req, struct stun_attrs *attrs, - struct sockaddr_in6 *sin) + struct sockaddr_in6 *sin, struct in6_addr *dst) { struct header hdr; struct xor_mapped_address xma; @@ -398,10 +469,10 @@ static int stun_binding_success(struct packet_stream *ps, struct header *req, st output_add(&mh, &xma, STUN_XOR_MAPPED_ADDRESS); } - integrity(&mh, &mi, &ps->media->ice_pwd); + integrity(&mh, &mi, &ps->media->ice_agent->pwd[1]); fingerprint(&mh, &fp); - output_finish(&mh, ps); + output_finish_src(&mh, dst); sendmsg(ps->sfd->fd.fd, &mh, 0); return 0; @@ -415,20 +486,56 @@ INLINE int u_int16_t_arr_len(u_int16_t *arr) { } + #define SLF " from %s" #define SLP smart_ntop_port_buf(sin) +static int __stun_request(struct packet_stream *ps, struct sockaddr_in6 *sin, + struct in6_addr *dst, struct header *req, struct stun_attrs *attrs) +{ + int ret; + + ret = ice_request(ps, sin, dst, attrs); + + if (ret == -2) { + ilog(LOG_DEBUG, "ICE role conflict detected"); + stun_error(ps, sin, dst, req, 487, "Role conflict"); + return 0; + } + if (ret < 0) + return -1; + + ilog(LOG_DEBUG, "Successful STUN binding request" SLF, SLP); + stun_binding_success(ps, req, attrs, sin, dst); + + return ret; +} +static int __stun_success(struct packet_stream *ps, struct sockaddr_in6 *sin, + struct in6_addr *dst, struct header *req, struct stun_attrs *attrs) +{ + return ice_response(ps, sin, dst, attrs, req->transaction); +} +static int __stun_error(struct packet_stream *ps, struct sockaddr_in6 *sin, + struct in6_addr *dst, struct header *req, struct stun_attrs *attrs) +{ + return ice_response(ps, sin, dst, attrs, req->transaction); +} + + /* 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 + * 1 = stun packet processed and ICE has completed + * + * call is locked in R */ -int stun(str *b, struct packet_stream *ps, struct sockaddr_in6 *sin) { +int stun(str *b, struct packet_stream *ps, struct sockaddr_in6 *sin, struct in6_addr *dst) { struct header *req = (void *) b->s; int msglen, method, class; str attr_str; struct stun_attrs attrs; u_int16_t unknowns[UNKNOWNS_COUNT]; const char *err; + int dst_idx, src_idx; msglen = ntohs(req->msg_len); err = "message-length mismatch"; @@ -446,51 +553,107 @@ int stun(str *b, struct packet_stream *ps, struct sockaddr_in6 *sin) { attr_str.s = &b->s[20]; attr_str.len = b->len - 20; - if (stun_attributes(&attrs, &attr_str, unknowns)) { + if (stun_attributes(&attrs, &attr_str, unknowns, req)) { err = "failed to parse attributes"; if (unknowns[0] == 0xffff) goto ignore; ilog(LOG_WARNING, "STUN packet contained unknown " "\"comprehension required\" attribute(s)" SLF, SLP); - stun_error_attrs(ps, sin, req, 420, "Unknown attribute", + stun_error_attrs(ps, sin, dst, req, 420, "Unknown attribute", STUN_UNKNOWN_ATTRIBUTES, unknowns, u_int16_t_arr_len(unknowns) * 2); return 0; } - if (class != STUN_CLASS_REQUEST) - return -1; - err = "FINGERPRINT attribute missing"; if (!attrs.fingerprint_attr) goto ignore; - err = "USERNAME attribute missing"; - if (!attrs.username.s) - goto bad_req; err = "MESSAGE_INTEGRITY attribute missing"; if (!attrs.msg_integrity.s) goto bad_req; + if (class == STUN_CLASS_REQUEST) { + err = "USERNAME attribute missing"; + if (!attrs.username.s) + goto bad_req; + dst_idx = 1; + src_idx = 0; + } + else { + dst_idx = 0; + src_idx = 1; + } + err = "FINGERPRINT mismatch"; if (check_fingerprint(b, &attrs)) goto ignore; - if (check_auth(b, &attrs, ps->media)) + if (check_auth(b, &attrs, ps->media, dst_idx, src_idx)) goto unauth; - ilog(LOG_INFO, "Successful STUN binding request" SLF, SLP); - stun_binding_success(ps, req, &attrs, sin); - - return attrs.use ? 1 : 0; + switch (class) { + case STUN_CLASS_REQUEST: + return __stun_request(ps, sin, dst, req, &attrs); + case STUN_CLASS_SUCCESS: + return __stun_success(ps, sin, dst, req, &attrs); + case STUN_CLASS_ERROR: + return __stun_error(ps, sin, dst, req, &attrs); + default: + return -1; + } + /* notreached */ bad_req: ilog(LOG_NOTICE, "Received invalid STUN packet" SLF ": %s", SLP, err); - stun_error(ps, sin, req, 400, "Bad request"); + stun_error(ps, sin, dst, req, 400, "Bad request"); return 0; unauth: ilog(LOG_NOTICE, "STUN authentication mismatch" SLF, SLP); - stun_error(ps, sin, req, 401, "Unauthorized"); + stun_error(ps, sin, dst, req, 401, "Unauthorized"); return 0; ignore: ilog(LOG_NOTICE, "Not handling potential STUN packet" SLF ": %s", SLP, err); return -1; } + +int stun_binding_request(struct sockaddr_in6 *dst, u_int32_t transaction[3], str *pwd, + str ufrags[2], int controlling, u_int64_t tiebreaker, u_int32_t priority, + struct in6_addr *src, int fd, int to_use) +{ + struct header hdr; + struct msghdr mh; + struct iovec iov[8]; /* hdr, username x2, ice_controlled/ing, priority, uc, fp, mi */ + unsigned char buf[256]; + char username_buf[256]; + int i; + struct generic un_attr; + struct controlled_ing cc; + struct priority prio; + struct generic uc; + struct fingerprint fp; + struct msg_integrity mi; + + output_init(&mh, iov, dst, &hdr, STUN_BINDING_REQUEST, transaction, buf, sizeof(buf)); + + i = snprintf(username_buf, sizeof(username_buf), STR_FORMAT":"STR_FORMAT, + STR_FMT(&ufrags[0]), STR_FMT(&ufrags[1])); + if (i <= 0 || i >= sizeof(username_buf)) + return -1; + output_add_data(&mh, &un_attr, STUN_USERNAME, username_buf, i); + + cc.tiebreaker = htobe64(tiebreaker); + output_add(&mh, &cc, controlling ? STUN_ICE_CONTROLLING : STUN_ICE_CONTROLLED); + + prio.priority = htonl(priority); + output_add(&mh, &prio, STUN_PRIORITY); + + if (to_use) + output_add(&mh, &uc, STUN_USE_CANDIDATE); + + integrity(&mh, &mi, pwd); + fingerprint(&mh, &fp); + + output_finish_src(&mh, src); + sendmsg(fd, &mh, 0); + + return 0; +} diff --git a/daemon/stun.h b/daemon/stun.h index c0e1b14b8..9a52336fb 100644 --- a/daemon/stun.h +++ b/daemon/stun.h @@ -4,6 +4,7 @@ #include #include +#include #include "compat.h" #include "call.h" #include "str.h" @@ -12,6 +13,24 @@ #define STUN_COOKIE 0x2112A442UL + +struct stun_attrs { + str username; + char *msg_integrity_attr; + str msg_integrity; + u_int32_t priority; + char *fingerprint_attr; + u_int32_t fingerprint; + u_int64_t tiebreaker; + struct in6_addr mapped_address; + unsigned int mapped_port; /* XXX use struct endpoint */ + unsigned int error_code; + int use:1, + controlled:1, + controlling:1; +}; + + INLINE int is_stun(const str *s) { const unsigned char *b = (const void *) s->s; const u_int32_t *u; @@ -30,7 +49,10 @@ INLINE int is_stun(const str *s) { } -int stun(str *, struct packet_stream *, struct sockaddr_in6 *); +int stun(str *, struct packet_stream *, struct sockaddr_in6 *, struct in6_addr *); +int stun_binding_request(struct sockaddr_in6 *dst, u_int32_t transaction[3], str *pwd, + str ufrags[2], int controlling, u_int64_t tiebreaker, u_int32_t priority, + struct in6_addr *src, int fd, int); #endif diff --git a/tests/stun-client b/tests/stun-client new file mode 100755 index 000000000..72c2dd881 --- /dev/null +++ b/tests/stun-client @@ -0,0 +1,91 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Socket; +use Socket6; +use Digest::SHA qw(hmac_sha1); +use Digest::CRC qw(crc32); + +my ($prio, $ip, $port, $username, $pwd) = @ARGV; + +my $fd; +my @dests = getaddrinfo($ip, $port, AF_UNSPEC, SOCK_DGRAM); +while (@dests >= 5) { + my ($fam, $type, $prot, $addr, $canon, @dests) = @dests; + socket($fd, $fam, $type, $prot) or undef($fd), next; + connect($fd, $addr) or undef($fd), next; + last; +} +$fd or die($!); + +my @rand = ('A' .. 'Z', 'a' .. 'z'); +my $ufrag = join('', (map {$rand[rand($#rand)]} (1 .. 10))); +my $tract = join('', (map {$rand[rand($#rand)]} (1 .. 12))); +my $control = rand() < .5; +my $tbreak = int(rand(0xffffffff)) * int(rand(0xffffffff)); + +print("transaction: $tract\n"); +print("my username fragment: $ufrag\n"); +print("controll".($control?"ing":'ed')."\n"); +print("tie breaker: $tbreak\n"); + + +my $packet = ''; +$packet .= attr(6, "$username:$ufrag"); +$packet .= attr($control ? 0x802a : 0x8029, pack('Q', $tbreak)); +$packet .= attr(0x24, pack('N', $prio)); +$packet .= integrity(); +$packet .= fingerprint(); +$packet = header() . $packet; + +send($fd, $packet, 0) or die $!; +my $buf; +recv($fd, $buf, 200, 0) or die; + +my ($code, $length, $cookie, $tract2, $attrs) = unpack('nnN a12 a*', $buf); + +if ($cookie == 0x2112A442 || $tract2 ne $tract) { + printf("code: \%x\n", $code); + while ($attrs ne '') { + my ($type, $len, $cont); + ($type, $len, $attrs) = unpack('nn a*', $attrs); + my $pad = 0; + while ((($len + $pad) % 4) != 0) { + $pad++; + } + ($cont, $pad, $attrs) = unpack("a$len a$pad a*", $attrs); + printf(" attr type: \%x\n", $type); + print(" content: $cont\n"); + } +} +else { + print("not stun: ".unpack('H*', $buf)."\n"); +} + +exit; + + +sub attr { + my ($type, $data) = @_; + my $len = length($data); + while ((length($data) % 4) != 0) { + $data .= "\0"; + } + return pack('nn a*', $type, $len, $data); +} +sub header { + my ($add_length) = @_; + $add_length ||= 0; + return pack('nnN a12', 1, length($packet) + $add_length, 0x2112A442, $tract); +} +sub integrity { + my $h = header(24); + my $hmac = hmac_sha1($h.$packet, $pwd); + return attr(8, $hmac); +} +sub fingerprint { + my $h = header(8); + my $crc = crc32($h.$packet); + return attr(0x8028, pack('N', ($crc ^ 0x5354554e))); +} diff --git a/tests/stun-server b/tests/stun-server new file mode 100755 index 000000000..b4446774b --- /dev/null +++ b/tests/stun-server @@ -0,0 +1,204 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Socket; +use Socket6; +use Digest::SHA qw(hmac_sha1); +use Digest::CRC qw(crc32); + +my ($controlling, $port, $username, $pwd, @addresses) = @ARGV; + +my %attrs = ( + 6 => \&attr_un, + 8 => \&attr_mi, + 0x8028 => \&attr_fp, + 0x25 => \&attr_use, + 0x8029 => \&attr_controlled, + 0x802a => \&attr_controlling, +); + +my (@sockets, $packet, $tract, $code); + +for my $addr (@addresses) { + my ($fam, $pka, $sin, $meth) = addrparse($addr, $port); + socket(my $fd, $fam, SOCK_DGRAM, 0) or die $!; + bind($fd, $sin) or die $!; + push(@sockets, {fd => $fd, fam => $fam, addr => $pka, sin => $sin, xormethod => $meth}); +} + +while (1) { + my $rin = ''; + for my $s (@sockets) { + vec($rin, fileno($$s{fd}), 1) = 1; + } + + select($rin, undef, undef, 1); + + for my $s (@sockets) { + vec($rin, fileno($$s{fd}), 1) or next; + + my $src = recv($$s{fd}, my $buf, 200, 0) or die $!; + print("\npacket from " . addrdeparse($$s{fam}, $src) . " on ". + addrdeparse($$s{fam}, $$s{sin}) . "\n"); + + my ($cmd, $len, $cookie, $attrs); + ($cmd, $len, $cookie, $tract, $attrs) = unpack('nnN a12 a*', $buf); + + if ($cookie != 0x2112A442) { + if ($buf =~ /^[\x14-\x3f]/s) { + print("DTLS\n"); + } + else { + print("not stun: " . unpack("H*", $buf)."\n"); + } + next; + } + $cmd == 1 or print("not stun request\n"), next; + length($attrs) == $len or print("length mismatch\n"), next; + + my ($list, $hash) = unpack_attrs($attrs); + + $$list[$#$list]{name} eq 'fingerprint' or print("last attr not fingerprint\n"), next; + $$list[$#$list-1]{name} eq 'message-integrity' or print("last but one attr not MI\n"), next; + $$hash{username} or print("no username\n"), next; + + $$hash{controlling} and print("is controlling\n"); + $$hash{controlled} and print("is controlled\n"); + $$hash{'use-candidate'} and print("nominated\n"); + + print("local username is $$hash{username}{split}[0], remote is $$hash{username}{split}[1]\n"); + + if ($$hash{controlling} && $controlling || $$hash{controlled} && !$controlling) { + print("role conflict, replying with 487"); + $code = 0x0111; # binding error + $packet = attr(0x9, pack('CCCC a16', 0, 0, 4, 87, 'Role conflict')); + $packet .= integrity(); + $packet .= fingerprint(); + } + else { + $code = 0x101; # binding success + my $xorattr = $$s{xormethod}($src); + $packet = attr(0x20, $xorattr); + $packet .= integrity(); + $packet .= fingerprint(); + } + + $packet = header() . $packet; + + print("sending reply\n"); + send($$s{fd}, $packet, 0, $src); + } +} + + + +exit; + +sub xor4 { + my ($src) = @_; + my @a = unpack_sockaddr_in($src); + return pack('nna4', 1, $a[0] ^ 0x2112, $a[1] ^ "\x21\x12\xa4\x42"); +} +sub xor6 { + my ($src) = @_; + my @a = unpack_sockaddr_in6($src); + return pack('nna16', 2, $a[0] ^ 0x2112, + $a[1] ^ ("\x21\x12\xa4\x42" . $tract)); +} +sub addrparse { + my ($addr, $port) = @_; + my $r = inet_pton(AF_INET, $addr); + $r and return (AF_INET, $r, pack_sockaddr_in($port, $r), \&xor4); + $r = inet_pton(AF_INET6, $addr); + $r and return (AF_INET6, $r, pack_sockaddr_in6($port, $r), \&xor6); + die; +} +sub addrdeparse { + my ($fam, $sin) = @_; + if ($fam == AF_INET) { + my @up = unpack_sockaddr_in($sin); + return inet_ntop(AF_INET, $up[1]) . ":$up[0]"; + } + if ($fam == AF_INET6) { + my @up = unpack_sockaddr_in6($sin); + return '['.inet_ntop(AF_INET6, $up[1]) . "]:$up[0]"; + } + die; +} + +sub attr_un { + my ($cont) = @_; + return {name => 'username', split => [$cont =~ /(.*):(.*)/]}; +} +sub attr_mi { + return {name => 'message-integrity'}; +} +sub attr_fp { + my ($cont) = @_; + return {name => 'fingerprint', value => unpack('N', $cont)}; +} +sub attr_use { + return {name => 'use-candidate'}; +} +sub attr_controlling { + my ($cont) = @_; + return {name => 'controlling', tiebreaker => unpack('Q', $cont)}; +} +sub attr_controlled { + my ($cont) = @_; + return {name => 'controlled', tiebreaker => unpack('Q', $cont)}; +} + + +sub unpack_attrs { + my ($s) = @_; + + my (@out, %out); + while ($s ne '') { + my ($type, $len, $cont); + ($type, $len, $s) = unpack('nn a*', $s); + my $pad = 0; + while ((($len + $pad) % 4) != 0) { + $pad++; + } + ($cont, $pad, $s) = unpack("a$len a$pad a*", $s); + my $ins = {type => $type, len => $len, content => $cont, + raw => pack('nna*a*', $type, $len, $cont, $pad)}; + push(@out, $ins); + $out{$type} = $ins; + + my $pars = $attrs{$type}; + $pars or next; + + my $ai = $pars->($cont); + %$ins = (%$ins, %$ai); + $out{$$ins{name}} = $ins; + } + return (\@out, \%out); +} + + +sub attr { + my ($type, $data) = @_; + my $len = length($data); + while ((length($data) % 4) != 0) { + $data .= "\0"; + } + return pack('nn a*', $type, $len, $data); +} +sub header { + my ($add_length) = @_; + $add_length ||= 0; + return pack('nnN a12', $code, length($packet) + $add_length, 0x2112A442, $tract); +} +sub integrity { + my $h = header(24); + my $hmac = hmac_sha1($h.$packet, $pwd); + return attr(8, $hmac); +} +sub fingerprint { + my $h = header(8); + my $crc = crc32($h.$packet); + return attr(0x8028, pack('N', ($crc ^ 0x5354554e))); +}